@phnx-labs/agents-cli 1.22.29 → 1.22.30

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 (73) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README.md +39 -1
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/accounts.d.ts +13 -0
  5. package/dist/commands/accounts.js +32 -0
  6. package/dist/commands/daemon.d.ts +18 -0
  7. package/dist/commands/daemon.js +581 -0
  8. package/dist/commands/exec.js +66 -20
  9. package/dist/commands/routines.js +29 -11
  10. package/dist/commands/secrets.d.ts +17 -0
  11. package/dist/commands/secrets.js +30 -15
  12. package/dist/commands/sessions-browser.js +6 -6
  13. package/dist/commands/sessions-favorite.d.ts +7 -7
  14. package/dist/commands/sessions-favorite.js +30 -30
  15. package/dist/commands/sessions-picker.d.ts +33 -1
  16. package/dist/commands/sessions-picker.js +102 -27
  17. package/dist/commands/sessions.d.ts +12 -1
  18. package/dist/commands/sessions.js +259 -20
  19. package/dist/commands/view.d.ts +11 -0
  20. package/dist/commands/view.js +56 -29
  21. package/dist/index.js +37 -2
  22. package/dist/lib/account-labels.d.ts +24 -0
  23. package/dist/lib/account-labels.js +72 -0
  24. package/dist/lib/agents.d.ts +32 -1
  25. package/dist/lib/agents.js +96 -31
  26. package/dist/lib/daemon-health.d.ts +24 -0
  27. package/dist/lib/daemon-health.js +84 -0
  28. package/dist/lib/daemon-ticks.d.ts +81 -0
  29. package/dist/lib/daemon-ticks.js +190 -0
  30. package/dist/lib/daemon.d.ts +68 -18
  31. package/dist/lib/daemon.js +303 -338
  32. package/dist/lib/device-config.d.ts +10 -0
  33. package/dist/lib/device-config.js +27 -0
  34. package/dist/lib/exec.d.ts +27 -0
  35. package/dist/lib/exec.js +49 -2
  36. package/dist/lib/hosts/dispatch.d.ts +4 -0
  37. package/dist/lib/hosts/dispatch.js +4 -0
  38. package/dist/lib/hosts/remote-cmd.js +1 -0
  39. package/dist/lib/hosts/run-target.d.ts +1 -0
  40. package/dist/lib/hosts/run-target.js +1 -0
  41. package/dist/lib/import.js +7 -6
  42. package/dist/lib/memory-cache.d.ts +19 -0
  43. package/dist/lib/memory-cache.js +31 -0
  44. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  45. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  46. package/dist/lib/migrate.d.ts +1 -1
  47. package/dist/lib/migrate.js +7 -2
  48. package/dist/lib/routine-activation.d.ts +2 -0
  49. package/dist/lib/routine-activation.js +16 -0
  50. package/dist/lib/runner.d.ts +18 -0
  51. package/dist/lib/runner.js +52 -0
  52. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  53. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  54. package/dist/lib/secrets/agent.d.ts +19 -0
  55. package/dist/lib/secrets/agent.js +32 -2
  56. package/dist/lib/secrets/scope.d.ts +3 -3
  57. package/dist/lib/secrets/scope.js +3 -3
  58. package/dist/lib/session/db.d.ts +15 -0
  59. package/dist/lib/session/db.js +90 -15
  60. package/dist/lib/session/discover.js +91 -39
  61. package/dist/lib/session/favorites.d.ts +2 -2
  62. package/dist/lib/session/favorites.js +2 -2
  63. package/dist/lib/session/parse.d.ts +63 -0
  64. package/dist/lib/session/parse.js +165 -20
  65. package/dist/lib/session/session-cache.d.ts +9 -6
  66. package/dist/lib/session/session-cache.js +23 -6
  67. package/dist/lib/shims.js +12 -0
  68. package/dist/lib/startup/command-registry.d.ts +15 -1
  69. package/dist/lib/startup/command-registry.js +49 -0
  70. package/dist/lib/usage-refresh.js +3 -2
  71. package/dist/lib/usage.d.ts +12 -10
  72. package/dist/lib/usage.js +63 -144
  73. package/package.json +4 -1
