@phnx-labs/agents-cli 1.22.5 → 1.22.7
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 +90 -0
- package/README.md +7 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/exec.js +46 -5
- package/dist/commands/feed.d.ts +2 -1
- package/dist/commands/feed.js +47 -20
- package/dist/commands/focus.js +22 -1
- package/dist/commands/models.js +2 -2
- package/dist/commands/monitors.js +1 -1
- package/dist/commands/projects.js +122 -107
- package/dist/commands/secrets.js +1 -1
- package/dist/commands/sessions-backfill.d.ts +33 -0
- package/dist/commands/sessions-backfill.js +83 -1
- package/dist/commands/sessions-stats.d.ts +36 -0
- package/dist/commands/sessions-stats.js +263 -0
- package/dist/commands/sessions.d.ts +1 -1
- package/dist/commands/sessions.js +21 -1
- package/dist/commands/view.d.ts +2 -1
- package/dist/commands/view.js +62 -11
- package/dist/index.js +3 -3
- package/dist/lib/activity.js +2 -2
- package/dist/lib/agents.js +68 -11
- package/dist/lib/analytics/recipes.js +11 -5
- package/dist/lib/browser/profiles.d.ts +15 -7
- package/dist/lib/browser/profiles.js +53 -12
- package/dist/lib/byok-usage.d.ts +38 -0
- package/dist/lib/byok-usage.js +117 -0
- package/dist/lib/capabilities.js +1 -1
- package/dist/lib/exec.d.ts +20 -0
- package/dist/lib/exec.js +73 -8
- package/dist/lib/feed-outcome.d.ts +1 -0
- package/dist/lib/feed-outcome.js +2 -0
- package/dist/lib/feed-post.js +1 -1
- package/dist/lib/feed-ranking.d.ts +1 -0
- package/dist/lib/feed-ranking.js +4 -0
- package/dist/lib/feed.d.ts +4 -1
- package/dist/lib/feed.js +25 -0
- package/dist/lib/hosts/passthrough.js +0 -1
- package/dist/lib/mcp.js +6 -1
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/model-tiers.js +4 -1
- package/dist/lib/models.js +63 -0
- package/dist/lib/profiles.d.ts +42 -1
- package/dist/lib/profiles.js +50 -2
- package/dist/lib/project-focus.d.ts +9 -0
- package/dist/lib/project-focus.js +23 -0
- package/dist/lib/project-key.d.ts +1 -1
- package/dist/lib/project-key.js +1 -1
- package/dist/lib/project-probe.d.ts +18 -0
- package/dist/lib/project-probe.js +46 -0
- package/dist/lib/project-status.d.ts +56 -0
- package/dist/lib/project-status.js +125 -18
- package/dist/lib/resources/mcp.js +3 -0
- package/dist/lib/resources/types.d.ts +1 -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/audit.js +1 -1
- package/dist/lib/secrets/index.d.ts +1 -0
- package/dist/lib/secrets/index.js +29 -15
- package/dist/lib/secrets/remote.d.ts +1 -1
- package/dist/lib/secrets/remote.js +1 -1
- package/dist/lib/session/active.d.ts +2 -0
- package/dist/lib/session/bash-command.d.ts +2 -3
- package/dist/lib/session/db.d.ts +92 -1
- package/dist/lib/session/db.js +230 -1
- package/dist/lib/session/digest.d.ts +1 -1
- package/dist/lib/session/digest.js +1 -1
- package/dist/lib/share/publish.js +24 -0
- package/dist/lib/startup/command-registry.d.ts +0 -1
- package/dist/lib/startup/command-registry.js +0 -2
- package/dist/lib/subagents-registry.js +3 -0
- package/dist/lib/types.d.ts +11 -2
- package/dist/lib/usage.d.ts +5 -0
- package/dist/lib/usage.js +3 -3
- package/package.json +1 -1
- package/dist/commands/activity.d.ts +0 -87
- package/dist/commands/activity.js +0 -346
package/dist/lib/agents.js
CHANGED
|
@@ -215,7 +215,7 @@ export const AGENTS = {
|
|
|
215
215
|
// Claude Code has no headless Anthropic-hosted dispatch CLI (only
|
|
216
216
|
// --remote-control, which bridges a *local* session). Its cloud is Rush.
|
|
217
217
|
cloudProvider: 'rush',
|
|
218
|
-
capabilities: { hooks: true, mcp: true, mcpHttp: true, mcpHeaders: true, allowlist: true, skills: true, commands: true, plugins: true, subagents: true, rules: { file: 'CLAUDE.md' }, workflows: true, memory: true, modes: ['plan', 'edit', 'auto', 'skip'], rulesImports: true },
|
|
218
|
+
capabilities: { hooks: true, mcp: true, mcpHttp: true, mcpHeaders: true, allowlist: true, skills: true, commands: true, plugins: true, subagents: true, rules: { file: 'CLAUDE.md' }, workflows: true, memory: true, modes: ['plan', 'edit', 'auto', 'skip'], rulesImports: true, interactiveRepl: true },
|
|
219
219
|
},
|
|
220
220
|
// codex hooks: gated to >= 0.116.0 (introduced [features] codex_hooks flag).
|
|
221
221
|
codex: {
|
|
@@ -237,7 +237,7 @@ export const AGENTS = {
|
|
|
237
237
|
cloudProvider: 'codex',
|
|
238
238
|
// Subagents: multi-agent plumbing since 0.117.0; custom agents as
|
|
239
239
|
// ~/.codex/agents/*.toml (name, description, developer_instructions).
|
|
240
|
-
capabilities: { hooks: { since: '0.116.0' }, mcp: true, mcpHttp: true, mcpHeaders: false, allowlist: { since: '0.138.0' }, skills: true, commands: { until: '0.117.0' }, plugins: { since: '0.128.0' }, subagents: { since: '0.117.0' }, rules: { file: 'AGENTS.md' }, workflows: false, memory: true, modes: ['plan', 'edit', 'skip'] },
|
|
240
|
+
capabilities: { hooks: { since: '0.116.0' }, mcp: true, mcpHttp: true, mcpHeaders: false, allowlist: { since: '0.138.0' }, skills: true, commands: { until: '0.117.0' }, plugins: { since: '0.128.0' }, subagents: { since: '0.117.0' }, rules: { file: 'AGENTS.md' }, workflows: false, memory: true, modes: ['plan', 'edit', 'skip'], interactiveRepl: true },
|
|
241
241
|
},
|
|
242
242
|
gemini: {
|
|
243
243
|
id: 'gemini',
|
|
@@ -269,7 +269,7 @@ export const AGENTS = {
|
|
|
269
269
|
},
|
|
270
270
|
// gemini hooks: shipped in v0.26.0 (Jan 2026); older binaries silently ignore the `hooks` key.
|
|
271
271
|
// extensions: gemini-extension.json bundles shipped in v0.8.0; custom subagents in v0.36.0.
|
|
272
|
-
capabilities: { hooks: { since: '0.26.0' }, mcp: true, mcpHttp: true, mcpHeaders: false, allowlist: true, skills: true, commands: true, plugins: { since: '0.8.0' }, subagents: { since: '0.36.0' }, rules: { file: 'GEMINI.md' }, workflows: false, memory: false, modes: ['plan', 'edit', 'skip'], rulesImports: true },
|
|
272
|
+
capabilities: { hooks: { since: '0.26.0' }, mcp: true, mcpHttp: true, mcpHeaders: false, allowlist: true, skills: true, commands: true, plugins: { since: '0.8.0' }, subagents: { since: '0.36.0' }, rules: { file: 'GEMINI.md' }, workflows: false, memory: false, modes: ['plan', 'edit', 'skip'], rulesImports: true, interactiveRepl: false },
|
|
273
273
|
},
|
|
274
274
|
cursor: {
|
|
275
275
|
id: 'cursor',
|
|
@@ -302,7 +302,9 @@ export const AGENTS = {
|
|
|
302
302
|
// + warns for pre-2.4 installs); the direct `subagents add --agents cursor` path
|
|
303
303
|
// writes unconditionally, same as the other since-gated agents.
|
|
304
304
|
// See transformSubagentForCursor / https://cursor.com/docs/subagents.
|
|
305
|
-
|
|
305
|
+
// interactiveRepl: false — cursor-agent exits immediately with no argv. It requires a
|
|
306
|
+
// prompt to do anything useful; a bare invocation is not a REPL (RUSH-2185, EXEC-23a).
|
|
307
|
+
capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: true, commands: true, plugins: true, subagents: { since: '2026.1.22' }, rules: { file: '.cursorrules' }, workflows: false, memory: false, modes: ['edit', 'skip'], interactiveRepl: false }, // allowlist: ~/.cursor/cli-config.json
|
|
306
308
|
},
|
|
307
309
|
opencode: {
|
|
308
310
|
id: 'opencode',
|
|
@@ -324,7 +326,45 @@ export const AGENTS = {
|
|
|
324
326
|
format: 'markdown',
|
|
325
327
|
variableSyntax: '$ARGUMENTS',
|
|
326
328
|
supportsHooks: true,
|
|
327
|
-
capabilities: { hooks: { since: '0.3.130' }, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: { since: '1.1.1' }, skills: true, commands: true, plugins: true, subagents: true, rules: { file: 'AGENTS.md' }, workflows: false, memory: false, modes: ['plan', 'edit'] },
|
|
329
|
+
capabilities: { hooks: { since: '0.3.130' }, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: { since: '1.1.1' }, skills: true, commands: true, plugins: true, subagents: true, rules: { file: 'AGENTS.md' }, workflows: false, memory: false, modes: ['plan', 'edit'], interactiveRepl: true },
|
|
330
|
+
},
|
|
331
|
+
// Oh My Pi (`omp`, omp.sh) — a Bun-based, terminal-first coding agent that runs
|
|
332
|
+
// against many model providers (OpenRouter, OpenAI, Anthropic, xAI, DeepSeek,
|
|
333
|
+
// Ollama, LM Studio, …). It is Claude-compatible: it natively discovers
|
|
334
|
+
// `.claude/commands`, `.mcp.json`, and Claude-shaped subagents, and keeps its
|
|
335
|
+
// own native resources under `~/.omp/agent/` (config dir reported by
|
|
336
|
+
// `omp config path`). configDir points AT the agent dir (not `~/.omp`) so the
|
|
337
|
+
// rules file lands at `~/.omp/agent/AGENTS.md`, the user context file omp reads.
|
|
338
|
+
pi: {
|
|
339
|
+
id: 'pi',
|
|
340
|
+
name: 'Pi',
|
|
341
|
+
color: 'magenta',
|
|
342
|
+
cliCommand: 'omp',
|
|
343
|
+
npmPackage: '@oh-my-pi/pi-coding-agent',
|
|
344
|
+
configDir: path.join(HOME, '.omp', 'agent'),
|
|
345
|
+
commandsDir: path.join(HOME, '.omp', 'agent', 'commands'),
|
|
346
|
+
commandsSubdir: 'commands',
|
|
347
|
+
skillsDir: path.join(HOME, '.omp', 'agent', 'skills'),
|
|
348
|
+
hooksDir: 'hooks',
|
|
349
|
+
instructionsFile: 'AGENTS.md',
|
|
350
|
+
format: 'markdown',
|
|
351
|
+
variableSyntax: '$ARGUMENTS',
|
|
352
|
+
// omp hooks are per-tool JS/TS extension modules discovered from
|
|
353
|
+
// `~/.omp/agent/hooks/{pre,post}/<tool>.<ext>` (loaded as HookFactory code),
|
|
354
|
+
// NOT the event->shell-command registrations agents-cli's hook sync writes.
|
|
355
|
+
// The two models don't map, so hooks stay off (capabilities.hooks:false).
|
|
356
|
+
supportsHooks: false,
|
|
357
|
+
// MCP: omp reads `.mcp.json` with the Claude `{ "mcpServers": {...} }` schema
|
|
358
|
+
// at user scope (`~/.omp/agent/.mcp.json`) and project scope (`<root>/.mcp.json`),
|
|
359
|
+
// stdio + http + sse with headers. skills (`~/.omp/agent/skills/<name>/SKILL.md`),
|
|
360
|
+
// commands (`~/.omp/agent/commands/*.md`), and subagents (`~/.omp/agent/agents/*.md`,
|
|
361
|
+
// Claude-shaped) are all native. allowlist is OFF: omp gates approval per-TOOL
|
|
362
|
+
// only (`tools.approval` record: allow|prompt|deny) with no command/path/domain
|
|
363
|
+
// patterns, so agents-cli's granular permission format has nothing to map to.
|
|
364
|
+
// plugins are npm packages / TS modules, not the Claude marketplace manifest.
|
|
365
|
+
// interactiveRepl: true — bare `omp` runs the TUI; `omp -p` is the one-shot
|
|
366
|
+
// form that answers a prompt and exits (upstream README, "Four entry points").
|
|
367
|
+
capabilities: { hooks: false, mcp: true, mcpHttp: true, mcpHeaders: true, allowlist: false, skills: true, commands: true, plugins: false, subagents: true, rules: { file: 'AGENTS.md' }, workflows: false, memory: false, modes: ['plan', 'edit', 'skip'], interactiveRepl: true },
|
|
328
368
|
},
|
|
329
369
|
openclaw: {
|
|
330
370
|
id: 'openclaw',
|
|
@@ -348,7 +388,7 @@ export const AGENTS = {
|
|
|
348
388
|
// OpenClaw is self-updating (no pinned since), so `true` is correct.
|
|
349
389
|
// Workflows sync as Lobster `.lobster` files under `.openclaw/workflows/`;
|
|
350
390
|
// the Lobster tool runs them by receiving the file path as `pipeline`.
|
|
351
|
-
capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: true, commands: false, plugins: true, subagents: true, rules: { file: 'workspace/AGENTS.md' }, workflows: true, memory: true, modes: ['plan', 'edit', 'skip'] },
|
|
391
|
+
capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: true, commands: false, plugins: true, subagents: true, rules: { file: 'workspace/AGENTS.md' }, workflows: true, memory: true, modes: ['plan', 'edit', 'skip'], interactiveRepl: true },
|
|
352
392
|
},
|
|
353
393
|
copilot: {
|
|
354
394
|
id: 'copilot',
|
|
@@ -373,7 +413,9 @@ export const AGENTS = {
|
|
|
373
413
|
format: 'markdown',
|
|
374
414
|
variableSyntax: '$ARGUMENTS',
|
|
375
415
|
supportsHooks: true,
|
|
376
|
-
|
|
416
|
+
// interactiveRepl: false — copilot requires a prompt for meaningful work; bare invocation
|
|
417
|
+
// opens a welcome screen but not a persistent coding REPL suitable for agents focus.
|
|
418
|
+
capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: true, commands: true, plugins: true, subagents: { since: '0.0.353' }, rules: { file: 'AGENTS.md' }, workflows: false, memory: false, modes: ['plan', 'edit', 'auto', 'skip'], interactiveRepl: false },
|
|
377
419
|
},
|
|
378
420
|
amp: {
|
|
379
421
|
id: 'amp',
|
|
@@ -390,7 +432,8 @@ export const AGENTS = {
|
|
|
390
432
|
format: 'markdown',
|
|
391
433
|
variableSyntax: '$ARGUMENTS',
|
|
392
434
|
supportsHooks: false,
|
|
393
|
-
|
|
435
|
+
// interactiveRepl: false — amp requires a prompt; bare invocation exits immediately.
|
|
436
|
+
capabilities: { hooks: false, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: false, skills: true, commands: true, plugins: false, subagents: false, rules: { file: 'AGENTS.md' }, workflows: false, memory: false, modes: ['plan', 'edit'], interactiveRepl: false },
|
|
394
437
|
},
|
|
395
438
|
kiro: {
|
|
396
439
|
id: 'kiro',
|
|
@@ -413,7 +456,9 @@ export const AGENTS = {
|
|
|
413
456
|
format: 'markdown',
|
|
414
457
|
variableSyntax: '$ARGUMENTS',
|
|
415
458
|
supportsHooks: true,
|
|
416
|
-
|
|
459
|
+
// interactiveRepl: false — kiro-cli requires a prompt for coding sessions; bare
|
|
460
|
+
// invocation does not open a persistent REPL.
|
|
461
|
+
capabilities: { hooks: { since: '0.10.0' }, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: { since: '2.8.0' }, skills: true, commands: true, plugins: false, subagents: { since: '1.23.0' }, rules: { file: 'AGENTS.md' }, workflows: false, memory: false, modes: ['edit'], interactiveRepl: false },
|
|
417
462
|
},
|
|
418
463
|
goose: {
|
|
419
464
|
id: 'goose',
|
|
@@ -445,7 +490,7 @@ export const AGENTS = {
|
|
|
445
490
|
// under `slash_commands: [{ command, recipe_path }]` (see goose-commands.ts).
|
|
446
491
|
// Subagents: recipe YAML named agents under ~/.config/goose/agents/<name>.yaml
|
|
447
492
|
// (goose auto-discovers and delegates to them by name in autonomous mode).
|
|
448
|
-
capabilities: { hooks: { since: '1.34.0' }, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: { since: '1.25.0' }, commands: true, plugins: true, subagents: true, rules: { file: 'AGENTS.md' }, workflows: true, memory: false, modes: ['edit'] },
|
|
493
|
+
capabilities: { hooks: { since: '1.34.0' }, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: { since: '1.25.0' }, commands: true, plugins: true, subagents: true, rules: { file: 'AGENTS.md' }, workflows: true, memory: false, modes: ['edit'], interactiveRepl: true },
|
|
449
494
|
},
|
|
450
495
|
// Google Antigravity CLI (`agy`) — official replacement for Gemini CLI as of IO 2026.
|
|
451
496
|
// configDir nests inside `~/.gemini/` since agy shares the parent dir with the Gemini
|
|
@@ -474,7 +519,7 @@ export const AGENTS = {
|
|
|
474
519
|
variableSyntax: '{{args}}',
|
|
475
520
|
supportsHooks: true,
|
|
476
521
|
cloudProvider: 'antigravity',
|
|
477
|
-
capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: true, commands: true, plugins: true, subagents: { since: '1.0.16' }, rules: { file: 'AGENTS.md' }, workflows: { since: '1.0.6' }, memory: false, modes: ['edit', 'skip'], rulesImports: false }, // workflows: markdown files in the shared, HOME-global ~/.gemini/config/global_workflows/ (agy scans it at startup; not version-isolated — see workflows.ts), invoked as /<name> slash commands
|
|
522
|
+
capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: true, commands: true, plugins: true, subagents: { since: '1.0.16' }, rules: { file: 'AGENTS.md' }, workflows: { since: '1.0.6' }, memory: false, modes: ['edit', 'skip'], rulesImports: false, interactiveRepl: true }, // workflows: markdown files in the shared, HOME-global ~/.gemini/config/global_workflows/ (agy scans it at startup; not version-isolated — see workflows.ts), invoked as /<name> slash commands
|
|
478
523
|
},
|
|
479
524
|
// xAI Grok Build CLI (`grok`) — early beta, SuperGrok Heavy. Auth via OAuth on
|
|
480
525
|
// first launch, or XAI_API_KEY env var for headless. MCP servers configured inline
|
|
@@ -525,6 +570,7 @@ export const AGENTS = {
|
|
|
525
570
|
// to auto (→ edit via resolveMode). Interactive plan is unaffected.
|
|
526
571
|
headlessPlan: false,
|
|
527
572
|
rulesImports: true,
|
|
573
|
+
interactiveRepl: true,
|
|
528
574
|
},
|
|
529
575
|
},
|
|
530
576
|
// Kimi Code CLI (`kimi`) — Moonshot AI coding agent.
|
|
@@ -568,6 +614,7 @@ export const AGENTS = {
|
|
|
568
614
|
// auto-runs). Interactive plan is unaffected.
|
|
569
615
|
headlessPlan: false,
|
|
570
616
|
rulesImports: false,
|
|
617
|
+
interactiveRepl: true,
|
|
571
618
|
},
|
|
572
619
|
},
|
|
573
620
|
// Factory AI Droid CLI (`droid`) — agentic coding CLI from factory.ai.
|
|
@@ -649,6 +696,7 @@ export const AGENTS = {
|
|
|
649
696
|
memory: false,
|
|
650
697
|
modes: ['plan', 'edit', 'auto', 'skip'],
|
|
651
698
|
rulesImports: false,
|
|
699
|
+
interactiveRepl: true,
|
|
652
700
|
},
|
|
653
701
|
},
|
|
654
702
|
// Nous Hermes Agent. Config lives under ~/.hermes/config.yaml; MCP servers
|
|
@@ -698,6 +746,7 @@ export const AGENTS = {
|
|
|
698
746
|
memory: true,
|
|
699
747
|
modes: ['edit'],
|
|
700
748
|
rulesImports: false,
|
|
749
|
+
interactiveRepl: true,
|
|
701
750
|
},
|
|
702
751
|
},
|
|
703
752
|
};
|
|
@@ -2350,6 +2399,9 @@ export function getUserMcpConfigPath(agentId) {
|
|
|
2350
2399
|
return path.join(agent.configDir, 'mcp.json');
|
|
2351
2400
|
case 'hermes':
|
|
2352
2401
|
return path.join(agent.configDir, 'config.yaml');
|
|
2402
|
+
case 'pi':
|
|
2403
|
+
// omp reads user-scope MCP from ~/.omp/agent/.mcp.json (Claude schema).
|
|
2404
|
+
return path.join(agent.configDir, '.mcp.json');
|
|
2353
2405
|
default:
|
|
2354
2406
|
// Gemini and others use settings.json
|
|
2355
2407
|
return path.join(agent.configDir, 'settings.json');
|
|
@@ -2386,6 +2438,8 @@ export function getMcpConfigPathForHome(agentId, home) {
|
|
|
2386
2438
|
return path.join(home, '.factory', 'mcp.json');
|
|
2387
2439
|
case 'hermes':
|
|
2388
2440
|
return path.join(home, '.hermes', 'config.yaml');
|
|
2441
|
+
case 'pi':
|
|
2442
|
+
return path.join(home, '.omp', 'agent', '.mcp.json');
|
|
2389
2443
|
default:
|
|
2390
2444
|
return path.join(home, agentConfigDirName(agentId), 'settings.json');
|
|
2391
2445
|
}
|
|
@@ -2425,6 +2479,9 @@ export function getProjectMcpConfigPath(agentId, cwd = process.cwd()) {
|
|
|
2425
2479
|
return path.join(cwd, '.factory', 'mcp.json');
|
|
2426
2480
|
case 'hermes':
|
|
2427
2481
|
return path.join(cwd, '.hermes', 'config.yaml');
|
|
2482
|
+
case 'pi':
|
|
2483
|
+
// omp reads project MCP from <root>/.mcp.json (Claude-compatible).
|
|
2484
|
+
return path.join(cwd, '.mcp.json');
|
|
2428
2485
|
default:
|
|
2429
2486
|
return path.join(cwd, `.${agentId}`, 'settings.json');
|
|
2430
2487
|
}
|
|
@@ -92,13 +92,19 @@ export function recipeToolsPerSession(win) {
|
|
|
92
92
|
if (!db)
|
|
93
93
|
return { id, title, store: 'sessions', rows: [], empty: true };
|
|
94
94
|
try {
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
// Counts come from tool_scan_ledger — one row per session the tool indexer
|
|
96
|
+
// has scanned, carrying that session's true call_count (0 included). The
|
|
97
|
+
// sessions.tool_call_count column is NOT the source: only the teams
|
|
98
|
+
// summarizer ever writes it (lib/teams/summarizer.ts), so reading it scored
|
|
99
|
+
// every non-teams session as 0 and pinned p50 at 0 fleet-wide.
|
|
100
|
+
const tables = db.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'tool_scan_ledger'`).all();
|
|
101
|
+
if (tables.length === 0) {
|
|
97
102
|
return { id, title, store: 'sessions', rows: [], empty: true };
|
|
98
103
|
}
|
|
99
|
-
const byAgent = db.prepare(`SELECT agent,
|
|
100
|
-
FROM
|
|
101
|
-
|
|
104
|
+
const byAgent = db.prepare(`SELECT s.agent AS agent, l.call_count AS n
|
|
105
|
+
FROM tool_scan_ledger l
|
|
106
|
+
JOIN sessions s ON s.id = l.session_id
|
|
107
|
+
WHERE s.timestamp >= ?`).all(win.sinceIso);
|
|
102
108
|
if (byAgent.length === 0)
|
|
103
109
|
return { id, title, store: 'sessions', rows: [], empty: true };
|
|
104
110
|
const groups = new Map();
|
|
@@ -30,13 +30,21 @@ export declare function getProfile(name: string): Promise<BrowserProfile | null>
|
|
|
30
30
|
* Resolve the profile `agents browser start` uses when no `--profile` is given.
|
|
31
31
|
*
|
|
32
32
|
* Order: (1) the device-local configured default (`agents browser profiles
|
|
33
|
-
* set-default <name>`) when it names an existing profile
|
|
34
|
-
* `default` profile
|
|
35
|
-
* browser per the platform priority list in
|
|
36
|
-
* edge > chromium > comet; Linux: chrome >
|
|
37
|
-
* edge > chrome > brave > comet) and pin a
|
|
38
|
-
* actionable error if none of those binaries
|
|
39
|
-
*
|
|
33
|
+
* set-default <name>`) when it names an existing profile that can launch here;
|
|
34
|
+
* (2) an existing `default` profile that can launch here; (3) auto-pick the
|
|
35
|
+
* first installed Chromium-family browser per the platform priority list in
|
|
36
|
+
* chrome.ts (macOS: chrome > brave > edge > chromium > comet; Linux: chrome >
|
|
37
|
+
* chromium > brave > edge; Windows: edge > chrome > brave > comet) and pin a
|
|
38
|
+
* `default` profile to it. Throws an actionable error if none of those binaries
|
|
39
|
+
* are installed.
|
|
40
|
+
*
|
|
41
|
+
* "Can launch here" matters because `agents.yaml` syncs across the fleet: a
|
|
42
|
+
* `default` auto-created on macOS carries a `/Applications/...` binary path that
|
|
43
|
+
* doesn't exist on a Linux box. Rather than hand that broken profile back (the
|
|
44
|
+
* top browser roadblock — "Custom binary not found"), we re-detect and
|
|
45
|
+
* regenerate the `default` for this machine. A configured or existing default
|
|
46
|
+
* that no longer exists, or can't launch here, warns and falls through — never a
|
|
47
|
+
* hard fail.
|
|
40
48
|
*/
|
|
41
49
|
export declare function ensureDefaultBrowserProfile(): Promise<BrowserProfile>;
|
|
42
50
|
/**
|
|
@@ -95,29 +95,63 @@ export async function getProfile(name) {
|
|
|
95
95
|
return null;
|
|
96
96
|
return configToProfile(name, config);
|
|
97
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* True when `profile` can actually launch or attach on THIS machine.
|
|
100
|
+
*
|
|
101
|
+
* A remote (`ssh://`) profile runs its browser on the far host, so a local
|
|
102
|
+
* binary check doesn't apply — assume launchable. A local profile must have its
|
|
103
|
+
* configured browser/binary present on disk here; a `default` profile
|
|
104
|
+
* auto-created on a different OS (e.g. a macOS Chrome path reused on Linux — the
|
|
105
|
+
* "Custom binary not found" failure) fails this check so the caller re-detects
|
|
106
|
+
* for the current machine.
|
|
107
|
+
*/
|
|
108
|
+
function isProfileLaunchableHere(profile) {
|
|
109
|
+
const remote = Object.values(getEndpointPresets(profile)).some((preset) => preset.target.startsWith('ssh://'));
|
|
110
|
+
if (remote)
|
|
111
|
+
return true;
|
|
112
|
+
try {
|
|
113
|
+
findBrowserPath(profile.browser, profile.binary);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
98
120
|
/**
|
|
99
121
|
* Resolve the profile `agents browser start` uses when no `--profile` is given.
|
|
100
122
|
*
|
|
101
123
|
* Order: (1) the device-local configured default (`agents browser profiles
|
|
102
|
-
* set-default <name>`) when it names an existing profile
|
|
103
|
-
* `default` profile
|
|
104
|
-
* browser per the platform priority list in
|
|
105
|
-
* edge > chromium > comet; Linux: chrome >
|
|
106
|
-
* edge > chrome > brave > comet) and pin a
|
|
107
|
-
* actionable error if none of those binaries
|
|
108
|
-
*
|
|
124
|
+
* set-default <name>`) when it names an existing profile that can launch here;
|
|
125
|
+
* (2) an existing `default` profile that can launch here; (3) auto-pick the
|
|
126
|
+
* first installed Chromium-family browser per the platform priority list in
|
|
127
|
+
* chrome.ts (macOS: chrome > brave > edge > chromium > comet; Linux: chrome >
|
|
128
|
+
* chromium > brave > edge; Windows: edge > chrome > brave > comet) and pin a
|
|
129
|
+
* `default` profile to it. Throws an actionable error if none of those binaries
|
|
130
|
+
* are installed.
|
|
131
|
+
*
|
|
132
|
+
* "Can launch here" matters because `agents.yaml` syncs across the fleet: a
|
|
133
|
+
* `default` auto-created on macOS carries a `/Applications/...` binary path that
|
|
134
|
+
* doesn't exist on a Linux box. Rather than hand that broken profile back (the
|
|
135
|
+
* top browser roadblock — "Custom binary not found"), we re-detect and
|
|
136
|
+
* regenerate the `default` for this machine. A configured or existing default
|
|
137
|
+
* that no longer exists, or can't launch here, warns and falls through — never a
|
|
138
|
+
* hard fail.
|
|
109
139
|
*/
|
|
110
140
|
export async function ensureDefaultBrowserProfile() {
|
|
111
141
|
const configured = getConfiguredDefaultProfileName();
|
|
112
142
|
if (configured) {
|
|
113
143
|
const chosen = await getProfile(configured);
|
|
114
|
-
if (chosen)
|
|
144
|
+
if (chosen && isProfileLaunchableHere(chosen))
|
|
115
145
|
return chosen;
|
|
116
|
-
console.warn(
|
|
117
|
-
`
|
|
146
|
+
console.warn(chosen
|
|
147
|
+
? `warning: configured default browser profile "${configured}" can't launch on this ` +
|
|
148
|
+
`machine (its browser/binary isn't installed here); falling back to auto-detect. ` +
|
|
149
|
+
`Fix with: agents browser profiles set-default <name> (or --unset)`
|
|
150
|
+
: `warning: configured default browser profile "${configured}" no longer exists; ` +
|
|
151
|
+
`falling back to auto-detect. Fix with: agents browser profiles set-default <name> (or --unset)`);
|
|
118
152
|
}
|
|
119
153
|
const existing = await getProfile(DEFAULT_BROWSER_PROFILE_NAME);
|
|
120
|
-
if (existing)
|
|
154
|
+
if (existing && isProfileLaunchableHere(existing))
|
|
121
155
|
return existing;
|
|
122
156
|
const detected = findFirstInstalledBrowser();
|
|
123
157
|
if (!detected) {
|
|
@@ -139,7 +173,14 @@ export async function ensureDefaultBrowserProfile() {
|
|
|
139
173
|
height: DEFAULT_VIEWPORT.height,
|
|
140
174
|
},
|
|
141
175
|
};
|
|
142
|
-
|
|
176
|
+
// A stale `default` (auto-created on another OS, unlaunchable here) is
|
|
177
|
+
// regenerated in place; otherwise this is the first-run create.
|
|
178
|
+
if (existing) {
|
|
179
|
+
await updateProfile(profile);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
await createProfile(profile);
|
|
183
|
+
}
|
|
143
184
|
return profile;
|
|
144
185
|
}
|
|
145
186
|
/**
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BYOK (Bring Your Own Key) budget fetcher.
|
|
3
|
+
*
|
|
4
|
+
* For custom harnesses that use a provider's API key stored in the keychain
|
|
5
|
+
* (e.g. OpenRouter), this module fetches the account's credit/usage data so
|
|
6
|
+
* `agents view` can show a budget bar alongside the harness detail row — the
|
|
7
|
+
* same bar style the native usage system uses, just sourced from the provider
|
|
8
|
+
* API rather than the CLI's own usage tracking.
|
|
9
|
+
*/
|
|
10
|
+
import type { Profile } from './profiles.js';
|
|
11
|
+
export interface ByokBudgetInfo {
|
|
12
|
+
limitUsd: number | null;
|
|
13
|
+
remainingUsd: number | null;
|
|
14
|
+
usedUsd: number;
|
|
15
|
+
usedPercent: number | null;
|
|
16
|
+
fetchedAt: Date;
|
|
17
|
+
}
|
|
18
|
+
export interface ByokUsageResult {
|
|
19
|
+
budget: ByokBudgetInfo | null;
|
|
20
|
+
error: string | null;
|
|
21
|
+
}
|
|
22
|
+
export declare function setByokFetchForTest(fn: typeof globalThis.fetch): void;
|
|
23
|
+
export declare function resetByokCacheForTest(): void;
|
|
24
|
+
export declare function hasByokProvider(provider: string): boolean;
|
|
25
|
+
export declare function renderByokBar(result: ByokUsageResult): string;
|
|
26
|
+
/**
|
|
27
|
+
* Fetch the BYOK budget for a harness.
|
|
28
|
+
*
|
|
29
|
+
* Returns `null` when the harness has no registered BYOK provider or no auth.
|
|
30
|
+
* Returns a `ByokUsageResult` with `budget: null` when the token is absent
|
|
31
|
+
* from the keychain (so the caller can skip rendering a bar without error).
|
|
32
|
+
*
|
|
33
|
+
* Results are SWR-cached per `keychainItem` so harnesses sharing one API key
|
|
34
|
+
* deduplicate to a single network request.
|
|
35
|
+
*/
|
|
36
|
+
export declare function getByokUsageForHarness(profile: Profile, opts?: {
|
|
37
|
+
forceRefresh?: boolean;
|
|
38
|
+
}): Promise<ByokUsageResult | null>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BYOK (Bring Your Own Key) budget fetcher.
|
|
3
|
+
*
|
|
4
|
+
* For custom harnesses that use a provider's API key stored in the keychain
|
|
5
|
+
* (e.g. OpenRouter), this module fetches the account's credit/usage data so
|
|
6
|
+
* `agents view` can show a budget bar alongside the harness detail row — the
|
|
7
|
+
* same bar style the native usage system uses, just sourced from the provider
|
|
8
|
+
* API rather than the CLI's own usage tracking.
|
|
9
|
+
*/
|
|
10
|
+
import { hasKeychainToken, getKeychainToken } from './secrets/profiles.js';
|
|
11
|
+
import { renderBar, getUsageColor, USAGE_CACHE_FRESH_MS, USAGE_CACHE_SWR_MS } from './usage.js';
|
|
12
|
+
// ─── Injectable fetch seam (tests only) ──────────────────────────────────────
|
|
13
|
+
let _fetch = globalThis.fetch;
|
|
14
|
+
export function setByokFetchForTest(fn) {
|
|
15
|
+
_fetch = fn;
|
|
16
|
+
}
|
|
17
|
+
const _cache = new Map();
|
|
18
|
+
export function resetByokCacheForTest() {
|
|
19
|
+
_cache.clear();
|
|
20
|
+
}
|
|
21
|
+
async function fetchOpenRouter(keychainItem) {
|
|
22
|
+
try {
|
|
23
|
+
if (!hasKeychainToken(keychainItem))
|
|
24
|
+
return { budget: null, error: null };
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return { budget: null, error: null };
|
|
28
|
+
}
|
|
29
|
+
let token;
|
|
30
|
+
try {
|
|
31
|
+
token = getKeychainToken(keychainItem);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return { budget: null, error: null };
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const res = await _fetch('https://openrouter.ai/api/v1/key', {
|
|
38
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
39
|
+
});
|
|
40
|
+
if (!res.ok)
|
|
41
|
+
return { budget: null, error: `HTTP ${res.status}` };
|
|
42
|
+
const json = (await res.json());
|
|
43
|
+
const { limit, limit_remaining, usage } = json.data;
|
|
44
|
+
const limitUsd = limit ?? null;
|
|
45
|
+
const usedUsd = usage;
|
|
46
|
+
const usedPercent = limitUsd !== null && limitUsd > 0 ? (usedUsd / limitUsd) * 100 : null;
|
|
47
|
+
return {
|
|
48
|
+
budget: {
|
|
49
|
+
limitUsd,
|
|
50
|
+
remainingUsd: limit_remaining ?? null,
|
|
51
|
+
usedUsd,
|
|
52
|
+
usedPercent,
|
|
53
|
+
fetchedAt: new Date(),
|
|
54
|
+
},
|
|
55
|
+
error: null,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
return { budget: null, error: String(err) };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const BYOK_REGISTRY = {
|
|
63
|
+
openrouter: { fetch: fetchOpenRouter },
|
|
64
|
+
};
|
|
65
|
+
export function hasByokProvider(provider) {
|
|
66
|
+
return Object.prototype.hasOwnProperty.call(BYOK_REGISTRY, provider);
|
|
67
|
+
}
|
|
68
|
+
// ─── Bar renderer ─────────────────────────────────────────────────────────────
|
|
69
|
+
export function renderByokBar(result) {
|
|
70
|
+
if (!result.budget)
|
|
71
|
+
return '';
|
|
72
|
+
const { budget } = result;
|
|
73
|
+
if (budget.limitUsd === null) {
|
|
74
|
+
const bar = renderBar(0, 10, 0);
|
|
75
|
+
return `$: ${bar} $${budget.usedUsd.toFixed(2)} used (unlimited)`;
|
|
76
|
+
}
|
|
77
|
+
const pct = budget.usedPercent ?? 0;
|
|
78
|
+
const bar = renderBar(pct, 10, pct > 0 ? 1 : 0);
|
|
79
|
+
const color = getUsageColor(pct);
|
|
80
|
+
const remaining = budget.remainingUsd !== null ? `$${budget.remainingUsd.toFixed(2)}` : '?';
|
|
81
|
+
const limit = `$${budget.limitUsd.toFixed(2)}`;
|
|
82
|
+
return `$: ${bar} ${color(`${Math.round(pct)}%`)} (${remaining} left of ${limit})`;
|
|
83
|
+
}
|
|
84
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
85
|
+
/**
|
|
86
|
+
* Fetch the BYOK budget for a harness.
|
|
87
|
+
*
|
|
88
|
+
* Returns `null` when the harness has no registered BYOK provider or no auth.
|
|
89
|
+
* Returns a `ByokUsageResult` with `budget: null` when the token is absent
|
|
90
|
+
* from the keychain (so the caller can skip rendering a bar without error).
|
|
91
|
+
*
|
|
92
|
+
* Results are SWR-cached per `keychainItem` so harnesses sharing one API key
|
|
93
|
+
* deduplicate to a single network request.
|
|
94
|
+
*/
|
|
95
|
+
export async function getByokUsageForHarness(profile, opts) {
|
|
96
|
+
if (!profile.provider || !hasByokProvider(profile.provider) || !profile.auth)
|
|
97
|
+
return null;
|
|
98
|
+
const keychainItem = profile.auth.keychainItem;
|
|
99
|
+
const provider = BYOK_REGISTRY[profile.provider];
|
|
100
|
+
const now = Date.now();
|
|
101
|
+
const cached = _cache.get(keychainItem);
|
|
102
|
+
if (!opts?.forceRefresh && cached) {
|
|
103
|
+
const age = now - cached.fetchedAt;
|
|
104
|
+
if (age < USAGE_CACHE_FRESH_MS) {
|
|
105
|
+
return cached.result;
|
|
106
|
+
}
|
|
107
|
+
if (age < USAGE_CACHE_SWR_MS) {
|
|
108
|
+
void provider.fetch(keychainItem).then((result) => {
|
|
109
|
+
_cache.set(keychainItem, { result, fetchedAt: Date.now() });
|
|
110
|
+
});
|
|
111
|
+
return cached.result;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const result = await provider.fetch(keychainItem);
|
|
115
|
+
_cache.set(keychainItem, { result, fetchedAt: now });
|
|
116
|
+
return result;
|
|
117
|
+
}
|
package/dist/lib/capabilities.js
CHANGED
|
@@ -54,7 +54,7 @@ export function supports(agent, cap, version) {
|
|
|
54
54
|
const c = getCapability(agent, cap);
|
|
55
55
|
if (c === false)
|
|
56
56
|
return { ok: false, reason: 'unsupported' };
|
|
57
|
-
if (c === true)
|
|
57
|
+
if (c === true || c === undefined)
|
|
58
58
|
return { ok: true };
|
|
59
59
|
if ('file' in c)
|
|
60
60
|
return { ok: true };
|
package/dist/lib/exec.d.ts
CHANGED
|
@@ -308,6 +308,26 @@ export declare function resolveShimSpawn(platform: NodeJS.Platform, binary: stri
|
|
|
308
308
|
* keeping version resolution in one place instead of reimplementing it in batch.
|
|
309
309
|
*/
|
|
310
310
|
export declare function execShimPassthrough(agent: AgentId, rawArgs: string[], cwd: string, pinnedVersion?: string): Promise<number>;
|
|
311
|
+
/**
|
|
312
|
+
* Whether a dead pane's failure should be recapped to stderr (RUSH-2185 / EXEC-23a).
|
|
313
|
+
*
|
|
314
|
+
* For headless runs only a nonzero exit is a failure worth surfacing — a
|
|
315
|
+
* clean 0 means the agent finished the task before we could attach.
|
|
316
|
+
* For interactive runs ANY exit (including 0) is a failure: an instant clean
|
|
317
|
+
* exit means the harness has no bare REPL and the user would see only a mute
|
|
318
|
+
* `[detached]` with no explanation.
|
|
319
|
+
*/
|
|
320
|
+
export declare function shouldRecapDeadPane(status: number | undefined, interactive: boolean): boolean;
|
|
321
|
+
/**
|
|
322
|
+
* True only when a `display-message #{pane_dead}` tmux query explicitly returned
|
|
323
|
+
* "0" (pane alive). Used to distinguish "pane alive" from "query failed" in
|
|
324
|
+
* situations where `paneExitStatus` conservatively returns `{dead: false}` for
|
|
325
|
+
* both (RUSH-2185 / EXEC-23a / F3).
|
|
326
|
+
*
|
|
327
|
+
* @param code The exit code of the `tmux display-message` command.
|
|
328
|
+
* @param stdout Its stdout (expected to be "0" when the pane is alive).
|
|
329
|
+
*/
|
|
330
|
+
export declare function isPaneKnownAliveFromQueryResult(code: number, stdout: string): boolean;
|
|
311
331
|
/** Inputs that decide whether an interactive spawn is wrapped in a shared-socket tmux session. */
|
|
312
332
|
export interface TmuxWrapContext {
|
|
313
333
|
/** resolveInteractive() result — only interactive REPL launches are wrapped. */
|