@phnx-labs/agents-cli 1.20.0 → 1.20.4

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 (111) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/README.md +4 -4
  3. package/dist/commands/cli.js +3 -3
  4. package/dist/commands/cloud.js +1 -1
  5. package/dist/commands/commands.js +24 -7
  6. package/dist/commands/exec.js +36 -16
  7. package/dist/commands/feedback.d.ts +7 -0
  8. package/dist/commands/feedback.js +89 -0
  9. package/dist/commands/helper.d.ts +12 -0
  10. package/dist/commands/helper.js +87 -0
  11. package/dist/commands/hooks.js +86 -7
  12. package/dist/commands/import.js +90 -37
  13. package/dist/commands/mcp.js +166 -10
  14. package/dist/commands/packages.js +196 -27
  15. package/dist/commands/permissions.js +21 -6
  16. package/dist/commands/profiles.d.ts +8 -0
  17. package/dist/commands/profiles.js +117 -4
  18. package/dist/commands/pull.js +4 -4
  19. package/dist/commands/routines.js +6 -6
  20. package/dist/commands/rules.js +8 -4
  21. package/dist/commands/secrets-migrate.d.ts +24 -0
  22. package/dist/commands/secrets-migrate.js +198 -0
  23. package/dist/commands/secrets-sync.d.ts +11 -0
  24. package/dist/commands/secrets-sync.js +155 -0
  25. package/dist/commands/secrets.js +74 -39
  26. package/dist/commands/skills.js +22 -5
  27. package/dist/commands/subagents.js +69 -49
  28. package/dist/commands/teams.js +48 -10
  29. package/dist/commands/utils.d.ts +33 -0
  30. package/dist/commands/utils.js +139 -0
  31. package/dist/commands/versions.js +4 -4
  32. package/dist/commands/view.d.ts +6 -0
  33. package/dist/commands/view.js +169 -8
  34. package/dist/commands/workflows.js +29 -6
  35. package/dist/index.js +4 -0
  36. package/dist/lib/acp/client.js +6 -1
  37. package/dist/lib/agents.d.ts +4 -0
  38. package/dist/lib/agents.js +41 -17
  39. package/dist/lib/auto-pull-worker.js +18 -1
  40. package/dist/lib/browser/chrome.js +4 -0
  41. package/dist/lib/browser/drivers/ssh.js +1 -1
  42. package/dist/lib/browser/profiles.d.ts +3 -3
  43. package/dist/lib/browser/profiles.js +3 -3
  44. package/dist/lib/browser/service.js +19 -0
  45. package/dist/lib/browser/types.d.ts +4 -4
  46. package/dist/lib/cli-resources.d.ts +36 -8
  47. package/dist/lib/cli-resources.js +268 -46
  48. package/dist/lib/cloud/factory.d.ts +1 -1
  49. package/dist/lib/cloud/factory.js +1 -1
  50. package/dist/lib/events.d.ts +16 -2
  51. package/dist/lib/events.js +33 -2
  52. package/dist/lib/exec.d.ts +39 -11
  53. package/dist/lib/exec.js +90 -31
  54. package/dist/lib/help.js +11 -5
  55. package/dist/lib/hooks/cache.d.ts +38 -0
  56. package/dist/lib/hooks/cache.js +242 -0
  57. package/dist/lib/hooks/profile.d.ts +33 -0
  58. package/dist/lib/hooks/profile.js +129 -0
  59. package/dist/lib/hooks.d.ts +0 -10
  60. package/dist/lib/hooks.js +68 -15
  61. package/dist/lib/import.d.ts +21 -0
  62. package/dist/lib/import.js +55 -2
  63. package/dist/lib/mcp.d.ts +15 -0
  64. package/dist/lib/mcp.js +40 -0
  65. package/dist/lib/permissions.d.ts +13 -0
  66. package/dist/lib/permissions.js +51 -1
  67. package/dist/lib/plugin-marketplace.d.ts +10 -0
  68. package/dist/lib/plugin-marketplace.js +47 -1
  69. package/dist/lib/plugins.js +15 -1
  70. package/dist/lib/profiles-presets.d.ts +26 -0
  71. package/dist/lib/profiles-presets.js +187 -8
  72. package/dist/lib/profiles.d.ts +34 -0
  73. package/dist/lib/profiles.js +112 -1
  74. package/dist/lib/pty-server.js +27 -3
  75. package/dist/lib/routines-format.d.ts +17 -5
  76. package/dist/lib/routines-format.js +37 -16
  77. package/dist/lib/routines.d.ts +1 -1
  78. package/dist/lib/routines.js +2 -2
  79. package/dist/lib/runner.js +64 -10
  80. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  81. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  82. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -9
  83. package/dist/lib/secrets/bundles.d.ts +18 -22
  84. package/dist/lib/secrets/bundles.js +75 -99
  85. package/dist/lib/secrets/index.d.ts +51 -27
  86. package/dist/lib/secrets/index.js +147 -156
  87. package/dist/lib/secrets/install-helper.d.ts +45 -0
  88. package/dist/lib/secrets/install-helper.js +165 -0
  89. package/dist/lib/secrets/linux.js +4 -4
  90. package/dist/lib/secrets/sync.d.ts +56 -0
  91. package/dist/lib/secrets/sync.js +180 -0
  92. package/dist/lib/session/render.js +4 -4
  93. package/dist/lib/session/types.d.ts +1 -1
  94. package/dist/lib/shims.d.ts +4 -1
  95. package/dist/lib/shims.js +5 -35
  96. package/dist/lib/state.d.ts +14 -1
  97. package/dist/lib/state.js +49 -5
  98. package/dist/lib/teams/agents.d.ts +5 -4
  99. package/dist/lib/teams/agents.js +47 -21
  100. package/dist/lib/teams/api.d.ts +2 -1
  101. package/dist/lib/teams/api.js +4 -3
  102. package/dist/lib/types.d.ts +57 -1
  103. package/dist/lib/types.js +2 -0
  104. package/dist/lib/usage.d.ts +27 -2
  105. package/dist/lib/usage.js +100 -17
  106. package/dist/lib/versions.d.ts +35 -1
  107. package/dist/lib/versions.js +288 -64
  108. package/package.json +13 -12
  109. package/scripts/install-helper.js +97 -0
  110. package/scripts/postinstall.js +16 -0
  111. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
package/dist/index.js CHANGED
@@ -59,6 +59,7 @@ import { registerPullCommand } from './commands/pull.js';
59
59
  import { registerRepoCommands } from './commands/repo.js';
60
60
  import { registerSetupCommand, runSetup } from './commands/setup.js';
61
61
  import { registerStatusCommand } from './commands/status.js';
62
+ import { registerFeedbackCommand } from './commands/feedback.js';
62
63
  import { registerViewCommand } from './commands/view.js';
63
64
  import { registerCommandsCommands } from './commands/commands.js';
64
65
  import { registerHooksCommands } from './commands/hooks.js';
@@ -89,6 +90,7 @@ import { registerBrowserCommand } from './commands/browser.js';
89
90
  import { registerComputerCommand } from './commands/computer.js';
90
91
  import { registerProfilesCommands } from './commands/profiles.js';
91
92
  import { registerSecretsCommands } from './commands/secrets.js';
93
+ import { registerHelperCommand } from './commands/helper.js';
92
94
  import { registerFactoryCommands } from './commands/factory.js';
93
95
  import { registerUsageCommand } from './commands/usage.js';
94
96
  import { registerAliasCommand } from './commands/alias.js';
@@ -509,6 +511,7 @@ async function maybeBootstrapShimIntegration(requestedCommand) {
509
511
  // Register all commands
510
512
  registerViewCommand(program);
511
513
  registerStatusCommand(program);
514
+ registerFeedbackCommand(program);
512
515
  registerCommandsCommands(program);
513
516
  registerHooksCommands(program);
514
517
  registerSkillsCommands(program);
@@ -565,6 +568,7 @@ program
565
568
  });
