* [root]chore: split CLI release into release-cli.yml and scope changeset to CLI only
- Rename .github/workflows/release.yml -> release-cli.yml, keeping only
version-or-release / build-cli / publish-cli jobs; drop publish-js,
release-extension, release-dify-plugin and their detection outputs
- Extend .changeset/config.json ignore list with JS packages, extension
and dify-plugin so `pnpm changeset` only proposes CLI-related packages
- Other package release pipelines will be re-introduced later as their
own independent workflows when needed
* [root]fix: silence shellcheck warnings in release-cli.yml
- SC2193: assign matrix.platform-suffix to a shell var before pattern
matching so shellcheck stops flagging the == win32-* comparison
- SC2086: switch TAG_FLAG string to a TAG_FLAGS bash array and expand
with "${TAG_FLAGS[@]}" in both platform and CLI publish steps
* [packages/cli]feat: add HYPERBROWSER_PROXY_COUNTRY env var
Allow operators to pin the Hyperbrowser proxy exit region via a new
optional env var. Defaults to "US" so every create-session request now
includes an explicit proxyCountry field, matching Hyperbrowser's
POST /api/session contract.
* [packages/cli]feat: opt-in proxy country + cross-provider fallback
- Switch HYPERBROWSER_PROXY_COUNTRY to opt-in: when unset, the field is
omitted so Hyperbrowser's own default takes effect, instead of the CLI
forcing "US" across every session.
- Introduce ACTIONBOOK_PROXY_COUNTRY as a cross-provider fallback read by
the driver.dev, Hyperbrowser, and Browser Use connect paths. Vendor-
specific env vars still win, so existing setups are unaffected, but an
agent can now set one env var and have it map to each provider's
native field (proxyCountry / proxyCountryCode / country).
- Route the new name through collect_provider_env_from_process via an
explicit allowlist so we don't leak unrelated ACTIONBOOK_* values
into the daemon.
* [packages/cli]fix: merge provider env on restart instead of replacing
Codex review caught that adding ACTIONBOOK_PROXY_COUNTRY to the
forwarded allowlist regresses stateful restart: the prior
"non-empty cmd.provider_env → replace saved" rule in restart.rs
would now drop the saved *_API_KEY whenever the user's shell only
exports the new non-credential tweak.
Switch to a merge: saved snapshot is the base, current shell
overrides per key. Credential rotation still works (new key wins),
the empty-current path still falls back to saved, and a shell with
only ACTIONBOOK_PROXY_COUNTRY now layers onto the saved creds.
Extract the merge as a pure helper on ProviderEnv so it's
unit-testable without a full registry fixture. Three new regression
tests cover the three paths.
* [packages/cli]style: apply rustfmt to merge_provider_env tests
* [packages/cli]revert: drop ACTIONBOOK_PROXY_COUNTRY cross-provider fallback
Per review: keep the PR scoped to the Hyperbrowser vendor env var.
Each provider already has its own proxy-country name
(DRIVER_DEV_COUNTRY, HYPERBROWSER_PROXY_COUNTRY,
BROWSER_USE_PROXY_COUNTRY_CODE) and the unified fallback wasn't
worth the extra allowlist machinery + restart-merge workaround.
- Remove PROVIDER_ENV_NAMES allowlist, restore prefix-only env
forwarding.
- Remove read_proxy_country / merge_provider_env helpers and their
tests.
- Revert connect_driver_dev / connect_hyperbrowser / connect_browser_use
to reading their vendor-specific env directly.
- Revert restart.rs to the original "non-empty replaces saved"
policy now that no non-credential allowlist entry can break it.
Add the bowtie U+22C8 logo mark before the Actionbook tab-group title so
agent-driven tabs are identifiable at a glance. Chrome's tabGroups API
title is plain text — using a Unicode glyph is the only way to show an
icon. Includes a patch changeset for @actionbookdev/extension.
* [packages/cli]feat: add cloud browser provider support (-p flag)
Adds first-class support for launching cloud browser sessions through
4 providers (driver.dev, hyperbrowser, browseruse, browserless) via
the new `-p`/`--provider` flag, plus supporting registry, restart and
output plumbing.
Provider module
- New `browser/session/provider.rs` with typed `connect_provider` /
`close_provider_session` and per-provider env-var resolution.
- HTTP client built with bounded request (30s) and connect (10s)
timeouts so a hung control-plane can't stall the daemon. Cleanup
client uses tighter 10s/5s budgets.
- Hyperbrowser profile IDs are normalised to UUID v5 so non-UUID
identifiers (e.g. "user-42") map deterministically.
Cloud session lifecycle
- Registry tracks `provider` + `provider_session` so cleanup paths
can release paid sessions on close/restart/error.
- New `find_cloud_session_by_provider(provider, profile)` reuse path
in `start::execute` performs a `Target.getTargets` health probe
before reuse and removes stale entries on failure (mirrors the
existing endpoint reuse path).
- `execute_cloud` cleanup paths funnel through `fail_reserved_cloud_start`
/ `cleanup_provider_session_if_any` so all four error branches
release the provider session — `panic = "abort"` means we cannot
rely on Drop.
- Restart hybrid policy: stateless providers (driver.dev, browseruse)
reuse the original cdp_endpoint + headers so a rotated env var
cannot break a stable session; stateful providers (hyperbrowser,
browserless) re-mint via the connect path. Provider tag is
re-applied to the new entry so observability survives.
Errors and redaction
- New `CliError::ApiUnauthorized` (401/403), `ApiRateLimited` (429),
`ApiServerError` (5xx) variants with stable codes and actionable
hints; rate-limit and server errors are marked retryable.
- `redact_endpoint` now scrubs query parameters whose name matches a
secret key list (apiKey, token, password, …, case-insensitive) in
addition to long path segments. Provider WSS URLs carry the API
key in the query string, so the previous path-only redactor was
leaking credentials in success responses.
- 5 new redaction unit tests cover query, path, case and edge cases.
Config / CLI / output
- `BrowserConfig.provider` + `ACTIONBOOK_BROWSER_PROVIDER` env var
with cloud-mode auto-flip when a provider is set implicitly.
- `browser status` / `browser list` / start responses now surface
the provider name when present.
* [packages/cli]fix: forward provider env vars from CLI client to daemon
The daemon's std::env::var reads its own frozen environment (captured at
daemon-spawn time), not the user's current shell. This silently broke
provider config: rotating an API key in the shell never reached the daemon
until it was killed and restarted.
Add a per-request snapshot mechanism: the CLI client process collects all
DRIVER_DEV_*/HYPERBROWSER_*/BROWSER_USE_*/BROWSERLESS_* env vars via
collect_provider_env_from_process() and ships them in start::Cmd.provider_env
and restart::Cmd.provider_env (#[arg(skip)], serde default). The daemon
reads from this map instead of touching its own env.
For close/restart, snapshot the env onto ProviderSession.provider_env so
the daemon can talk to the provider control plane later, even when the
calling shell no longer has the keys exported.
* [packages/cli]fix: rewrite driver.dev as stateful provider with REST API
The previous implementation hardcoded wss://cdp.driver.dev as a "stateless"
provider, which DNS-resolves to NXDOMAIN. The real driver.dev is stateful:
mint a session via POST https://api.driver.dev/v1/browser/session and use
the per-session distributed cdpUrl returned in the response.
Changes:
- Replace DRIVER_DEV_WS_BASE constant with DRIVER_DEV_API_BASE
- Rewrite connect_driver_dev to POST /v1/browser/session with bearer auth
and parse {sessionId, cdpUrl}; supports country, nodeId, type, proxyUrl,
windowSize, profile body fields
- Add driver.dev case to close_provider_session (DELETE with sessionId)
- DRIVER_DEV_WS_URL escape hatch preserved for tests / private deployments
- Accept both DRIVER_DEV_API_KEY (Actionbook namespace) and DRIVER_API_KEY
(driver.dev's official docs name) via read_driver_dev_api_key
- Broaden PROVIDER_ENV_PREFIXES from "DRIVER_DEV_" to "DRIVER_" so the
fallback official-name keys also reach the daemon
- Add is_driver_dev_auth_failure body sniffer: driver.dev returns HTTP 500
with {"error":"Invalid consumer token"} for bad credentials, which the
generic 5xx → ApiServerError(retryable=true) mapping wrongly classifies.
Reclassify known auth bodies to ApiUnauthorized(retryable=false).
- 3 new unit tests
* [packages/cli]fix: filter dangerous URLs from preserved open_url on restart
restart preserves the registry's tab.url so the new session lands on the
same page. But the registry's tab.url is captured at session-launch time
and only refreshed by list-tabs — after a goto, the in-memory copy is
stale. Worse, driver.dev launches with a data:text/html,<title>... watermark
page that the L3 dangerous-protocol guard blocks on re-navigation.
Drop data:, javascript:, and about:blank from the preserved open_url so
the new session boots to the provider default and the user can goto again
from a clean state.
Also update the stale comment that listed driver.dev as a stateless
provider — after the API rewrite it has a control-plane session that needs
explicit cleanup.
* [packages/cli]fix: redact cdp_endpoint in reuse and local start paths
reuse_running_session emitted entry.ws_url verbatim, which for cloud sessions
contains the raw provider WSS URL with tokens embedded as query params
(e.g. Hyperbrowser's JWT). HYP-3 reproduced this in E2E by reusing a running
session — the JWT showed up plaintext in stdout.
execute_local was inconsistent for the same reason: local CDP URLs don't
carry secrets, but the rule should be uniform — every cdp_endpoint emission
must go through redact_endpoint().
Both sites now route through redact_endpoint(). All four cdp_endpoint JSON
emissions in start.rs are now redacted.
* Fix cloud provider session lifecycle
* [packages/cli]fix: address PR #507 cloud provider review comments
Bundle of PR #507 review fixes for cloud provider session lifecycle:
P1 — correctness / resource leaks
- start.rs: cleanup provider session on race with concurrent close
during startup (codex P1, start.rs:1064). When the placeholder
reserved at the top of execute_cloud is removed while we were busy
minting the remote session, the local provider_session handle was
dropped without stopping the remote browser. Now release the lock
and call cleanup_provider_session_if_any before returning
SESSION_NOT_FOUND.
- start.rs: close stale provider session before reconnecting on
failed health probe (codex P2, start.rs:314). A transient CDP hiccup
would orphan the previous remote session and spin up a duplicate.
- close.rs: introduce Closing state to close out the TOCTOU race
between Phase 1 (clone handle) and Phase 2 (HTTP PUT stop). A second
concurrent close now short-circuits with SESSION_CLOSING; on
provider stop failure the state reverts to the prior status so the
caller can retry.
- registry.rs: find_cloud_session_by_provider ignores WS_URL override
sessions (they have no provider_session handle); is_active() now
excludes both Closing and Closed.
P2 — output / configuration hygiene
- start.rs: endpoint_for_mode() emits local loopback CDP URLs
verbatim (so the caller can actually attach) while still redacting
cloud ws_urls that embed tokens.
- restart.rs: stateful provider restart keeps user-supplied CDP
headers (codex P2, restart.rs:177) instead of clearing them to
vec![], so sessions that needed custom headers for the initial
connect can also reconnect.
- goto.rs: refresh TabEntry.url/title in the registry after a
successful navigation so a restart-preserved open_url reflects the
navigated page, not the launch-time stub.
- Cargo.toml: release profile uses lto = "thin" (full LTO was
SIGKILLing the binary on macOS before commands reached the daemon).
P3 — observability / internals
- provider.rs: each connect_* helper now sets provider_env directly
when constructing ProviderSession, removing the post-hoc assignment
in connect_provider.
- provider.rs: is_driver_dev_auth_failure logs its classification
decision (ApiUnauthorized vs ApiServerError) with a 120-char body
snippet for easier triage.
Verified: cargo check --all-targets clean, cargo test --lib 335/335
passing. Pre-existing clippy warnings (collapsible_if, too_many_args)
are unrelated to this change.
* [packages/cli]style: fix fmt + clippy lints for CI
Addresses `Lint (fmt + clippy)` check failures on PR #507.
fmt: let cargo fmt rewrap a handful of long function signatures,
assert!() args, and closures to the project default width. No
semantic changes.
clippy:
- restart.rs:134 — collapse nested `if let Some(..)` / `if let Err(..)`
into a single `let`-chain condition.
- restart.rs:187 — replace the `if let Some(saved) / else` fallback
with `saved_provider_env.unwrap_or_default()`.
- start.rs:861 — collapse the same nested `if let` pattern in
`cleanup_provider_session_if_any`.
- start.rs:908 — annotate `execute_cloud` with
`#[allow(clippy::too_many_arguments)]`. All 8 arguments are
independent inputs resolved by the caller; folding them into a
struct would just be lint placation.
Verified locally: cargo fmt -- --check, cargo clippy --all-targets
-- -D warnings, and cargo test --lib (335/335) all clean.
* [packages/cli]docs: make -p/--provider self-documenting for agents
Before: \`-p <PROVIDER>\` rendered a single-line help string with no
list of valid values and no hint about the required auth env var.
Agents reading \`browser start --help\` had to guess a name, run the
command, read the runtime error, then guess the env var — 2–3 round
trips per provider.
After: the same field uses a \`PossibleValuesParser\` with per-value
help text, and the doc comment / \`after_help\` block spell out the
conflict and env-var rules up front. One \`--help\` read is enough
for an agent to pick a provider, know what to export, and see a
working command line.
Rendered output:
-p, --provider <PROVIDER>
Cloud browser provider (implies --mode cloud).
... mutual exclusion + stateful-restart note ...
Possible values:
- driver: driver.dev — requires DRIVER_DEV_API_KEY (or DRIVER_API_KEY)
- hyperbrowser: hyperbrowser.ai — requires HYPERBROWSER_API_KEY
- browseruse: browser-use.com — requires BROWSER_USE_API_KEY
Plus an after_help section with export-then-run examples for each
provider and a note about \`browser restart\` minting fresh remote
sessions for provider-backed IDs.
Implementation notes:
- Kept \`Cmd.provider\` as \`Option<String>\` deliberately. Switching to
a \`ValueEnum\` would have required touching \`config.rs\`,
\`restart.rs\`, registry lookups, and three in-repo test setups —
the string form survives round-tripping through IPC, TOML config,
and env merging with no conversion helpers.
- The \`browseruse\` alias for \`browser-use\` is preserved via
\`PossibleValue::aliases\`.
- \`normalize_provider_name\` still runs at \`execute()\` time so
daemon-side IPC input gets the same validation that clap now
enforces for CLI input.
- Clap now rejects invalid \`-p\` values at parse time with
\`[possible values: driver, hyperbrowser, browseruse]\` — the
runtime branch at \`execute()\` remains as an IPC safety net.
* [packages/cli]refactor: drop DRIVER_DEV_API_KEY alias, accept only DRIVER_API_KEY
driver.dev's official docs use the bare `DRIVER_API_KEY` name for the
auth credential. We previously accepted both `DRIVER_DEV_API_KEY` and
`DRIVER_API_KEY` as a transitional alias, but that exposed two names
for the same secret in every surface (--help, error hints, docs) and
confused agents reading the help output.
Keep only `DRIVER_API_KEY`:
- provider.rs: `read_driver_dev_api_key` no longer falls back to the
namespaced name; error message names only `DRIVER_API_KEY`.
- start.rs: after_help and PossibleValue help text drop the alias.
- error.rs: ApiUnauthorized hint names `DRIVER_API_KEY`.
- tests: rewritten to assert the retired name is rejected so a
future silent-fallback regression is caught.
`PROVIDER_ENV_PREFIXES` still includes `DRIVER_` (not `DRIVER_DEV_`),
so `DRIVER_API_KEY` and any `DRIVER_DEV_*` tuning knobs both survive
the CLI→daemon env forward.
* [root]chore: add changeset for CLI v1.4.0 (cloud provider support)
Minor bump for the `-p / --provider` cloud browser provider feature
(driver.dev, hyperbrowser.ai, browser-use.com) plus the stateful
session lifecycle, self-documenting --help, and the DRIVER_API_KEY
alias cleanup shipped on feature/cli-cloud-provider.
* feat(cli): restore skills install step and --target quick mode in setup
- Add setup/skills.rs porting the `npx skills add actionbook/actionbook`
integration from the previous actionbook-rs CLI. Defines SetupTarget
enum, SkillsResult, and both wizard-flow and quick-mode install paths.
- Wire Step 5 "Install Skills" into the setup wizard (TOTAL_STEPS 4 -> 5),
running after Save. Non-interactive / JSON mode auto-installs with -y;
interactive mode prompts Install / Skip.
- Add quick mode: `actionbook setup --target <agent>` skips the full
wizard and only runs skills install for the specified target. Restores
the CI / one-shot bootstrap path. Standalone target is special-cased
to skip entirely (no agent integration needed).
- Change --target flag from Option<String> to Option<SetupTarget> (clap
ValueEnum, kebab-case values: claude, codex, cursor, windsurf,
antigravity, opencode, standalone, all). Unknown values rejected at
parse time.
- Propagate skills install failure as CliError so CI / non-interactive
callers see a non-zero exit.
Scope: only packages/cli/src/setup/ plus the cli.rs test block. No
changes to browser, daemon, config, error types, or Cargo.toml.
* feat(cli): show API key input and guide extension install to CWS / GitHub Releases
- API key prompt switches from `Password` to `Input` so typed characters
echo by default. Users reported the hidden-by-default behavior felt
broken when pasting. `allow_empty` + the validation loop are unchanged.
- Extension mode now guides the user to install the Chrome extension:
- Primary: Chrome Web Store (recommended, one-click).
- Fallback: manual install from GitHub Releases when CWS is
unavailable (region-blocked, offline, corporate policy). 5-step
Load-unpacked flow covers download -> unzip -> chrome://extensions
-> Developer mode -> Load unpacked.
- Interactive flow asks "Installed from CWS?" -> if no, shows the
GitHub Releases path and asks "Loaded the unpacked extension?"
-> if no, errors with both URLs listed for out-of-band install.
- Non-interactive (`--browser extension`) text output lists both
URLs; JSON payload gains `recommended_install_source`,
`web_store_url`, `fallback_install_source`, `github_releases_url`.
- The GitHub Releases URL uses a tag-search query
`?q=%22Chrome+Extension%22&expanded=true` so users land directly on
extension releases (the repo mixes `actionbook-cli-v*`,
`actionbook-extension-v*`, and `actionbook-dify-plugin-v*` in the
same feed — a plain /releases URL buries the extension zip).
Tests: 292 lib passed. New: `test_chrome_web_store_url_is_canonical`,
`test_github_releases_url_is_extension_filtered`,
`test_apply_extension_mode_records_web_store_hint_in_json`.
* fix(cli): address Codex P1 review feedback on setup skills step
Two P1 issues flagged by Codex automated review on PR #499:
1. setup/skills.rs: JSON mode deadlock risk when `npx skills add`
output exceeds the OS pipe buffer (~16KB on macOS). The code used
`Stdio::piped()` for child stdout/stderr but then called `.status()`
instead of `.output()`, which does not drain the pipes. A verbose
skills install (common on first run) would fill the buffer and block
the child on write, hanging setup indefinitely in CI. Switch to
`Stdio::null()` in JSON mode to discard subprocess output entirely —
we already emit our own JSON status event, so the subprocess output
has no consumer anyway. Also close stdin in JSON mode (`Stdio::null`)
so a stray read attempt can't hang either.
2. setup/mod.rs: quick mode (`setup --target <agent>`) silently exited
with status 0 when npx was not available. `install_skills_for_target`
returns `SkillsAction::Prompted` in that case, but `run_target_only`
only mapped `SkillsAction::Failed` to an error. CI bootstrap relying
on `--target` would silently pass broken runs. Extract a pure helper
`target_only_exit_status` that maps every non-`Installed` outcome to
`CliError::Internal` with a target-specific message; in the `Prompted`
branch the message points at the Node.js install docs and the exact
`npx` command to retry manually. The full-wizard Skills step is
unchanged — there `Prompted` is still a soft prompt because the user
can finish setup without skills.
Add 4 unit tests on `target_only_exit_status` covering all four
`SkillsAction` variants, including a regression guard for the P1 #2
silent-success case.
Tests: 296 lib passed (71 in setup::, 4 new).
* fix(cli): tighten setup target quick mode semantics
* fix(cli): install setup target all for all agents
* style(cli): format setup quick mode tests and JSON output
* chore(cli): add changeset for setup improvements
The session_list and session_destroy functions used crate::daemon directly
without #[cfg(unix)] guards, causing compilation failure on Windows (E0433
unresolved import, E0282 type annotations needed). Daemon functionality
relies on Unix sockets and is unavailable on Windows.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
npm publish does not resolve workspace:* dependencies, causing
@actionbookdev/openclaw-plugin to ship with unresolvable deps on npm.
Switch to pnpm publish --no-git-checks which auto-resolves workspace
references to actual version numbers.