@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.
- package/CHANGELOG.md +140 -0
- package/README.md +72 -12
- package/dist/browser.js +0 -0
- package/dist/commands/browser.js +88 -16
- package/dist/commands/cli.d.ts +14 -0
- package/dist/commands/cli.js +244 -0
- package/dist/commands/cloud.js +1 -1
- package/dist/commands/commands.js +27 -10
- package/dist/commands/computer.js +18 -1
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.js +2 -2
- package/dist/commands/exec.js +38 -18
- package/dist/commands/factory.d.ts +3 -14
- package/dist/commands/factory.js +3 -3
- package/dist/commands/feedback.d.ts +7 -0
- package/dist/commands/feedback.js +89 -0
- package/dist/commands/helper.d.ts +12 -0
- package/dist/commands/helper.js +87 -0
- package/dist/commands/hooks.js +89 -10
- package/dist/commands/mcp.js +166 -10
- package/dist/commands/packages.js +196 -27
- package/dist/commands/permissions.js +21 -6
- package/dist/commands/plugins.js +11 -4
- package/dist/commands/profiles.d.ts +8 -0
- package/dist/commands/profiles.js +118 -5
- package/dist/commands/prune.js +39 -160
- package/dist/commands/pull.js +58 -5
- package/dist/commands/routines.js +107 -14
- package/dist/commands/rules.js +8 -4
- package/dist/commands/secrets-migrate.d.ts +24 -0
- package/dist/commands/secrets-migrate.js +198 -0
- package/dist/commands/secrets-sync.d.ts +11 -0
- package/dist/commands/secrets-sync.js +155 -0
- package/dist/commands/secrets.js +79 -46
- package/dist/commands/sessions.d.ts +28 -0
- package/dist/commands/sessions.js +98 -33
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +37 -28
- package/dist/commands/skills.js +25 -8
- package/dist/commands/subagents.js +69 -49
- package/dist/commands/teams.js +61 -10
- package/dist/commands/utils.d.ts +33 -0
- package/dist/commands/utils.js +139 -0
- package/dist/commands/versions.d.ts +4 -3
- package/dist/commands/versions.js +134 -130
- package/dist/commands/view.d.ts +6 -0
- package/dist/commands/view.js +175 -19
- package/dist/commands/workflows.js +29 -6
- package/dist/computer.js +0 -0
- package/dist/index.js +38 -6
- package/dist/lib/acp/client.js +6 -1
- package/dist/lib/acp/harnesses.js +8 -0
- package/dist/lib/agents.d.ts +4 -0
- package/dist/lib/agents.js +125 -34
- package/dist/lib/auto-pull-worker.js +18 -1
- package/dist/lib/browser/cdp.d.ts +8 -1
- package/dist/lib/browser/cdp.js +40 -3
- package/dist/lib/browser/chrome.d.ts +13 -0
- package/dist/lib/browser/chrome.js +46 -3
- package/dist/lib/browser/domain-skills.d.ts +51 -0
- package/dist/lib/browser/domain-skills.js +157 -0
- package/dist/lib/browser/drivers/local.js +45 -4
- package/dist/lib/browser/drivers/ssh.js +2 -2
- package/dist/lib/browser/ipc.d.ts +8 -1
- package/dist/lib/browser/ipc.js +37 -28
- package/dist/lib/browser/profiles.d.ts +16 -3
- package/dist/lib/browser/profiles.js +44 -4
- package/dist/lib/browser/service.d.ts +3 -0
- package/dist/lib/browser/service.js +40 -5
- package/dist/lib/browser/types.d.ts +11 -4
- package/dist/lib/cli-resources.d.ts +137 -0
- package/dist/lib/cli-resources.js +477 -0
- package/dist/lib/cloud/factory.d.ts +1 -1
- package/dist/lib/cloud/factory.js +1 -1
- package/dist/lib/cloud/rush.js +5 -5
- package/dist/lib/command-skills.js +0 -2
- package/dist/lib/computer-rpc.d.ts +3 -0
- package/dist/lib/computer-rpc.js +53 -0
- package/dist/lib/daemon.js +20 -0
- package/dist/lib/events.d.ts +16 -2
- package/dist/lib/events.js +33 -2
- package/dist/lib/exec.d.ts +42 -13
- package/dist/lib/exec.js +127 -33
- package/dist/lib/help.js +11 -5
- package/dist/lib/hooks/cache.d.ts +38 -0
- package/dist/lib/hooks/cache.js +242 -0
- package/dist/lib/hooks/profile.d.ts +33 -0
- package/dist/lib/hooks/profile.js +129 -0
- package/dist/lib/hooks.d.ts +0 -10
- package/dist/lib/hooks.js +246 -11
- package/dist/lib/mcp.d.ts +15 -0
- package/dist/lib/mcp.js +46 -0
- package/dist/lib/migrate.js +1 -1
- package/dist/lib/overdue.d.ts +26 -0
- package/dist/lib/overdue.js +101 -0
- package/dist/lib/permissions.d.ts +13 -0
- package/dist/lib/permissions.js +55 -1
- package/dist/lib/plugin-marketplace.js +1 -1
- package/dist/lib/plugins.js +15 -1
- package/dist/lib/profiles-presets.d.ts +26 -0
- package/dist/lib/profiles-presets.js +216 -0
- package/dist/lib/profiles.d.ts +34 -0
- package/dist/lib/profiles.js +112 -1
- package/dist/lib/resources/mcp.js +37 -0
- package/dist/lib/resources.d.ts +1 -1
- package/dist/lib/rotate.js +10 -4
- package/dist/lib/routines-format.d.ts +47 -0
- package/dist/lib/routines-format.js +194 -0
- package/dist/lib/routines.d.ts +8 -2
- package/dist/lib/routines.js +34 -14
- package/dist/lib/runner.js +83 -15
- package/dist/lib/scheduler.js +8 -1
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -9
- package/dist/lib/secrets/bundles.d.ts +34 -17
- package/dist/lib/secrets/bundles.js +210 -36
- package/dist/lib/secrets/index.d.ts +49 -30
- package/dist/lib/secrets/index.js +126 -115
- package/dist/lib/secrets/install-helper.d.ts +45 -0
- package/dist/lib/secrets/install-helper.js +165 -0
- package/dist/lib/secrets/linux.js +4 -4
- package/dist/lib/secrets/sync.d.ts +56 -0
- package/dist/lib/secrets/sync.js +180 -0
- package/dist/lib/session/active.d.ts +8 -0
- package/dist/lib/session/active.js +3 -2
- package/dist/lib/session/db.d.ts +0 -4
- package/dist/lib/session/db.js +0 -26
- package/dist/lib/session/parse.d.ts +1 -0
- package/dist/lib/session/parse.js +44 -0
- package/dist/lib/session/render.js +4 -4
- package/dist/lib/session/types.d.ts +2 -2
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/shims.d.ts +5 -2
- package/dist/lib/shims.js +70 -38
- package/dist/lib/state.d.ts +14 -2
- package/dist/lib/state.js +51 -20
- package/dist/lib/teams/agents.d.ts +5 -4
- package/dist/lib/teams/agents.js +48 -22
- package/dist/lib/teams/api.d.ts +2 -1
- package/dist/lib/teams/api.js +4 -3
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/teams/parsers.js +153 -3
- package/dist/lib/teams/summarizer.js +18 -2
- package/dist/lib/teams/worktree.js +14 -3
- package/dist/lib/types.d.ts +63 -4
- package/dist/lib/types.js +8 -3
- package/dist/lib/usage.d.ts +27 -2
- package/dist/lib/usage.js +100 -17
- package/dist/lib/versions.d.ts +45 -3
- package/dist/lib/versions.js +455 -60
- package/package.json +15 -14
- package/scripts/install-helper.js +97 -0
- package/scripts/postinstall.js +16 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
- package/npm-shrinkwrap.json +0 -3162
package/dist/lib/agents.js
CHANGED
|
@@ -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:
|
|
358
|
-
|
|
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
|
|
363
|
-
//
|
|
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: '
|
|
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:
|
|
373
|
-
commandsSubdir: '
|
|
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:
|
|
380
|
-
capabilities: {
|
|
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
|
-
|
|
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', '
|
|
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', '
|
|
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
|
-
|
|
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
|
|
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<{
|
package/dist/lib/browser/cdp.js
CHANGED
|
@@ -152,10 +152,47 @@ export class CDPClient {
|
|
|
152
152
|
this.transport = null;
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
|
-
export
|
|
156
|
-
|
|
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
|
|
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 {
|
|
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
|
|
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;
|