@phnx-labs/agents-cli 1.22.6 → 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 +87 -1
- package/README.md +7 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/exec.js +35 -4
- 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/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 +1 -2
- 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
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { discoverSessions, parseTimeFilter } from '../lib/session/discover.js';
|
|
3
|
+
import { queryResourceUsageStats, resourceUsageCoverage, } from '../lib/session/db.js';
|
|
4
|
+
import { listResources } from '../lib/resources.js';
|
|
5
|
+
import { discoverPlugins } from '../lib/plugins.js';
|
|
6
|
+
import { setHelpSections } from '../lib/help.js';
|
|
7
|
+
import { terminalWidth, truncateToWidth, padToWidth, stringWidth } from '../lib/session/width.js';
|
|
8
|
+
const DEFAULT_TOP = 20;
|
|
9
|
+
/**
|
|
10
|
+
* The both-ends "dead weight" set: installed resources whose identity
|
|
11
|
+
* (kind + name — name already embeds `plugin:short`, so it matches the stored
|
|
12
|
+
* invoked name) never appears in the invoked rows. Sorted kind then name.
|
|
13
|
+
* Pure set-difference so it is unit-testable without touching the filesystem.
|
|
14
|
+
*/
|
|
15
|
+
export function diffZeroInvoked(installed, invoked) {
|
|
16
|
+
const invokedKeys = new Set(invoked.map(r => `${r.kind}:${r.name}`));
|
|
17
|
+
return installed
|
|
18
|
+
.filter(r => !invokedKeys.has(`${r.kind}:${r.name}`))
|
|
19
|
+
.sort((a, b) => (a.kind === b.kind ? a.name.localeCompare(b.name) : a.kind.localeCompare(b.kind)));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Enumerate the installed resource set the invoked rows are measured against:
|
|
23
|
+
* flat skills/commands from the DotAgents layers (`listResources`) plus each
|
|
24
|
+
* plugin's own skills/commands as `plugin:short` (invisible to the flat scan) —
|
|
25
|
+
* exactly the union `resolveResourceProvenance` reconciles against at write time.
|
|
26
|
+
*/
|
|
27
|
+
function installedResources(kindFilter, pluginFilter, cwd) {
|
|
28
|
+
const kinds = kindFilter ? [kindFilter] : ['skill', 'command'];
|
|
29
|
+
const byKey = new Map();
|
|
30
|
+
const plugins = discoverPlugins({ cwd });
|
|
31
|
+
for (const kind of kinds) {
|
|
32
|
+
const listKind = kind === 'skill' ? 'skills' : 'commands';
|
|
33
|
+
// Flat (non-namespaced) resources are plugin-less; skip them when the caller
|
|
34
|
+
// narrows to one plugin.
|
|
35
|
+
if (!pluginFilter) {
|
|
36
|
+
for (const r of listResources(listKind, cwd)) {
|
|
37
|
+
byKey.set(`${kind}:${r.name}`, { kind, name: r.name, plugin: null, source: r.source });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
for (const p of plugins) {
|
|
41
|
+
if (pluginFilter && p.name !== pluginFilter)
|
|
42
|
+
continue;
|
|
43
|
+
const names = kind === 'skill' ? p.skills : p.commands;
|
|
44
|
+
for (const short of names) {
|
|
45
|
+
const name = `${p.name}:${short}`;
|
|
46
|
+
byKey.set(`${kind}:${name}`, { kind, name, plugin: p.name, source: p.marketplace ?? null });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return [...byKey.values()];
|
|
51
|
+
}
|
|
52
|
+
export function registerSessionsStatsCommand(sessionsCmd) {
|
|
53
|
+
const stats = sessionsCmd
|
|
54
|
+
.command('stats')
|
|
55
|
+
.description('Which skills/commands you actually invoke, and which installed ones are dead weight.')
|
|
56
|
+
.option('--kind <kind>', 'Limit to one kind: skill or command')
|
|
57
|
+
.option('--plugin <name>', "Only this plugin's resources (r.plugin), not every resource a plugin-touching session used")
|
|
58
|
+
.option('-a, --agent <agent>', 'Only sessions from this agent (e.g. claude)')
|
|
59
|
+
.option('-p, --project <name>', 'Only sessions in this project')
|
|
60
|
+
.option('--machine <name>', 'Only sessions recorded on this machine (host)')
|
|
61
|
+
.option('--since <time>', 'Only sessions newer than this (e.g. 7d, 30d, or ISO date)')
|
|
62
|
+
.option('--top <n>', `How many ranked rows to show (default ${DEFAULT_TOP})`)
|
|
63
|
+
.option('--bottom', 'Rank the least-invoked resources first instead of the most-invoked')
|
|
64
|
+
.option('--zero', 'Show only installed-but-never-invoked resources (the dead weight)')
|
|
65
|
+
.option('--json', 'Output the stats as JSON')
|
|
66
|
+
.action(async (_opts, cmd) => {
|
|
67
|
+
await statsAction(cmd);
|
|
68
|
+
});
|
|
69
|
+
setHelpSections(stats, {
|
|
70
|
+
examples: `
|
|
71
|
+
# Most-invoked skills and commands, plus the installed ones you never invoke
|
|
72
|
+
agents sessions stats
|
|
73
|
+
|
|
74
|
+
# Just the dead weight — installed but never explicitly invoked
|
|
75
|
+
agents sessions stats --zero
|
|
76
|
+
|
|
77
|
+
# Skills only, last 30 days, machine-readable
|
|
78
|
+
agents sessions stats --kind skill --since 30d --json
|
|
79
|
+
|
|
80
|
+
# One plugin's resources, least-used first
|
|
81
|
+
agents sessions stats --plugin rush --bottom
|
|
82
|
+
|
|
83
|
+
# Backfill historical sessions first if coverage is low
|
|
84
|
+
agents sessions backfill resources
|
|
85
|
+
`,
|
|
86
|
+
notes: `
|
|
87
|
+
- The signal captures EXPLICIT invocations only: slash commands and \`Skill\` tool calls. An auto-triggered skill (loaded by description match) emits no event, so it reads as 0 — that means "never explicitly invoked", not "never loaded".
|
|
88
|
+
- Skill invocations are recorded for Claude and Kimi; slash-commands for Claude only. Other harnesses contribute nothing to these counts.
|
|
89
|
+
- Counts come from the SQLite index. New/changed sessions are recorded on their normal scan; run \`agents sessions backfill resources\` once to fold in historical sessions (a low coverage line means it hasn't run).
|
|
90
|
+
- --plugin filters the resource rows (this plugin's skills/commands), distinct from the top-level \`agents sessions --plugin\` which filters SESSIONS.
|
|
91
|
+
`,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
async function statsAction(cmd) {
|
|
95
|
+
// The parent `sessions` command owns --agent/--project/--plugin/--since/--json
|
|
96
|
+
// and keeps parsing them past the subcommand name (it has a positional
|
|
97
|
+
// [query]), binding them to the PARENT — so read the merged view, not the
|
|
98
|
+
// action's own options. Same reason sessions-favorite.ts uses optsWithGlobals.
|
|
99
|
+
const opts = cmd.optsWithGlobals();
|
|
100
|
+
const kind = normalizeKind(opts.kind);
|
|
101
|
+
const sinceMs = opts.since ? parseTimeFilter(opts.since) : undefined;
|
|
102
|
+
const top = resolveTop(opts.top);
|
|
103
|
+
// Warm the incremental index (like `agents cost`) so recent sessions' usage is
|
|
104
|
+
// current before we read. Historical gaps still need the explicit backfill.
|
|
105
|
+
await discoverSessions({ all: true, since: opts.since, limit: 1 });
|
|
106
|
+
const filter = {};
|
|
107
|
+
if (opts.agent)
|
|
108
|
+
filter.agent = opts.agent;
|
|
109
|
+
if (opts.project)
|
|
110
|
+
filter.project = opts.project;
|
|
111
|
+
if (opts.machine)
|
|
112
|
+
filter.machine = opts.machine;
|
|
113
|
+
if (typeof sinceMs === 'number')
|
|
114
|
+
filter.sinceMs = sinceMs;
|
|
115
|
+
const statsOpts = {
|
|
116
|
+
...filter,
|
|
117
|
+
kind,
|
|
118
|
+
pluginFilter: opts.plugin,
|
|
119
|
+
order: opts.bottom ? 'bottom' : 'top',
|
|
120
|
+
};
|
|
121
|
+
// Full invoked set (no limit) drives both the zero-invoked diff and the totals;
|
|
122
|
+
// the ranked view is a slice of it.
|
|
123
|
+
const allInvoked = queryResourceUsageStats(statsOpts);
|
|
124
|
+
const ranked = top > 0 ? allInvoked.slice(0, top) : allInvoked;
|
|
125
|
+
const installed = installedResources(kind, opts.plugin, process.cwd());
|
|
126
|
+
const zeroInvoked = diffZeroInvoked(installed, allInvoked);
|
|
127
|
+
const coverage = resourceUsageCoverage();
|
|
128
|
+
const totalInvocations = allInvoked.reduce((s, r) => s + r.invocations, 0);
|
|
129
|
+
if (opts.json) {
|
|
130
|
+
process.stdout.write(JSON.stringify({
|
|
131
|
+
schemaVersion: 1,
|
|
132
|
+
kind: 'sessions-stats',
|
|
133
|
+
generatedAt: new Date().toISOString(),
|
|
134
|
+
filters: {
|
|
135
|
+
kind: kind ?? null,
|
|
136
|
+
plugin: opts.plugin ?? null,
|
|
137
|
+
agent: opts.agent ?? null,
|
|
138
|
+
project: opts.project ?? null,
|
|
139
|
+
machine: opts.machine ?? null,
|
|
140
|
+
since: opts.since ?? null,
|
|
141
|
+
},
|
|
142
|
+
signal: {
|
|
143
|
+
explicitOnly: true,
|
|
144
|
+
note: 'Explicit invocations only (slash commands + Skill tool calls). Auto-triggered skills emit no event and read as 0. Skill invocations are recorded for Claude and Kimi; slash-commands for Claude only.',
|
|
145
|
+
},
|
|
146
|
+
coverage: {
|
|
147
|
+
sessionsWithUsage: coverage.covered,
|
|
148
|
+
sessionsIndexed: coverage.total,
|
|
149
|
+
},
|
|
150
|
+
totals: {
|
|
151
|
+
invokedResources: allInvoked.length,
|
|
152
|
+
invocations: totalInvocations,
|
|
153
|
+
zeroInvoked: zeroInvoked.length,
|
|
154
|
+
},
|
|
155
|
+
order: opts.bottom ? 'bottom' : 'top',
|
|
156
|
+
ranked: ranked.map(serializeRow),
|
|
157
|
+
zeroInvoked: zeroInvoked.map(r => ({
|
|
158
|
+
kind: r.kind,
|
|
159
|
+
name: r.name,
|
|
160
|
+
plugin: r.plugin,
|
|
161
|
+
source: r.source,
|
|
162
|
+
})),
|
|
163
|
+
}, null, 2) + '\n');
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
renderHuman({ opts, kind, ranked, zeroInvoked, coverage, totalInvocations, allInvokedCount: allInvoked.length, top });
|
|
167
|
+
}
|
|
168
|
+
function serializeRow(r) {
|
|
169
|
+
return {
|
|
170
|
+
kind: r.kind,
|
|
171
|
+
name: r.name,
|
|
172
|
+
plugin: r.plugin,
|
|
173
|
+
source: r.source,
|
|
174
|
+
sessions: r.sessions,
|
|
175
|
+
invocations: r.invocations,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function normalizeKind(raw) {
|
|
179
|
+
if (raw === undefined)
|
|
180
|
+
return undefined;
|
|
181
|
+
const k = raw.toLowerCase();
|
|
182
|
+
if (k === 'skill' || k === 'skills')
|
|
183
|
+
return 'skill';
|
|
184
|
+
if (k === 'command' || k === 'commands')
|
|
185
|
+
return 'command';
|
|
186
|
+
console.error(chalk.red('error: --kind must be one of: skill, command'));
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
function resolveTop(raw) {
|
|
190
|
+
if (raw === undefined)
|
|
191
|
+
return DEFAULT_TOP;
|
|
192
|
+
const n = Number.parseInt(raw, 10);
|
|
193
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
194
|
+
console.error(chalk.red('error: --top must be a non-negative integer'));
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
return n;
|
|
198
|
+
}
|
|
199
|
+
function renderHuman(args) {
|
|
200
|
+
const { opts, kind, ranked, zeroInvoked, coverage, totalInvocations, allInvokedCount } = args;
|
|
201
|
+
const out = [];
|
|
202
|
+
const scopeBits = [
|
|
203
|
+
kind ? kind : null,
|
|
204
|
+
opts.plugin ? `plugin ${opts.plugin}` : null,
|
|
205
|
+
opts.agent ? opts.agent : null,
|
|
206
|
+
opts.project ? `project ${opts.project}` : null,
|
|
207
|
+
opts.machine ? opts.machine : null,
|
|
208
|
+
opts.since ? `since ${opts.since}` : null,
|
|
209
|
+
].filter(Boolean);
|
|
210
|
+
out.push(chalk.bold('Resource usage') +
|
|
211
|
+
chalk.gray(` · ${allInvokedCount} invoked · ${totalInvocations} invocation${totalInvocations !== 1 ? 's' : ''}` +
|
|
212
|
+
(scopeBits.length ? ` · ${scopeBits.join(' · ')}` : '')));
|
|
213
|
+
out.push(chalk.gray(` coverage: ${coverage.covered}/${coverage.total} sessions carry the signal`) +
|
|
214
|
+
(coverage.total > 0 && coverage.covered / coverage.total < 0.5
|
|
215
|
+
? chalk.yellow(' — run `agents sessions backfill resources` to fold in history')
|
|
216
|
+
: ''));
|
|
217
|
+
out.push(chalk.gray(' signal: explicit invocations only (slash commands + Skill tool); auto-triggered skills read as 0; skills from Claude+Kimi, slash-commands Claude-only'));
|
|
218
|
+
out.push('');
|
|
219
|
+
// Ranked section (skipped by --zero).
|
|
220
|
+
if (!opts.zero) {
|
|
221
|
+
const heading = opts.bottom ? 'Least invoked' : 'Most invoked';
|
|
222
|
+
out.push(chalk.bold(heading));
|
|
223
|
+
if (ranked.length === 0) {
|
|
224
|
+
out.push(chalk.gray(' (no explicit invocations recorded — run the backfill, or nothing has been invoked yet)'));
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
out.push(...renderRankedTable(ranked));
|
|
228
|
+
}
|
|
229
|
+
out.push('');
|
|
230
|
+
}
|
|
231
|
+
// Zero-invoked section (the dead weight).
|
|
232
|
+
out.push(chalk.bold('Installed but never invoked') + chalk.gray(` (${zeroInvoked.length})`));
|
|
233
|
+
if (zeroInvoked.length === 0) {
|
|
234
|
+
out.push(chalk.gray(' (every installed resource has at least one explicit invocation in this window)'));
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
const cols = terminalWidth();
|
|
238
|
+
for (const r of zeroInvoked) {
|
|
239
|
+
const tag = chalk.gray(`[${r.kind}]`);
|
|
240
|
+
const owner = r.plugin ? chalk.gray(` ${r.plugin}`) : r.source ? chalk.gray(` ${r.source}`) : '';
|
|
241
|
+
const nameW = Math.max(12, cols - 12 - stringWidth(owner));
|
|
242
|
+
out.push(` ${tag} ${truncateToWidth(r.name, nameW)}${owner}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
console.log(out.join('\n'));
|
|
246
|
+
}
|
|
247
|
+
function renderRankedTable(rows) {
|
|
248
|
+
const cols = terminalWidth();
|
|
249
|
+
const invW = Math.max(...rows.map(r => String(r.invocations).length), 3);
|
|
250
|
+
const sessW = Math.max(...rows.map(r => `${r.sessions} sess`.length), 6);
|
|
251
|
+
const lines = [];
|
|
252
|
+
for (const r of rows) {
|
|
253
|
+
const inv = String(r.invocations).padStart(invW);
|
|
254
|
+
const sess = padToWidth(`${r.sessions} sess`, sessW);
|
|
255
|
+
const tag = chalk.gray(`[${r.kind}]`);
|
|
256
|
+
const owner = r.plugin ? chalk.gray(` ${r.plugin}`) : r.source ? chalk.gray(` ${r.source}`) : '';
|
|
257
|
+
const prefix = ` ${chalk.green(inv)} ${chalk.gray(sess)} ${tag} `;
|
|
258
|
+
const suffix = owner;
|
|
259
|
+
const nameW = Math.max(12, cols - stringWidth(prefix) - stringWidth(suffix));
|
|
260
|
+
lines.push(prefix + truncateToWidth(r.name, nameW) + suffix);
|
|
261
|
+
}
|
|
262
|
+
return lines;
|
|
263
|
+
}
|
|
@@ -389,7 +389,7 @@ export declare function flatSessionRow(session: SessionMeta, live?: ActiveSessio
|
|
|
389
389
|
export declare function maybeLiveIndex(options: SessionsOptions): Promise<Map<string, ActiveSession> | undefined>;
|
|
390
390
|
/**
|
|
391
391
|
* Group key for the overview: resolve the cwd through the same canonical
|
|
392
|
-
* resolver the `agents
|
|
392
|
+
* resolver the `agents feed` timeline groups by — a defined project's name
|
|
393
393
|
* when `defs` contains the cwd (multi-repo projects read as one group), else
|
|
394
394
|
* the repo-level key, so a monorepo subdir (`<repo>/apps/cli`) reads as
|
|
395
395
|
* `<repo>` in both views. Falls back to the indexed project name (stamped at
|
|
@@ -59,6 +59,7 @@ import { registerSessionsExportCommand } from './sessions-export.js';
|
|
|
59
59
|
import { registerSessionsImportCommand } from './sessions-import.js';
|
|
60
60
|
import { registerSessionsMigrateCommand, registerSessionsMigrationsCommand } from './sessions-migrate.js';
|
|
61
61
|
import { registerSessionsBackfillCommand } from './sessions-backfill.js';
|
|
62
|
+
import { registerSessionsStatsCommand } from './sessions-stats.js';
|
|
62
63
|
import { runBrowserSessions } from '../lib/browser/sessions-list.js';
|
|
63
64
|
import { countToolProgramOccurrences, parseToolProgramCountClause, readToolIndexCoverage, searchToolCalls, TOOL_QUERY_MAX_CLAUSE_BYTES, TOOL_QUERY_MAX_CLAUSES, TOOL_QUERY_MAX_RESULT_SESSIONS, serializeToolSearchEnvelope, toolSearchRemoteReceiveBudget, } from '../lib/session/tool-index.js';
|
|
64
65
|
const SESSION_AGENT_FILTER_HELP = `Filter by agent, e.g. claude, codex, claude@2.0.65`;
|
|
@@ -2210,7 +2211,7 @@ export async function maybeLiveIndex(options) {
|
|
|
2210
2211
|
}
|
|
2211
2212
|
/**
|
|
2212
2213
|
* Group key for the overview: resolve the cwd through the same canonical
|
|
2213
|
-
* resolver the `agents
|
|
2214
|
+
* resolver the `agents feed` timeline groups by — a defined project's name
|
|
2214
2215
|
* when `defs` contains the cwd (multi-repo projects read as one group), else
|
|
2215
2216
|
* the repo-level key, so a monorepo subdir (`<repo>/apps/cli`) reads as
|
|
2216
2217
|
* `<repo>` in both views. Falls back to the indexed project name (stamped at
|
|
@@ -3643,6 +3644,18 @@ export function registerSessionsCommands(program) {
|
|
|
3643
3644
|
# Show only what's running right now (terminals, teams, cloud, headless)
|
|
3644
3645
|
agents sessions --active
|
|
3645
3646
|
|
|
3647
|
+
# --- Session lifecycle (one verb per intent) ---
|
|
3648
|
+
# Jump to a live session (attach its terminal, or open a tab + resume)
|
|
3649
|
+
agents sessions focus a1b2c3d4
|
|
3650
|
+
# Attach only — never fork a copy (old: sessions go)
|
|
3651
|
+
agents sessions focus a1b2c3d4 --attach-only
|
|
3652
|
+
# Interactive → headless (keep working unattended)
|
|
3653
|
+
agents sessions detach a1b2c3d4
|
|
3654
|
+
# Headless → interactive in this terminal
|
|
3655
|
+
agents sessions attach a1b2c3d4
|
|
3656
|
+
# Multi-select history and open each in a tab
|
|
3657
|
+
agents sessions resume
|
|
3658
|
+
|
|
3646
3659
|
# The interactive list folds in other online machines automatically,
|
|
3647
3660
|
# labelled by host with this machine first. Stay local with --local:
|
|
3648
3661
|
agents sessions --local
|
|
@@ -3683,6 +3696,12 @@ export function registerSessionsCommands(program) {
|
|
|
3683
3696
|
agents sessions --all "deploy script" --host box-a --host box-b
|
|
3684
3697
|
`,
|
|
3685
3698
|
notes: `
|
|
3699
|
+
Session lifecycle (pick one verb — they are not synonyms):
|
|
3700
|
+
focus [id] jump to a live session (attach, or open tab + resume)
|
|
3701
|
+
focus [id] --attach-only attach only; never fork (replaces sessions go)
|
|
3702
|
+
detach <id> interactive → headless continuation
|
|
3703
|
+
attach <id> headless → interactive in this terminal
|
|
3704
|
+
resume [query] multi-select history → open tabs (or run --resume <id>)
|
|
3686
3705
|
- The interactive listing folds in your other online machines automatically (live over SSH, no sync) — each row is labelled by host, this machine first. Use --local to skip the fan-out; --json and single-id lookups stay local.
|
|
3687
3706
|
- --host runs the query on the remote's own index over SSH (host alias or user@host); repeat or pass several to fan out. SSH access is the only auth.
|
|
3688
3707
|
- --in-team matches both ends of the lineage: the session that ran 'agents teams create/add', and (with --teams) that team's teammates. In the interactive list, 't' cycles the same filter over the teams in view.
|
|
@@ -3720,6 +3739,7 @@ export function registerSessionsCommands(program) {
|
|
|
3720
3739
|
registerSessionsMigrateCommand(sessionsCmd);
|
|
3721
3740
|
registerSessionsMigrationsCommand(sessionsCmd);
|
|
3722
3741
|
registerSessionsBackfillCommand(sessionsCmd);
|
|
3742
|
+
registerSessionsStatsCommand(sessionsCmd);
|
|
3723
3743
|
}
|
|
3724
3744
|
function formatNoSessionsMessage(showAll, project) {
|
|
3725
3745
|
const projectQuery = project?.trim();
|
package/dist/commands/view.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { AccountInfo } from '../lib/agents.js';
|
|
|
12
12
|
import type { AgentId } from '../lib/types.js';
|
|
13
13
|
import { type ConfiguredModelSource } from '../lib/models.js';
|
|
14
14
|
import { type ProfileSummary } from '../lib/profiles.js';
|
|
15
|
+
import { type ByokUsageResult } from '../lib/byok-usage.js';
|
|
15
16
|
/** Shared account identity formatter, re-exported for the view-specific tests. */
|
|
16
17
|
export declare const accountColumnLabel: typeof accountDisplayLabel;
|
|
17
18
|
/**
|
|
@@ -46,7 +47,7 @@ export declare function descriptionForPrefix(desc: string | undefined, prefix: s
|
|
|
46
47
|
* `installedHosts` is the set of agent ids with a usable install; a harness whose
|
|
47
48
|
* host is missing is flagged rather than silently listed as runnable.
|
|
48
49
|
*/
|
|
49
|
-
export declare function renderHarnessBlocks(harnesses: ProfileSummary[], installedHosts: Set<AgentId>, showPaths: boolean): void;
|
|
50
|
+
export declare function renderHarnessBlocks(harnesses: ProfileSummary[], installedHosts: Set<AgentId>, showPaths: boolean, byokMap?: Map<string, ByokUsageResult>): void;
|
|
50
51
|
/** Machine-readable entry for a single installed version. */
|
|
51
52
|
export interface ViewJsonVersion {
|
|
52
53
|
version: string;
|
package/dist/commands/view.js
CHANGED
|
@@ -27,6 +27,7 @@ import { getConfiguredRunStrategy } from '../lib/rotate.js';
|
|
|
27
27
|
import { resolveRunDefaults } from '../lib/run-defaults.js';
|
|
28
28
|
import { resolveConfiguredModel } from '../lib/models.js';
|
|
29
29
|
import { listProfiles, profileExists, profileSummary, readProfile } from '../lib/profiles.js';
|
|
30
|
+
import { getByokUsageForHarness, hasByokProvider, renderByokBar } from '../lib/byok-usage.js';
|
|
30
31
|
import { renderHarnessDetail } from './harness.js';
|
|
31
32
|
import { loadManifest, isStale } from '../lib/staleness/index.js';
|
|
32
33
|
import { confirm } from '@inquirer/prompts';
|
|
@@ -67,9 +68,28 @@ function getHarnesses(filterAgentId) {
|
|
|
67
68
|
.filter((profile) => !filterAgentId || profile.host.agent === filterAgentId)
|
|
68
69
|
.map(profileSummary);
|
|
69
70
|
}
|
|
70
|
-
/**
|
|
71
|
-
function
|
|
72
|
-
|
|
71
|
+
/** Version-first label: "<version> (forked from <host>[, tracks default])" */
|
|
72
|
+
function harnessVersionLabel(harness, globalDefault) {
|
|
73
|
+
const version = harness.hostVersion ?? globalDefault;
|
|
74
|
+
if (version) {
|
|
75
|
+
const trailer = harness.hostVersion
|
|
76
|
+
? chalk.gray(` (forked from ${harness.agent})`)
|
|
77
|
+
: chalk.gray(` (forked from ${harness.agent}, `) +
|
|
78
|
+
chalk.green(`tracks default`) +
|
|
79
|
+
chalk.gray(`)`);
|
|
80
|
+
return `${version}${trailer}`;
|
|
81
|
+
}
|
|
82
|
+
return chalk.gray(`(forked from ${harness.agent})`);
|
|
83
|
+
}
|
|
84
|
+
/** One-hop or two-hop fork origin label for the harness block header. */
|
|
85
|
+
function buildHarnessOrigin(harness, allHarnesses) {
|
|
86
|
+
if (!harness.forkedFrom || harness.forkedFrom === harness.agent)
|
|
87
|
+
return 'custom';
|
|
88
|
+
const parent = allHarnesses.find((h) => h.name === harness.forkedFrom);
|
|
89
|
+
if (parent?.forkedFrom) {
|
|
90
|
+
return `custom · forked from ${harness.forkedFrom} -> ${parent.forkedFrom}`;
|
|
91
|
+
}
|
|
92
|
+
return `custom · forked from ${harness.forkedFrom}`;
|
|
73
93
|
}
|
|
74
94
|
/**
|
|
75
95
|
* Resolve a resource path to something the IDE can open inline. When `p` is a
|
|
@@ -172,24 +192,23 @@ export function descriptionForPrefix(desc, prefix) {
|
|
|
172
192
|
* `installedHosts` is the set of agent ids with a usable install; a harness whose
|
|
173
193
|
* host is missing is flagged rather than silently listed as runnable.
|
|
174
194
|
*/
|
|
175
|
-
export function renderHarnessBlocks(harnesses, installedHosts, showPaths) {
|
|
195
|
+
export function renderHarnessBlocks(harnesses, installedHosts, showPaths, byokMap) {
|
|
176
196
|
if (harnesses.length === 0)
|
|
177
197
|
return;
|
|
178
198
|
const modelWidth = Math.max(...harnesses.map((h) => h.model.length));
|
|
179
199
|
const authWidth = Math.max(...harnesses.map((h) => h.auth.length));
|
|
180
200
|
for (const harness of harnesses) {
|
|
181
|
-
|
|
182
|
-
// only a fork of another custom harness adds lineage worth printing.
|
|
183
|
-
const origin = harness.forkedFrom && harness.forkedFrom !== harness.agent
|
|
184
|
-
? `custom · forked from ${harness.forkedFrom}`
|
|
185
|
-
: 'custom';
|
|
201
|
+
const origin = buildHarnessOrigin(harness, harnesses);
|
|
186
202
|
const missingHost = installedHosts.has(harness.agent)
|
|
187
203
|
? ''
|
|
188
204
|
: chalk.yellow(` (host ${harness.agent} not installed)`);
|
|
189
205
|
console.log(` ${chalk.bold(harness.label)}${chalk.gray(` (${origin})`)}${missingHost}`);
|
|
206
|
+
const versionTag = harnessVersionLabel(harness, getGlobalDefault(harness.agent));
|
|
207
|
+
const byokEntry = byokMap?.get(harness.name);
|
|
208
|
+
const byokBar = byokEntry ? ` ${renderByokBar(byokEntry)}` : '';
|
|
190
209
|
console.log(` ${chalk.yellow(harness.model.padEnd(modelWidth))} ` +
|
|
191
210
|
`${chalk.cyan(harness.auth.padEnd(authWidth))} ` +
|
|
192
|
-
|
|
211
|
+
versionTag + byokBar);
|
|
193
212
|
if (showPaths)
|
|
194
213
|
console.log(chalk.gray(` ${harness.path}`));
|
|
195
214
|
console.log();
|
|
@@ -616,7 +635,39 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
616
635
|
// Custom harnesses sit in the same list as the native ones — they are run the
|
|
617
636
|
// same way (`agents run <name>`), so they read as their own agent type rather
|
|
618
637
|
// than as an indented row under whichever host CLI executes them.
|
|
619
|
-
|
|
638
|
+
const byokMap = new Map();
|
|
639
|
+
const byKeychainItem = new Map();
|
|
640
|
+
for (const h of harnesses) {
|
|
641
|
+
if (!h.provider || !hasByokProvider(h.provider))
|
|
642
|
+
continue;
|
|
643
|
+
let prof = null;
|
|
644
|
+
try {
|
|
645
|
+
prof = readProfile(h.name);
|
|
646
|
+
}
|
|
647
|
+
catch {
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
if (!prof.auth)
|
|
651
|
+
continue;
|
|
652
|
+
const item = prof.auth.keychainItem;
|
|
653
|
+
const existing = byKeychainItem.get(item);
|
|
654
|
+
if (existing) {
|
|
655
|
+
existing.names.push(h.name);
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
byKeychainItem.set(item, { profile: prof, names: [h.name] });
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
if (byKeychainItem.size > 0) {
|
|
662
|
+
const fetched = await Promise.all([...byKeychainItem.values()].map(({ profile, names }) => getByokUsageForHarness(profile, { forceRefresh: viewOpts?.forceRefresh }).then((result) => ({ result, names }))));
|
|
663
|
+
for (const { result, names } of fetched) {
|
|
664
|
+
if (!result?.budget)
|
|
665
|
+
continue;
|
|
666
|
+
for (const name of names)
|
|
667
|
+
byokMap.set(name, result);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
renderHarnessBlocks(harnesses, installedHosts, showPaths, byokMap.size > 0 ? byokMap : undefined);
|
|
620
671
|
// Show globally installed (not managed) agents
|
|
621
672
|
if (globallyInstalled.length > 0) {
|
|
622
673
|
console.log(chalk.bold('Not Managed by Agents CLI\n'));
|
package/dist/index.js
CHANGED
|
@@ -94,7 +94,7 @@ if (IS_DEV_BUILD) {
|
|
|
94
94
|
// module on each invocation (which loaded the whole ~50-module tree before the
|
|
95
95
|
// first byte of output), the registry maps a command name to a thunk that
|
|
96
96
|
// imports only what that command needs. See src/lib/startup/command-registry.ts.
|
|
97
|
-
import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadMemory, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadExport, loadPackages, loadRoutines, loadMonitors, loadProjects, loadRun, loadFork, loadDefaults, loadSet, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadApply, loadStatus, loadProfiles, loadHarness, loadSecrets, loadLogin, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadLock, loadRefreshRules, loadFactory, loadUsage, loadCost, loadPerf, loadTrends, loadOutput, loadBudget, loadAlias, loadMine, loadPty, loadTmux, loadWatchdog, loadBrowser, loadComputer, loadHosts, loadLogs, loadEvents, loadAudit, loadWebhook, loadFunnel, loadSsh, loadPull, loadPush, loadRepo, loadSetup, loadUninstall, loadShare, loadSend, loadFeed,
|
|
97
|
+
import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadMemory, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadExport, loadPackages, loadRoutines, loadMonitors, loadProjects, loadRun, loadFork, loadDefaults, loadSet, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadApply, loadStatus, loadProfiles, loadHarness, loadSecrets, loadLogin, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadLock, loadRefreshRules, loadFactory, loadUsage, loadCost, loadPerf, loadTrends, loadOutput, loadBudget, loadAlias, loadMine, loadPty, loadTmux, loadWatchdog, loadBrowser, loadComputer, loadHosts, loadLogs, loadEvents, loadAudit, loadWebhook, loadFunnel, loadSsh, loadPull, loadPush, loadRepo, loadSetup, loadUninstall, loadShare, loadSend, loadFeed, loadMailboxes, } from './lib/startup/command-registry.js';
|
|
98
98
|
import { applyGlobalHelpConventions } from './lib/help.js';
|
|
99
99
|
import { renderWhatsNew } from './lib/whats-new.js';
|
|
100
100
|
import { getCliLaunch } from './lib/cli-entry.js';
|
|
@@ -972,7 +972,6 @@ async function registerAllEagerCommands() {
|
|
|
972
972
|
await reg(loadFunnel);
|
|
973
973
|
registerHqTombstoneCommand(program);
|
|
974
974
|
await reg(loadFeed);
|
|
975
|
-
await reg(loadActivity);
|
|
976
975
|
await reg(loadMailboxes);
|
|
977
976
|
await reg(loadSsh);
|
|
978
977
|
registerJobsCronAliasCommand(program, 'jobs');
|
package/dist/lib/activity.js
CHANGED
|
@@ -300,7 +300,7 @@ export function readActivityAsEventRecords(opts = {}) {
|
|
|
300
300
|
return readRecentActivity(opts).map(activityEventToRecord);
|
|
301
301
|
}
|
|
302
302
|
// ---------------------------------------------------------------------------
|
|
303
|
-
// Rendering (shared by
|
|
303
|
+
// Rendering (shared by the feed activity lane and `agents feed --filter updates`)
|
|
304
304
|
// ---------------------------------------------------------------------------
|
|
305
305
|
/** Glyph + color + human label per event, so the lane reads at a glance. */
|
|
306
306
|
export const EVENT_STYLE = {
|
|
@@ -669,7 +669,7 @@ export function formatActivityGroupMeta(group, opts = {}) {
|
|
|
669
669
|
* a logging hiccup never blocks a tool call.
|
|
670
670
|
*/
|
|
671
671
|
export const ACTIVITY_LOG_HOOK_SCRIPT = String.raw `#!/usr/bin/env python3
|
|
672
|
-
"""Append agent-activity events for 'agents feed'
|
|
672
|
+
"""Append agent-activity events for 'agents feed'.
|
|
673
673
|
|
|
674
674
|
Bound to PreToolUse (ExitPlanMode, Task) and PostToolUse (Bash, Write, Edit,
|
|
675
675
|
MultiEdit, TodoWrite, update_plan, TaskUpdate, todo_write, TaskCreate). One
|