Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion daemon/src/sandbox/__tests__/protocol-bridge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
4 changes: 2 additions & 2 deletions daemon/src/sandbox/__tests__/sandbox-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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 () => {
Expand Down
Binary file added docs/img/tab-group-title.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions extension/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion extension/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "browser-hand-extension",
"version": "1.0.0",
"version": "1.1.0",
"type": "module",
"scripts": {
"dev": "wxt",
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading