@phnx-labs/agents-cli 1.20.0 → 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 (105) hide show
  1. package/CHANGELOG.md +73 -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/mcp.js +166 -10
  13. package/dist/commands/packages.js +196 -27
  14. package/dist/commands/permissions.js +21 -6
  15. package/dist/commands/profiles.d.ts +8 -0
  16. package/dist/commands/profiles.js +117 -4
  17. package/dist/commands/pull.js +4 -4
  18. package/dist/commands/routines.js +6 -6
  19. package/dist/commands/rules.js +8 -4
  20. package/dist/commands/secrets-migrate.d.ts +24 -0
  21. package/dist/commands/secrets-migrate.js +198 -0
  22. package/dist/commands/secrets-sync.d.ts +11 -0
  23. package/dist/commands/secrets-sync.js +155 -0
  24. package/dist/commands/secrets.js +74 -39
  25. package/dist/commands/skills.js +22 -5
  26. package/dist/commands/subagents.js +69 -49
  27. package/dist/commands/teams.js +48 -10
  28. package/dist/commands/utils.d.ts +33 -0
  29. package/dist/commands/utils.js +139 -0
  30. package/dist/commands/versions.js +4 -4
  31. package/dist/commands/view.d.ts +6 -0
  32. package/dist/commands/view.js +164 -8
  33. package/dist/commands/workflows.js +29 -6
  34. package/dist/index.js +4 -0
  35. package/dist/lib/acp/client.js +6 -1
  36. package/dist/lib/agents.d.ts +4 -0
  37. package/dist/lib/agents.js +18 -14
  38. package/dist/lib/auto-pull-worker.js +18 -1
  39. package/dist/lib/browser/chrome.js +4 -0
  40. package/dist/lib/browser/drivers/ssh.js +1 -1
  41. package/dist/lib/browser/profiles.d.ts +3 -3
  42. package/dist/lib/browser/profiles.js +3 -3
  43. package/dist/lib/browser/service.js +19 -0
  44. package/dist/lib/browser/types.d.ts +4 -4
  45. package/dist/lib/cli-resources.d.ts +36 -8
  46. package/dist/lib/cli-resources.js +268 -46
  47. package/dist/lib/cloud/factory.d.ts +1 -1
  48. package/dist/lib/cloud/factory.js +1 -1
  49. package/dist/lib/events.d.ts +16 -2
  50. package/dist/lib/events.js +33 -2
  51. package/dist/lib/exec.d.ts +39 -11
  52. package/dist/lib/exec.js +90 -31
  53. package/dist/lib/help.js +11 -5
  54. package/dist/lib/hooks/cache.d.ts +38 -0
  55. package/dist/lib/hooks/cache.js +242 -0
  56. package/dist/lib/hooks/profile.d.ts +33 -0
  57. package/dist/lib/hooks/profile.js +129 -0
  58. package/dist/lib/hooks.d.ts +0 -10
  59. package/dist/lib/hooks.js +68 -15
  60. package/dist/lib/mcp.d.ts +15 -0
  61. package/dist/lib/mcp.js +40 -0
  62. package/dist/lib/permissions.d.ts +13 -0
  63. package/dist/lib/permissions.js +51 -1
  64. package/dist/lib/plugins.js +15 -1
  65. package/dist/lib/profiles-presets.d.ts +26 -0
  66. package/dist/lib/profiles-presets.js +187 -8
  67. package/dist/lib/profiles.d.ts +34 -0
  68. package/dist/lib/profiles.js +112 -1
  69. package/dist/lib/routines-format.d.ts +17 -5
  70. package/dist/lib/routines-format.js +37 -16
  71. package/dist/lib/routines.d.ts +1 -1
  72. package/dist/lib/routines.js +2 -2
  73. package/dist/lib/runner.js +64 -10
  74. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  75. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  76. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -9
  77. package/dist/lib/secrets/bundles.d.ts +18 -22
  78. package/dist/lib/secrets/bundles.js +75 -99
  79. package/dist/lib/secrets/index.d.ts +51 -27
  80. package/dist/lib/secrets/index.js +147 -156
  81. package/dist/lib/secrets/install-helper.d.ts +45 -0
  82. package/dist/lib/secrets/install-helper.js +165 -0
  83. package/dist/lib/secrets/linux.js +4 -4
  84. package/dist/lib/secrets/sync.d.ts +56 -0
  85. package/dist/lib/secrets/sync.js +180 -0
  86. package/dist/lib/session/render.js +4 -4
  87. package/dist/lib/session/types.d.ts +1 -1
  88. package/dist/lib/shims.d.ts +4 -1
  89. package/dist/lib/shims.js +5 -35
  90. package/dist/lib/state.d.ts +14 -1
  91. package/dist/lib/state.js +49 -5
  92. package/dist/lib/teams/agents.d.ts +5 -4
  93. package/dist/lib/teams/agents.js +47 -21
  94. package/dist/lib/teams/api.d.ts +2 -1
  95. package/dist/lib/teams/api.js +4 -3
  96. package/dist/lib/types.d.ts +57 -1
  97. package/dist/lib/types.js +2 -0
  98. package/dist/lib/usage.d.ts +27 -2
  99. package/dist/lib/usage.js +100 -17
  100. package/dist/lib/versions.d.ts +35 -1
  101. package/dist/lib/versions.js +267 -64
  102. package/package.json +9 -8
  103. package/scripts/install-helper.js +97 -0
  104. package/scripts/postinstall.js +16 -0
  105. 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
  */
@@ -191,7 +191,7 @@ export const AGENTS = {
191
191
  format: 'markdown',
192
192
  variableSyntax: '$ARGUMENTS',
193
193
  supportsHooks: true,
194
- 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 },
195
195
  },
196
196
  // codex hooks: gated to >= 0.116.0 (introduced [features] codex_hooks flag).
197
197
  codex: {
@@ -210,7 +210,7 @@ export const AGENTS = {
210
210
  format: 'markdown',
211
211
  variableSyntax: '$ARGUMENTS',
212
212
  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' } },
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'] },
214
214
  },
215
215
  gemini: {
216
216
  id: 'gemini',
@@ -229,7 +229,7 @@ export const AGENTS = {
229
229
  supportsHooks: true,
230
230
  nativeAgentsSkillsDir: true,
231
231
  // 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 },
232
+ capabilities: { hooks: { since: '0.26.0' }, mcp: true, allowlist: false, skills: true, commands: true, plugins: false, modes: ['plan', 'edit', 'skip'], rulesImports: true },
233
233
  },
234
234
  cursor: {
235
235
  id: 'cursor',
@@ -247,7 +247,7 @@ export const AGENTS = {
247
247
  format: 'markdown',
248
248
  variableSyntax: '$ARGUMENTS',
249
249
  supportsHooks: false,
250
- 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'] },
251
251
  },
252
252
  opencode: {
253
253
  id: 'opencode',
@@ -264,7 +264,7 @@ export const AGENTS = {
264
264
  format: 'markdown',
265
265
  variableSyntax: '$ARGUMENTS',
266
266
  supportsHooks: false,
267
- 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'] },
268
268
  },
269
269
  openclaw: {
270
270
  id: 'openclaw',
@@ -281,7 +281,7 @@ export const AGENTS = {
281
281
  format: 'markdown',
282
282
  variableSyntax: '{{ARGUMENTS}}',
283
283
  supportsHooks: true,
284
- 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'] },
285
285
  },
286
286
  copilot: {
287
287
  id: 'copilot',
@@ -298,7 +298,7 @@ export const AGENTS = {
298
298
  format: 'markdown',
299
299
  variableSyntax: '$ARGUMENTS',
300
300
  supportsHooks: false,
301
- 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'] },
302
302
  },
303
303
  amp: {
304
304
  id: 'amp',
@@ -315,7 +315,7 @@ export const AGENTS = {
315
315
  format: 'markdown',
316
316
  variableSyntax: '$ARGUMENTS',
317
317
  supportsHooks: false,
318
- 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'] },
319
319
  },
320
320
  kiro: {
321
321
  id: 'kiro',
@@ -333,7 +333,7 @@ export const AGENTS = {
333
333
  format: 'markdown',
334
334
  variableSyntax: '$ARGUMENTS',
335
335
  supportsHooks: false,
336
- 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'] },
337
337
  },
338
338
  goose: {
339
339
  id: 'goose',
@@ -351,7 +351,7 @@ export const AGENTS = {
351
351
  format: 'markdown',
352
352
  variableSyntax: '$ARGUMENTS',
353
353
  supportsHooks: false,
354
- 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'] },
355
355
  },
356
356
  roo: {
357
357
  id: 'roo',
@@ -369,7 +369,7 @@ export const AGENTS = {
369
369
  format: 'markdown',
370
370
  variableSyntax: '$ARGUMENTS',
371
371
  supportsHooks: false,
372
- 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'] },
373
373
  },
374
374
  // Google Antigravity CLI (`agy`) — official replacement for Gemini CLI as of IO 2026.
375
375
  // configDir nests inside `~/.gemini/` since agy shares the parent dir with the Gemini
@@ -396,7 +396,7 @@ export const AGENTS = {
396
396
  format: 'markdown',
397
397
  variableSyntax: '{{args}}',
398
398
  supportsHooks: true,
399
- capabilities: { hooks: true, mcp: true, allowlist: true, skills: true, commands: true, plugins: true, rulesImports: false },
399
+ capabilities: { hooks: true, mcp: true, allowlist: true, skills: true, commands: true, plugins: true, modes: ['edit', 'skip'], rulesImports: false },
400
400
  },
401
401
  // xAI Grok Build CLI (`grok`) — early beta, SuperGrok Heavy. Auth via OAuth on
402
402
  // first launch, or XAI_API_KEY env var for headless. MCP servers configured inline
@@ -410,7 +410,7 @@ export const AGENTS = {
410
410
  color: 'cyanBright',
411
411
  cliCommand: 'grok',
412
412
  npmPackage: '',
413
- installScript: 'curl -fsSL https://x.ai/cli/install.sh | bash -s VERSION',
413
+ installScript: 'curl -fsSL https://x.ai/cli/install.sh | bash',
414
414
  configDir: path.join(HOME, '.grok'),
415
415
  commandsDir: '', // Grok primarily uses skills + slash commands from skills
416
416
  commandsSubdir: '',
@@ -427,6 +427,7 @@ export const AGENTS = {
427
427
  skills: true,
428
428
  commands: false, // covered by skills
429
429
  plugins: true,
430
+ modes: ['plan', 'edit', 'skip'],
430
431
  rulesImports: true,
431
432
  },
432
433
  },
@@ -1197,7 +1198,7 @@ function parseMcpFromOpenCodeConfig(configPath) {
1197
1198
  /**
1198
1199
  * Get user-scoped MCP config path for an agent.
1199
1200
  */
1200
- function getUserMcpConfigPath(agentId) {
1201
+ export function getUserMcpConfigPath(agentId) {
1201
1202
  const agent = AGENTS[agentId];
1202
1203
  switch (agentId) {
1203
1204
  case 'claude':
@@ -1215,6 +1216,9 @@ function getUserMcpConfigPath(agentId) {
1215
1216
  case 'openclaw':
1216
1217
  // OpenClaw uses openclaw.json
1217
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');
1218
1222
  case 'antigravity':
1219
1223
  // agy uses mcp_config.json inside its nested config dir (~/.gemini/antigravity-cli/)
1220
1224
  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;