@phnx-labs/agents-cli 1.22.21 → 1.22.23

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 +256 -0
  2. package/README.md +8 -2
  3. package/dist/commands/doctor.js +15 -7
  4. package/dist/commands/exec.js +20 -6
  5. package/dist/commands/focus.d.ts +76 -4
  6. package/dist/commands/focus.js +219 -40
  7. package/dist/commands/fork.d.ts +20 -2
  8. package/dist/commands/fork.js +91 -64
  9. package/dist/commands/go.d.ts +25 -0
  10. package/dist/commands/go.js +63 -2
  11. package/dist/commands/harness-wizard.d.ts +206 -0
  12. package/dist/commands/harness-wizard.js +403 -0
  13. package/dist/commands/harness.d.ts +12 -0
  14. package/dist/commands/harness.js +97 -101
  15. package/dist/commands/insights.d.ts +32 -0
  16. package/dist/commands/insights.js +478 -0
  17. package/dist/commands/resume.js +10 -3
  18. package/dist/commands/secrets.js +25 -30
  19. package/dist/commands/sessions-picker.d.ts +2 -0
  20. package/dist/commands/sessions-picker.js +1 -0
  21. package/dist/commands/sessions-resume.d.ts +21 -3
  22. package/dist/commands/sessions-resume.js +50 -11
  23. package/dist/commands/sessions.d.ts +81 -5
  24. package/dist/commands/sessions.js +331 -66
  25. package/dist/commands/watchdog.js +13 -2
  26. package/dist/index.js +3 -1
  27. package/dist/lib/agents.d.ts +1 -1
  28. package/dist/lib/agents.js +132 -0
  29. package/dist/lib/codex-policy.d.ts +17 -0
  30. package/dist/lib/codex-policy.js +48 -0
  31. package/dist/lib/crabbox/lease.d.ts +25 -0
  32. package/dist/lib/crabbox/lease.js +62 -0
  33. package/dist/lib/daemon.js +70 -0
  34. package/dist/lib/exec.d.ts +4 -0
  35. package/dist/lib/exec.js +88 -54
  36. package/dist/lib/feed-broadcast.d.ts +1 -20
  37. package/dist/lib/feed-broadcast.js +31 -1
  38. package/dist/lib/hooks.js +12 -2
  39. package/dist/lib/hosts/passthrough.js +1 -0
  40. package/dist/lib/mcp.js +44 -0
  41. package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +1 -5
  42. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  43. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +2 -15
  44. package/dist/lib/models.d.ts +0 -5
  45. package/dist/lib/models.js +48 -0
  46. package/dist/lib/picker.d.ts +45 -0
  47. package/dist/lib/picker.js +75 -6
  48. package/dist/lib/plugin-marketplace.js +9 -0
  49. package/dist/lib/pricing/prices.json +119 -92
  50. package/dist/lib/pricing/table.js +13 -0
  51. package/dist/lib/remote-agents-json.d.ts +29 -1
  52. package/dist/lib/remote-agents-json.js +47 -10
  53. package/dist/lib/resources/mcp.js +2 -0
  54. package/dist/lib/resources/permissions.js +3 -0
  55. package/dist/lib/resources/types.d.ts +2 -1
  56. package/dist/lib/runner.js +28 -19
  57. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  58. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -2
  59. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  60. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -13
  61. package/dist/lib/secrets/index.d.ts +15 -1
  62. package/dist/lib/secrets/index.js +118 -49
  63. package/dist/lib/secrets/reaper.d.ts +87 -0
  64. package/dist/lib/secrets/reaper.js +184 -0
  65. package/dist/lib/secrets/remote.d.ts +29 -0
  66. package/dist/lib/secrets/remote.js +37 -1
  67. package/dist/lib/session/active.d.ts +36 -1
  68. package/dist/lib/session/active.js +60 -19
  69. package/dist/lib/session/actor-sidecar.d.ts +14 -0
  70. package/dist/lib/session/actor-sidecar.js +67 -4
  71. package/dist/lib/session/db.d.ts +35 -1
  72. package/dist/lib/session/db.js +132 -1
  73. package/dist/lib/session/digest.d.ts +3 -0
  74. package/dist/lib/session/digest.js +3 -3
  75. package/dist/lib/session/discover.js +168 -0
  76. package/dist/lib/session/insights.d.ts +126 -0
  77. package/dist/lib/session/insights.js +330 -0
  78. package/dist/lib/session/parse.d.ts +29 -2
  79. package/dist/lib/session/parse.js +111 -5
  80. package/dist/lib/session/remote-list.d.ts +10 -1
  81. package/dist/lib/session/remote-list.js +2 -8
  82. package/dist/lib/session/remote.d.ts +57 -3
  83. package/dist/lib/session/remote.js +90 -26
  84. package/dist/lib/session/resume-command.d.ts +6 -0
  85. package/dist/lib/session/resume-command.js +8 -0
  86. package/dist/lib/session/session-cache.d.ts +173 -0
  87. package/dist/lib/session/session-cache.js +399 -0
  88. package/dist/lib/session/types.d.ts +2 -2
  89. package/dist/lib/session/types.js +1 -1
  90. package/dist/lib/session/width.d.ts +1 -1
  91. package/dist/lib/session/width.js +12 -2
  92. package/dist/lib/shims.d.ts +2 -2
  93. package/dist/lib/shims.js +40 -5
  94. package/dist/lib/staleness/writers/hooks.js +1 -1
  95. package/dist/lib/startup/command-registry.d.ts +1 -0
  96. package/dist/lib/startup/command-registry.js +2 -0
  97. package/dist/lib/types.d.ts +1 -1
  98. package/dist/lib/usage.d.ts +13 -0
  99. package/dist/lib/usage.js +254 -3
  100. package/dist/lib/versions.js +13 -2
  101. package/package.json +1 -1
  102. package/dist/bin/agents +0 -0
  103. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  104. package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
  105. package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
