diff --git a/README.md b/README.md index 54860c0..ea5c33e 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,15 @@ as a plugin because it does not discover the shared skill collection recursively Upstream skills are installed through `home/.chezmoiexternal.toml`. Keep personal rules in the Agents repository so upstream packages stay unmodified. +## Browser Tools + +[agent-browser](https://agent-browser.dev) uses the Browserless container started +by `mise run services:browserless` when needed. Mise installs the CLI; chezmoi +configures its skill and native endpoint setting. +Both platforms use the same Compose definition. On Linux it runs under the user’s +Podman runtime; start it explicitly when needed. Apply its configuration with +chezmoi first; the start task does not apply preferences or enable boot startup. + ## Shared Tools & Tasks `home/dot_config/mise/conf.d/tools.toml` declares shared tools; diff --git a/home/.chezmoidata.toml b/home/.chezmoidata.toml index 2b19424..24df109 100644 --- a/home/.chezmoidata.toml +++ b/home/.chezmoidata.toml @@ -25,6 +25,11 @@ url = "https://bifrost.excloo.com/mcp/reactive-resume" [mcp.unifi_network] url = "https://bifrost.excloo.com/mcp/unifi-network" +[services.browserless] +port = 9222 +# renovate: datasource=docker depName=ghcr.io/browserless/chromium +version = "v2.62.0" + [services.connect] # renovate: datasource=docker depName=1password/connect-api version = "1.8.4" diff --git a/home/.chezmoiexternal.toml b/home/.chezmoiexternal.toml index 15bfac4..09d0498 100644 --- a/home/.chezmoiexternal.toml +++ b/home/.chezmoiexternal.toml @@ -1,3 +1,13 @@ +[".agents/skills/agent-browser/LICENSE"] +type = "file" +# renovate: datasource=github-releases depName=vercel-labs/agent-browser +url = "https://raw.githubusercontent.com/vercel-labs/agent-browser/v0.42.0/LICENSE" + +[".agents/skills/agent-browser/SKILL.md"] +type = "file" +# renovate: datasource=github-releases depName=vercel-labs/agent-browser +url = "https://raw.githubusercontent.com/vercel-labs/agent-browser/v0.42.0/skills/agent-browser/SKILL.md" + [".agents/skills/humanizer"] type = "archive" exact = true diff --git a/home/dot_agent-browser/private_config.json.tmpl b/home/dot_agent-browser/private_config.json.tmpl new file mode 100644 index 0000000..615ea48 --- /dev/null +++ b/home/dot_agent-browser/private_config.json.tmpl @@ -0,0 +1,3 @@ +{ + "cdp": "ws://127.0.0.1:{{ .services.browserless.port }}/chromium" +} diff --git a/home/dot_claude/skills/symlink_agent-browser.tmpl b/home/dot_claude/skills/symlink_agent-browser.tmpl new file mode 100644 index 0000000..3bd7e20 --- /dev/null +++ b/home/dot_claude/skills/symlink_agent-browser.tmpl @@ -0,0 +1 @@ +{{ joinPath .chezmoi.homeDir ".agents" "skills" "agent-browser" }} diff --git a/home/dot_config/mise/config.toml b/home/dot_config/mise/config.toml index f30e020..00a3fd8 100644 --- a/home/dot_config/mise/config.toml +++ b/home/dot_config/mise/config.toml @@ -16,6 +16,7 @@ claude-code = "latest" codex = "latest" k9s = "latest" "npm:@deepseek-ai/dsh" = "latest" +"npm:agent-browser" = "latest" opencode = "latest" pi = "latest" rclone = "latest" diff --git a/home/dot_config/mise/tasks/services.toml b/home/dot_config/mise/tasks/services.toml index 1578b37..041fe01 100644 --- a/home/dot_config/mise/tasks/services.toml +++ b/home/dot_config/mise/tasks/services.toml @@ -1,3 +1,13 @@ +["services:browserless"] +description = "Start Browserless" +run = ''' +{% if os() == "macos" %} +docker compose --file "$HOME/.config/services/browserless/compose.yaml" up --detach +{% else %} +podman-compose --file "$HOME/.config/services/browserless/compose.yaml" up --detach +{% endif %} +''' + ["services:connect"] description = "Start 1Password Connect" run = ''' diff --git a/home/dot_config/services/browserless/compose.yaml.tmpl b/home/dot_config/services/browserless/compose.yaml.tmpl new file mode 100644 index 0000000..b8d7144 --- /dev/null +++ b/home/dot_config/services/browserless/compose.yaml.tmpl @@ -0,0 +1,12 @@ +name: browserless +services: + browserless: + image: ghcr.io/browserless/chromium:{{ .services.browserless.version }} + restart: unless-stopped + shm_size: 1gb + environment: + CONCURRENT: 4 + QUEUED: 4 + TIMEOUT: 3600000 + ports: + - "127.0.0.1:{{ .services.browserless.port }}:3000" diff --git a/tests/services.test.mjs b/tests/services.test.mjs index 82d2240..e287534 100644 --- a/tests/services.test.mjs +++ b/tests/services.test.mjs @@ -10,10 +10,10 @@ const root = fileURLToPath(new URL("..", import.meta.url)); const base = ["--source", root, "--config", "/dev/null", "--config-format", "toml"]; const execute = (args, input = "") => execFileSync("chezmoi", [...base, ...args], { input, encoding: "utf8" }); -const render = (path, platform = "darwin") => +const render = (path, platform = "darwin", services) => execute([ "--override-data", - JSON.stringify({ chezmoi: { os: platform } }), + JSON.stringify({ chezmoi: { os: platform }, ...(services ? { services } : {}) }), "execute-template", "--file", `home/${path}`, @@ -94,3 +94,21 @@ test("Linux Compose reuses Connect’s rootful external volume and secret", () = ]); } }); + +test("Browserless uses the same configured port across both runtimes and the client", () => { + const services = JSON.parse(execute(["execute-template", "{{ .services | toJson }}"])); + services.browserless.port = 19333; + const compose = yaml( + render("dot_config/services/browserless/compose.yaml.tmpl", "darwin", services), + ); + assert.equal(compose.name, "browserless"); + assert.deepEqual(compose.services.browserless.ports, ["127.0.0.1:19333:3000"]); + const linux = yaml( + render("dot_config/services/browserless/compose.yaml.tmpl", "linux", services), + ); + assert.deepEqual(linux, compose); + const client = JSON.parse( + render("dot_agent-browser/private_config.json.tmpl", "darwin", services), + ); + assert.equal(client.cdp, "ws://127.0.0.1:19333/chromium"); +});