diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b733fe5..3a7dd01 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,8 +6,30 @@ on: - 'v*' jobs: + release-plan: + name: Decide publish path + runs-on: ubuntu-latest + outputs: + publish_npm: ${{ steps.check.outputs.publish_npm }} + steps: + - uses: actions/checkout@v4 + - id: check + shell: bash + run: | + tag="${GITHUB_REF_NAME#v}" + pkg="$(node -p "require('./package.json').version")" + if [ "$tag" = "$pkg" ]; then + echo "publish_npm=true" >> "$GITHUB_OUTPUT" + echo "Tag v$tag matches package.json. Publishing the headless package." + else + echo "publish_npm=false" >> "$GITHUB_OUTPUT" + echo "Tag v$tag does not match package.json $pkg. Skipping the headless npm publish and binary build." + fi + build-daemon: name: Build Daemon Bundles + needs: release-plan + if: needs.release-plan.outputs.publish_npm == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c70f24..4fa09b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.1.0] - 2026-10-14 + +- Added `--group-title` on `open` and `goto`. Tabs for one task join a Chrome tab group with that title and a color. The same title keeps joining the group. A different title starts a new group. With no title, the group stays named Browser Hand. If a saved group cannot be reused in that tab's window, Browser Hand opens a replacement group instead of failing the command. +- Added `steps`, one call for a repeated form (click, fill, type, evaluate, snapshot). The call stops at the first miss, and step snapshots leave formatted secrets out. +- Added a scoped snapshot (open dialog plus main by default) and clicks by snapshot ref or by role and accessible name. Covered controls are skipped. `doctor` stays a short health check. `tabs` is the inventory. +- Snapshot `forms` lists the labeled fields in that same scope. +- Fixed fill so a missed field fails the command, and the read-back keeps unicode and punctuation. A control whose form id is missing is not attached. +- Fixed combobox fill so it types, then chooses the matching option. +- Fixed `open` so an existing tab is reused instead of a new blank one. Snapshot and evaluate no longer create a named tab just to read the page. +- Fixed the debugger infobar: after you dismiss "Browser Hand started debugging this browser", that tab stays on the scripting path and the bar is not brought back. +- Improved the toolbar icon so the crop is the solid glove, not the wrist mist. + ## [0.2.9] - 2026-07-14 - Added configurable per-browser idle cleanup with `--idle-timeout`, `DEV_BROWSER_IDLE_TIMEOUT_MS`, and `~/.dev-browser/config.json` support. Idle cleanup preserves persistent profiles, excludes externally connected Chrome, and safely rechecks activity under the per-browser lock before closing. diff --git a/README.md b/README.md index bb5be8f..0c44c75 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Browser Hand takes a different route: a small **Chrome extension** bridges your - **Real sessions** — use the Chrome profile you already use (cookies, passkeys, extensions, open tabs). - **Background-first** — open and automate tabs without stealing OS focus; optional one-shot focus only when a human must look (2FA, captcha, confirm). - **Agent-shaped CLI** — `tabs`, `doctor`, named pages, snapshot, fill, click, type, evaluate, screenshot — JSON out, logs on stderr. +- **Named tab groups.** `--group-title` puts the tabs for one task in a Chrome group with that name and a color. A different title starts a new group. - **Hard pages** — soft recovery when password managers detach the debugger; scripting fallback; shadow DOM / iframe pierce; human-like pointer and contenteditable input. - **Agent gym** — local challenge pages under `extension/challenges/` built to exercise agent failure modes (labels, modals, SPA routes, username fields), not just “click the demo button once.” - **Optional headless** — upstream sandboxed QuickJS + Playwright CLI when you want CI or a clean browser, not your daily profile. @@ -76,12 +77,16 @@ Removes legacy skill aliases named `dev-browser` and prints how to remove an old browser-hand tabs browser-hand snapshot # unique-active tab (last-focused when stamped) browser-hand snapshot --query stripe -browser-hand open --url https://example.com --page-name demo +browser-hand open --url https://example.com --page-name demo --group-title "DNS records" browser-hand snapshot --page-name demo browser-hand click --page-name demo --text "More information..." browser-hand screenshot --page-name demo ``` +The first `open` of a task can name the Chrome tab group. Later opens with the same title join that group. A different title starts a new group and a new color. Leave the flag off and the group stays named Browser Hand. + +![Chrome tab group titled DNS records, holding the Example Domain and Example Domains tabs](docs/img/tab-group-title.png) + Live smoke (uses your real Chrome): ```bash diff --git a/daemon/src/sandbox/__tests__/protocol-bridge.test.ts b/daemon/src/sandbox/__tests__/protocol-bridge.test.ts index 61da58c..732f123 100644 --- a/daemon/src/sandbox/__tests__/protocol-bridge.test.ts +++ b/daemon/src/sandbox/__tests__/protocol-bridge.test.ts @@ -37,7 +37,7 @@ describe.sequential("protocol bridge", () => { await page.goto("https://example.com"); await expect(page.title()).resolves.toBe("Example Domain"); - await expect(page.locator("h1").textContent()).resolves.toBe("Example Domain"); + await expect(page.locator("body").textContent()).resolves.toContain("documentation examples"); await expect(page.evaluate(() => document.title)).resolves.toBe("Example Domain"); }, 120_000); diff --git a/daemon/src/sandbox/__tests__/sandbox-integration.test.ts b/daemon/src/sandbox/__tests__/sandbox-integration.test.ts index 923ce8b..fb11dbe 100644 --- a/daemon/src/sandbox/__tests__/sandbox-integration.test.ts +++ b/daemon/src/sandbox/__tests__/sandbox-integration.test.ts @@ -85,7 +85,7 @@ describe.sequential("QuickJS sandbox integration", () => { ` const page = await browser.getPage("locator"); await page.goto("https://example.com"); - console.log(await page.locator("h1").textContent()); + console.log(await page.locator("body").textContent()); `, manager, "default", @@ -95,7 +95,7 @@ describe.sequential("QuickJS sandbox integration", () => { } ); - expect(output.stdout.join("")).toContain("Example Domain"); + expect(output.stdout.join("")).toContain("documentation examples"); }, 120_000); it("supports page.evaluate", async () => { diff --git a/docs/img/tab-group-title.png b/docs/img/tab-group-title.png new file mode 100644 index 0000000..8f9eee0 Binary files /dev/null and b/docs/img/tab-group-title.png differ diff --git a/extension/package-lock.json b/extension/package-lock.json index cb66bd0..c06915b 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "dev-browser-extension", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dev-browser-extension", - "version": "1.0.0", + "version": "1.1.0", "devDependencies": { "@types/chrome": "^0.1.32", "typescript": "^5.0.0", diff --git a/extension/package.json b/extension/package.json index 25aa576..21cb9de 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1,6 +1,6 @@ { "name": "browser-hand-extension", - "version": "1.0.0", + "version": "1.1.0", "type": "module", "scripts": { "dev": "wxt", diff --git a/package-lock.json b/package-lock.json index c4ccd8b..784e3be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,7 @@ }, "extension": { "name": "browser-hand-extension", - "version": "1.0.0", + "version": "1.1.0", "devDependencies": { "@types/chrome": "^0.1.32", "typescript": "^5.0.0",