@phnx-labs/agents-cli 1.19.2 → 1.20.3

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 (156) hide show
  1. package/CHANGELOG.md +140 -0
  2. package/README.md +72 -12
  3. package/dist/browser.js +0 -0
  4. package/dist/commands/browser.js +88 -16
  5. package/dist/commands/cli.d.ts +14 -0
  6. package/dist/commands/cli.js +244 -0
  7. package/dist/commands/cloud.js +1 -1
  8. package/dist/commands/commands.js +27 -10
  9. package/dist/commands/computer.js +18 -1
  10. package/dist/commands/doctor.d.ts +1 -1
  11. package/dist/commands/doctor.js +2 -2
  12. package/dist/commands/exec.js +38 -18
  13. package/dist/commands/factory.d.ts +3 -14
  14. package/dist/commands/factory.js +3 -3
  15. package/dist/commands/feedback.d.ts +7 -0
  16. package/dist/commands/feedback.js +89 -0
  17. package/dist/commands/helper.d.ts +12 -0
  18. package/dist/commands/helper.js +87 -0
  19. package/dist/commands/hooks.js +89 -10
  20. package/dist/commands/mcp.js +166 -10
  21. package/dist/commands/packages.js +196 -27
  22. package/dist/commands/permissions.js +21 -6
  23. package/dist/commands/plugins.js +11 -4
  24. package/dist/commands/profiles.d.ts +8 -0
  25. package/dist/commands/profiles.js +118 -5
  26. package/dist/commands/prune.js +39 -160
  27. package/dist/commands/pull.js +58 -5
  28. package/dist/commands/routines.js +107 -14
  29. package/dist/commands/rules.js +8 -4
  30. package/dist/commands/secrets-migrate.d.ts +24 -0
  31. package/dist/commands/secrets-migrate.js +198 -0
  32. package/dist/commands/secrets-sync.d.ts +11 -0
  33. package/dist/commands/secrets-sync.js +155 -0
  34. package/dist/commands/secrets.js +79 -46
  35. package/dist/commands/sessions.d.ts +28 -0
  36. package/dist/commands/sessions.js +98 -33
  37. package/dist/commands/setup.d.ts +1 -0
  38. package/dist/commands/setup.js +37 -28
  39. package/dist/commands/skills.js +25 -8
  40. package/dist/commands/subagents.js +69 -49
  41. package/dist/commands/teams.js +61 -10
  42. package/dist/commands/utils.d.ts +33 -0
  43. package/dist/commands/utils.js +139 -0
  44. package/dist/commands/versions.d.ts +4 -3
  45. package/dist/commands/versions.js +134 -130
  46. package/dist/commands/view.d.ts +6 -0
  47. package/dist/commands/view.js +175 -19
  48. package/dist/commands/workflows.js +29 -6
  49. package/dist/computer.js +0 -0
  50. package/dist/index.js +38 -6
  51. package/dist/lib/acp/client.js +6 -1
  52. package/dist/lib/acp/harnesses.js +8 -0
  53. package/dist/lib/agents.d.ts +4 -0
  54. package/dist/lib/agents.js +125 -34
  55. package/dist/lib/auto-pull-worker.js +18 -1
  56. package/dist/lib/browser/cdp.d.ts +8 -1
  57. package/dist/lib/browser/cdp.js +40 -3
  58. package/dist/lib/browser/chrome.d.ts +13 -0
  59. package/dist/lib/browser/chrome.js +46 -3
  60. package/dist/lib/browser/domain-skills.d.ts +51 -0
  61. package/dist/lib/browser/domain-skills.js +157 -0
  62. package/dist/lib/browser/drivers/local.js +45 -4
  63. package/dist/lib/browser/drivers/ssh.js +2 -2
  64. package/dist/lib/browser/ipc.d.ts +8 -1
  65. package/dist/lib/browser/ipc.js +37 -28
  66. package/dist/lib/browser/profiles.d.ts +16 -3
  67. package/dist/lib/browser/profiles.js +44 -4
  68. package/dist/lib/browser/service.d.ts +3 -0
  69. package/dist/lib/browser/service.js +40 -5
  70. package/dist/lib/browser/types.d.ts +11 -4
  71. package/dist/lib/cli-resources.d.ts +137 -0
  72. package/dist/lib/cli-resources.js +477 -0
  73. package/dist/lib/cloud/factory.d.ts +1 -1
  74. package/dist/lib/cloud/factory.js +1 -1
  75. package/dist/lib/cloud/rush.js +5 -5
  76. package/dist/lib/command-skills.js +0 -2
  77. package/dist/lib/computer-rpc.d.ts +3 -0
  78. package/dist/lib/computer-rpc.js +53 -0
  79. package/dist/lib/daemon.js +20 -0
  80. package/dist/lib/events.d.ts +16 -2
  81. package/dist/lib/events.js +33 -2
  82. package/dist/lib/exec.d.ts +42 -13
  83. package/dist/lib/exec.js +127 -33
  84. package/dist/lib/help.js +11 -5
  85. package/dist/lib/hooks/cache.d.ts +38 -0
  86. package/dist/lib/hooks/cache.js +242 -0
  87. package/dist/lib/hooks/profile.d.ts +33 -0
  88. package/dist/lib/hooks/profile.js +129 -0
  89. package/dist/lib/hooks.d.ts +0 -10
  90. package/dist/lib/hooks.js +246 -11
  91. package/dist/lib/mcp.d.ts +15 -0
  92. package/dist/lib/mcp.js +46 -0
  93. package/dist/lib/migrate.js +1 -1
  94. package/dist/lib/overdue.d.ts +26 -0
  95. package/dist/lib/overdue.js +101 -0
  96. package/dist/lib/permissions.d.ts +13 -0
  97. package/dist/lib/permissions.js +55 -1
  98. package/dist/lib/plugin-marketplace.js +1 -1
  99. package/dist/lib/plugins.js +15 -1
  100. package/dist/lib/profiles-presets.d.ts +26 -0
  101. package/dist/lib/profiles-presets.js +216 -0
  102. package/dist/lib/profiles.d.ts +34 -0
  103. package/dist/lib/profiles.js +112 -1
  104. package/dist/lib/resources/mcp.js +37 -0
  105. package/dist/lib/resources.d.ts +1 -1
  106. package/dist/lib/rotate.js +10 -4
  107. package/dist/lib/routines-format.d.ts +47 -0
  108. package/dist/lib/routines-format.js +194 -0
  109. package/dist/lib/routines.d.ts +8 -2
  110. package/dist/lib/routines.js +34 -14
  111. package/dist/lib/runner.js +83 -15
  112. package/dist/lib/scheduler.js +8 -1
  113. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  114. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  115. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -9
  116. package/dist/lib/secrets/bundles.d.ts +34 -17
  117. package/dist/lib/secrets/bundles.js +210 -36
  118. package/dist/lib/secrets/index.d.ts +49 -30
  119. package/dist/lib/secrets/index.js +126 -115
  120. package/dist/lib/secrets/install-helper.d.ts +45 -0
  121. package/dist/lib/secrets/install-helper.js +165 -0
  122. package/dist/lib/secrets/linux.js +4 -4
  123. package/dist/lib/secrets/sync.d.ts +56 -0
  124. package/dist/lib/secrets/sync.js +180 -0
  125. package/dist/lib/session/active.d.ts +8 -0
  126. package/dist/lib/session/active.js +3 -2
  127. package/dist/lib/session/db.d.ts +0 -4
  128. package/dist/lib/session/db.js +0 -26
  129. package/dist/lib/session/parse.d.ts +1 -0
  130. package/dist/lib/session/parse.js +44 -0
  131. package/dist/lib/session/render.js +4 -4
  132. package/dist/lib/session/types.d.ts +2 -2
  133. package/dist/lib/session/types.js +1 -1
  134. package/dist/lib/shims.d.ts +5 -2
  135. package/dist/lib/shims.js +70 -38
  136. package/dist/lib/state.d.ts +14 -2
  137. package/dist/lib/state.js +51 -20
  138. package/dist/lib/teams/agents.d.ts +5 -4
  139. package/dist/lib/teams/agents.js +48 -22
  140. package/dist/lib/teams/api.d.ts +2 -1
  141. package/dist/lib/teams/api.js +4 -3
  142. package/dist/lib/teams/parsers.d.ts +1 -1
  143. package/dist/lib/teams/parsers.js +153 -3
  144. package/dist/lib/teams/summarizer.js +18 -2
  145. package/dist/lib/teams/worktree.js +14 -3
  146. package/dist/lib/types.d.ts +63 -4
  147. package/dist/lib/types.js +8 -3
  148. package/dist/lib/usage.d.ts +27 -2
  149. package/dist/lib/usage.js +100 -17
  150. package/dist/lib/versions.d.ts +45 -3
  151. package/dist/lib/versions.js +455 -60
  152. package/package.json +15 -14
  153. package/scripts/install-helper.js +97 -0
  154. package/scripts/postinstall.js +16 -0
  155. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
  156. package/npm-shrinkwrap.json +0 -3162
