@phnx-labs/agents-cli 1.19.1 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +70 -10
  3. package/dist/commands/browser.js +88 -16
  4. package/dist/commands/cli.d.ts +14 -0
  5. package/dist/commands/cli.js +244 -0
  6. package/dist/commands/commands.js +3 -3
  7. package/dist/commands/computer.js +18 -1
  8. package/dist/commands/doctor.d.ts +1 -1
  9. package/dist/commands/doctor.js +2 -2
  10. package/dist/commands/exec.js +3 -3
  11. package/dist/commands/factory.d.ts +3 -14
  12. package/dist/commands/factory.js +3 -3
  13. package/dist/commands/hooks.js +3 -3
  14. package/dist/commands/mcp.js +29 -0
  15. package/dist/commands/plugins.js +11 -4
  16. package/dist/commands/profiles.js +1 -1
  17. package/dist/commands/prune.js +39 -160
  18. package/dist/commands/pull.js +56 -3
  19. package/dist/commands/routines.js +106 -13
  20. package/dist/commands/secrets.js +6 -8
  21. package/dist/commands/sessions.d.ts +36 -7
  22. package/dist/commands/sessions.js +130 -53
  23. package/dist/commands/setup.d.ts +1 -0
  24. package/dist/commands/setup.js +37 -28
  25. package/dist/commands/skills.js +3 -3
  26. package/dist/commands/teams.js +13 -0
  27. package/dist/commands/versions.d.ts +4 -3
  28. package/dist/commands/versions.js +147 -124
  29. package/dist/commands/view.js +12 -12
  30. package/dist/index.js +34 -6
  31. package/dist/lib/acp/harnesses.js +8 -0
  32. package/dist/lib/agents.js +162 -9
  33. package/dist/lib/browser/cdp.d.ts +8 -1
  34. package/dist/lib/browser/cdp.js +40 -3
  35. package/dist/lib/browser/chrome.d.ts +13 -0
  36. package/dist/lib/browser/chrome.js +42 -3
  37. package/dist/lib/browser/domain-skills.d.ts +51 -0
  38. package/dist/lib/browser/domain-skills.js +157 -0
  39. package/dist/lib/browser/drivers/local.js +45 -4
  40. package/dist/lib/browser/drivers/ssh.js +1 -1
  41. package/dist/lib/browser/ipc.d.ts +8 -1
  42. package/dist/lib/browser/ipc.js +37 -28
  43. package/dist/lib/browser/profiles.d.ts +13 -0
  44. package/dist/lib/browser/profiles.js +41 -1
  45. package/dist/lib/browser/service.d.ts +3 -0
  46. package/dist/lib/browser/service.js +21 -5
  47. package/dist/lib/browser/types.d.ts +7 -0
  48. package/dist/lib/cli-resources.d.ts +109 -0
  49. package/dist/lib/cli-resources.js +255 -0
  50. package/dist/lib/cloud/rush.js +5 -5
  51. package/dist/lib/command-skills.js +0 -2
  52. package/dist/lib/computer-rpc.d.ts +3 -0
  53. package/dist/lib/computer-rpc.js +53 -0
  54. package/dist/lib/daemon.js +20 -0
  55. package/dist/lib/exec.d.ts +3 -2
  56. package/dist/lib/exec.js +62 -6
  57. package/dist/lib/hooks.js +182 -0
  58. package/dist/lib/mcp.js +6 -0
  59. package/dist/lib/migrate.js +1 -1
  60. package/dist/lib/overdue.d.ts +26 -0
  61. package/dist/lib/overdue.js +101 -0
  62. package/dist/lib/permissions.js +5 -1
  63. package/dist/lib/plugin-marketplace.js +1 -1
  64. package/dist/lib/profiles-presets.js +37 -0
  65. package/dist/lib/registry.d.ts +18 -0
  66. package/dist/lib/registry.js +44 -0
  67. package/dist/lib/resources/mcp.js +43 -1
  68. package/dist/lib/resources/types.d.ts +1 -1
  69. package/dist/lib/resources.d.ts +1 -1
  70. package/dist/lib/rotate.js +10 -4
  71. package/dist/lib/routines-format.d.ts +35 -0
  72. package/dist/lib/routines-format.js +173 -0
  73. package/dist/lib/routines.d.ts +7 -1
  74. package/dist/lib/routines.js +32 -12
  75. package/dist/lib/runner.js +19 -5
  76. package/dist/lib/scheduler.js +8 -1
  77. package/dist/lib/secrets/{AgentsKeychain.app → Agents CLI.app}/Contents/CodeResources +0 -0
  78. package/dist/lib/secrets/{AgentsKeychain.app/Contents/Info.plist → Agents CLI.app/Contents/Info.plist } +4 -2
  79. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  80. package/dist/lib/secrets/bundles.d.ts +33 -2
  81. package/dist/lib/secrets/bundles.js +249 -26
  82. package/dist/lib/secrets/index.d.ts +10 -1
  83. package/dist/lib/secrets/index.js +143 -48
  84. package/dist/lib/session/active.d.ts +8 -0
  85. package/dist/lib/session/active.js +3 -2
  86. package/dist/lib/session/db.d.ts +10 -4
  87. package/dist/lib/session/db.js +16 -16
  88. package/dist/lib/session/parse.d.ts +1 -0
  89. package/dist/lib/session/parse.js +44 -0
  90. package/dist/lib/session/types.d.ts +1 -1
  91. package/dist/lib/session/types.js +1 -1
  92. package/dist/lib/shims.d.ts +6 -2
  93. package/dist/lib/shims.js +88 -10
  94. package/dist/lib/state.d.ts +0 -1
  95. package/dist/lib/state.js +2 -15
  96. package/dist/lib/teams/agents.js +1 -1
  97. package/dist/lib/teams/parsers.d.ts +1 -1
  98. package/dist/lib/teams/parsers.js +153 -3
  99. package/dist/lib/teams/summarizer.js +18 -2
  100. package/dist/lib/teams/worktree.js +14 -3
  101. package/dist/lib/types.d.ts +7 -4
  102. package/dist/lib/types.js +6 -3
  103. package/dist/lib/versions.d.ts +10 -2
  104. package/dist/lib/versions.js +227 -35
  105. package/package.json +9 -9
  106. package/dist/lib/secrets/AgentsKeychain.app/Contents/MacOS/AgentsKeychain +0 -0
  107. package/npm-shrinkwrap.json +0 -3162
  108. /package/dist/lib/secrets/{AgentsKeychain.app → Agents CLI.app}/Contents/_CodeSignature/CodeResources +0 -0
  109. /package/dist/lib/secrets/{AgentsKeychain.app → Agents CLI.app}/Contents/embedded.provisionprofile +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,72 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.20.0