@@ -13,10 +13,10 @@
13
13
  import chalk from 'chalk';
14
14
  import { addProfile, ensureProviderToken, applyFromSecrets } from './profiles.js';
15
15
  import { isInteractiveTerminal } from './utils.js';
16
- import { listProfiles, readProfile, writeProfile, deleteProfile, profileExists, profileHostLabel, profileProviderLabel, profileModelLabel, profileAuthLabel, profileLabel, forkProfile, editProfile, renameProfile, profileFromHostModel, authEnvKeyForHost, getProfilePath, validateProfileName, } from '../lib/profiles.js';
16
+ import { listProfiles, readProfile, writeProfile, deleteProfile, profileExists, profileHostLabel, profileProviderLabel, profileModelLabel, profileAuthLabel, profileLabel, forkProfile, editProfile, renameProfile, profileFromHostModel, authEnvKeyForHost, modelEnvKeyForHost, baseUrlEnvKeyForHost, getProfilePath, validateProfileName, } from '../lib/profiles.js';
17
17
  import { listPresets, getPreset } from '../lib/profiles-presets.js';
18
- import { listBundles } from '../lib/secrets/bundles.js';
19
18
  import { AGENTS, ALL_AGENT_IDS, resolveAgentName } from '../lib/agents.js';
19
+ import { runWizardSteps, createSteps, editSteps, defaultWizardIO, } from './harness-wizard.js';
20
20
  /** Short capability summary for a native harness — its supported run modes. */