@@ -2,7 +2,7 @@
2
2
  * Core agent configuration and detection module.
3
3
  *
4
4
  * Defines the canonical registry of all supported AI coding agents (Claude, Codex,
5
- * Gemini, Cursor, OpenCode, OpenClaw, Copilot, Amp, Kiro, Goose, Roo) with their
5
+ * Gemini, Cursor, OpenCode, OpenClaw, Copilot, Amp, Kiro, Goose, Roo, Grok) with their
6
6
  * CLI commands, config paths, capability flags, and MCP integration points.
7
7
  *
8
8
  * Provides functions for detecting installed CLIs, resolving version-managed binaries,
@@ -18,7 +18,6 @@ import chalk from 'chalk';
18
18
  import { walkForFiles } from './fs-walk.js';
19
19
  import { getVersionsDir, getShimsDir, getCliVersionCachePath } from './state.js';
20
20
  import { resolveVersion, getVersionHomePath, getBinaryPath } from './versions.js';
21
- import { loadClaudeOauth } from './usage.js';
22
21
  const execFileAsync = promisify(execFile);
23
22
  const HOME = os.homedir();
24
23
  /**
@@ -77,6 +76,43 @@ function findInPath(command) {
77
76
  }
78
77
  return null;
79
78
  }
79
+ /** Grok-specific binary resolution.
80
+ * Grok does not live in node_modules/.bin. Its versioned binaries live in
81
+ * ~/.grok/downloads/ with names like `grok-0.1.218-macos-aarch64`.
82
+ * We still use the agents-cli version dir for *config isolation* via GROK_HOME.
83
+ */
84
+ function resolveGrokBinary(version) {
85
+ const grokDownloads = path.join(HOME, '.grok', 'downloads');
86
+ if (!fs.existsSync(grokDownloads))
87
+ return null;
88
+ const entries = fs.readdirSync(grokDownloads);
89
+ // Prefer exact version match in filename
90
+ if (version && version !== 'latest') {
91
+ const match = entries.find((e) => e.includes(version) && e.startsWith('grok-'));
92
+ if (match)
93
+ return path.join(grokDownloads, match);
94
+ }
95
+ // Fallback: the "current" symlink or the plain `grok-*` without version in name
96
+ const current = entries.find((e) => e === 'grok' || e.startsWith('grok-') && !e.match(/grok-\d/));
97
+ if (current)
98
+ return path.join(grokDownloads, current);
99
+ // Last resort: newest file by mtime
100
+ let latest = null;
101
+ let latestMtime = 0;
102
+ for (const e of entries) {
103
+ if (!e.startsWith('grok-'))
104
+ continue;
105
+ try {
106
+ const stat = fs.statSync(path.join(grokDownloads, e));
107
+ if (stat.mtimeMs > latestMtime) {
108
+ latestMtime = stat.mtimeMs;
109
+ latest = e;
110
+ }
111
+ }
112
+ catch { }
113
+ }
114
+ return latest ? path.join(grokDownloads, latest) : null;
115
+ }
80
116
  function splitCommandLine(command) {
81
117
  const args = [];
82
118
  let current = '';
@@ -155,7 +191,7 @@ export const AGENTS = {
155
191
  format: 'markdown',
156
192
  variableSyntax: '$ARGUMENTS',
157
193
  supportsHooks: true,
158
- capabilities: { hooks: true, mcp: true, allowlist: true, skills: true, commands: true, plugins: true, rulesImports: true },
194
+ capabilities: { hooks: true, mcp: true, allowlist: true, skills: true, commands: true, plugins: true, modes: ['plan', 'edit', 'auto', 'skip'], rulesImports: true },
159
195
  },
160
196
  // codex hooks: gated to >= 0.116.0 (introduced [features] codex_hooks flag).
161
197
  codex: {
@@ -174,7 +210,7 @@ export const AGENTS = {
174
210
  format: 'markdown',
175
211
  variableSyntax: '$ARGUMENTS',
176
212
  supportsHooks: true,
177
- capabilities: { hooks: { since: '0.116.0' }, mcp: true, allowlist: false, skills: true, commands: { until: '0.117.0' }, plugins: { since: '0.128.0' } },
213
+ capabilities: { hooks: { since: '0.116.0' }, mcp: true, allowlist: false, skills: true, commands: { until: '0.117.0' }, plugins: { since: '0.128.0' }, modes: ['plan', 'edit', 'skip'] },
178
214
  },
179
215
  gemini: {
180
216
  id: 'gemini',
@@ -193,7 +229,7 @@ export const AGENTS = {
193
229
  supportsHooks: true,
194
230
  nativeAgentsSkillsDir: true,
195
231
  // gemini hooks: shipped in v0.26.0 (Jan 2026); older binaries silently ignore the `hooks` key.
196
- capabilities: { hooks: { since: '0.26.0' }, mcp: true, allowlist: false, skills: true, commands: true, plugins: false, rulesImports: true },
232
+ capabilities: { hooks: { since: '0.26.0' }, mcp: true, allowlist: false, skills: true, commands: true, plugins: false, modes: ['plan', 'edit', 'skip'], rulesImports: true },
197
233
  },
198
234
  cursor: {
199
235
  id: 'cursor',
@@ -211,7 +247,7 @@ export const AGENTS = {
211
247
  format: 'markdown',
212
248
  variableSyntax: '$ARGUMENTS',
213
249
  supportsHooks: false,
214
- capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false },
250
+ capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false, modes: ['edit', 'skip'] },
215
251
  },
216
252
  opencode: {
217
253
  id: 'opencode',
@@ -228,7 +264,7 @@ export const AGENTS = {
228
264
  format: 'markdown',
229
265
  variableSyntax: '$ARGUMENTS',
230
266
  supportsHooks: false,
231
- capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false },
267
+ capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false, modes: ['plan', 'edit'] },
232
268
  },
233
269
  openclaw: {
234
270
  id: 'openclaw',
@@ -245,7 +281,7 @@ export const AGENTS = {
245
281
  format: 'markdown',
246
282
  variableSyntax: '{{ARGUMENTS}}',
247
283
  supportsHooks: true,
248
- capabilities: { hooks: true, mcp: true, allowlist: false, skills: true, commands: false, plugins: true },
284
+ capabilities: { hooks: true, mcp: true, allowlist: false, skills: true, commands: false, plugins: true, modes: ['plan', 'edit', 'skip'] },
249
285
  },
250
286
  copilot: {
251
287
  id: 'copilot',
@@ -262,7 +298,7 @@ export const AGENTS = {
262
298
  format: 'markdown',
263
299
  variableSyntax: '$ARGUMENTS',
264
300
  supportsHooks: false,
265
- capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false },
301
+ capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false, modes: ['plan', 'edit', 'auto', 'skip'] },
266
302
  },
267
303
  amp: {
268
304
  id: 'amp',
@@ -279,7 +315,7 @@ export const AGENTS = {
279
315
  format: 'markdown',
280
316
  variableSyntax: '$ARGUMENTS',
281
317
  supportsHooks: false,
282
- capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false },
318
+ capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false, modes: ['plan', 'edit'] },
283
319
  },