4
+
5
+ **Routines (overdue detection + catchup)**
6
+
7
+ - Detect routines whose most recent scheduled fire was missed (laptop off, daemon crashed, reboot). The daemon logs them on startup and pops a native desktop notification (`osascript` on macOS, `notify-send` on Linux).
8
+ - `agents routines list` annotates overdue rows with `(overdue)` and prints a footer pointing at the catchup command.
9
+ - New `agents routines catchup` command: lists overdue routines and fires them in the background under the scheduler. `--dry-run` lists without triggering.
10
+ - `JobScheduler.schedule` now sets croner's `catch: true` and forwards `timezone` defensively, so a synchronous throw in one job's callback can't kill the whole cron loop.
11
+
12
+ **Landing page (agents-cli.sh)**
13
+
14
+ - Expanded the homepage with seven new sections: rotate accounts (`--rotate`), parallel teams (`agents teams`), browser automation, cross-agent session search, routines/cron, keychain secrets, and machine-to-machine sync (`agents drive`).
15
+ - Rewrote meta description + lede to spell out the actual feature set (pin versions, swap models, rotate accounts, drive a browser, spawn parallel teams, schedule on cron) instead of just "same interface, on your machine."
16
+
17
+ **Codex (commands-as-skills sync fix)**
18
+
19
+ - Fix recurring "N commands new" prompt on `agents view codex` for Codex >= 0.117.0. `getActuallySyncedResources` now detects converted command-skills via the `agents_command` marker in `~/.codex/skills/<name>/SKILL.md` instead of only scanning the empty legacy `prompts/` directory.
20
+ - Summary and selection prompts are version-aware: the static `COMMANDS_CAPABLE_AGENTS` gate is replaced by `supports(agent, 'commands', version)` so the "X commands" line only appears for versions that can actually take them.
21
+ - Generalize `shouldInstallCommandAsSkill` beyond Codex — any agent where commands are gated off and skills are on (e.g. Grok) now gets the same automatic slash-command → skill conversion at install/sync time.
22
+
23
+ **Grok Build (first-class support)**
24
+
25
+ - Add `grok` as a first-class supported agent (AgentId + full registry entry using official `~/.grok/README.md` paths).
26
+ - Implement proper binary resolution from `~/.grok/downloads/`.
27
+ - Add `GROK_HOME` isolation to generated shims for true versioned config (skills, hooks, plugins, agents/, MCP, memory, etc.).
28
+ - Extend `installVersion` to support Grok via its official installer script (`curl ... -s <version>`).
29
+ - Update shims, exec templates, MCP path helpers, session helpers, unmanaged detection, and docs.
30
+ - `agents add grok@<ver>`, `agents use grok@<ver>`, resource sync, and shims now work end-to-end for Grok Build.
31
+
32
+ **Browser**
33
+
34
+ - `agents browser start --record` convenience flag for one-shot recording sessions.
35
+ - Auto-discover per-site `SKILL.md` on `browser start` so skills appear under the active task without manual wiring.
36
+ - Auto-pick a Chromium-family browser when `--profile` is omitted; the limitation is surfaced in `--help` and the auto-pick error.
37
+ - No more stacktraces when the daemon is down or CDP is unreachable — error paths print a single human-readable line.
38
+ - Drop the Playwright `bundled-chromium` devdependency.
39
+
40
+ **Secrets / Keychain**
41
+
42
+ - `agents secrets list` and `agents run --secrets <bundle>` collapse to one Touch ID prompt per bundle instead of one per key. Previously every secret in a bundle would re-prompt for keychain unlock.
43
+
44
+ **Sessions**
45
+
46
+ - Extract `groupActiveSessions` into a tested helper for `--active` window grouping.
47
+ - Propagate `windowid` from live-terminals into the active session record.
48
+
49
+ **Copilot**
50
+
51
+ - Emit `COPILOT_HOME` in the shim and exec env builder for versioned isolation.
52
+ - Wire the Copilot session dir and `.jsonl` extension into the sessions reader.
53
+
54
+ **OpenClaw**
55
+
56
+ - Carry OpenClaw user data forward on version switch.
57
+
58
+ **Teams**
59
+
60
+ - Warn loudly when `--after` teammates reference a name whose watch process never launched, instead of silently sitting in pending state.
61
+
62
+ **Plugins**
63
+
64
+ - Use `'directory'` source discriminator (not `'local'`) for marketplace registration so plugins reload correctly.
65
+
66
+ **Dependencies**
67
+
68
+ - Bump `@inquirer/prompts` 7.10.1 → 8.5.1, `diff` 8.0.4 → 9.0.0, `tsx` 4.22.2 → 4.22.3, `actions/setup-node` 4.4.0 → 6.4.0.
69
+
3
70
  ## 1.18.6