21
21
  function nativeModes(id) {
22
22
  const modes = AGENTS[id]?.capabilities?.modes ?? [];
@@ -193,108 +193,93 @@ async function runForkFlow(source, name, opts) {
193
193
  console.log(chalk.green(`Harness '${name}' forked from ${source}.`));
194
194
  console.log(chalk.gray(`Try: agents run ${name} "hello"`));
195
195
  }
196
- /** The first `_MODEL`-suffixed env var in a preset's static env block, if any. */
197
- function presetModel(preset) {
198
- return Object.entries(preset.env).find(([k]) => k.endsWith('_MODEL'))?.[1];
199
- }
200
196
  /**
201
197
  * Interactive `agents harness add`/`fork` wizard — runs when required info is
202
- * missing and stdout is a TTY (see {@link forkNeedsWizard}, {@link addNeedsWizard}).
203
- * Always resolves to the same `(source, name, opts)` shape {@link buildFork}
204
- * accepts via {@link runForkFlow}, so a wizard run and a hand-written fork call
205
- * build an identical profile.
198
+ * missing and stdin+stdout are a TTY (see {@link forkNeedsWizard}, {@link addNeedsWizard}).
199
+ * Drives the shared step engine ({@link createSteps}) and maps its finished draft
200
+ * back to the same `(source, name, opts)` shape {@link buildFork} accepts via
201
+ * {@link runForkFlow}, so a wizard run and a hand-written fork call build an
202
+ * identical profile.
206
203
  */
207
- async function runHarnessWizard() {
208
- const { select, input } = await import('@inquirer/prompts');
209
- const customNames = listProfiles().map((p) => p.name);
210
- const source = await select({
211
- message: 'Fork from',
212
- choices: [
213
- ...ALL_AGENT_IDS.map((id) => ({ name: `${AGENTS[id].name} ${chalk.gray('(native)')}`, value: id })),
214
- ...customNames.map((n) => ({ name: `${n} ${chalk.gray('(custom harness)')}`, value: n })),
215
- ],
216
- });
217
- const presets = listPresets();
218
- const CUSTOM = '__custom__';
219
- const presetChoice = await select({
220
- message: 'Preset',
221
- choices: [
222
- ...presets.map((p) => ({ name: `${p.name} ${chalk.gray(p.description.slice(0, 60))}`, value: p.name })),
223
- { name: 'Build custom (host + model + provider)', value: CUSTOM },
224
- ],
225
- });
226
- let model;
227
- let baseUrl;
228
- let authProvider;
229
- let defaultName = source;
230
- if (presetChoice === CUSTOM) {
231
- model = await input({ message: 'Model id' });
232
- const NO_AUTH = '__none__';
233
- const providers = [...new Set(presets.map((p) => p.provider))];
234
- const providerChoice = await select({
235
- message: 'Provider',
236
- choices: [
237
- ...providers.map((p) => ({ name: p, value: p })),
238
- { name: 'no auth / host manages its own login', value: NO_AUTH },
239
- ],
240
- });
241
- authProvider = providerChoice === NO_AUTH ? undefined : providerChoice;
242
- const baseUrlInput = await input({ message: 'Base URL (optional)', default: '' });
243
- baseUrl = baseUrlInput || undefined;
204
+ async function runCreateWizard() {
205
+ const io = await defaultWizardIO();
206
+ const draft = await runWizardSteps(createSteps(), { mode: 'create' }, io);
207
+ return {
208
+ source: draft.source,
209
+ name: draft.name,
210
+ opts: {
211
+ model: draft.model,
212
+ baseUrl: draft.baseUrl,
213
+ authProvider: draft.authProvider,
214
+ fromSecrets: draft.fromSecrets,
215
+ },
216
+ };
217
+ }
218
+ /**
219
+ * Map a finished edit-wizard draft onto {@link EditOptions}, keeping only the
220
+ * fields the user actually changed from the profile's current values. Unchanged
221
+ * accepts (the wizard pre-fills each prompt with the current value) drop out, so
222
+ * the resulting {@link buildEdit} touches nothing the user left alone — and the
223
+ * "no changes" case is detectable via {@link hasEditFlags}. Base-URL clearing is
224
+ * intentionally not expressed here: the flag path can't clear it either (an empty
225
+ * `--base-url` is a no-op in `forkProfile`), so the wizard matches that until a
226
+ * later subtask adds explicit clearing.
227
+ */
228
+ export function draftToEditOptions(draft, original) {
229
+ const host = original.host.agent;
230
+ const curModel = original.env[modelEnvKeyForHost(host)];
231
+ const baseKey = baseUrlEnvKeyForHost(host);
232
+ const curBaseUrl = baseKey ? original.env[baseKey] : undefined;
233
+ const curVersion = original.host.version ?? '';
234
+ const curFallback = original.fallback_model ?? '';
235
+ const curDescription = original.description ?? '';
236
+ const opts = {};
237
+ if (draft.model !== undefined && draft.model !== curModel)
238
+ opts.model = draft.model;
239
+ if (draft.baseUrl && draft.baseUrl !== curBaseUrl)
240
+ opts.baseUrl = draft.baseUrl;
241
+ if (draft.authProvider !== undefined)
242
+ opts.authProvider = draft.authProvider;
243
+ if (draft.fromSecrets !== undefined)
244
+ opts.fromSecrets = draft.fromSecrets;
245
+ if (draft.version !== undefined && draft.version !== curVersion)
246
+ opts.version = draft.version;
247
+ if (draft.fallbackModel !== undefined && draft.fallbackModel !== curFallback)
248
+ opts.fallbackModel = draft.fallbackModel;
249
+ if (draft.description !== undefined && draft.description !== curDescription)
250
+ opts.description = draft.description;
251
+ return opts;
252
+ }
253
+ /**
254
+ * Interactive `agents harness edit <name>` wizard — runs when no edit flags were
255
+ * given and stdin+stdout are a TTY. Loads the profile, drives the shared step
256
+ * engine ({@link editSteps}) pre-filled with current values, then persists via the
257
+ * same build+write path as the flag-driven edit. `--key-stdin` is honored for the
258
+ * auth step's key entry. When the user changes nothing, it says so and writes
259
+ * nothing.
260
+ */
261
+ async function runEditWizard(name, cliOpts) {
262
+ if (!profileExists(name)) {
263
+ throw new Error(`Harness '${name}' not found. Create it first: agents harness add ${name} ...`);
244
264
  }
245
- else {
246
- const preset = getPreset(presetChoice);
247
- model = presetModel(preset);
248
- baseUrl = preset.env.ANTHROPIC_BASE_URL || preset.env.OPENAI_BASE_URL;
249
- authProvider = preset.authOptional ? undefined : preset.provider;
250
- // Pre-fill with the preset's own name (e.g. 'deepseek'), not a model
251
- // detail, so users aren't nudged toward baking one into the identity name.
252
- defaultName = preset.name;
265
+ const original = readProfile(name);
266
+ const io = await defaultWizardIO();
267
+ const draft = await runWizardSteps(editSteps(original), { mode: 'edit', original, host: original.host.agent, name }, io);
268
+ const opts = { ...draftToEditOptions(draft, original), keyStdin: cliOpts.keyStdin };
269
+ if (!hasEditFlags(opts)) {
270
+ console.log(chalk.gray(`No changes made to '${name}'.`));
271
+ return;
253
272
  }
254
- const name = await input({
255
- message: 'Harness name',
256
- default: defaultName,
257
- validate: (v) => {
258
- try {
259
- validateProfileName(v);
260
- return true;
261
- }
262
- catch (err) {
263
- return err.message;
264
- }
265
- },
266
- });
267
- const opts = { model, baseUrl, authProvider };
268
- if (authProvider) {
269
- const bundles = listBundles();
270
- const TYPE_NOW = 'type';
271
- const FROM_SECRETS = 'secrets';
272
- const keySource = bundles.length > 0
273
- ? await select({
274
- message: `How should '${authProvider}' get its key?`,
275
- choices: [
276
- { name: 'Type a key now', value: TYPE_NOW },
277
- { name: 'Use an existing agents secrets bundle', value: FROM_SECRETS },
278
- ],
279
- })
280
- : TYPE_NOW;
281
- if (keySource === FROM_SECRETS) {
282
- const bundleName = await select({
283
- message: 'Bundle',
284
- choices: bundles.map((b) => ({
285
- name: b.description ? `${b.name} ${chalk.gray(b.description)}` : b.name,
286
- value: b.name,
287
- })),
288
- });
289
- const bundle = bundles.find((b) => b.name === bundleName);
290
- const keys = Object.keys(bundle.vars);
291
- const key = keys.length === 1
292
- ? keys[0]
293
- : await select({ message: 'Key', choices: keys.map((k) => ({ name: k, value: k })) });
294
- opts.fromSecrets = `${bundleName}:${key}`;
295
- }
273
+ const edited = buildEdit(name, opts);
274
+ if (opts.fromSecrets) {
275
+ await applyFromSecrets(edited, opts.fromSecrets, opts.authProvider);
276
+ }
277
+ else if (opts.authProvider) {
278
+ await ensureProviderToken(opts.authProvider, undefined, opts.keyStdin);
296
279
  }
297
- return { source, name, opts };
280
+ writeProfile(edited);
281
+ console.log(chalk.green(`Harness '${name}' updated.`));
282
+ console.log(chalk.gray(`Model: ${profileModelLabel(edited)}`));
298
283
  }
299
284
  export function registerHarnessCommands(program) {
300
285
  const cmd = program
@@ -354,7 +339,7 @@ Examples:
354
339
  if (!isInteractiveTerminal()) {
355
340
  throw new Error("'agents harness add' needs --preset or --host + --model (or a name and an interactive terminal for the wizard).");
356
341
  }
357
- const wiz = await runHarnessWizard();
342
+ const wiz = await runCreateWizard();
358
343
  await runForkFlow(wiz.source, wiz.name, { ...wiz.opts, force: opts.force, keyStdin: opts.keyStdin });
359
344
  return;
360
345
  }
@@ -399,7 +384,7 @@ Examples:
399
384
  if (!isInteractiveTerminal()) {
400
385
  throw new Error("'agents harness fork' needs <source> and <name> (or an interactive terminal for the wizard).");
401
386
  }
402
- const wiz = await runHarnessWizard();
387
+ const wiz = await runCreateWizard();
403
388
  await runForkFlow(wiz.source, wiz.name, { ...wiz.opts, force: opts.force, keyStdin: opts.keyStdin });
404
389
  return;
405
390
  }
@@ -412,7 +397,7 @@ Examples:
412
397
  });
413
398
  cmd
414
399
  .command('edit <name>')
415
- .description('Edit an existing custom harness in place — model, endpoint, auth, version, description, fallback.')
400
+ .description('Edit an existing custom harness in place — model, endpoint, auth, version, description, fallback. Omit flags in a terminal for the interactive wizard.')
416
401
  .option('--model <id>', 'Swap the pinned model')
417
402
  .option('--base-url <url>', 'Swap the custom endpoint base URL')
418
403
  .option('--auth-provider <provider>', 'Repoint auth at a different provider (keychain-backed)')
@@ -437,9 +422,20 @@ Examples:
437
422
 
438
423
  # Copy a key out of an existing secrets bundle instead of typing it
439
424
  agents harness edit corp --from-secrets prod:OPENROUTER_KEY
425
+
426
+ # No flags, in an interactive terminal: a wizard walks each field pre-filled
427
+ agents harness edit deepseek
440
428
  `)
441
429
  .action(async (name, opts) => {
442
430
  try {
431
+ // No edit flags + a real terminal → the interactive wizard, pre-filled
432
+ // with current values. Any flag (or a non-interactive caller) takes the
433
+ // flag path unchanged; a flagless non-interactive call still errors via
434
+ // buildEdit's EDIT_FLAGS_HELP.
435
+ if (!hasEditFlags(opts) && isInteractiveTerminal()) {
436
+ await runEditWizard(name, opts);
437
+ return;
438
+ }
443
439
  const edited = buildEdit(name, opts);
444
440
  if (opts.fromSecrets) {
445
441
  await applyFromSecrets(edited, opts.fromSecrets, opts.authProvider);
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Insights command — how you actually work, split by the account that did the work.
3
+ *
4
+ * The behavioural sibling of the existing rollups, and deliberately not a duplicate of
5
+ * any of them:
6
+ *
7
+ * agents cost what you spent ($ and duration)
8
+ * agents output what shipped (burn vs PRs and commits)
9
+ * agents usage live quota headroom (rate-limit windows, right now)
10
+ * agents trends aggregate distributions (harness mix, tools-per-session, token ratios)
11
+ * agents sessions browse individual work (search, resume, render)
12
+ * agents insights HOW you work (tools, friction, rhythm, per account)
13
+ *
14
+ * The closest neighbour is `agents trends`, and the boundary is the data path: trends
15
+ * reads counters — `tool_scan_ledger` call counts and the analytics warehouse — to
16
+ * produce distributions ("how many tool calls per session, by harness"). This reads
17
+ * transcript CONTENT through `parseSession` to produce behaviour ("which tools, which
18
+ * languages, where it went wrong, when you were working"), and splits all of it by
19
+ * account, a dimension trends does not have. They overlap in spirit on tool and model
20
+ * mix; they do not read the same store or answer the same question.
21
+ *
22
+ * Modelled on Claude Code's `/insights`, with the difference that motivated it: that
23
+ * command reads one account's directory, while `balanced` rotation sprays sessions
24
+ * across every signed-in account. This reads the whole index and reports the accounts
25
+ * apart — see lib/session/claude-accounts.ts for how a transcript is attributed.
26
+ *
27
+ * The deterministic report makes zero network calls. `--narrative` is opt-in and adds
28
+ * the coaching prose by piping the AGGREGATE (never raw transcripts) through a headless
29
+ * `claude -p`.
30
+ */
31
+ import type { Command } from 'commander';
32
+ export declare function registerInsightsCommand(program: Command): void;