284
320
  kiro: {
285
321
  id: 'kiro',
@@ -297,7 +333,7 @@ export const AGENTS = {
297
333
  format: 'markdown',
298
334
  variableSyntax: '$ARGUMENTS',
299
335
  supportsHooks: false,
300
- capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false },
336
+ capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false, modes: ['edit'] },
301
337
  },
302
338
  goose: {
303
339
  id: 'goose',
@@ -315,7 +351,7 @@ export const AGENTS = {
315
351
  format: 'markdown',
316
352
  variableSyntax: '$ARGUMENTS',
317
353
  supportsHooks: false,
318
- capabilities: { hooks: false, mcp: true, allowlist: false, skills: false, commands: false, plugins: false },
354
+ capabilities: { hooks: false, mcp: true, allowlist: false, skills: false, commands: false, plugins: false, modes: ['edit'] },
319
355
  },
320
356
  roo: {
321
357
  id: 'roo',
@@ -333,12 +369,17 @@ export const AGENTS = {
333
369
  format: 'markdown',
334
370
  variableSyntax: '$ARGUMENTS',
335
371
  supportsHooks: false,
336
- capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false },
372
+ capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false, modes: ['plan', 'edit'] },
337
373
  },
338
374
  // Google Antigravity CLI (`agy`) — official replacement for Gemini CLI as of IO 2026.