@@ -17,8 +17,10 @@ import { linearIssueUrl } from '../lib/session/linear.js';
17
17
  import { extractTodoProgress, WORKTREE_RE } from '../lib/session/state.js';
18
18
  import { renderMarkdown } from '../lib/markdown.js';
19
19
  import { itemPicker } from '../lib/picker.js';
20
+ import { createMemoryCache } from '../lib/memory-cache.js';
20
21
  import { classifyFileChanges, changeCounts, toolHistogram, detectTestResult } from '../lib/session/digest.js';
21
22
  import { extractArtifacts, extractHooks, extractLinks, extractRepos, extractSkills } from '../lib/session/highlights.js';
23
+ import { getSessionPlugins, readSessionPreviewCache, writeSessionPreviewCache } from '../lib/session/db.js';
22
24
  /** A session whose transcript FILE is on another machine (folded in over the
23
25
  * live cross-machine fan-out): its `filePath` is on that peer's disk, so the
24
26
  * preview can't parse it locally — it shows metadata + a "resume there" note
@@ -100,11 +102,71 @@ function sanitizeMeta(s) {
100
102
  todos,
101
103
  };
102
104
  }
103
- const previewCache = new Map();
105
+ const previewCache = createMemoryCache({
106
+ max: 256,
107
+ ttlMs: 5 * 60_000,
108
+ });
109
+ function previewCacheKey(session, remote) {
110
+ let fileStamp = '';
111
+ if (!remote && session.filePath) {
112
+ try {
113
+ const stat = fs.statSync(session.filePath);
114
+ fileStamp = `${stat.mtimeMs}:${stat.size}`;
115
+ }
116
+ catch {
117
+ fileStamp = 'missing';
118
+ }
119
+ }
120
+ // The transcript stamp invalidates derived activity. Metadata that can change
121
+ // independently (label/ticket/PR/live scanner enrichment) also rides the key.
122
+ return JSON.stringify([
123
+ remote ?? '', session.id, fileStamp, session.lastActivity, session.label,
124
+ session.topic, session.ticketId, session.prUrl, session.messageCount,
125
+ session.tokenCount, session.model, session.todos, session.plan,
126
+ session.recentDirectoriesTouched, session.skillsUsed,
127
+ ]);
128
+ }
129
+ export function clearPreviewMemoryCacheForTest() {
130
+ previewCache.clear();
131
+ }
132
+ export function loadSessionPreviewDigest(session) {
133
+ if (!session.filePath || !fs.existsSync(session.filePath))
134
+ return { events: [] };
135
+ const safe = sanitizeMeta(session);
136
+ let events = [];
137
+ let sourceStamp;
138
+ try {
139
+ sourceStamp = fs.statSync(session.filePath);
140
+ }
141
+ catch (err) {
142
+ return { events, error: sanitizeForTerminal(err?.message ?? String(err)) };
143
+ }
144
+ let digest = readSessionPreviewCache(session.id, {
145
+ fileMtimeMs: sourceStamp.mtimeMs,
146
+ fileSize: sourceStamp.size,
147
+ });
148
+ if (!digest) {
149
+ try {
150
+ events = parseSession(session.filePath, session.agent);
151
+ digest = buildSessionPreviewDigest(events, safe);
152
+ writeSessionPreviewCache({
153
+ id: session.id,
154
+ fileMtimeMs: sourceStamp.mtimeMs,
155
+ fileSize: sourceStamp.size,
156
+ preview: digest,
157
+ });
158
+ }
159
+ catch (err) {
160
+ return { events, error: sanitizeForTerminal(err?.message ?? String(err)) };
161
+ }
162
+ }
163
+ digest.plugins = getSessionPlugins(session.id);
164
+ return { digest, events };
165
+ }
104
166
  /** Build a cached multi-line preview string for display in the session picker. */