4
71
 
5
72
  **Claude**
package/README.md CHANGED
@@ -27,6 +27,8 @@
27
27
  <a href="https://github.com/openclaw/openclaw" title="OpenClaw"><img src="assets/harnesses/openclaw.svg" height="36" alt="OpenClaw" /></a>
28
28
  &nbsp;&nbsp;&nbsp;&nbsp;
29
29
  <a href="https://github.com/NousResearch/hermes-agent" title="Hermes Agent"><img src="assets/harnesses/hermes.png" height="32" alt="Hermes Agent" /></a>
30
+ &nbsp;&nbsp;&nbsp;&nbsp;
31
+ <a href="https://x.ai" title="Grok Build (xAI)"><strong>Grok</strong></a>
30
32
  </p>
31
33
 
32
34
  https://agents-cli.sh/demo.mp4
@@ -79,7 +81,7 @@ agents:
79
81
  codex: "0.116.0"
80
82
  ```
81
83
 
82
- Think `requirements.txt` for CLI coding agents, on steroids. A shim reads `agents.yaml` from the project root and routes `claude` / `codex` / `gemini` to the right version automatically. Each version gets its own isolated home -- switching backs up config and re-syncs resources.
84
+ Think `requirements.txt` for CLI coding agents, on steroids. A shim reads `agents.yaml` from the project root and routes `claude` / `codex` / `gemini` / `grok` (and others) to the right version automatically. Each version gets its own isolated home -- switching backs up config and re-syncs resources.
83
85
 
84
86
  ```bash
85
87
  agents add claude@2.0.65 # Install a specific version
@@ -154,9 +156,9 @@ Supports plan (read-only) and edit modes, effort levels, JSON output for scripti
154
156
  agents run claude "review this diff" --acp --json