339
375
  // configDir nests inside `~/.gemini/` since agy shares the parent dir with the Gemini
340
376
  // CLI but isolates its own state in the `antigravity-cli/` subdir. Per-version HOME
341
377
  // isolation works because the shim's configDirName carries the full nested path.
378
+ // Auth: Google OAuth on first launch, or ANTIGRAVITY_API_KEY env var for headless.
379
+ // Hooks: JSON entries under a top-level `hooks` key in settings.json; events are
380
+ // before_tool_call, after_model_call, on_loop_stop, on_error. Plugins are the
381
+ // renamed Gemini CLI extensions. Permissions live in settings.json under a
382
+ // `permissions` key with allow/deny arrays.
342
383
  antigravity: {
343
384
  id: 'antigravity',
344
385
  name: 'Antigravity',
@@ -354,30 +395,41 @@ export const AGENTS = {
354
395
  instructionsFile: 'AGENTS.md',
355
396
  format: 'markdown',
356
397
  variableSyntax: '{{args}}',
357
- supportsHooks: false,
358
- nativeAgentsSkillsDir: true,
359
- capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false, rulesImports: false },
398
+ supportsHooks: true,
399
+ capabilities: { hooks: true, mcp: true, allowlist: true, skills: true, commands: true, plugins: true, modes: ['edit', 'skip'], rulesImports: false },
360
400
  },
361
401
  // xAI Grok Build CLI (`grok`) — early beta, SuperGrok Heavy. Auth via OAuth on
362
- // first launch, or GROK_CODE_XAI_API_KEY env var for headless. MCP supported
363
- // out of the box; exact config file path verified at first install.
402
+ // first launch, or XAI_API_KEY env var for headless. MCP servers configured inline
403
+ // under [mcp_servers] in ~/.grok/config.toml. Hooks auto-discovered from
404
+ // ~/.grok/hooks/ (+ project .grok/hooks/) — events PreToolUse, PostToolUse, etc.
405
+ // Plugins live in ~/.grok/plugins/ with marketplaces. Permissions: --allow/--deny
406
+ // CLI flags or [permission] TOML block in ~/.grok/config.toml.
364
407
  grok: {
365
408
  id: 'grok',
366
409
  name: 'Grok',
367
- color: 'whiteBright',
410
+ color: 'cyanBright',
368
411
  cliCommand: 'grok',
369
412
  npmPackage: '',
370
413
  installScript: 'curl -fsSL https://x.ai/cli/install.sh | bash',
371
414
  configDir: path.join(HOME, '.grok'),
372
- commandsDir: path.join(HOME, '.grok', 'commands'),
373
- commandsSubdir: 'commands',
415
+ commandsDir: '', // Grok primarily uses skills + slash commands from skills
416
+ commandsSubdir: '',
374
417
  skillsDir: path.join(HOME, '.grok', 'skills'),
375
- hooksDir: 'hooks',
418
+ hooksDir: path.join(HOME, '.grok', 'hooks'),
376
419
  instructionsFile: 'AGENTS.md',
377
420
  format: 'markdown',
378
421
  variableSyntax: '$ARGUMENTS',
379
- supportsHooks: false,
380
- capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false },
422
+ supportsHooks: true,
423
+ capabilities: {
424
+ hooks: true,
425
+ mcp: true,
426
+ allowlist: true, // maps to Grok's granular Bash/Edit/Write/Read/Grep/WebFetch/MCPTool rules
427
+ skills: true,
428
+ commands: false, // covered by skills
429
+ plugins: true,
430
+ modes: ['plan', 'edit', 'skip'],
431
+ rulesImports: true,
432
+ },
381
433
  },