105
167
  export function buildPreview(session) {
106
168
  const remote = transcriptOnPeerOf(session);
107
- const cacheKey = remote ? `${remote}:${session.id}` : session.id;
169
+ const cacheKey = previewCacheKey(session, remote);
108
170
  const cached = previewCache.get(cacheKey);
109
171
  if (cached)
110
172
  return cached;
@@ -129,18 +191,11 @@ export function buildPreview(session) {
129
191
  previewCache.set(cacheKey, output);
130
192
  return output;
131
193
  }
132
- let events = [];
133
- let parseError;
134
- try {
135
- events = parseSession(session.filePath, session.agent);
136
- }
137
- catch (err) {
138
- parseError = sanitizeForTerminal(err?.message ?? String(err));
139
- }
194
+ const { digest, events, error: parseError } = loadSessionPreviewDigest(session);
140
195
  const header = formatHeader(safe, events);
141
196
  const body = parseError
142
197
  ? ' ' + chalk.red(`Failed to parse session: ${parseError}`)
143
- : formatCompactPreview(events, safe);
198
+ : formatCompactPreview(digest, safe);
144
199
  const output = [header, '', body].filter(Boolean).join('\n');
145
200
  previewCache.set(cacheKey, output);
146
201
  return output;
@@ -368,7 +423,8 @@ const LAST_RESPONSE_MAX_LINES = 15;
368
423
  const LAST_RESPONSE_MAX_LINES_WITH_TODOS = 8;
369
424
  const TODOS_MAX_ITEMS = 5;
370
425
  const DIRS_TOUCHED_MAX = 5;
371
- function formatCompactPreview(events, session) {
426
+ /** Fold a harness-normalized event stream into the stable preview data model. */
427
+ export function buildSessionPreviewDigest(events, session) {
372
428
  let firstUser = '';
373
429
  let lastAssistant = '';
374
430
  const filesRead = new Set();
@@ -436,9 +492,35 @@ function formatCompactPreview(events, session) {
436
492
  // checklist write); fall back to SessionMeta.todos for rows where the scan/
437
493
  // fan-out attached progress but the event stream has no TodoWrite yet.
438
494
  const todos = latestTodos ?? session.todos;
439
- // Digest signals folded into the preview: change lifecycle, tool mix, tests.
440
495
  const changes = classifyFileChanges(events);
441
496
  const chg = changeCounts(changes);
497
+ const errorEvents = events.filter(e => e.type === 'error');
498
+ return {
499
+ schemaVersion: 1,
500
+ firstUser,
501
+ lastAssistant,
502
+ filesRead: filesRead.size,
503
+ toolCalls,
504
+ planFile,
505
+ todos,
506
+ subAgentCount,
507
+ toolTags: [...toolTags],
508
+ changes: chg,
509
+ dirs: directoriesTouched(session, events, changes),
510
+ repos: extractRepos(events, session.cwd),
511
+ artifacts: extractArtifacts(changes),
512
+ skills: extractSkills(events),
513
+ plugins: getSessionPlugins(session.id),
514
+ hooks: extractHooks(events),
515
+ links: extractLinks(events),
516
+ errorCount: errorEvents.length,
517
+ firstError: errorEvents[0]?.tool,
518
+ toolHistogram: toolHistogram(toolCounts, 4),
519
+ test: detectTestResult(events),
520
+ };
521
+ }
522
+ function formatCompactPreview(digest, session) {
523
+ const { firstUser, lastAssistant, filesRead, toolCalls, planFile, todos, subAgentCount, toolTags, changes: chg, dirs, repos, artifacts, skills, plugins, hooks, links, errorCount, firstError, toolHistogram: hist, test, } = digest;
442
524
  const lines = [];
443
525
  const termWidth = process.stdout.columns || 80;
444
526
  // Originating user prompt (first non-system user turn).
@@ -464,12 +546,10 @@ function formatCompactPreview(events, session) {
464
546
  // Recent activity = directories touched (not raw tool calls). Prefer a
465
547
  // parser-supplied dirsTouched when present; else derive from event paths.
466
548
  // Width-capped: a long Dirs line used to wrap and swamp the whole pane.
467
- const dirs = directoriesTouched(session, events, changes);
468
549
  if (dirs.length) {
469
550
  lines.push(chalk.cyan('Dirs: ') + joinWidthCapped(dirs, termWidth - 12));
470
551
  }
471
552
  // Repos worked in (basename of each `.git` root under the touched paths).
472
- const repos = extractRepos(events, session.cwd);
473
553
  if (repos.length) {
474
554
  lines.push(chalk.cyan('Repos: ') + chalk.white(repos.slice(0, 4).join(chalk.gray(' · '))));
475
555
  }
@@ -487,8 +567,8 @@ function formatCompactPreview(events, session) {
487
567
  ].filter(Boolean).join(' ');
488
568
  activity.push(`${parts} ${chalk.gray('changed')}`);
489
569
  }
490
- if (filesRead.size)
491
- activity.push(chalk.gray(`${filesRead.size} read`));
570
+ if (filesRead)
571
+ activity.push(chalk.gray(`${filesRead} read`));
492
572
  if (toolCalls)
493
573
  activity.push(chalk.gray(`${toolCalls} tool${toolCalls === 1 ? '' : 's'}`));
494
574
  if (activity.length) {
@@ -496,38 +576,35 @@ function formatCompactPreview(events, session) {
496
576
  }
497
577
  // Documents the session produced (`.agents/artifacts|plans|reports`, other
498
578
  // *.md/*.html creations) — the files a human browses later, named + clickable.
499
- const artifacts = extractArtifacts(changes);
500
579
  if (artifacts.length) {
501
580
  const shown = artifacts.slice(0, 5).map(a => linkPath(a.path, a.basename));
502
581
  const more = artifacts.length > 5 ? chalk.gray(` · +${artifacts.length - 5} more`) : '';
503
582
  lines.push(chalk.cyan('Artifacts: ') + shown.join(chalk.gray(' · ')) + more);
504
583
  }
505
584
  // Skills invoked (plugin skills included — they ride the same Skill tool).
506
- const skills = extractSkills(events);
507
585
  if (skills.length) {
508
586
  const shown = skills.slice(0, 5).map(s => chalk.white(s.name) + (s.count > 1 ? chalk.gray(` ×${s.count}`) : ''));
509
587
  const more = skills.length > 5 ? chalk.gray(` · +${skills.length - 5} more`) : '';
510
588
  lines.push(chalk.cyan('Skills: ') + shown.join(chalk.gray(' · ')) + more);
511
589
  }
590
+ if (plugins.length) {
591
+ lines.push(chalk.cyan('Plugins: ') + chalk.white(plugins.slice(0, 5).join(chalk.gray(' · '))));
592
+ }
512
593
  // Hooks fired (Claude transcripts record firings; other harnesses don't).
513
- const hooks = extractHooks(events);
514
594
  if (hooks.length) {
515
595
  const shown = hooks.slice(0, 4).map(h => chalk.white(h.name) + (h.count > 1 ? chalk.gray(` ×${h.count}`) : '') + (h.failed ? chalk.red(` (${h.failed} failed)`) : ''));
516
596
  const more = hooks.length > 4 ? chalk.gray(` · +${hooks.length - 4} more`) : '';
517
597
  lines.push(chalk.cyan('Hooks: ') + shown.join(chalk.gray(' · ')) + more);
518
598
  }
519
599
  // Links mentioned in the conversation — clickable (OSC 8), tracker-classified.
520
- const links = extractLinks(events);
521
600
  if (links.length) {
522
601
  const shown = links.slice(0, 5).map(l => chalk.blue(linkUrl(l.url, l.label)));
523
602
  const more = links.length > 5 ? chalk.gray(` · +${links.length - 5} more`) : '';
524
603
  lines.push(chalk.cyan('Links: ') + shown.join(chalk.gray(' · ')) + more);
525
604
  }
526
605
  // Error tally, mirroring the full summary's Errors section in one line.
527
- const errorEvents = events.filter(e => e.type === 'error');
528
- if (errorEvents.length) {
529
- const first = errorEvents[0].tool || 'unknown';
530
- lines.push(chalk.cyan('Errors: ') + chalk.red(`${errorEvents.length} failure${errorEvents.length === 1 ? '' : 's'}`) + chalk.gray(` — first: ${first}`));
606
+ if (errorCount) {
607
+ lines.push(chalk.cyan('Errors: ') + chalk.red(`${errorCount} failure${errorCount === 1 ? '' : 's'}`) + chalk.gray(` — first: ${firstError || 'unknown'}`));
531
608
  }
532
609
  const metadata = [
533
610
  ...toolTags,
@@ -537,12 +614,10 @@ function formatCompactPreview(events, session) {
537
614
  lines.push(chalk.cyan('Meta: ') + chalk.gray(metadata.join(' · ')));
538
615
  }
539
616
  // Tool mix (top 4) — what kind of work this was.
540
- const hist = toolHistogram(toolCounts, 4);
541
617
  if (hist.length) {
542
618
  lines.push(chalk.cyan('Tools: ') + chalk.gray(hist.map(h => `${h.tool} ${h.count}`).join(' · ')));
543
619
  }
544
620
  // Last test/build verdict.
545
- const test = detectTestResult(events);
546
621
  if (test?.ok) {
547
622
  const bits = [
548
623
  test.passed !== undefined ? chalk.green(`${test.passed} pass`) : '',
@@ -7,6 +7,9 @@ import { type PickedSession } from './sessions-picker.js';
7
7
  import { type ToolSearchEnvelope, type ToolProgramCountEnvelope } from '../lib/session/tool-index.js';
8
8
  interface SessionFilterOptions {
9
9
  agent?: string;
10
+ version?: string;
11
+ /** Internal Commander spelling after index.ts disambiguates root --version. */
12
+ sessionVersion?: string;
10
13
  project?: string;
11
14
  all?: boolean;
12
15
  teams?: boolean;
@@ -56,7 +59,7 @@ interface SessionsOptions extends SessionFilterOptions {
56
59
  abandoned?: boolean;
57
60
  queued?: boolean;
58
61
  unknown?: boolean;
59
- /** Show only favorited (starred) sessions — the `f` key's flag twin. */
62
+ /** Show only favorited sessions — the `f` key's flag twin. */
60
63
  favorites?: boolean;
61
64
  /** Enrich the listing with live glyphs/preview for running rows. Default on;
62
65
  * `--no-live` sets this false. Commander's `--no-` convention. */
@@ -94,6 +97,12 @@ interface SessionsOptions extends SessionFilterOptions {
94
97
  /** Only sessions that used a skill/command owned by this plugin (#12). */
95
98
  plugin?: string;
96
99
  }
100
+ type InstalledVersionsForAgent = (agent: SessionAgentId) => string[];
101
+ /**
102
+ * Treat a positional `agent@version` as a structured filter only when it names
103
+ * a real installed version. Everything else remains ordinary free-text search.
104
+ */
105
+ export declare function parseInstalledAgentVersionQuery(query: string | undefined, installedVersions?: InstalledVersionsForAgent): string | undefined;
97
106
  /**
98
107
  * Strip terminal/harness noise from a preview so the column stays a single line
99
108
  * of plain prose: OSC title escapes, CSI/SGR ANSI, and the harness wrapper tags
@@ -431,6 +440,8 @@ export declare function renderSessionPreview(query: string, scope: {
431
440
  agent?: string;
432
441
  project?: string;
433
442
  local?: boolean;
443
+ hosts?: string[];
444
+ json?: boolean;
434
445
  }): Promise<void>;
435
446
  /**
436
447
  * The one-line live status banner shown above a session preview: the glyph, the