@phnx-labs/agents-cli 1.22.28 → 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.
- package/CHANGELOG.md +88 -0
- package/README.md +39 -1
- package/dist/bin/agents +0 -0
- package/dist/commands/accounts.d.ts +13 -0
- package/dist/commands/accounts.js +32 -0
- package/dist/commands/daemon.d.ts +18 -0
- package/dist/commands/daemon.js +581 -0
- package/dist/commands/exec.js +66 -20
- package/dist/commands/routines.js +29 -11
- package/dist/commands/secrets.d.ts +17 -0
- package/dist/commands/secrets.js +30 -15
- package/dist/commands/sessions-browser.js +6 -6
- package/dist/commands/sessions-favorite.d.ts +7 -7
- package/dist/commands/sessions-favorite.js +30 -30
- package/dist/commands/sessions-picker.d.ts +33 -1
- package/dist/commands/sessions-picker.js +102 -27
- package/dist/commands/sessions.d.ts +12 -1
- package/dist/commands/sessions.js +259 -20
- package/dist/commands/view.d.ts +11 -0
- package/dist/commands/view.js +56 -29
- package/dist/index.js +37 -2
- package/dist/lib/account-labels.d.ts +24 -0
- package/dist/lib/account-labels.js +72 -0
- package/dist/lib/agents.d.ts +32 -1
- package/dist/lib/agents.js +96 -31
- package/dist/lib/daemon-health.d.ts +24 -0
- package/dist/lib/daemon-health.js +84 -0
- package/dist/lib/daemon-ticks.d.ts +81 -0
- package/dist/lib/daemon-ticks.js +190 -0
- package/dist/lib/daemon.d.ts +68 -18
- package/dist/lib/daemon.js +303 -338
- package/dist/lib/device-config.d.ts +10 -0
- package/dist/lib/device-config.js +27 -0
- package/dist/lib/exec.d.ts +27 -0
- package/dist/lib/exec.js +49 -2
- package/dist/lib/hosts/dispatch.d.ts +4 -0
- package/dist/lib/hosts/dispatch.js +4 -0
- package/dist/lib/hosts/remote-cmd.js +1 -0
- package/dist/lib/hosts/run-target.d.ts +1 -0
- package/dist/lib/hosts/run-target.js +1 -0
- package/dist/lib/import.js +7 -6
- package/dist/lib/memory-cache.d.ts +19 -0
- package/dist/lib/memory-cache.js +31 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/snapshot.d.ts +16 -0
- package/dist/lib/menubar/snapshot.js +22 -1
- package/dist/lib/migrate.d.ts +1 -1
- package/dist/lib/migrate.js +7 -2
- package/dist/lib/routine-activation.d.ts +2 -0
- package/dist/lib/routine-activation.js +16 -0
- package/dist/lib/runner.d.ts +18 -0
- package/dist/lib/runner.js +52 -0
- 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/agent.d.ts +19 -0
- package/dist/lib/secrets/agent.js +32 -2
- package/dist/lib/secrets/scope.d.ts +3 -3
- package/dist/lib/secrets/scope.js +3 -3
- package/dist/lib/session/db.d.ts +15 -0
- package/dist/lib/session/db.js +90 -15
- package/dist/lib/session/discover.js +91 -39
- package/dist/lib/session/favorites.d.ts +2 -2
- package/dist/lib/session/favorites.js +2 -2
- package/dist/lib/session/parse.d.ts +63 -0
- package/dist/lib/session/parse.js +165 -20
- package/dist/lib/session/session-cache.d.ts +9 -6
- package/dist/lib/session/session-cache.js +23 -6
- package/dist/lib/shims.js +12 -0
- package/dist/lib/startup/command-registry.d.ts +15 -1
- package/dist/lib/startup/command-registry.js +49 -0
- package/dist/lib/usage-refresh.js +3 -2
- package/dist/lib/usage.d.ts +12 -10
- package/dist/lib/usage.js +81 -154
- 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 =
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
491
|
-
activity.push(chalk.gray(`${filesRead
|
|
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
|
-
|
|
528
|
-
|
|
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
|
|
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
|