382
434
  };
383
435
  /** All registered agent IDs derived from the AGENTS registry. */
@@ -499,6 +551,18 @@ export async function getCliState(agentId) {
499
551
  }
500
552
  }
501
553
  // Non-version-managed: single PATH lookup + cached version read
554
+ // Special case for grok: it manages its own binaries in ~/.grok/downloads/
555
+ if (agentId === 'grok') {
556
+ const grokBin = resolveGrokBinary();
557
+ if (!grokBin) {
558
+ return { installed: false, version: null, path: null };
559
+ }
560
+ return {
561
+ installed: true,
562
+ version: await getCachedVersionForBinary(agentId, grokBin),
563
+ path: grokBin,
564
+ };
565
+ }
502
566
  const binaryPath = findInPath(agent.cliCommand);
503
567
  if (!binaryPath) {
504
568
  return { installed: false, version: null, path: null };
@@ -532,7 +596,7 @@ export function isConfigured(agentId) {
532
596
  */
533
597
  export async function getUnmanagedAgentInstalls() {
534
598
  const unmanaged = [];
535
- const candidates = ['claude', 'codex', 'gemini'];
599
+ const candidates = ['claude', 'codex', 'gemini', 'grok'];
536
600
  for (const agentId of candidates) {
537
601
  const agent = AGENTS[agentId];
538
602
  try {
@@ -611,13 +675,15 @@ export async function getAccountInfo(agentId, home) {
611
675
  ['org', organizationId],
612
676
  ]);
613
677
  const usageKey = buildIdentityKey(agentId, [['org', organizationId]]);
678
+ // Plan is derived from .claude.json's billingType only. Reading
679
+ // subscriptionType from the keychain item ("Claude Code-credentials-<hash>")
680
+ // forces a macOS Keychain ACL prompt on every `agents run` (one prompt per
681
+ // installed version under balanced rotation) because Claude Code writes its
682
+ // credentials with its own process in the ACL — our helper isn't trusted by
683
+ // that item. Callers that genuinely need subscriptionType (e.g. detailed
684
+ // `agents view`) should call loadClaudeOauth() directly.
614
685
  let plan = null;
615
- const keychainOauth = await loadClaudeOauth(home);
616
- if (keychainOauth?.subscriptionType) {
617
- plan = keychainOauth.subscriptionType.charAt(0).toUpperCase()
618
- + keychainOauth.subscriptionType.slice(1);
619
- }
620
- else if (oa?.billingType === 'stripe_subscription') {
686
+ if (oa?.billingType === 'stripe_subscription') {
621
687
  plan = 'Pro';
622
688
  }
623
689
  else if (oa?.billingType) {
@@ -698,6 +764,19 @@ export async function getAccountInfo(agentId, home) {
698
764
  const data = JSON.parse(await fs.promises.readFile(path.join(base, '.gemini', 'google_accounts.json'), 'utf-8'));
699
765
  return { ...empty, email: data.active || null, lastActive };
700
766
  }
767
+ case 'grok': {
768
+ // Grok stores auth in ~/.grok/auth.json
769
+ try {
770
+ const authPath = path.join(base, '.grok', 'auth.json');
771
+ if (fs.existsSync(authPath)) {
772
+ const data = JSON.parse(await fs.promises.readFile(authPath, 'utf-8'));
773
+ const email = data.email || data.user?.email || data.account?.email || null;
774
+ return { ...empty, email, lastActive };
775
+ }
776
+ }
777
+ catch { }
778
+ return { ...empty, lastActive };
779
+ }
701
780
  default:
702
781
  return { ...empty, lastActive };
703
782
  }
@@ -735,6 +814,12 @@ function getSessionDir(agentId, base) {
735
814
  return path.join(base, '.codex', 'sessions');
736
815
  case 'gemini':
737
816
  return path.join(base, '.gemini', 'tmp');
817
+ case 'grok':
818
+ return path.join(base, '.grok', 'sessions');
819
+ case 'copilot':
820
+ // Copilot persists sessions at ~/.copilot/session-state/<id>/events.jsonl.
821
+ // The events.jsonl is the canonical NDJSON event stream per session.
822
+ return path.join(base, '.copilot', 'session-state');
738
823
  default:
739
824
  return null;
740
825
  }
@@ -744,9 +829,12 @@ function getSessionExtension(agentId) {
744
829
  switch (agentId) {
745
830
  case 'claude':
746
831
  case 'codex':
832
+ case 'copilot':
747
833
  return '.jsonl';
748
834
  case 'gemini':
749
835
  return '.json';
836
+ case 'grok':
837
+ return '.json'; // sessions contain summary.json, events.jsonl, etc.
750
838
  default:
751
839
  return null;
752
840
  }
@@ -1110,7 +1198,7 @@ function parseMcpFromOpenCodeConfig(configPath) {
1110
1198
  /**
1111
1199
  * Get user-scoped MCP config path for an agent.
1112
1200
  */
1113
- function getUserMcpConfigPath(agentId) {
1201
+ export function getUserMcpConfigPath(agentId) {
1114
1202
  const agent = AGENTS[agentId];
1115
1203
  switch (agentId) {
1116
1204
  case 'claude':
@@ -1128,6 +1216,9 @@ function getUserMcpConfigPath(agentId) {
1128
1216
  case 'openclaw':
1129
1217
  // OpenClaw uses openclaw.json
1130
1218
  return path.join(agent.configDir, 'openclaw.json');
1219
+ case 'copilot':
1220
+ // GitHub Copilot CLI uses mcp-config.json (matches versioned + project paths)
1221
+ return path.join(agent.configDir, 'mcp-config.json');
1131
1222
  case 'antigravity':
1132
1223
  // agy uses mcp_config.json inside its nested config dir (~/.gemini/antigravity-cli/)
1133
1224
  return path.join(agent.configDir, 'mcp_config.json');
@@ -1167,7 +1258,7 @@ export function getMcpConfigPathForHome(agentId, home) {
1167
1258
  case 'antigravity':
1168
1259
  return path.join(home, '.gemini', 'antigravity-cli', 'mcp_config.json');
1169
1260
  case 'grok':
1170
- return path.join(home, '.grok', 'mcp.json');
1261
+ return path.join(home, '.grok', 'config.toml');
1171
1262
  default:
1172
1263
  return path.join(home, `.${agentId}`, 'settings.json');
1173
1264
  }
@@ -1203,7 +1294,7 @@ function getProjectMcpConfigPath(agentId, cwd = process.cwd()) {
1203
1294
  case 'antigravity':
1204
1295
  return path.join(cwd, '.gemini', 'antigravity-cli', 'mcp_config.json');
1205
1296
  case 'grok':
1206
- return path.join(cwd, '.grok', 'mcp.json');
1297
+ return path.join(cwd, '.grok', 'config.toml');
1207
1298
  default:
1208
1299
  return path.join(cwd, `.${agentId}`, 'settings.json');
1209
1300
  }
@@ -14,6 +14,15 @@ import { tryAutoPull, isGitRepo } from './git.js';
14
14
  import { getSystemAgentsDir, getUserAgentsDir, getEnabledExtraRepos, getFetchCacheDir, } from './state.js';
15
15
  import { lockFilePath, statusFilePath } from './auto-pull.js';
16
16
  const LOCK_TTL_MS = 5 * 60 * 1000;
17
+ /**
18
+ * Background auto-pull of ~/.agents-system/ is off by default. When enabled it
19
+ * silently fast-forwards a tracked source tree that the CLI then reads as a
20
+ * source of skills, hooks, install manifests, and commands — anyone with push
21
+ * access to that upstream gets remote code execution on every user the next
22
+ * time they invoke a command that loads a system resource. Operators that
23
+ * really want the convenience can set AGENTS_AUTO_PULL=1.
24
+ */
25
+ const ENABLE_AUTO_PULL = process.env.AGENTS_AUTO_PULL === '1';
17
26
  function ensureFetchDir() {
18
27
  const dir = getFetchCacheDir();
19
28
  if (!fs.existsSync(dir)) {
@@ -84,7 +93,15 @@ async function processTarget(target) {
84
93
  return;
85
94
  try {
86
95
  if (target.mode === 'pull') {
87
- await tryAutoPull(target.dir);
96
+ if (!ENABLE_AUTO_PULL) {
97
+ // Demote to a fetch + notify; the user still sees ahead/behind on the
98
+ // next foreground CLI invocation, but the source tree is never mutated
99
+ // by a detached worker.
100
+ await notifyRepo(target);
101
+ }
102
+ else {
103
+ await tryAutoPull(target.dir);
104
+ }
88
105
  }
89
106
  else {
90
107
  await notifyRepo(target);
@@ -32,7 +32,14 @@ export interface BrowserDiscovery {
32
32
  wsUrl: string;
33
33
  browser: string;
34
34
  }
35
- export declare function discoverBrowserWsUrl(port: number, host?: string): Promise<BrowserDiscovery>;
35
+ export declare class BrowserCdpConnectionError extends Error {
36
+ readonly port: number;
37
+ readonly profileName: string;
38
+ readonly host: string;
39
+ constructor(port: number, profileName?: string, host?: string);
40
+ }
41
+ export declare function formatBrowserCdpConnectionError(port: number, profileName?: string, host?: string): string;
42
+ export declare function discoverBrowserWsUrl(port: number, host?: string, profileName?: string): Promise<BrowserDiscovery>;
36
43
  export declare function normalizeBrowserName(s: string): string;
37
44
  export declare function verifyBrowserIdentity(reported: string, expected: string, port: number, host?: string): void;
38
45
  export declare function listTargets(port: number, host?: string): Promise<Array<{
@@ -152,10 +152,47 @@ export class CDPClient {
152
152
  this.transport = null;
153
153
  }
154
154
  }
155
- export async function discoverBrowserWsUrl(port, host = 'localhost') {
156
- const response = await fetch(`http://${host}:${port}/json/version`);
155
+ export class BrowserCdpConnectionError extends Error {
156
+ port;
157
+ profileName;
158
+ host;
159
+ constructor(port, profileName = '<name>', host = 'localhost') {
160
+ super(formatBrowserCdpConnectionError(port, profileName, host));
161
+ this.port = port;
162
+ this.profileName = profileName;
163
+ this.host = host;
164
+ this.name = 'BrowserCdpConnectionError';
165
+ }
166
+ }
167
+ export function formatBrowserCdpConnectionError(port, profileName = '<name>', host = 'localhost') {
168
+ const target = host === 'localhost' || host === '127.0.0.1'
169
+ ? `port ${port}`
170
+ : `${host}:${port}`;
171
+ return [
172
+ `Could not connect to Chrome on ${target}.`,
173
+ `- Is Chrome running with --remote-debugging-port=${port}?`,
174
+ `- Try: agents browser start --profile ${profileName}`,
175
+ ].join('\n');
176
+ }
177
+ export async function discoverBrowserWsUrl(port, host = 'localhost', profileName = '<name>') {
178
+ // Node's fetch has no default timeout — a port that ACKs the TCP connect
179
+ // but never sends an HTTP response will hang here indefinitely. Bound the
180
+ // discovery probe so the caller can surface an actionable error in seconds,
181
+ // not minutes.
182
+ const controller = new AbortController();
183
+ const timer = setTimeout(() => controller.abort(), 3000);
184
+ let response;
185
+ try {
186
+ response = await fetch(`http://${host}:${port}/json/version`, { signal: controller.signal });
187
+ }
188
+ catch {
189
+ throw new BrowserCdpConnectionError(port, profileName, host);
190
+ }
191
+ finally {
192
+ clearTimeout(timer);
193
+ }
157
194
  if (!response.ok) {
158
- throw new Error(`Failed to discover browser: ${response.status}`);
195
+ throw new BrowserCdpConnectionError(port, profileName, host);
159
196
  }
160
197
  const data = (await response.json());
161
198
  const browserField = data.Browser || data.Product || '';
@@ -1,6 +1,19 @@
1
1
  import type { ChromeOptions } from './types.js';
2
2
  import type { BrowserType } from './types.js';
3
3
  export declare function findBrowserPath(browserType: BrowserType, customBinary?: string): string;
4
+ /**
5
+ * Walk the per-platform priority list and return the first browser that's
6
+ * actually installed on disk. Returns null if none of them are present.
7
+ *
8
+ * This is the auto-pick the `agents browser start` command uses when the user
9
+ * doesn't pass `--profile`. The intent matches "use whatever's preinstalled,"
10
+ * but constrained to Chromium-family binaries so CDP works without a new
11
+ * driver layer.
12
+ */
13
+ export declare function findFirstInstalledBrowser(platform?: string): {
14
+ browserType: BrowserType;
15
+ binary: string;
16
+ } | null;
4
17
  export interface LaunchResult {
5
18
  pid: number;
6
19
  port: number;
@@ -4,7 +4,7 @@ import * as path from 'path';
4
4
  import * as os from 'os';
5
5
  import { getProfileRuntimeDir } from './profiles.js';
6
6
  import { discoverBrowserWsUrl, registerPipeTransport } from './cdp.js';
7
- import { readBundle, resolveBundleEnv, bundleExists } from '../secrets/bundles.js';
7
+ import { readAndResolveBundleEnv, bundleExists } from '../secrets/bundles.js';
8
8
  import { writeProfileRuntime, readProfileRuntime } from './runtime-state.js';
9
9
  const BROWSER_PATHS = {
10
10
  darwin: {
@@ -65,6 +65,45 @@ export function findBrowserPath(browserType, customBinary) {
65
65
  }
66
66
  throw new Error(`Browser "${browserType}" not found. Install it first.`);
67
67
  }
68
+ // Per-platform Chromium-family priority list for "no --profile" auto-pick.
69
+ // Order is: most-likely-installed-and-stable first. Safari and Firefox are
70
+ // intentionally excluded — they don't speak the Chrome DevTools Protocol the
71
+ // way cdp.ts expects, so they'd need separate drivers.
72
+ const DEFAULT_BROWSER_PRIORITY = {
73
+ // macOS: Chrome leads (>70% of dev machines), then the rest of the family.
74
+ darwin: ['chrome', 'brave', 'edge', 'chromium', 'comet'],
75
+ // Linux: Chrome/Chromium first (apt/snap), then Brave/Edge if present.
76
+ linux: ['chrome', 'chromium', 'brave', 'edge'],
77
+ // Windows: Edge is preinstalled on every supported build, so it's the
78
+ // reliable always-there default.
79
+ win32: ['edge', 'chrome', 'brave'],
80
+ };
81
+ /**
82
+ * Walk the per-platform priority list and return the first browser that's
83
+ * actually installed on disk. Returns null if none of them are present.
84
+ *
85
+ * This is the auto-pick the `agents browser start` command uses when the user
86
+ * doesn't pass `--profile`. The intent matches "use whatever's preinstalled,"
87
+ * but constrained to Chromium-family binaries so CDP works without a new
88
+ * driver layer.
89
+ */
90
+ export function findFirstInstalledBrowser(platform = os.platform()) {
91
+ const priority = DEFAULT_BROWSER_PRIORITY[platform];
92
+ if (!priority)
93
+ return null;
94
+ const platformPaths = BROWSER_PATHS[platform];
95
+ if (!platformPaths)
96
+ return null;
97
+ for (const browserType of priority) {
98
+ const candidates = platformPaths[browserType] || [];
99
+ for (const p of candidates) {
100
+ if (fs.existsSync(p)) {
101
+ return { browserType, binary: p };
102
+ }
103
+ }
104
+ }
105
+ return null;
106
+ }
68
107
  export async function launchBrowser(profileName, browserType, port, options = {}, secrets, customBinary,
69
108
  // `electron: true` distinguishes Notion / VS Code-style apps from
70
109
  // regular Chrome — purely informational, stored in meta.json so the
@@ -99,6 +138,11 @@ isElectron = false) {
99
138
  '--no-first-run',
100
139
  '--no-default-browser-check',
101
140
  '--disable-features=DefaultBrowserSetting,ChromeWhatsNewUI',
141
+ '--disable-crash-reporter',
142
+ // Suppress navigator.webdriver = true, which Chromium sets whenever a
143
+ // remote-debugging transport is active. That property is the loudest
144
+ // signal Cloudflare Turnstile, hCaptcha, and similar checks read.
145
+ '--disable-blink-features=AutomationControlled',
102
146
  ...(options.headless ? ['--headless=new'] : []),
103
147
  `--window-size=${viewport.width},${viewport.height}`,
104
148
  ...(viewport.x !== undefined && viewport.y !== undefined
@@ -109,8 +153,7 @@ isElectron = false) {
109
153
  let env = { ...process.env };
110
154
  if (secrets && bundleExists(secrets)) {
111
155
  try {
112
- const bundle = readBundle(secrets);
113
- const bundleEnv = resolveBundleEnv(bundle, { caller: 'browser profile' });
156
+ const { env: bundleEnv } = readAndResolveBundleEnv(secrets, { caller: 'browser profile' });
114
157
  env = { ...env, ...bundleEnv };
115
158
  }
116
159
  catch {
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Domain-skill discovery for `agents browser start`.
3
+ *
4
+ * When a browser task opens a URL, look up a site-specific SKILL.md from
5
+ * `~/.agents/skills/browser/domain-skills/<dir>/SKILL.md` and surface its
6
+ * contents so the calling agent gets per-site operating instructions
7
+ * (selectors, gotchas, sign-in quirks) before it starts driving the page.
8
+ *
9
+ * Matching is intentionally simple: derive the hostname's second-level
10
+ * label (e.g. `perplexity` from `perplexity.ai`, `slack` from `app.slack.com`)
11
+ * and look for a directory of the same name. If the user wants a different
12
+ * mapping (e.g. `mail.google.com` -> `gmail/`), they can pin it via a
13
+ * `domains: [...]` array in the SKILL.md frontmatter; that override beats
14
+ * the directory-name match.
15
+ */
16
+ /** Result of resolving a URL to a domain-skill. */
17
+ export interface ResolvedDomainSkill {
18
+ /** Skill identifier — the directory name under domain-skills/. */
19
+ name: string;
20
+ /** Absolute path to the SKILL.md that was matched. */
21
+ path: string;
22
+ /** Full SKILL.md contents (frontmatter included). */
23
+ content: string;
24
+ /** Hostname the match was made against (post-www strip). */
25
+ hostname: string;
26
+ }
27
+ /** Where domain-skills live. Override via $AGENTS_BROWSER_DOMAIN_SKILLS_DIR for tests. */
28
+ export declare function domainSkillsRoot(): string;
29
+ /**
30
+ * Derive match candidates from a hostname. Order matters — earlier candidates
31
+ * are tried first.
32
+ *
33
+ * Examples:
34
+ * perplexity.ai -> ['perplexity.ai', 'perplexity']
35
+ * app.slack.com -> ['app.slack.com', 'slack.com', 'slack']
36
+ * mail.google.com -> ['mail.google.com', 'google.com', 'google', 'mail']
37
+ * higgsfield.ai -> ['higgsfield.ai', 'higgsfield']
38
+ */
39
+ export declare function hostnameMatchCandidates(hostname: string): string[];
40
+ /**
41
+ * Resolve a URL to its matching domain-skill, or null if none.
42
+ *
43
+ * Two-pass strategy:
44
+ * 1. Index every SKILL.md in the root and read its `domains:` frontmatter.
45
+ * If any pinned domain matches a candidate, return that skill.
46
+ * 2. Fall back to directory-name match against the candidate list.
47
+ *
48
+ * Errors (missing root, unreadable file, invalid URL) are swallowed and
49
+ * yield null — domain-skill discovery must never break browser start.
50
+ */
51
+ export declare function resolveDomainSkill(url: string): ResolvedDomainSkill | null;