566
569
  registerProfilesCommands(program);
567
570
  registerSecretsCommands(program);
571
+ registerHelperCommand(program);
568
572
  registerBetaCommands(program);
569
573
  registerSyncCommand(program);
570
574
  registerRefreshRulesCommand(program);
@@ -82,7 +82,12 @@ function buildClient(opts) {
82
82
  return {};
83
83
  },
84
84
  async requestPermission(params) {
85
- const optionId = mode === 'full'
85
+ // `skip` (formerly `full`) and `auto` blanket-approve; in `auto` the
86
+ // upstream model has its own classifier so we just say "allow once" and
87
+ // let it decide. `edit` says allow_once. `plan` should never reach here
88
+ // (canWrite gates writes earlier), but if it does we cancel.
89
+ const skipAll = mode === 'skip';
90
+ const optionId = skipAll
86
91
  ? (params.options.find(o => o.kind === 'allow_always')?.optionId
87
92
  ?? params.options[0]?.optionId)
88
93
  : params.options.find(o => o.kind === 'allow_once')?.optionId;
@@ -156,6 +156,10 @@ interface McpConfigEntry {
156
156
  type?: string;
157
157
  url?: string;
158
158
  }
159
+ /**
160
+ * Get user-scoped MCP config path for an agent.
161
+ */
162
+ export declare function getUserMcpConfigPath(agentId: AgentId): string;
159
163
  /**
160
164
  * Get MCP config path for a specific HOME directory (used for version-managed agents).
161
165
  */
@@ -55,13 +55,25 @@ function saveCliVersionCache() {
55
55
  /* best-effort cache persist */
56
56
  }
57
57
  }
