Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0644fb2d0b | ||
|
|
1ea6b1a2c5 | ||
|
|
7bb50d54b3 |
@@ -133,6 +133,9 @@ jobs:
|
||||
- name: Generate changelog
|
||||
id: changelog
|
||||
run: |
|
||||
# fetch-depth:0 gets history, but the tag refs the changelog needs
|
||||
# aren't always present in a detached-HEAD tag checkout — pull them in.
|
||||
git fetch --tags --force --quiet origin 2>/dev/null || true
|
||||
TAG="${{ github.event.inputs.tag || github.ref_name }}"
|
||||
PREV="$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || true)"
|
||||
{
|
||||
|
||||
Generated
+1
-1
@@ -290,7 +290,7 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chrome-use"
|
||||
version = "1.2.2"
|
||||
version = "1.2.3"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes-gcm",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "chrome-use"
|
||||
version = "1.2.2"
|
||||
version = "1.2.3"
|
||||
edition = "2021"
|
||||
description = "Fast browser automation CLI for AI agents"
|
||||
license = "Apache-2.0"
|
||||
|
||||
+50
-3
@@ -408,6 +408,12 @@ fn parse_command_inner(args: &[String], flags: &Flags) -> Result<Value, ParseErr
|
||||
"back" => Ok(json!({ "id": id, "action": "back" })),
|
||||
"forward" => Ok(json!({ "id": id, "action": "forward" })),
|
||||
"reload" => Ok(json!({ "id": id, "action": "reload" })),
|
||||
// Explicit opt-in to raise the active tab to the foreground (the core
|
||||
// skill references it; the daemon handler existed but the CLI didn't map
|
||||
// it — issue #19). Accept the documented camelCase + kebab/lowercase.
|
||||
"bringToFront" | "bring-to-front" | "bringtofront" => {
|
||||
Ok(json!({ "id": id, "action": "bringtofront" }))
|
||||
}
|
||||
|
||||
// === Core Actions ===
|
||||
"click" => {
|
||||
@@ -1496,7 +1502,12 @@ fn parse_command_inner(args: &[String], flags: &Flags) -> Result<Value, ParseErr
|
||||
// `tabs` (plural) is a natural guess for the `tab` subcommand tree —
|
||||
// alias it so `tabs` / `tabs list` / `tabs new` all work (issue #8.4).
|
||||
"tab" | "tabs" => {
|
||||
match rest.first().copied() {
|
||||
// `--full` makes `tab list` emit untruncated URLs (needed to re-open
|
||||
// a long SSO/redirect URL after a stale session — issue #19). Pick
|
||||
// the subcommand as the first non-flag arg so the flag can appear
|
||||
// anywhere (`tab --full`, `tab list --full`).
|
||||
let full = rest.contains(&"--full");
|
||||
match rest.iter().find(|a| !a.starts_with("--")).copied() {
|
||||
Some("new") => {
|
||||
// Accepted forms:
|
||||
// tab new [url]
|
||||
@@ -1528,7 +1539,13 @@ fn parse_command_inner(args: &[String], flags: &Flags) -> Result<Value, ParseErr
|
||||
}
|
||||
Ok(cmd)
|
||||
}
|
||||
Some("list") => Ok(json!({ "id": id, "action": "tab_list" })),
|
||||
Some("list") => {
|
||||
let mut cmd = json!({ "id": id, "action": "tab_list" });
|
||||
if full {
|
||||
cmd["full"] = json!(true);
|
||||
}
|
||||
Ok(cmd)
|
||||
}
|
||||
Some("close") => {
|
||||
let mut cmd = json!({ "id": id, "action": "tab_close" });
|
||||
if let Some(tab_ref) = rest.get(1) {
|
||||
@@ -1541,7 +1558,13 @@ fn parse_command_inner(args: &[String], flags: &Flags) -> Result<Value, ParseErr
|
||||
"action": "tab_switch",
|
||||
"tabId": tab_ref,
|
||||
})),
|
||||
None => Ok(json!({ "id": id, "action": "tab_list" })),
|
||||
None => {
|
||||
let mut cmd = json!({ "id": id, "action": "tab_list" });
|
||||
if full {
|
||||
cmd["full"] = json!(true);
|
||||
}
|
||||
Ok(cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3716,6 +3739,30 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tab_list_full_flag() {
|
||||
// issue #19: `--full` → untruncated URLs; works as `tab list --full`,
|
||||
// `tab --full`, and `tabs --full`. Plain list has no `full`.
|
||||
for inv in ["tab list --full", "tab --full", "tabs --full"] {
|
||||
let cmd = parse_command(&args(inv), &default_flags()).unwrap();
|
||||
assert_eq!(cmd["action"], "tab_list", "{inv}");
|
||||
assert_eq!(cmd["full"], true, "{inv}");
|
||||
}
|
||||
let plain = parse_command(&args("tab list"), &default_flags()).unwrap();
|
||||
assert_eq!(plain["action"], "tab_list");
|
||||
assert!(plain.get("full").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bring_to_front_aliases() {
|
||||
// issue #19: the documented `bringToFront` (+ kebab/lowercase) maps to
|
||||
// the existing daemon action.
|
||||
for inv in ["bringToFront", "bring-to-front", "bringtofront"] {
|
||||
let cmd = parse_command(&args(inv), &default_flags()).unwrap();
|
||||
assert_eq!(cmd["action"], "bringtofront", "{inv}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_text_hyphen_and_underscore_aliases() {
|
||||
for verb in ["get-text", "get_text"] {
|
||||
|
||||
@@ -1364,7 +1364,7 @@ pub async fn execute_command(cmd: &Value, state: &mut DaemonState) -> Value {
|
||||
"recording_stop" => handle_recording_stop(state).await,
|
||||
"recording_restart" => handle_recording_restart(cmd, state).await,
|
||||
"pdf" => handle_pdf(cmd, state).await,
|
||||
"tab_list" => handle_tab_list(state).await,
|
||||
"tab_list" => handle_tab_list(cmd, state).await,
|
||||
"tab_new" => handle_tab_new(cmd, state).await,
|
||||
"tab_switch" => handle_tab_switch(cmd, state).await,
|
||||
"tab_close" => handle_tab_close(cmd, state).await,
|
||||
@@ -4355,10 +4355,15 @@ async fn handle_keyboard(cmd: &Value, state: &DaemonState) -> Result<Value, Stri
|
||||
// Phase 5 handlers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async fn handle_tab_list(state: &DaemonState) -> Result<Value, String> {
|
||||
async fn handle_tab_list(cmd: &Value, state: &DaemonState) -> Result<Value, String> {
|
||||
let mgr = state.browser.as_ref().ok_or("Browser not launched")?;
|
||||
let tabs = mgr.tab_list();
|
||||
Ok(json!({ "tabs": tabs }))
|
||||
// Echo `full` so the formatter prints untruncated URLs (issue #19).
|
||||
if cmd.get("full").and_then(|v| v.as_bool()).unwrap_or(false) {
|
||||
Ok(json!({ "tabs": tabs, "full": true }))
|
||||
} else {
|
||||
Ok(json!({ "tabs": tabs }))
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_tab_new(cmd: &Value, state: &mut DaemonState) -> Result<Value, String> {
|
||||
|
||||
+10
-2
@@ -477,6 +477,9 @@ pub fn print_response_with_opts(resp: &Response, action: Option<&str>, opts: &Ou
|
||||
}
|
||||
// Tabs
|
||||
if let Some(tabs) = data.get("tabs").and_then(|v| v.as_array()) {
|
||||
// `tab list --full` prints untruncated URLs so a long SSO/redirect
|
||||
// URL can actually be re-opened after a stale session (issue #19).
|
||||
let full = data.get("full").and_then(|v| v.as_bool()).unwrap_or(false);
|
||||
for tab in tabs {
|
||||
let tab_id = tab.get("tabId").and_then(|v| v.as_str()).unwrap_or("?");
|
||||
let tab_label = tab.get("label").and_then(|v| v.as_str());
|
||||
@@ -491,8 +494,13 @@ pub fn print_response_with_opts(resp: &Response, action: Option<&str>, opts: &Ou
|
||||
let title = title.as_str();
|
||||
let url = tab.get("url").and_then(|v| v.as_str()).unwrap_or("");
|
||||
// Truncate very long URLs (e.g. multi-KB JWT/OTP login links) so
|
||||
// the list stays readable instead of flooding the terminal.
|
||||
let url = truncate_middle(url, 120);
|
||||
// the list stays readable instead of flooding the terminal —
|
||||
// unless `--full` was asked for (to re-open the exact URL).
|
||||
let url = if full {
|
||||
url.to_string()
|
||||
} else {
|
||||
truncate_middle(url, 120)
|
||||
};
|
||||
let active = tab.get("active").and_then(|v| v.as_bool()).unwrap_or(false);
|
||||
let marker = if active {
|
||||
color::cyan("→")
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chrome-use",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.3",
|
||||
"description": "chrome-use — drive your real, logged-in Chrome from any AI agent, stealth by default",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@11.1.3",
|
||||
|
||||
@@ -610,6 +610,13 @@ forbids debugging). The session no longer has a live tab — re-run
|
||||
replaces the old silent behaviour where the command ran on some *other*
|
||||
tab and returned wrong data.
|
||||
|
||||
To recover, you need the tab's **exact** URL (query params and all — a long
|
||||
SSO/redirect link breaks if truncated). `tab list` shortens long URLs with
|
||||
`…`; use **`tab list --full`** to print them untruncated, then re-`open` the
|
||||
right one. For multi-redirect SSO flows, re-open the **stable entry URL**
|
||||
(not the mid-redirect one) and `wait` a few seconds for the SPA to settle
|
||||
before snapshotting.
|
||||
|
||||
**Reads landing on the wrong page**
|
||||
`eval`, `screenshot`, and `network requests` print the page they ran
|
||||
against to stderr: `eval @ <url>`, `screenshot @ <url>`, `network @ <url>`.
|
||||
|
||||
Reference in New Issue
Block a user