155
157
  ```
156
158
 
157
- `--acp` routes through the [Agent Client Protocol](https://agentclientprotocol.com/) so you get a unified event stream -- `agent_message_chunk`, `tool_call`, `plan_update`, `stop_reason` -- instead of writing a parser per CLI. File writes and shell commands flow through agents-cli, which means `--mode plan` becomes a real sandbox: the write RPC is denied, not just unused.
159
+ `--acp` routes through the [Agent Client Protocol](https://github.com/zed-industries/agent-client-protocol) so you get a unified event stream -- `agent_message_chunk`, `tool_call`, `plan_update`, `stop_reason` -- instead of writing a parser per CLI. File writes and shell commands flow through agents-cli, which means `--mode plan` becomes a real sandbox: the write RPC is denied, not just unused.
158
160
 
159
- ACP adapters are documented for claude, codex, gemini, cursor, opencode, and openclaw. Other harnesses keep running on the direct-exec path.
161
+ ACP adapters are documented for claude, codex, gemini, cursor, opencode, openclaw, and grok. Other harnesses keep running on the direct-exec path.
160
162
 
161
163
  ---
162
164
 
@@ -281,7 +283,7 @@ A workflow is a directory:
281
283
  ---
282
284
  name: Code Review
283
285
  description: Evidence-grounded PR review with file:line citations.
284
- model: claude-opus-4-7
286
+ model: opus
285
287
  tools:
286
288
  - Read
287
289
  - Grep
@@ -296,15 +298,70 @@ Resolution is project > user > system: a `<repo>/.agents/workflows/<name>/` over
296
298
 
297
299
  ---
298
300
 
301
+ ## Plugins
302
+
303
+ Bundle skills, commands, hooks, MCP servers, settings, and permissions under a single manifest. One source dir at `~/.agents/plugins/<name>/`, mirrored into every installed Claude / OpenClaw version automatically.
304
+
305
+ ```bash
306
+ # Install from a git URL or local path
307
+ agents plugins install hivemind@https://github.com/activeloopai/hivemind.git
308
+ agents plugins install ./my-plugin
309
+
310
+ # Apply to one agent (default version) or all supported
311
+ agents plugins sync rush-toolkit claude
312
+ agents plugins sync rush-toolkit
313
+ ```
314
+
315
+ A plugin is a directory with a manifest:
316
+
317
+ ```
318
+ ~/.agents/plugins/my-plugin/
319
+ .claude-plugin/plugin.json # required: { name, version, description }
320
+ skills/<name>/SKILL.md # optional
321
+ commands/*.md # optional
322
+ hooks/hooks.json # optional — executable surface
323
+ .mcp.json # optional — executable surface
324
+ bin/, scripts/, settings.json # optional — executable surface
325
+ permissions/ # optional — executable surface
326
+ ```
327
+
328
+ On sync, agents-cli copies the plugin into each version home's marketplace (`<home>/.claude/plugins/marketplaces/agents-cli/plugins/<name>/`), registers the synthetic marketplace, and flips `settings.json#enabledPlugins[<name>@agents-cli] = true` so Claude / OpenClaw load it.
329
+
330
+ ### Executable-surface gate
331
+
332
+ Plugins that ship `hooks/`, `.mcp.json`, `bin/`, `scripts/`, `settings.json` (non-permissions), or `permissions/` can execute code on session events. agents-cli requires explicit consent before flipping `enabledPlugins`:
333
+
334
+ ```bash
335
+ # Hooks-bearing plugins copy in but stay disabled by default
336
+ agents plugins install hivemind@https://github.com/activeloopai/hivemind.git \
337
+ --allow-exec-surfaces
338
+
339
+ # Same gate on re-sync (e.g., after upstream updates)
340
+ agents plugins sync hivemind claude --allow-exec-surfaces
341
+ ```
342
+
343
+ Skills, commands, and subagents are declarative and never trip the gate. The gate is per-plugin, per-install: consenting to hivemind doesn't grant blanket exec-surface trust to anything else.
344
+
345
+ ### Version portability
346
+
347
+ Plugins live in the user repo (`~/.agents/plugins/`), not inside any single version home. Switching Claude via `agents use claude@<v>` re-syncs the plugin into the new version automatically — no re-install. New Claude versions added later pick it up on their first sync. Project-level `<repo>/.agents/plugins/<name>/` overrides a same-named user plugin (resolution is project > user > system, same as every other resource).
348
+
349
+ ---
350
+
299
351
  ## Browser
300
352
 
301
353
  Give agents access to a real browser — no relay extension, no cloud service, no Playwright getting blocked.
302
354
 
303
355
  ```bash
304
- # Create an isolated profile for automation
356
+ # First run: omit --profile and we auto-pick the first installed Chromium-family
357
+ # browser. macOS prefers Chrome > Brave > Edge > Chromium > Comet; Linux prefers
358
+ # Chrome > Chromium > Brave > Edge; Windows prefers Edge (always preinstalled) >
359
+ # Chrome > Brave. The auto-picked profile is saved as "default" for later runs.
360
+ export AGENTS_BROWSER_TASK=$(agents browser start --url https://app.example.com)
361
+
362
+ # Or pin a named profile to a specific browser (chrome, comet, brave, chromium,
363
+ # edge, or custom) when you want isolation from "default".
305
364
  agents browser profiles create work --browser chrome
306
-
307
- # Start a task once, then bind it to this shell — every later command picks it up.
308
365
  # `start` writes the resolved name (e.g. `swift-crab-falcon-a3f92b1c`) to stdout
309
366
  # and human-friendly commentary to stderr, so $(...) capture stays clean.
310
367
  export AGENTS_BROWSER_TASK=$(agents browser start --profile work --url https://app.example.com)
@@ -384,6 +441,9 @@ agents browser profiles create cloud \
384
441
 
385
442
  ## Secrets
386
443
 
444
+ > **Platform:** `agents secrets` requires macOS Keychain or Linux libsecret.
445
+ > On Windows (non-WSL), use environment variables or a `.env` file instead.
446
+
387
447
  ```bash
388
448
  # API keys in Keychain, not in .env files.
389
449
  agents secrets create prod-stripe
@@ -414,7 +474,7 @@ agents secrets list # npm-tokens is already there;
414
474
  agents run claude "..." --secrets npm-tokens # injects NPM_TOKEN automatically
415
475
  ```
416
476
 
417
- Under the hood, synced bundles route writes through a notarized helper app (`AgentsKeychain.app`) that holds the entitlement macOS requires for `kSecAttrSynchronizable`. Bundles created with `--no-icloud-sync` stay device-local.
477
+ Under the hood, synced bundles route writes through a notarized helper app (`Agents CLI.app`) that holds the entitlement macOS requires for `kSecAttrSynchronizable`. Bundles created with `--no-icloud-sync` stay device-local.
418
478
 
419
479
  Bundle definitions sync via iCloud Keychain too — no `agents repo push` needed for secrets, no recreate step on each Mac. Nothing about secrets ever lives in plaintext on disk.
420
480
 
@@ -538,7 +598,7 @@ Every agent run, version install, browser launch, and secrets access is logged t
538
598
  }
539
599
  ```
540
600
 
541
- **What's logged:** Operation type, agent, version, timing, truncated prompts (first 200 chars), exit codes, errors. **What's NOT logged:** Full prompts, outputs, file contents, secret values (only bundle names).
601
+ **What's logged:** Operation type, agent, version, timing, truncated prompts (first 200 chars), exit codes, errors, and secret bundle/key names with caller context. **What's NOT logged:** Full prompts, outputs, file contents, or secret values.
542
602
 
543
603
  **Permissions:** Logs directory is `0700` (owner-only), files are `0600`. Only you can read them.
544
604
 
@@ -603,7 +663,7 @@ Codex command sync is version-aware: Codex `0.116.x` and older receive slash com
603
663
 
604
664
  ### Why use `agents` instead of `claude` / `codex` / `gemini` directly?
605
665
 
606
- Claude Code, Codex CLI, and Gemini CLI each have their own config format, MCP setup, version management, and skill system. If you use more than one, you maintain N copies of everything. `agents` gives you one interface, one config source, and one place to pin versions -- plus features the individual CLIs don't ship: cross-agent pipelines, shared teams, unified session search, and project-pinned versions like `.nvmrc`.
666
+ Claude Code, Codex CLI, Gemini CLI, Grok Build, and others each have their own config format, MCP setup, version management, and skill system. If you use more than one, you maintain N copies of everything. `agents` gives you one interface, one config source, and one place to pin versions -- plus features the individual CLIs don't ship: cross-agent pipelines, shared teams, unified session search, and project-pinned versions like `.nvmrc`.
607
667
 
608
668
  ### Is it free?
609
669
 
@@ -1,12 +1,12 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
- import { listProfiles, getProfile, createProfile, deleteProfile, getProfileRuntimeDir, extractConfiguredPort, findFreeProfilePort, getEndpointPresets, } from '../lib/browser/profiles.js';
3
+ import { listProfiles, getProfile, createProfile, deleteProfile, ensureDefaultBrowserProfile, getProfileRuntimeDir, extractConfiguredPort, findFreeProfilePort, getEndpointPresets, } from '../lib/browser/profiles.js';
4
4
  import { findBrowserPath, getPortOccupant } from '../lib/browser/chrome.js';
5
5
  import { listProfileCacheDirs, removeProfileCache, listAllProfileSnapshots, } from '../lib/browser/runtime-state.js';
6
6
  import { DEFAULT_VIEWPORT } from '../lib/browser/devices.js';
7
7
  import { discoverBrowserWsUrl, verifyBrowserIdentity } from '../lib/browser/cdp.js';
8
8
  import { parseTargetFilter } from '../lib/browser/service.js';
9
- import { sendIPCRequest } from '../lib/browser/ipc.js';
9
+ import { BrowserDaemonNotRunningError, formatBrowserDaemonNotRunningError, sendIPCRequest, } from '../lib/browser/ipc.js';
10
10
  import { browserTaskPicker } from './browser-picker.js';
11
11
  import { isInteractiveTerminal } from './utils.js';
12
12
  import { registerCommandGroups, setHelpSections } from '../lib/help.js';
@@ -59,8 +59,11 @@ export function registerBrowserCommand(program) {
59
59
  # Create a Chrome profile pointed at a CDP endpoint
60
60
  agents browser profiles create work --browser chrome --endpoint http://localhost:9222
61
61
 
62
- # Start a session against a profile
63
- agents browser start work
62
+ # Start a session auto-picks the first installed Chromium-family browser
63
+ agents browser start
64
+
65
+ # Or pin to a specific profile
66
+ agents browser start --profile work
64
67
 
65
68
  # Drive the page
66
69
  agents browser navigate https://example.com
@@ -72,6 +75,12 @@ export function registerBrowserCommand(program) {
72
75
  notes: `
73
76
  Most agent workflows should use the 'browser' skill instead of raw subcommands.
74
77
  The skill wraps profile selection, snapshotting, and tunneling.
78
+
79
+ Browser support: Chromium-family only (Chrome, Comet, Chromium, Brave, Edge).
80
+ Safari and Firefox are not supported — they don't speak the Chrome DevTools
81
+ Protocol the way agents browser expects. On Windows, Edge is the default
82
+ because it's preinstalled. On macOS and Linux, Chrome is preferred when
83
+ installed; otherwise the first Chromium-family binary on disk wins.
75
84
  `,
76
85
  });
77
86
  registerProfilesCommands(browser);
@@ -346,7 +355,7 @@ function registerProfilesCommands(browser) {
346
355
  }
347
356
  else {
348
357
  try {
349
- const { browser } = await discoverBrowserWsUrl(port);
358
+ const { browser } = await discoverBrowserWsUrl(port, 'localhost', profile.name);
350
359
  verifyBrowserIdentity(browser, profile.browser, port);
351
360
  checks.push({
352
361
  label: 'port',
@@ -437,39 +446,56 @@ function registerProfilesCommands(browser) {
437
446
  function registerTaskCommands(browser) {
438
447
  browser
439
448
  .command('start')
440
- .description('Start a browser task with a profile')
441
- .requiredOption('-p, --profile <name>', 'Browser profile to use')
449
+ .description('Start a browser task. Pass --profile <name>, or omit to auto-pick a Chromium-family browser already installed on this machine.')
450
+ .option('-p, --profile <name>', 'Browser profile to use (auto-picks from installed Chromium-family browsers if omitted)')
442
451
  .option(TASK_OPTION_FLAG, 'Task name (auto-generated if omitted)')
443
452
  .option('-e, --endpoint <name>', 'Endpoint preset (defaults to the profile\'s default)')
444
453
  .option('-u, --url <url>', 'Open URL in first tab')
454
+ .option('--no-skills', 'Skip auto-discovery of site-specific SKILL.md from ~/.agents/skills/browser/domain-skills/')
455
+ .option('--record', 'Start recording right after the tab opens (shorthand for `agents browser record start` as a follow-up)')
456
+ .option('--fps <n>', 'Recording frames per second (with --record; 1–30, default 5)', (v) => parseInt(v, 10))
457
+ .option('--duration <sec>', 'Recording duration cap in seconds (with --record; default 60)', (v) => parseInt(v, 10))
458
+ .option('--max-mb <mb>', 'Recording size cap in MB (with --record; default 25)', (v) => parseInt(v, 10))
445
459
  .action(async (opts) => {
460
+ let profileName = opts.profile;
461
+ if (!profileName) {
462
+ try {
463
+ const detected = await ensureDefaultBrowserProfile();
464
+ profileName = detected.name;
465
+ }
466
+ catch (err) {
467
+ console.error(err instanceof Error ? err.message : String(err));
468
+ process.exit(1);
469
+ }
470
+ }
446
471
  // Pre-check the profile locally so we fail fast with a helpful error
447
472
  // instead of round-tripping a generic "Profile not found" through the daemon.
448
- const profile = await getProfile(opts.profile);
473
+ const profile = await getProfile(profileName);
449
474
  if (!profile) {
450
- console.error(`Profile "${opts.profile}" not found.`);
475
+ console.error(`Profile "${profileName}" not found.`);
451
476
  const all = await listProfiles();
452
477
  if (all.length > 0) {
453
478
  console.error(`Available profiles: ${all.map((p) => p.name).join(', ')}`);
454
479
  }
455
- console.error(`Create one with: agents browser profiles create ${opts.profile} --browser <chrome|comet|chromium|brave|edge|custom>`);
480
+ console.error(`Create one with: agents browser profiles create ${profileName} --browser <chrome|comet|chromium|brave|edge|custom>`);
456
481
  process.exit(1);
457
482
  }
458
483
  // Pre-check the endpoint name too — same fail-fast rationale.
459
484
  if (opts.endpoint) {
460
485
  const presets = getEndpointPresets(profile);
461
486
  if (!presets[opts.endpoint]) {
462
- console.error(`Endpoint "${opts.endpoint}" not found on profile "${opts.profile}". ` +
487
+ console.error(`Endpoint "${opts.endpoint}" not found on profile "${profileName}". ` +
463
488
  `Available: ${Object.keys(presets).join(', ')}`);
464
489
  process.exit(1);
465
490
  }
466
491
  }
467
492
  const response = await sendIPCRequest({
468
493
  action: 'start',
469
- profile: opts.profile,
494
+ profile: profileName,
470
495
  taskName: opts.task,
471
496
  url: opts.url,
472
497
  endpoint: opts.endpoint,
498
+ skipDomainSkill: opts.skills === false,
473
499
  });
474
500
  if (!response.ok) {
475
501
  console.error(response.error);
@@ -488,6 +514,36 @@ function registerTaskCommands(browser) {
488
514
  }
489
515
  console.error(`Tip: export AGENTS_BROWSER_TASK=${response.task}`);
490
516
  console.error('Try: agents browser screenshot | agents browser console --level error');
517
+ // Surface the matched domain-skill (if any) so an agent driving the
518
+ // task picks up site-specific selectors and gotchas before it starts
519
+ // clicking. Header is recognizable so an agent parsing the stream can
520
+ // extract the skill content; suffix repeats the skill name for greps.
521
+ if (response.skill) {
522
+ console.error('');
523
+ console.error(`--- domain-skill: ${response.skill.name} (${response.skill.hostname}) ---`);
524
+ console.error(response.skill.content);
525
+ console.error(`--- end domain-skill: ${response.skill.name} ---`);
526
+ }
527
+ // --record convenience: fire record-start right after the tab opens so
528
+ // the user gets a single-command capture flow. Failures here are
529
+ // reported but don't fail the start — the task is already running.
530
+ if (opts.record) {
531
+ const recordResponse = await sendIPCRequest({
532
+ action: 'record-start',
533
+ task: response.task,
534
+ tabId: response.tabId,
535
+ fps: opts.fps,
536
+ duration: opts.duration,
537
+ maxMb: opts.maxMb,
538
+ });
539
+ if (!recordResponse.ok) {
540
+ console.error(`Recording failed to start: ${recordResponse.error}`);
541
+ }
542
+ else {
543
+ console.error(`Recording at ${recordResponse.fps} fps (cap ${recordResponse.durationCapSec}s / ${recordResponse.maxMb} MB) -> ${recordResponse.path}`);
544
+ console.error('Stop with: agents browser record stop');
545
+ }
546
+ }
491
547
  });
492
548
  browser
493
549
  .command('done')
@@ -798,10 +854,26 @@ function registerTaskCommands(browser) {
798
854
  .option('-p, --profile <name>', 'Filter by profile')
799
855
  .option('--json', 'Output machine-readable JSON')
800
856
  .action(async (opts) => {
801
- const response = await sendIPCRequest({
802
- action: 'status',
803
- profile: opts.profile,
804
- });
857
+ let response;
858
+ try {
859
+ response = await sendIPCRequest({
860
+ action: 'status',
861
+ profile: opts.profile,
862
+ }, { autoStartDaemon: false });
863
+ }
864
+ catch (err) {
865
+ if (err instanceof BrowserDaemonNotRunningError) {
866
+ const message = formatBrowserDaemonNotRunningError();
867
+ if (opts.json) {
868
+ console.log(JSON.stringify({ ok: false, error: message }));
869
+ }
870
+ else {
871
+ console.error(message);
872
+ }
873
+ process.exit(1);
874
+ }
875
+ throw err;
876
+ }
805
877
  if (!response.ok) {
806
878
  if (opts.json) {
807
879
  console.log(JSON.stringify({ ok: false, error: response.error }));
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `agents cli` — manage declarative CLI binary installs.
3
+ *
4
+ * Each entry under <repo>/cli/<name>.yaml declares a CLI tool the user wants on
5
+ * the host PATH (e.g. higgsfield, gh, glab). On a fresh machine `agents cli
6
+ * install` runs the first install method whose package manager is available
7
+ * (npm > brew > script > binary, in declared order).
8
+ *
9
+ * This is a sibling to `agents mcp` but one layer down: MCP wires servers into
10
+ * agent configs; CLI puts binaries on the user's normal PATH. CLI manifests are
11
+ * NOT copied into per-agent version homes — they are global to the user.
12
+ */
13
+ import type { Command } from 'commander';
14
+ export declare function registerCliCommands(program: Command): void;
@@ -0,0 +1,244 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import chalk from 'chalk';
4
+ import { confirm } from '@inquirer/prompts';
5
+ import { listCliManifests, listCliStatus, resolveCliManifest, installCli, describeMethod, selectInstallMethod, isCliInstalled, } from '../lib/cli-resources.js';
6
+ import { getUserAgentsDir } from '../lib/state.js';
7
+ import { isPromptCancelled } from './utils.js';
8
+ function userCliDir() {
9
+ return path.join(getUserAgentsDir(), 'cli');
10
+ }
11
+ /** Render the status table — one row per declared CLI. */
12
+ function printStatus(rows) {
13
+ if (rows.length === 0) {
14
+ console.log(chalk.gray('No CLIs declared.'));
15
+ console.log(chalk.gray(`Create one with: agents cli add <name>`));
16
+ return;
17
+ }
18
+ const nameWidth = Math.max(4, ...rows.map((r) => r.manifest.name.length));
19
+ for (const row of rows) {
20
+ const status = row.installed
21
+ ? chalk.green('installed')
22
+ : chalk.yellow('missing');
23
+ const name = row.manifest.name.padEnd(nameWidth);
24
+ const source = chalk.gray(`[${row.manifest.source}]`);
25
+ const desc = row.manifest.description ? ' ' + chalk.gray(row.manifest.description) : '';
26
+ console.log(` ${name} ${status} ${source}${desc}`);
27
+ }
28
+ }
29
+ export function registerCliCommands(program) {
30
+ const cliCmd = program
31
+ .command('cli')
32
+ .description('Declare and install host CLI binaries (gh, higgsfield, glab, ...)')
33
+ .addHelpText('after', `
34
+ CLI manifests live in <repo>/cli/<name>.yaml and declare how to install a
35
+ binary on the host. On a fresh machine, 'agents cli install' runs the first
36
+ compatible method (npm > brew > script > binary) for every declared entry.
37
+
38
+ Examples:
39
+ # See which declared CLIs are installed on this host
40
+ agents cli list
41
+
42
+ # Install everything that's missing
43
+ agents cli install
44
+
45
+ # Install one
46
+ agents cli install higgsfield
47
+
48
+ # Show the manifest detail
49
+ agents cli view higgsfield
50
+
51
+ # Exit 0 if all declared CLIs are installed (use in CI / setup scripts)
52
+ agents cli check
53
+
54
+ When to use:
55
+ - After 'agents pull' on a new machine, to materialize host binaries
56
+ - In a team setup: commit cli/ entries so teammates get the same toolchain
57
+ `);
58
+ cliCmd
59
+ .command('list')
60
+ .description('Show all declared CLIs and whether each is installed on this host')
61
+ .action(() => {
62
+ const { statuses, errors } = listCliStatus(process.cwd());
63
+ printStatus(statuses);
64
+ for (const err of errors) {
65
+ console.log(chalk.red(` parse error: ${err.file}: ${err.reason}`));
66
+ }
67
+ });
68
+ cliCmd
69
+ .command('check')
70
+ .description('Exit 0 if every declared CLI is installed, 1 otherwise')
71
+ .action(() => {
72
+ const { statuses, errors } = listCliStatus(process.cwd());
73
+ const missing = statuses.filter((s) => !s.installed);
74
+ if (errors.length > 0) {
75
+ for (const err of errors) {
76
+ console.error(chalk.red(`parse error: ${err.file}: ${err.reason}`));
77
+ }
78
+ process.exit(1);
79
+ }
80
+ if (missing.length === 0) {
81
+ console.log(chalk.green(`All ${statuses.length} declared CLI(s) installed.`));
82
+ return;
83
+ }
84
+ console.log(chalk.yellow(`Missing: ${missing.map((s) => s.manifest.name).join(', ')}`));
85
+ process.exit(1);
86
+ });
87
+ cliCmd
88
+ .command('install [name]')
89
+ .description('Install one (by name) or all missing declared CLIs')
90
+ .option('-y, --yes', 'skip the confirmation prompt')
91
+ .option('--dry-run', 'print install commands without executing them')
92
+ .option('--force', 'reinstall even when the check command currently passes')
93
+ .action(async (nameArg, opts) => {
94
+ let targets;
95
+ if (nameArg) {
96
+ const manifest = resolveCliManifest(nameArg, process.cwd());
97
+ if (!manifest) {
98
+ console.error(chalk.red(`No CLI manifest named "${nameArg}".`));
99
+ console.error(chalk.gray(`Looked in: ${userCliDir()}`));
100
+ process.exit(1);
101
+ }
102
+ targets = [manifest];
103
+ }
104
+ else {
105
+ const { manifests, errors } = listCliManifests(process.cwd());
106
+ for (const err of errors) {
107
+ console.error(chalk.red(`parse error: ${err.file}: ${err.reason}`));
108
+ }
109
+ if (manifests.length === 0) {
110
+ console.log(chalk.gray('No CLIs declared. Nothing to install.'));
111
+ return;
112
+ }
113
+ targets = manifests;
114
+ }
115
+ // Filter out already-installed unless --force
116
+ const work = targets.filter((m) => opts.force || !isCliInstalled(m));
117
+ if (work.length === 0) {
118
+ console.log(chalk.green(`All ${targets.length} declared CLI(s) already installed.`));
119
+ return;
120
+ }
121
+ // Preview + confirm
122
+ console.log(chalk.bold('\nWill install:'));
123
+ for (const m of work) {
124
+ const method = selectInstallMethod(m);
125
+ const action = method ? describeMethod(method) : chalk.red('no compatible install method');
126
+ console.log(` ${chalk.cyan(m.name.padEnd(20))} ${chalk.gray(action)}`);
127
+ }
128
+ console.log('');
129
+ if (!opts.yes && !opts.dryRun) {
130
+ try {
131
+ const proceed = await confirm({ message: 'Proceed?', default: true });
132
+ if (!proceed) {
133
+ console.log(chalk.gray('Cancelled.'));
134
+ return;
135
+ }
136
+ }
137
+ catch (err) {
138
+ if (isPromptCancelled(err)) {
139
+ console.log(chalk.gray('Cancelled.'));
140
+ return;
141
+ }
142
+ throw err;
143
+ }
144
+ }
145
+ // Execute
146
+ let failures = 0;
147
+ for (const m of work) {
148
+ console.log(chalk.bold(`\n→ ${m.name}`));
149
+ const result = installCli(m, { dryRun: opts.dryRun });
150
+ if (result.output)
151
+ console.log(chalk.gray(result.output));
152
+ if (result.error) {
153
+ console.log(chalk.red(` ${result.error}`));
154
+ failures++;
155
+ continue;
156
+ }
157
+ if (opts.dryRun)
158
+ continue;
159
+ if (result.installed) {
160
+ console.log(chalk.green(` installed (${describeMethod(result.method)})`));
161
+ if (m.postInstall) {
162
+ console.log(chalk.gray(m.postInstall.trim().split('\n').map((l) => ' ' + l).join('\n')));
163
+ }
164
+ }
165
+ else {
166
+ console.log(chalk.yellow(` install command ran but \`${m.check}\` still fails — check the output above`));
167
+ failures++;
168
+ }
169
+ }
170
+ if (failures > 0)
171
+ process.exit(1);
172
+ });
173
+ cliCmd
174
+ .command('view <name>')
175
+ .description('Show the parsed manifest detail')
176
+ .action((name) => {
177
+ const manifest = resolveCliManifest(name, process.cwd());
178
+ if (!manifest) {
179
+ console.error(chalk.red(`No CLI manifest named "${name}".`));
180
+ process.exit(1);
181
+ }
182
+ console.log(chalk.bold.cyan(manifest.name));
183
+ if (manifest.description)
184
+ console.log(' ' + chalk.gray(manifest.description));
185
+ if (manifest.homepage)
186
+ console.log(' ' + chalk.gray(manifest.homepage));
187
+ console.log(' ' + chalk.gray(`source: ${manifest.source}`));
188
+ console.log(' ' + chalk.gray(`file: ${manifest.path}`));
189
+ console.log('');
190
+ console.log(chalk.bold(' check'));
191
+ console.log(' ' + manifest.check);
192
+ console.log('');
193
+ console.log(chalk.bold(' install methods'));
194
+ for (const method of manifest.install) {
195
+ console.log(' ' + describeMethod(method));
196
+ }
197
+ if (manifest.postInstall) {
198
+ console.log('');
199
+ console.log(chalk.bold(' post_install'));
200
+ for (const line of manifest.postInstall.trim().split('\n')) {
201
+ console.log(' ' + line);
202
+ }
203
+ }
204
+ console.log('');
205
+ console.log(` status: ${isCliInstalled(manifest) ? chalk.green('installed') : chalk.yellow('missing')}`);
206
+ });
207
+ cliCmd
208
+ .command('add <name>')
209
+ .description('Scaffold a new manifest at ~/.agents/cli/<name>.yaml')
210
+ .option('--npm <pkg>', 'declare an npm install method')
211
+ .option('--brew <formula>', 'declare a brew install method')
212
+ .option('--script <url>', 'declare a curl|sh install method')
213
+ .option('--description <text>', 'one-line description')
214
+ .option('--homepage <url>', 'project homepage')
215
+ .action((name, opts) => {
216
+ const dir = userCliDir();
217
+ fs.mkdirSync(dir, { recursive: true });
218
+ const target = path.join(dir, `${name}.yaml`);
219
+ if (fs.existsSync(target)) {
220
+ console.error(chalk.red(`Already exists: ${target}`));
221
+ process.exit(1);
222
+ }
223
+ const methods = [];
224
+ if (opts.npm)
225
+ methods.push(` - npm: "${opts.npm}"`);
226
+ if (opts.brew)
227
+ methods.push(` - brew: ${opts.brew}`);
228
+ if (opts.script)
229
+ methods.push(` - script: ${opts.script}`);
230
+ if (methods.length === 0) {
231
+ methods.push(` - npm: "${name}"`);
232
+ }
233
+ const lines = [
234
+ `name: ${name}`,
235
+ ...(opts.description ? [`description: ${opts.description}`] : []),
236
+ ...(opts.homepage ? [`homepage: ${opts.homepage}`] : []),
237
+ `check: ${name} --version`,
238
+ `install:`,
239
+ ...methods,
240
+ ];
241
+ fs.writeFileSync(target, lines.join('\n') + '\n');
242
+ console.log(chalk.green(`Created ${target}`));
243
+ });
244
+ }