58
- /** Synchronous PATH search -- no subprocess. Returns first matching binary path. */
58
+ /**
59
+ * Synchronous PATH search -- no subprocess. Returns first matching binary path.
60
+ *
61
+ * Skips our own shims dir (`~/.agents/.cache/shims/`) — those shims are
62
+ * dispatch helpers, not real installs. Counting them as installed produced a
63
+ * false positive where agents with NO real binary on the host (e.g. a
64
+ * never-installed Cursor whose only PATH entry was our `cursor-agent` shim
65
+ * dispatcher) showed up under `agents view`'s "Not Managed by Agents CLI"
66
+ * section, even though the user had nothing to import.
67
+ */
59
68
  function findInPath(command) {
60
69
  const pathEnv = process.env.PATH || '';
61
70
  const pathExt = process.platform === 'win32' ? (process.env.PATHEXT || '').split(';') : [''];
71
+ const shimsDir = getShimsDir();
62
72
  for (const dir of pathEnv.split(path.delimiter)) {
63
73
  if (!dir)
64
74
  continue;
75
+ if (path.resolve(dir) === path.resolve(shimsDir))
76
+ continue;
65
77
  for (const ext of pathExt) {
66
78
  const full = path.join(dir, command + ext);
67
79
  try {
@@ -191,7 +203,7 @@ export const AGENTS = {
191
203
  format: 'markdown',
192
204
  variableSyntax: '$ARGUMENTS',
193
205
  supportsHooks: true,
194
- capabilities: { hooks: true, mcp: true, allowlist: true, skills: true, commands: true, plugins: true, rulesImports: true },
206
+ capabilities: { hooks: true, mcp: true, allowlist: true, skills: true, commands: true, plugins: true, modes: ['plan', 'edit', 'auto', 'skip'], rulesImports: true },
195
207
  },
196
208
  // codex hooks: gated to >= 0.116.0 (introduced [features] codex_hooks flag).
197
209
  codex: {
@@ -210,7 +222,7 @@ export const AGENTS = {
210
222
  format: 'markdown',
211
223
  variableSyntax: '$ARGUMENTS',
212
224
  supportsHooks: true,
213
- capabilities: { hooks: { since: '0.116.0' }, mcp: true, allowlist: false, skills: true, commands: { until: '0.117.0' }, plugins: { since: '0.128.0' } },
225
+ 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'] },
214
226
  },
215
227
  gemini: {
216
228
  id: 'gemini',
@@ -229,7 +241,7 @@ export const AGENTS = {
229
241
  supportsHooks: true,
230
242
  nativeAgentsSkillsDir: true,
231
243
  // gemini hooks: shipped in v0.26.0 (Jan 2026); older binaries silently ignore the `hooks` key.
232
- capabilities: { hooks: { since: '0.26.0' }, mcp: true, allowlist: false, skills: true, commands: true, plugins: false, rulesImports: true },
244
+ capabilities: { hooks: { since: '0.26.0' }, mcp: true, allowlist: false, skills: true, commands: true, plugins: false, modes: ['plan', 'edit', 'skip'], rulesImports: true },
233
245
  },
234
246
  cursor: {
235
247
  id: 'cursor',
@@ -247,7 +259,7 @@ export const AGENTS = {
247
259
  format: 'markdown',
248
260
  variableSyntax: '$ARGUMENTS',
249
261
  supportsHooks: false,
250
- capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false },
262
+ capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false, modes: ['edit', 'skip'] },
251
263
  },
252
264
  opencode: {
253
265
  id: 'opencode',
@@ -264,7 +276,7 @@ export const AGENTS = {
264
276
  format: 'markdown',
265
277
  variableSyntax: '$ARGUMENTS',
266
278
  supportsHooks: false,
267
- capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false },
279
+ capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false, modes: ['plan', 'edit'] },
268
280
  },
269
281
  openclaw: {
270
282
  id: 'openclaw',
@@ -281,7 +293,7 @@ export const AGENTS = {
281
293
  format: 'markdown',
282
294
  variableSyntax: '{{ARGUMENTS}}',
283
295
  supportsHooks: true,
284
- capabilities: { hooks: true, mcp: true, allowlist: false, skills: true, commands: false, plugins: true },
296
+ capabilities: { hooks: true, mcp: true, allowlist: false, skills: true, commands: false, plugins: true, modes: ['plan', 'edit', 'skip'] },
285
297
  },
286
298
  copilot: {
287
299
  id: 'copilot',
@@ -298,7 +310,7 @@ export const AGENTS = {
298
310
  format: 'markdown',
299
311
  variableSyntax: '$ARGUMENTS',
300
312
  supportsHooks: false,
301
- capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false },
313
+ capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false, modes: ['plan', 'edit', 'auto', 'skip'] },
302
314
  },
303
315
  amp: {
304
316
  id: 'amp',
@@ -315,7 +327,7 @@ export const AGENTS = {
315
327
  format: 'markdown',
316
328
  variableSyntax: '$ARGUMENTS',
317
329
  supportsHooks: false,
318
- capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false },
330
+ capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false, modes: ['plan', 'edit'] },
319
331
  },
320
332
  kiro: {
321
333
  id: 'kiro',
@@ -333,7 +345,7 @@ export const AGENTS = {
333
345
  format: 'markdown',
334
346
  variableSyntax: '$ARGUMENTS',
335
347
  supportsHooks: false,
336
- capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false },
348
+ capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false, modes: ['edit'] },
337
349
  },
338
350
  goose: {
339
351
  id: 'goose',
@@ -351,7 +363,7 @@ export const AGENTS = {
351
363
  format: 'markdown',
352
364
  variableSyntax: '$ARGUMENTS',
353
365
  supportsHooks: false,
354
- capabilities: { hooks: false, mcp: true, allowlist: false, skills: false, commands: false, plugins: false },
366
+ capabilities: { hooks: false, mcp: true, allowlist: false, skills: false, commands: false, plugins: false, modes: ['edit'] },
355
367
  },
356
368
  roo: {
357
369
  id: 'roo',
@@ -369,7 +381,7 @@ export const AGENTS = {
369
381
  format: 'markdown',
370
382
  variableSyntax: '$ARGUMENTS',
371
383
  supportsHooks: false,
372
- capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false },
384
+ capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false, modes: ['plan', 'edit'] },
373
385
  },
374
386
  // Google Antigravity CLI (`agy`) — official replacement for Gemini CLI as of IO 2026.
375
387
  // configDir nests inside `~/.gemini/` since agy shares the parent dir with the Gemini
@@ -396,7 +408,7 @@ export const AGENTS = {
396
408
  format: 'markdown',
397
409
  variableSyntax: '{{args}}',
398
410
  supportsHooks: true,
399
- capabilities: { hooks: true, mcp: true, allowlist: true, skills: true, commands: true, plugins: true, rulesImports: false },
411
+ capabilities: { hooks: true, mcp: true, allowlist: true, skills: true, commands: true, plugins: true, modes: ['edit', 'skip'], rulesImports: false },
400
412
  },
401
413
  // xAI Grok Build CLI (`grok`) — early beta, SuperGrok Heavy. Auth via OAuth on
402
414
  // first launch, or XAI_API_KEY env var for headless. MCP servers configured inline
@@ -410,7 +422,7 @@ export const AGENTS = {
410
422
  color: 'cyanBright',
411
423
  cliCommand: 'grok',
412
424
  npmPackage: '',
413
- installScript: 'curl -fsSL https://x.ai/cli/install.sh | bash -s VERSION',
425
+ installScript: 'curl -fsSL https://x.ai/cli/install.sh | bash',
414
426
  configDir: path.join(HOME, '.grok'),
415
427
  commandsDir: '', // Grok primarily uses skills + slash commands from skills
416
428
  commandsSubdir: '',
@@ -427,6 +439,7 @@ export const AGENTS = {
427
439
  skills: true,
428
440
  commands: false, // covered by skills
429
441
  plugins: true,
442
+ modes: ['plan', 'edit', 'skip'],
430
443
  rulesImports: true,
431
444
  },
432
445
  },
@@ -506,8 +519,16 @@ async function getCachedVersionForBinary(agentId, binaryPath) {
506
519
  /* version command failed */
507
520
  version = null;
508
521
  }
509
- cache[agentId] = { binaryPath, mtime, version };
510
- saveCliVersionCache();
522
+ // Skip persisting null results the most common cause is a transient
523
+ // `--version` failure (slow startup, stdout race, etc.). A sticky-null
524
+ // entry kept users in a broken state where every subsequent
525
+ // `getCachedVersionForBinary` short-circuited to null forever, even
526
+ // after the binary started working. Re-probing on the next call costs
527
+ // one execFile; persisting null costs the whole feature.
528
+ if (version !== null) {
529
+ cache[agentId] = { binaryPath, mtime, version };
530
+ saveCliVersionCache();
531
+ }
511
532
  return version;
512
533
  }
513
534
  /**
@@ -1197,7 +1218,7 @@ function parseMcpFromOpenCodeConfig(configPath) {
1197
1218
  /**
1198
1219
  * Get user-scoped MCP config path for an agent.
1199
1220
  */
1200
- function getUserMcpConfigPath(agentId) {
1221
+ export function getUserMcpConfigPath(agentId) {
1201
1222
  const agent = AGENTS[agentId];
1202
1223
  switch (agentId) {
1203
1224
  case 'claude':
@@ -1215,6 +1236,9 @@ function getUserMcpConfigPath(agentId) {
1215
1236
  case 'openclaw':
1216
1237
  // OpenClaw uses openclaw.json
1217
1238
  return path.join(agent.configDir, 'openclaw.json');
1239
+ case 'copilot':
1240
+ // GitHub Copilot CLI uses mcp-config.json (matches versioned + project paths)
1241
+ return path.join(agent.configDir, 'mcp-config.json');
1218
1242
  case 'antigravity':
1219
1243
  // agy uses mcp_config.json inside its nested config dir (~/.gemini/antigravity-cli/)
1220
1244
  return path.join(agent.configDir, 'mcp_config.json');
@@ -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);
@@ -139,6 +139,10 @@ isElectron = false) {
139
139
  '--no-default-browser-check',
140
140
  '--disable-features=DefaultBrowserSetting,ChromeWhatsNewUI',
141
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',
142
146
  ...(options.headless ? ['--headless=new'] : []),
143
147
  `--window-size=${viewport.width},${viewport.height}`,
144
148
  ...(viewport.x !== undefined && viewport.y !== undefined
@@ -180,7 +180,7 @@ async function ensureRemoteBrowser(user, host, browserType, port, customBinary)
180
180
  const remoteCmd = [
181
181
  shellQuote(browserPath),
182
182
  `--remote-debugging-port=${port}`,
183
- shellQuote('--remote-allow-origins=*'),
183
+ shellQuote(`--remote-allow-origins=http://127.0.0.1:${port}`),
184
184
  '--disable-background-timer-throttling',
185
185
  `--user-data-dir=/tmp/agents-browser-${port}`,
186
186
  '</dev/null >/dev/null 2>&1 &',
@@ -65,12 +65,12 @@ export declare function resolveEndpoint(profile: BrowserProfile, endpointName?:
65
65
  * Returns undefined for endpoint shapes that don't carry a port (e.g. ws:// without one).
66
66
  *
67
67
  * Ports are scoped by host: a `cdp://127.0.0.1:9222` profile (local Chrome on
68
- * this machine) and an `ssh://mac-mini:9222` profile (Comet on mac-mini)
69
- * point at different physical ports — the host disambiguates them.
68
+ * this machine) and an `ssh://remote-host:9222` profile (Comet on a remote
69
+ * host) point at different physical ports — the host disambiguates them.
70
70
  *
71
71
  * Accepts both `scheme://host:port` and `scheme://host?port=N` shapes (the
72
72
  * latter is the documented form in `types.ts` for `ssh://`). Without this,
73
- * `ssh://mac-mini?port=18805` would silently fall back to 9222 and every
73
+ * `ssh://remote-host?port=18805` would silently fall back to 9222 and every
74
74
  * `?port=`-style SSH profile would collide on creation.
75
75
  */
76
76
  export declare function extractConfiguredEndpoint(profile: BrowserProfile): {
@@ -299,12 +299,12 @@ export function resolveEndpoint(profile, endpointName) {
299
299
  * Returns undefined for endpoint shapes that don't carry a port (e.g. ws:// without one).
300
300
  *
301
301
  * Ports are scoped by host: a `cdp://127.0.0.1:9222` profile (local Chrome on
302
- * this machine) and an `ssh://mac-mini:9222` profile (Comet on mac-mini)
303
- * point at different physical ports — the host disambiguates them.
302
+ * this machine) and an `ssh://remote-host:9222` profile (Comet on a remote
303
+ * host) point at different physical ports — the host disambiguates them.
304
304
  *
305
305
  * Accepts both `scheme://host:port` and `scheme://host?port=N` shapes (the
306
306
  * latter is the documented form in `types.ts` for `ssh://`). Without this,
307
- * `ssh://mac-mini?port=18805` would silently fall back to 9222 and every
307
+ * `ssh://remote-host?port=18805` would silently fall back to 9222 and every
308
308
  * `?port=`-style SSH profile would collide on creation.
309
309
  */
310
310
  export function extractConfiguredEndpoint(profile) {
@@ -1701,6 +1701,25 @@ export class BrowserService {
1701
1701
  targetId: tabId,
1702
1702
  flatten: true,
1703
1703
  }));
1704
+ // Inject a one-shot stealth shim before any page script runs. Chromium
1705
+ // unconditionally exposes navigator.webdriver = true when a remote-debug
1706
+ // transport is attached; Cloudflare Turnstile, hCaptcha, and similar bot
1707
+ // checks read that property first. For browsers agents-cli spawns the
1708
+ // --disable-blink-features=AutomationControlled launch flag already
1709
+ // covers this, but for attach-to-running profiles (the Comet / Arc /
1710
+ // Brave case where the user launched the browser themselves) the flag
1711
+ // is unavailable — Page.addScriptToEvaluateOnNewDocument is the only
1712
+ // lever. Non-page targets (workers, service workers) will reject these
1713
+ // calls; we swallow the error and keep going.
1714
+ try {
1715
+ await conn.cdp.send('Page.enable', {}, sessionId);
1716
+ await conn.cdp.send('Page.addScriptToEvaluateOnNewDocument', {
1717
+ source: "Object.defineProperty(navigator,'webdriver',{get:()=>undefined});",
1718
+ }, sessionId);
1719
+ }
1720
+ catch {
1721
+ // Target doesn't support Page domain — nothing to inject.
1722
+ }
1704
1723
  conn.sessionCache.set(tabId, sessionId);
1705
1724
  return sessionId;
1706
1725
  }
@@ -1,15 +1,15 @@
1
1
  export type BrowserType = 'chrome' | 'comet' | 'chromium' | 'brave' | 'edge' | 'custom';
2
2
  /**
3
3
  * A single named endpoint preset within a profile. Lets one profile cover
4
- * the local + remote variants of the same app (e.g. Rush on this Mac vs.
5
- * Rush on mac-mini) instead of forcing two parallel profiles.
4
+ * the local + remote variants of the same app (e.g. an Electron app on this
5
+ * Mac vs. on a remote host) instead of forcing two parallel profiles.
6
6
  *
7
7
  * Per-endpoint overrides take precedence over profile-level fields.
8
8
  */
9
9
  export interface EndpointPreset {
10
10
  /** CDP URL — `cdp://host:port` or `ssh://host?port=N` */
11
11
  target: string;
12
- /** Override the profile-level binary (e.g. mac-mini has no local binary). */
12
+ /** Override the profile-level binary (e.g. a remote host has no local binary). */
13
13
  binary?: string;
14
14
  /** Override the profile-level targetFilter (Electron app builds may diverge). */
15
15
  targetFilter?: string;
@@ -43,7 +43,7 @@ export interface BrowserProfile {
43
43
  };
44
44
  /** Directory holding source-side JSONL logs (e.g. ~/.rush/logs). */
45
45
  logDir?: string;
46
- /** Optional SSH host where logDir lives, e.g. "user@mac-mini". */
46
+ /** Optional SSH host where logDir lives, e.g. "user@remote-host". */
47
47
  logHost?: string;
48
48
  }
49
49
  /** Parsed form of `BrowserProfile.targetFilter`. */
@@ -16,6 +16,21 @@ export interface BinarySpec {
16
16
  extract?: string;
17
17
  };
18
18
  }
19
+ /**
20
+ * How to verify a CLI is installed. Structured so we can dispatch to spawnSync
21
+ * with an argv array — never through a shell.
22
+ *
23
+ * `which` — just check PATH for `cmd`.
24
+ * `version` — spawn `cmd` with `args` and require exit 0.
25
+ */
26
+ export type CheckSpec = {
27
+ kind: 'which';
28
+ cmd: string;
29
+ } | {
30
+ kind: 'version';
31
+ cmd: string;
32
+ args: string[];
33
+ };
19
34
  /** Parsed CLI manifest. */
20
35
  export interface CliManifest {
21
36
  /** Name as it appears on the command line (e.g. "higgsfield"). */
@@ -24,8 +39,8 @@ export interface CliManifest {
24
39
  description?: string;
25
40
  /** Project homepage; used in detail view + post-install messaging. */
26
41
  homepage?: string;
27
- /** Command run to verify the binary is installed (default: "<name> --version"). */
28
- check: string;
42
+ /** Structured check spec; never a raw shell command. */
43
+ check: CheckSpec;
29
44
  /** Install methods tried in order; first one whose tool is available is used. */
30
45
  install: InstallMethod[];
31
46
  /** Message printed after successful install — typically auth instructions. */
@@ -42,6 +57,13 @@ export interface CliManifestError {
42
57
  /** Human-readable reason. */
43
58
  reason: string;
44
59
  }
60
+ /**
61
+ * Parse a `check:` field into a CheckSpec. Accepts either a structured object
62
+ * (`{ kind: 'which'|'version', cmd, args? }`) or a legacy whitespace-separated
63
+ * string. String form is split on whitespace and each token is validated against
64
+ * SAFE_CHECK_TOKEN — manifests cannot smuggle in shell metacharacters.
65
+ */
66
+ export declare function parseCheckSpec(raw: unknown, defaultName: string): CheckSpec;
45
67
  /**
46
68
  * Parse a single CLI manifest from its YAML contents.
47
69
  * Returns a manifest on success; throws on schema violations so callers can
@@ -63,13 +85,18 @@ export declare function listCliManifests(cwd?: string): {
63
85
  /** Resolve a single CLI manifest by name. Returns null when not declared. */
64
86
  export declare function resolveCliManifest(name: string, cwd?: string): CliManifest | null;
65
87
  export declare function hasCommand(cmd: string): boolean;
66
- /** Run the manifest's `check` command. Returns true when it exits 0. */
88
+ /**
89
+ * Run the manifest's check. Dispatches on CheckSpec.kind — never invokes a
90
+ * shell, never interpolates strings into a command line.
91
+ */
67
92
  export declare function isCliInstalled(manifest: CliManifest): boolean;
68
93
  /**
69
94
  * Pick the first install method whose required host tool is available.
70
95
  * Returns null when none of the declared methods can run on this host.
71
96
  */
72
97
  export declare function selectInstallMethod(manifest: CliManifest): InstallMethod | null;
98
+ /** Render a CheckSpec back to a human-readable command string (display only). */
99
+ export declare function describeCheck(check: CheckSpec): string;
73
100
  /** Short description of a method for display. */
74
101
  export declare function describeMethod(method: InstallMethod): string;
75
102
  export interface InstallResult {
@@ -83,6 +110,12 @@ export interface InstallResult {
83
110
  /** Set when the install runner threw or exited non-zero. */
84
111
  error?: string;
85
112
  }
113
+ /**
114
+ * Display-only rendering of how a method would be run, for `--dry-run` and
115
+ * status output. Not used by installCli — execution goes through runInstallMethod
116
+ * which dispatches to spawnSync with argv arrays.
117
+ */
118
+ export declare function buildInstallCommand(method: InstallMethod): string;
86
119
  /**
87
120
  * Install a single CLI by running its first compatible method. Streams the
88
121
  * underlying command's output to the parent terminal so users see brew/npm
@@ -91,11 +124,6 @@ export interface InstallResult {
91
124
  export declare function installCli(manifest: CliManifest, opts?: {
92
125
  dryRun?: boolean;
93
126
  }): InstallResult;
94
- /**
95
- * Map a declarative method to a shell command. Centralized so tests and dry-run
96
- * surface the exact string that would execute.
97
- */
98
- export declare function buildInstallCommand(method: InstallMethod): string;
99
127
  export interface CliStatus {
100
128
  manifest: CliManifest;
101
129
  installed: boolean;