@phnx-labs/agents-cli 1.20.33 → 1.20.35
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 +29 -0
- package/README.md +32 -2
- package/dist/commands/computer-actions.d.ts +15 -0
- package/dist/commands/computer-actions.js +120 -22
- package/dist/commands/computer.d.ts +23 -0
- package/dist/commands/computer.js +45 -3
- package/dist/commands/cost.js +17 -12
- package/dist/commands/doctor.d.ts +11 -0
- package/dist/commands/doctor.js +108 -14
- package/dist/commands/events.d.ts +16 -0
- package/dist/commands/events.js +173 -0
- package/dist/commands/exec.js +6 -1
- package/dist/commands/hosts.js +28 -24
- package/dist/commands/import.js +1 -1
- package/dist/commands/inspect.d.ts +2 -0
- package/dist/commands/inspect.js +54 -7
- package/dist/commands/logs.d.ts +17 -0
- package/dist/commands/logs.js +139 -0
- package/dist/commands/models.d.ts +3 -0
- package/dist/commands/models.js +27 -8
- package/dist/commands/permissions.js +9 -2
- package/dist/commands/repo.d.ts +34 -0
- package/dist/commands/repo.js +243 -65
- package/dist/commands/resource-view.d.ts +20 -0
- package/dist/commands/resource-view.js +90 -28
- package/dist/commands/rules.js +1 -1
- package/dist/commands/secrets-migrate.js +23 -11
- package/dist/commands/secrets.d.ts +20 -0
- package/dist/commands/secrets.js +93 -17
- package/dist/commands/sessions-resume.d.ts +2 -0
- package/dist/commands/sessions-resume.js +209 -0
- package/dist/commands/sessions-tail.d.ts +10 -0
- package/dist/commands/sessions-tail.js +11 -0
- package/dist/commands/sessions.d.ts +73 -1
- package/dist/commands/sessions.js +273 -53
- package/dist/commands/status.d.ts +12 -0
- package/dist/commands/status.js +81 -0
- package/dist/commands/teams.js +70 -6
- package/dist/commands/versions.js +2 -1
- package/dist/commands/view.d.ts +42 -0
- package/dist/commands/view.js +213 -83
- package/dist/commands/wallet.d.ts +6 -0
- package/dist/commands/wallet.js +22 -5
- package/dist/index.js +61 -32
- package/dist/lib/acp/harnesses.d.ts +1 -1
- package/dist/lib/acp/harnesses.js +2 -2
- package/dist/lib/agents.d.ts +12 -0
- package/dist/lib/agents.js +145 -41
- package/dist/lib/browser/cdp.js +5 -1
- package/dist/lib/browser/chrome.js +20 -0
- package/dist/lib/browser/drivers/ssh.d.ts +27 -0
- package/dist/lib/browser/drivers/ssh.js +94 -19
- package/dist/lib/browser/ipc.d.ts +3 -0
- package/dist/lib/browser/ipc.js +13 -9
- package/dist/lib/browser/service.js +71 -15
- package/dist/lib/daemon.d.ts +35 -0
- package/dist/lib/daemon.js +33 -5
- package/dist/lib/devices/connect.d.ts +3 -2
- package/dist/lib/devices/connect.js +5 -3
- package/dist/lib/doctor-diff.js +29 -2
- package/dist/lib/drift-sync.d.ts +43 -0
- package/dist/lib/drift-sync.js +179 -0
- package/dist/lib/events.d.ts +9 -2
- package/dist/lib/events.js +70 -11
- package/dist/lib/exec.d.ts +15 -0
- package/dist/lib/exec.js +43 -11
- package/dist/lib/hooks.js +8 -2
- package/dist/lib/hosts/dispatch.js +6 -7
- package/dist/lib/hosts/logs.d.ts +16 -0
- package/dist/lib/hosts/logs.js +45 -0
- package/dist/lib/hosts/progress.d.ts +66 -0
- package/dist/lib/hosts/progress.js +125 -17
- package/dist/lib/hosts/ready.d.ts +23 -2
- package/dist/lib/hosts/ready.js +35 -13
- package/dist/lib/hosts/reconcile.d.ts +53 -0
- package/dist/lib/hosts/reconcile.js +81 -0
- package/dist/lib/hosts/tasks.d.ts +8 -0
- package/dist/lib/hosts/tasks.js +14 -0
- package/dist/lib/permissions.d.ts +4 -0
- package/dist/lib/permissions.js +35 -0
- package/dist/lib/picker.d.ts +24 -0
- package/dist/lib/picker.js +124 -0
- package/dist/lib/platform/winpath.d.ts +31 -2
- package/dist/lib/platform/winpath.js +133 -24
- package/dist/lib/plugin-marketplace.d.ts +30 -0
- package/dist/lib/plugin-marketplace.js +215 -2
- package/dist/lib/plugins.d.ts +5 -0
- package/dist/lib/plugins.js +45 -4
- package/dist/lib/pwsh.d.ts +11 -0
- package/dist/lib/pwsh.js +13 -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 +42 -1
- package/dist/lib/secrets/agent.js +89 -11
- package/dist/lib/secrets/bundles.js +40 -9
- package/dist/lib/secrets/filestore.js +31 -1
- package/dist/lib/secrets/index.d.ts +33 -1
- package/dist/lib/secrets/index.js +90 -9
- package/dist/lib/secrets/windows.d.ts +74 -0
- package/dist/lib/secrets/windows.js +440 -0
- package/dist/lib/session/active.d.ts +7 -0
- package/dist/lib/session/active.js +11 -3
- package/dist/lib/session/discover.js +94 -0
- package/dist/lib/session/parse.d.ts +6 -0
- package/dist/lib/session/parse.js +283 -2
- package/dist/lib/session/pid-registry.d.ts +21 -0
- package/dist/lib/session/pid-registry.js +80 -0
- package/dist/lib/session/remote-active.d.ts +26 -0
- package/dist/lib/session/remote-active.js +141 -0
- package/dist/lib/session/remote.js +2 -6
- package/dist/lib/session/render.js +1 -1
- package/dist/lib/session/sync/config.d.ts +8 -0
- package/dist/lib/session/sync/config.js +11 -2
- package/dist/lib/session/types.d.ts +1 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/shims.d.ts +30 -2
- package/dist/lib/shims.js +107 -50
- package/dist/lib/ssh-exec.d.ts +26 -3
- package/dist/lib/ssh-exec.js +45 -3
- package/dist/lib/ssh-tunnel.d.ts +24 -5
- package/dist/lib/ssh-tunnel.js +60 -62
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +3 -0
- package/dist/lib/startup/command-registry.js +6 -0
- package/dist/lib/sync-status.d.ts +102 -0
- package/dist/lib/sync-status.js +135 -0
- package/dist/lib/teams/agents.d.ts +24 -0
- package/dist/lib/teams/agents.js +30 -1
- package/dist/lib/teams/registry.js +25 -9
- package/dist/lib/terminal/backends/ghostty.d.ts +6 -0
- package/dist/lib/terminal/backends/ghostty.js +69 -0
- package/dist/lib/terminal/backends/index.d.ts +18 -0
- package/dist/lib/terminal/backends/index.js +29 -0
- package/dist/lib/terminal/backends/iterm.d.ts +6 -0
- package/dist/lib/terminal/backends/iterm.js +62 -0
- package/dist/lib/terminal/backends/tmux.d.ts +14 -0
- package/dist/lib/terminal/backends/tmux.js +23 -0
- package/dist/lib/terminal/engine.d.ts +39 -0
- package/dist/lib/terminal/engine.js +54 -0
- package/dist/lib/terminal/index.d.ts +14 -0
- package/dist/lib/terminal/index.js +5 -0
- package/dist/lib/terminal/policy.d.ts +11 -0
- package/dist/lib/terminal/policy.js +11 -0
- package/dist/lib/terminal/quote.d.ts +11 -0
- package/dist/lib/terminal/quote.js +13 -0
- package/dist/lib/terminal/shell.d.ts +6 -0
- package/dist/lib/terminal/shell.js +23 -0
- package/dist/lib/terminal/transport.d.ts +15 -0
- package/dist/lib/terminal/transport.js +40 -0
- package/dist/lib/terminal/types.d.ts +59 -0
- package/dist/lib/terminal/types.js +13 -0
- package/dist/lib/types.d.ts +27 -1
- package/dist/lib/usage.d.ts +30 -0
- package/dist/lib/usage.js +159 -2
- package/dist/lib/versions.d.ts +7 -3
- package/dist/lib/versions.js +10 -5
- package/dist/lib/whats-new.d.ts +9 -0
- package/dist/lib/whats-new.js +35 -0
- package/package.json +1 -1
package/dist/commands/doctor.js
CHANGED
|
@@ -12,6 +12,8 @@ import { unifiedDiff, colorizeUnifiedDiff } from '../lib/diff-text.js';
|
|
|
12
12
|
import { listCliStatus } from '../lib/cli-resources.js';
|
|
13
13
|
import { setHelpSections } from '../lib/help.js';
|
|
14
14
|
import { heal, healChangedAnything } from '../lib/heal.js';
|
|
15
|
+
import { blocksLocalScripts, getEffectiveExecutionPolicy } from '../lib/platform/winpath.js';
|
|
16
|
+
import { terminalWidth, truncateToWidth, stringWidth, padToWidth } from '../lib/session/width.js';
|
|
15
17
|
import * as fs from 'fs';
|
|
16
18
|
const AGENT_NAMES = Object.fromEntries(ALL_AGENT_IDS.map((id) => [id, AGENTS[id].name]));
|
|
17
19
|
// ─── overview mode (no target) ────────────────────────────────────────────────
|
|
@@ -28,7 +30,6 @@ function divergenceLines(report) {
|
|
|
28
30
|
else if (p.status === 'diff')
|
|
29
31
|
lines.push(`plugin ${p.name} — ${p.detail ?? 'mirror drifted'}`);
|
|
30
32
|
}
|
|
31
|
-
const counts = [];
|
|
32
33
|
for (const kind of ['commands', 'skills', 'hooks', 'rules', 'mcp', 'permissions', 'subagents']) {
|
|
33
34
|
const rows = report.kinds[kind];
|
|
34
35
|
const miss = rows.filter((r) => r.status === 'missing').length;
|
|
@@ -39,12 +40,44 @@ function divergenceLines(report) {
|
|
|
39
40
|
if (dif)
|
|
40
41
|
bits.push(`${dif} drifted`);
|
|
41
42
|
if (bits.length)
|
|
42
|
-
|
|
43
|
+
lines.push(`${kind.padEnd(11)} ${bits.join(' · ')}`);
|
|
43
44
|
}
|
|
44
|
-
if (counts.length)
|
|
45
|
-
lines.push(counts.join(' · '));
|
|
46
45
|
return lines;
|
|
47
46
|
}
|
|
47
|
+
function collapseWhitespace(s) {
|
|
48
|
+
return s.replace(/\s+/g, ' ').trim();
|
|
49
|
+
}
|
|
50
|
+
export function wrapLine(prefix, text, width = terminalWidth()) {
|
|
51
|
+
const words = collapseWhitespace(text).split(' ').filter(Boolean);
|
|
52
|
+
if (words.length === 0)
|
|
53
|
+
return [prefix.trimEnd()];
|
|
54
|
+
const continuation = ' '.repeat(stringWidth(prefix));
|
|
55
|
+
const lines = [];
|
|
56
|
+
let linePrefix = prefix;
|
|
57
|
+
let line = prefix;
|
|
58
|
+
let hasWord = false;
|
|
59
|
+
for (const word of words) {
|
|
60
|
+
const room = Math.max(1, width - stringWidth(linePrefix));
|
|
61
|
+
const piece = stringWidth(word) > room ? truncateToWidth(word, room) : word;
|
|
62
|
+
const candidate = hasWord ? `${line} ${piece}` : `${line}${piece}`;
|
|
63
|
+
if (hasWord && stringWidth(candidate) > width) {
|
|
64
|
+
lines.push(line);
|
|
65
|
+
linePrefix = continuation;
|
|
66
|
+
line = continuation + piece;
|
|
67
|
+
hasWord = true;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
line = candidate;
|
|
71
|
+
hasWord = true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
lines.push(line);
|
|
75
|
+
return lines;
|
|
76
|
+
}
|
|
77
|
+
function printWrappedLine(prefix, text) {
|
|
78
|
+
for (const line of wrapLine(prefix, text))
|
|
79
|
+
console.log(chalk.gray(line));
|
|
80
|
+
}
|
|
48
81
|
function checkSyncStatus(cwd) {
|
|
49
82
|
const rows = [];
|
|
50
83
|
// Every installed version, not just the default — a stale NON-default version
|
|
@@ -129,7 +162,7 @@ function renderOverviewText(clis, syncRows, orphanRows, hostClis) {
|
|
|
129
162
|
}
|
|
130
163
|
}
|
|
131
164
|
if (hidden.length > 0) {
|
|
132
|
-
|
|
165
|
+
printWrappedLine(' ', `+${hidden.length} more supported (${hidden.join(', ')}) — \`agents add <name>\` to manage`);
|
|
133
166
|
}
|
|
134
167
|
console.log();
|
|
135
168
|
console.log(chalk.bold('Sync status (installed versions)'));
|
|
@@ -161,7 +194,7 @@ function renderOverviewText(clis, syncRows, orphanRows, hostClis) {
|
|
|
161
194
|
// Version homes are reconciled only by management commands, so point at one
|
|
162
195
|
// rather than promising an auto-sync that never happens.
|
|
163
196
|
if (anyOutOfSync) {
|
|
164
|
-
|
|
197
|
+
printWrappedLine(' ', 'Reconcile with `agents doctor <agent>@<version> --fix` or `agents sync <agent>@<version>` (not applied on launch).');
|
|
165
198
|
}
|
|
166
199
|
}
|
|
167
200
|
console.log();
|
|
@@ -198,16 +231,65 @@ function renderOverviewText(clis, syncRows, orphanRows, hostClis) {
|
|
|
198
231
|
const label = manifest.name.padEnd(nameWidth);
|
|
199
232
|
const src = chalk.gray(`[${manifest.source}]`);
|
|
200
233
|
if (installed) {
|
|
201
|
-
|
|
234
|
+
const prefix = ` ${chalk.green('ready')} ${label} ${src}`;
|
|
235
|
+
const desc = manifest.description
|
|
236
|
+
? ` ${truncateToWidth(collapseWhitespace(manifest.description), Math.max(1, terminalWidth() - stringWidth(prefix) - 2))}`
|
|
237
|
+
: '';
|
|
238
|
+
console.log(prefix + chalk.gray(desc));
|
|
202
239
|
}
|
|
203
240
|
else {
|
|
204
|
-
|
|
241
|
+
const prefix = ` ${chalk.red('miss ')} ${label} ${src}`;
|
|
242
|
+
const msg = `not installed — run \`agents cli install ${manifest.name}\``;
|
|
243
|
+
const budget = Math.max(1, terminalWidth() - stringWidth(prefix) - 2);
|
|
244
|
+
console.log(prefix + chalk.gray(` ${truncateToWidth(msg, budget)}`));
|
|
205
245
|
}
|
|
206
246
|
}
|
|
207
247
|
}
|
|
208
248
|
for (const err of hostClis.errors) {
|
|
209
249
|
console.log(` ${chalk.red('err ')} ${chalk.gray(err.file)}: ${chalk.gray(err.reason)}`);
|
|
210
250
|
}
|
|
251
|
+
// On Windows a Restricted/AllSigned execution policy silently breaks the
|
|
252
|
+
// generated `agents.ps1` launcher — postinstall diagnoses it interactively,
|
|
253
|
+
// but a non-interactive install never sees that. Surface it here too.
|
|
254
|
+
renderExecPolicyAdvisory();
|
|
255
|
+
}
|
|
256
|
+
// ─── windows execution-policy advisory ─────────────────────────────────────────
|
|
257
|
+
/**
|
|
258
|
+
* Windows-only advisory lines. When the effective PowerShell execution policy
|
|
259
|
+
* blocks unsigned local `.ps1` scripts (`Restricted`/`AllSigned`), the generated
|
|
260
|
+
* `agents.ps1` launcher fails in PowerShell even when it is on PATH. Surface the
|
|
261
|
+
* remediation; the `.cmd` companion still works, so this is a warning, not an
|
|
262
|
+
* error, and doctor never auto-changes the policy. Pure — returns `[]` on
|
|
263
|
+
* non-Windows or a permissive policy, so it is testable without invoking
|
|
264
|
+
* PowerShell.
|
|
265
|
+
*/
|
|
266
|
+
export function execPolicyWarningLines(platform, policy) {
|
|
267
|
+
if (platform !== 'win32')
|
|
268
|
+
return [];
|
|
269
|
+
if (!blocksLocalScripts(policy))
|
|
270
|
+
return [];
|
|
271
|
+
return [
|
|
272
|
+
`PowerShell execution policy is ${policy} — it blocks the generated agents.ps1 launcher.`,
|
|
273
|
+
'Fix: Set-ExecutionPolicy -Scope CurrentUser RemoteSigned',
|
|
274
|
+
'The agents.cmd shim still works regardless of the policy.',
|
|
275
|
+
];
|
|
276
|
+
}
|
|
277
|
+
function renderExecPolicyAdvisory() {
|
|
278
|
+
// Only probe the policy on Windows — getEffectiveExecutionPolicy() spawns
|
|
279
|
+
// powershell, which is a wasted (doomed) process on POSIX where the advisory
|
|
280
|
+
// never applies.
|
|
281
|
+
if (process.platform !== 'win32')
|
|
282
|
+
return;
|
|
283
|
+
const lines = execPolicyWarningLines(process.platform, getEffectiveExecutionPolicy());
|
|
284
|
+
if (lines.length === 0)
|
|
285
|
+
return;
|
|
286
|
+
console.log();
|
|
287
|
+
console.log(chalk.bold('Execution policy (Windows)'));
|
|
288
|
+
const [headline, ...rest] = lines;
|
|
289
|
+
console.log(` ${chalk.yellow('warn ')} ${headline}`);
|
|
290
|
+
for (const line of rest) {
|
|
291
|
+
console.log(chalk.gray(` ${line}`));
|
|
292
|
+
}
|
|
211
293
|
}
|
|
212
294
|
function parseTargetArg(arg) {
|
|
213
295
|
const at = arg.indexOf('@');
|
|
@@ -314,8 +396,12 @@ function renderKindSection(kind, rows, layers, options) {
|
|
|
314
396
|
}
|
|
315
397
|
for (const r of visible) {
|
|
316
398
|
const src = sourceLabel(r, layers);
|
|
317
|
-
const
|
|
318
|
-
|
|
399
|
+
const name = padToWidth(truncateToWidth(r.name, 28), 28);
|
|
400
|
+
const prefix = ` ${statusLabel(r.status)} ${name} ${src}`;
|
|
401
|
+
const detail = r.detail
|
|
402
|
+
? chalk.gray(` ${truncateToWidth(collapseWhitespace(r.detail), Math.max(1, terminalWidth() - stringWidth(prefix) - 2))}`)
|
|
403
|
+
: '';
|
|
404
|
+
console.log(prefix + detail);
|
|
319
405
|
if (options.showDiff && r.status === 'diff' && r.sourcePath && r.homePath) {
|
|
320
406
|
const expected = readExpectedForDiff(kind, r);
|
|
321
407
|
const actual = safeRead(r.homePath);
|
|
@@ -352,8 +438,10 @@ function readExpectedForDiff(kind, row) {
|
|
|
352
438
|
function renderTargetText(report, options) {
|
|
353
439
|
const label = `${AGENT_NAMES[report.agent] || report.agent}@${report.version}`;
|
|
354
440
|
console.log(chalk.bold(label));
|
|
355
|
-
|
|
356
|
-
|
|
441
|
+
const homePrefix = ' home: ';
|
|
442
|
+
const cwdPrefix = ' cwd: ';
|
|
443
|
+
console.log(chalk.gray(homePrefix + truncateToWidth(report.home, Math.max(1, terminalWidth() - stringWidth(homePrefix)))));
|
|
444
|
+
console.log(chalk.gray(cwdPrefix + truncateToWidth(report.cwd, Math.max(1, terminalWidth() - stringWidth(cwdPrefix)))));
|
|
357
445
|
const layerStr = [
|
|
358
446
|
report.layers.project ? `project=${report.layers.project}` : null,
|
|
359
447
|
`user=${report.layers.user}`,
|
|
@@ -362,7 +450,7 @@ function renderTargetText(report, options) {
|
|
|
362
450
|
? `extras=[${report.layers.extras.map((e) => e.alias).join(',')}]`
|
|
363
451
|
: null,
|
|
364
452
|
].filter(Boolean).join(' ');
|
|
365
|
-
|
|
453
|
+
printWrappedLine(' layers: ', layerStr);
|
|
366
454
|
// Staleness manifest verdict — single-line summary from the staleness
|
|
367
455
|
// library, sitting alongside the detailed per-resource diff below.
|
|
368
456
|
const manifest = loadManifest(report.agent, report.version);
|
|
@@ -403,7 +491,7 @@ function renderTargetText(report, options) {
|
|
|
403
491
|
}
|
|
404
492
|
else {
|
|
405
493
|
console.log(` Verdict: ${verdictParts.join(', ')}.`);
|
|
406
|
-
|
|
494
|
+
printWrappedLine(' ', `Run \`agents doctor ${report.agent}@${report.version} --fix\` to heal, or \`agents prune cleanup\` to drop extras.`);
|
|
407
495
|
}
|
|
408
496
|
}
|
|
409
497
|
// ─── fix / heal mode ───────────────────────────────────────────────────────────
|
|
@@ -547,6 +635,12 @@ export function registerDoctorCommand(program) {
|
|
|
547
635
|
return;
|
|
548
636
|
}
|
|
549
637
|
renderOverviewText(clis, syncRows, orphanRows, hostClis);
|
|
638
|
+
// Point at the interactive reconcile when anything is out of sync — the
|
|
639
|
+
// report shouldn't be a dead end. `agents status` runs the unified
|
|
640
|
+
// home-reading engine and offers to sync (opt-in, never auto-fires here).
|
|
641
|
+
if (syncRows.some((r) => r.status !== 'fresh')) {
|
|
642
|
+
console.log(chalk.gray('\nRun `agents status` to review and sync what has drifted.'));
|
|
643
|
+
}
|
|
550
644
|
return;
|
|
551
645
|
}
|
|
552
646
|
const parsed = parseTargetArg(target);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents events` — read the structured audit/event log.
|
|
3
|
+
*
|
|
4
|
+
* The event log (`~/.agents/.cache/logs/events-YYYY-MM-DD.jsonl`) records every
|
|
5
|
+
* `agents <module> <cmd>` invocation plus richer typed events (secrets access,
|
|
6
|
+
* version installs, ...), each stamped with who ran it and from where (OS user,
|
|
7
|
+
* local vs SSH, remote client IP). This command reads it back — the audit trail
|
|
8
|
+
* for "who accessed a secret / created a team / started an agent, and from which
|
|
9
|
+
* host?".
|
|
10
|
+
*
|
|
11
|
+
* Filter by `--module` (top-level group, e.g. teams), `--command` (path prefix,
|
|
12
|
+
* e.g. "teams create"), `--event` (typed event), `--agent`, and `--since`.
|
|
13
|
+
* `--follow` tails today's log live.
|
|
14
|
+
*/
|
|
15
|
+
import type { Command } from 'commander';
|
|
16
|
+
export declare function registerEventsCommand(program: Command): void;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents events` — read the structured audit/event log.
|
|
3
|
+
*
|
|
4
|
+
* The event log (`~/.agents/.cache/logs/events-YYYY-MM-DD.jsonl`) records every
|
|
5
|
+
* `agents <module> <cmd>` invocation plus richer typed events (secrets access,
|
|
6
|
+
* version installs, ...), each stamped with who ran it and from where (OS user,
|
|
7
|
+
* local vs SSH, remote client IP). This command reads it back — the audit trail
|
|
8
|
+
* for "who accessed a secret / created a team / started an agent, and from which
|
|
9
|
+
* host?".
|
|
10
|
+
*
|
|
11
|
+
* Filter by `--module` (top-level group, e.g. teams), `--command` (path prefix,
|
|
12
|
+
* e.g. "teams create"), `--event` (typed event), `--agent`, and `--since`.
|
|
13
|
+
* `--follow` tails today's log live.
|
|
14
|
+
*/
|
|
15
|
+
import chalk from 'chalk';
|
|
16
|
+
import * as fs from 'fs';
|
|
17
|
+
import { query, LOGS_PATH } from '../lib/events.js';
|
|
18
|
+
/** Parse `--since`: relative offsets (30s/5m/2h/7d/4w) or an ISO/absolute date. */
|
|
19
|
+
function parseSince(s) {
|
|
20
|
+
const m = s.match(/^(\d+)([smhdw])$/);
|
|
21
|
+
if (m) {
|
|
22
|
+
const n = parseInt(m[1], 10);
|
|
23
|
+
const unitMs = {
|
|
24
|
+
s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000, w: 604_800_000,
|
|
25
|
+
};
|
|
26
|
+
return new Date(Date.now() - n * unitMs[m[2]]);
|
|
27
|
+
}
|
|
28
|
+
const ms = Date.parse(s);
|
|
29
|
+
if (isNaN(ms))
|
|
30
|
+
throw new Error(`Invalid --since value: ${s} (use e.g. 2h, 7d, or an ISO date)`);
|
|
31
|
+
return new Date(ms);
|
|
32
|
+
}
|
|
33
|
+
/** How the run reached this host — 'local' or 'ssh 203.0.113.7'. */
|
|
34
|
+
function originLabel(r) {
|
|
35
|
+
if (r.transport === 'ssh') {
|
|
36
|
+
return chalk.yellow(`ssh${r.sshClientIp ? ' ' + r.sshClientIp : ''}`);
|
|
37
|
+
}
|
|
38
|
+
return chalk.gray('local');
|
|
39
|
+
}
|
|
40
|
+
/** The most useful one-line detail for a record, by event family. */
|
|
41
|
+
function detailFor(r) {
|
|
42
|
+
if (r.command)
|
|
43
|
+
return r.command;
|
|
44
|
+
const bits = [];
|
|
45
|
+
if (typeof r.team === 'string')
|
|
46
|
+
bits.push(`team=${r.team}`);
|
|
47
|
+
if (typeof r.bundle === 'string')
|
|
48
|
+
bits.push(`bundle=${r.bundle}`);
|
|
49
|
+
if (typeof r.skill === 'string')
|
|
50
|
+
bits.push(`skill=${r.skill}`);
|
|
51
|
+
if (typeof r.version === 'string')
|
|
52
|
+
bits.push(`v=${r.version}`);
|
|
53
|
+
if (typeof r.profile === 'string')
|
|
54
|
+
bits.push(`profile=${r.profile}`);
|
|
55
|
+
if (typeof r.error === 'string')
|
|
56
|
+
bits.push(chalk.red(r.error));
|
|
57
|
+
return bits.join(' ');
|
|
58
|
+
}
|
|
59
|
+
function renderRow(r) {
|
|
60
|
+
const time = chalk.gray(r.ts.slice(0, 19).replace('T', ' '));
|
|
61
|
+
const user = `${r.osUser ?? '?'}@${r.hostname}`;
|
|
62
|
+
const ev = r.event.startsWith('error') ? chalk.red(r.event) : chalk.cyan(r.event);
|
|
63
|
+
const agent = r.agent ? chalk.gray(` ${r.agent}`) : '';
|
|
64
|
+
return `${time} ${originLabel(r).padEnd(24)} ${user.padEnd(22)} ${ev.padEnd(26)}${agent} ${detailFor(r)}`;
|
|
65
|
+
}
|
|
66
|
+
export function registerEventsCommand(program) {
|
|
67
|
+
program
|
|
68
|
+
.command('events')
|
|
69
|
+
.description('Read the structured audit/event log (who ran what, from where)')
|
|
70
|
+
.option('--module <name>', 'Only events from this command group (e.g. teams, secrets)')
|
|
71
|
+
.option('--command <path>', 'Only this command path — prefix match (e.g. "teams create")')
|
|
72
|
+
.option('--event <type>', 'Only this typed event (repeatable, e.g. secrets.get)', collect, [])
|
|
73
|
+
.option('--agent <name>', 'Only events tagged with this agent')
|
|
74
|
+
.option('--since <time>', 'Only events newer than this (e.g. 2h, 7d, or ISO date)')
|
|
75
|
+
.option('--limit <n>', 'Max records to show (default 50)', '50')
|
|
76
|
+
.option('--json', 'Output raw records as JSON')
|
|
77
|
+
.option('-f, --follow', "Tail today's log live")
|
|
78
|
+
.addHelpText('after', `
|
|
79
|
+
Examples:
|
|
80
|
+
agents events Recent activity across everything
|
|
81
|
+
agents events --module teams Team lifecycle (create / add / disband)
|
|
82
|
+
agents events --module secrets Every secret accessed or revealed
|
|
83
|
+
agents events --command "teams create" Just team creations
|
|
84
|
+
agents events --event secrets.get --since 7d --json
|
|
85
|
+
agents events -f Live tail`)
|
|
86
|
+
.action(async (options) => {
|
|
87
|
+
if (options.follow) {
|
|
88
|
+
await followLog();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const limit = Math.max(1, parseInt(options.limit ?? '50', 10) || 50);
|
|
92
|
+
let startDate;
|
|
93
|
+
try {
|
|
94
|
+
startDate = options.since ? parseSince(options.since) : undefined;
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
console.error(chalk.red(err.message));
|
|
98
|
+
process.exit(2);
|
|
99
|
+
}
|
|
100
|
+
const records = query({
|
|
101
|
+
startDate,
|
|
102
|
+
eventTypes: options.event && options.event.length ? options.event : undefined,
|
|
103
|
+
agent: options.agent,
|
|
104
|
+
command: options.command,
|
|
105
|
+
module: options.module,
|
|
106
|
+
limit,
|
|
107
|
+
});
|
|
108
|
+
if (options.json) {
|
|
109
|
+
console.log(JSON.stringify(records, null, 2));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (records.length === 0) {
|
|
113
|
+
console.log(chalk.gray('No matching events.'));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// query() returns newest-first; print oldest-first so a tail reads naturally.
|
|
117
|
+
for (const r of records.slice().reverse())
|
|
118
|
+
console.log(renderRow(r));
|
|
119
|
+
console.log(chalk.gray(`\n${records.length} event(s). Log: ${LOGS_PATH}`));
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/** commander repeatable-option collector. */
|
|
123
|
+
function collect(value, previous) {
|
|
124
|
+
return previous.concat([value]);
|
|
125
|
+
}
|
|
126
|
+
/** Tail today's event file, printing new lines as they land. */
|
|
127
|
+
async function followLog() {
|
|
128
|
+
const today = new Date();
|
|
129
|
+
const file = `${LOGS_PATH}/events-${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}.jsonl`;
|
|
130
|
+
let offset = 0;
|
|
131
|
+
try {
|
|
132
|
+
offset = fs.statSync(file).size;
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// File may not exist yet — start at 0 and pick it up on first write.
|
|
136
|
+
}
|
|
137
|
+
console.log(chalk.gray(`Tailing ${file} — Ctrl-C to stop`));
|
|
138
|
+
const drain = () => {
|
|
139
|
+
let size = 0;
|
|
140
|
+
try {
|
|
141
|
+
size = fs.statSync(file).size;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (size <= offset) {
|
|
147
|
+
if (size < offset)
|
|
148
|
+
offset = 0; // rotated/truncated
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const fd = fs.openSync(file, 'r');
|
|
152
|
+
try {
|
|
153
|
+
const buf = Buffer.alloc(size - offset);
|
|
154
|
+
fs.readSync(fd, buf, 0, buf.length, offset);
|
|
155
|
+
offset = size;
|
|
156
|
+
for (const line of buf.toString('utf-8').split('\n').filter(Boolean)) {
|
|
157
|
+
try {
|
|
158
|
+
console.log(renderRow(JSON.parse(line)));
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// Skip malformed lines.
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
fs.closeSync(fd);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
// Poll — simpler and more portable than fs.watch across platforms/editors.
|
|
170
|
+
await new Promise(() => {
|
|
171
|
+
setInterval(drain, 500);
|
|
172
|
+
});
|
|
173
|
+
}
|
package/dist/commands/exec.js
CHANGED
|
@@ -294,7 +294,12 @@ export function registerRunCommand(program) {
|
|
|
294
294
|
console.log(chalk.green(`Dispatched to ${host.name}.`) + chalk.gray(' Track: agents hosts ps · Follow: agents hosts logs <id> -f'));
|
|
295
295
|
process.exit(0);
|
|
296
296
|
}
|
|
297
|
-
|
|
297
|
+
// -1 = the follow window closed but the run continues on the host (the
|
|
298
|
+
// reattach hint is already printed). That's a detach, not a failure —
|
|
299
|
+
// exit 0. Any real remote code passes through.
|
|
300
|
+
if (exitCode === -1)
|
|
301
|
+
process.exit(0);
|
|
302
|
+
process.exit(exitCode ?? 1);
|
|
298
303
|
}
|
|
299
304
|
catch (err) {
|
|
300
305
|
console.error(chalk.red(err.message));
|
package/dist/commands/hosts.js
CHANGED
|
@@ -7,15 +7,16 @@
|
|
|
7
7
|
* hosts. Dispatch itself is `agents run --host <name>` (see commands/exec.ts).
|
|
8
8
|
*/
|
|
9
9
|
import chalk from 'chalk';
|
|
10
|
+
import { terminalWidth, truncateToWidth, stringWidth } from '../lib/session/width.js';
|
|
10
11
|
import { checkbox, confirm } from '@inquirer/prompts';
|
|
11
12
|
import { assertValidSshTarget } from '../lib/ssh-exec.js';
|
|
12
13
|
import { getProvider, listAllHosts, resolveHost } from '../lib/hosts/registry.js';
|
|
13
14
|
import { sshTargetFor } from '../lib/hosts/types.js';
|
|
14
15
|
import { listSshConfigHosts, listKnownHosts, isSshConfigHost } from '../lib/hosts/ssh-config.js';
|
|
15
16
|
import { probeHost, remoteAgentsVersion, bootstrapAgentsCli, localCliVersion, } from '../lib/hosts/ready.js';
|
|
16
|
-
import { listTasks
|
|
17
|
-
import {
|
|
18
|
-
import
|
|
17
|
+
import { listTasks } from '../lib/hosts/tasks.js';
|
|
18
|
+
import { reconcileRunningTasks } from '../lib/hosts/reconcile.js';
|
|
19
|
+
import { showHostTaskLog } from '../lib/hosts/logs.js';
|
|
19
20
|
/** Parse `user@host` or `host` into its pieces. */
|
|
20
21
|
function parseTarget(target) {
|
|
21
22
|
const at = target.indexOf('@');
|
|
@@ -23,9 +24,12 @@ function parseTarget(target) {
|
|
|
23
24
|
return { address: target };
|
|
24
25
|
return { user: target.slice(0, at), address: target.slice(at + 1) };
|
|
25
26
|
}
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Bootstrap/verify agents-cli on a freshly-enrolled host (best-effort, prompts).
|
|
29
|
+
* Callers that just probed the host pass the result through to avoid a second,
|
|
30
|
+
* redundant `uname` round-trip; otherwise we probe here.
|
|
31
|
+
*/
|
|
32
|
+
async function maybeBootstrap(target, hostName, probe = probeHost(target)) {
|
|
29
33
|
if (!probe.reachable) {
|
|
30
34
|
console.log(chalk.yellow(` Not reachable over SSH yet — skipping bootstrap. Fix key auth, then: agents hosts check ${hostName}`));
|
|
31
35
|
return;
|
|
@@ -75,7 +79,7 @@ async function doAdd(name, target, opts) {
|
|
|
75
79
|
await registerHost({ name: c, provider: 'local', source, ...(source === 'inline' ? { address: c } : {}), os: probe.os, caps: opts.cap });
|
|
76
80
|
console.log(chalk.green(`Enrolled ${c}`) + chalk.gray(` (${source}${probe.os ? `, ${probe.os}` : ''})`));
|
|
77
81
|
if (opts.enroll !== false)
|
|
78
|
-
await maybeBootstrap(c, c);
|
|
82
|
+
await maybeBootstrap(c, c, probe);
|
|
79
83
|
}
|
|
80
84
|
return;
|
|
81
85
|
}
|
|
@@ -107,7 +111,7 @@ async function doAdd(name, target, opts) {
|
|
|
107
111
|
await registerHost(spec);
|
|
108
112
|
console.log(chalk.green(`Enrolled ${name}`) + chalk.gray(` (${spec.source}${spec.os ? `, ${spec.os}` : ''}${spec.caps?.length ? `, caps: ${spec.caps.join(',')}` : ''})`));
|
|
109
113
|
if (opts.enroll !== false)
|
|
110
|
-
await maybeBootstrap(sshTarget, name);
|
|
114
|
+
await maybeBootstrap(sshTarget, name, probe);
|
|
111
115
|
}
|
|
112
116
|
async function doList(json) {
|
|
113
117
|
const hosts = await listAllHosts();
|
|
@@ -122,8 +126,11 @@ async function doList(json) {
|
|
|
122
126
|
console.log(chalk.bold('NAME').padEnd(20) + chalk.bold('SOURCE').padEnd(13) + chalk.bold('TARGET').padEnd(28) + chalk.bold('CAPS'));
|
|
123
127
|
for (const h of hosts) {
|
|
124
128
|
const tgt = h.source === 'ssh-config' ? chalk.gray('(ssh-config)') : `${h.user ? h.user + '@' : ''}${h.address ?? ''}`;
|
|
129
|
+
// Cap TARGET at its column so a long user@host can't shove CAPS out of alignment.
|
|
130
|
+
const tgtW = stringWidth(tgt);
|
|
131
|
+
const tgtCol = tgtW > 28 ? truncateToWidth(tgt, 28) : tgt + ' '.repeat(28 - tgtW);
|
|
125
132
|
const mark = h.enrolled ? '' : chalk.gray(' ·available');
|
|
126
|
-
console.log(h.name.padEnd(20) + h.source.padEnd(13) +
|
|
133
|
+
console.log(h.name.padEnd(20) + h.source.padEnd(13) + tgtCol + (h.caps?.join(',') ?? '') + mark);
|
|
127
134
|
}
|
|
128
135
|
}
|
|
129
136
|
async function doCheck(name) {
|
|
@@ -155,7 +162,10 @@ async function doRemove(name) {
|
|
|
155
162
|
console.log(chalk.green(`Removed ${name}`));
|
|
156
163
|
}
|
|
157
164
|
async function doPs(json) {
|
|
158
|
-
|
|
165
|
+
// Heal any 'running' record whose local follower died (dropped connection,
|
|
166
|
+
// laptop sleep) against the remote `.exit` before listing, so `ps` never shows
|
|
167
|
+
// a finished run stuck at 'running'.
|
|
168
|
+
const tasks = reconcileRunningTasks(listTasks());
|
|
159
169
|
if (json) {
|
|
160
170
|
console.log(JSON.stringify(tasks, null, 2));
|
|
161
171
|
return;
|
|
@@ -164,30 +174,24 @@ async function doPs(json) {
|
|
|
164
174
|
console.log(chalk.gray('No host tasks yet. Dispatch one: agents run <agent> "<task>" --host <name>'));
|
|
165
175
|
return;
|
|
166
176
|
}
|
|
177
|
+
const cols = terminalWidth();
|
|
167
178
|
console.log(chalk.bold('ID').padEnd(11) + chalk.bold('HOST').padEnd(16) + chalk.bold('AGENT').padEnd(10) + chalk.bold('STATUS').padEnd(11) + chalk.bold('PROMPT'));
|
|
168
179
|
for (const t of tasks) {
|
|
169
180
|
const status = t.status === 'completed' ? chalk.green(t.status) : t.status === 'failed' ? chalk.red(t.status) : chalk.yellow(t.status);
|
|
170
|
-
|
|
181
|
+
// Prompt fills the remaining width instead of a fixed 50-char byte slice (98-char rows).
|
|
182
|
+
const promptCol = truncateToWidth(t.prompt, Math.max(12, cols - (11 + 16 + 10 + 11)));
|
|
183
|
+
console.log(t.id.padEnd(11) + t.host.padEnd(16) + t.agent.padEnd(10) + status.padEnd(11) + promptCol);
|
|
171
184
|
}
|
|
172
185
|
}
|
|
173
186
|
async function doLogs(id, follow) {
|
|
174
|
-
const
|
|
175
|
-
if (!
|
|
187
|
+
const res = await showHostTaskLog(id, follow);
|
|
188
|
+
if (!res.found) {
|
|
176
189
|
console.log(chalk.red(`Unknown task "${id}".`));
|
|
177
190
|
process.exitCode = 1;
|
|
178
191
|
return;
|
|
179
192
|
}
|
|
180
|
-
if (
|
|
181
|
-
|
|
182
|
-
process.exitCode = code === -1 ? 1 : code;
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
try {
|
|
186
|
-
process.stdout.write(fs.readFileSync(localLogPath(id), 'utf-8'));
|
|
187
|
-
}
|
|
188
|
-
catch {
|
|
189
|
-
console.log(chalk.gray('(no local log captured for this task)'));
|
|
190
|
-
}
|
|
193
|
+
if (res.exitCode !== undefined)
|
|
194
|
+
process.exitCode = res.exitCode;
|
|
191
195
|
}
|
|
192
196
|
/** Register the `agents hosts` command tree. */
|
|
193
197
|
export function registerHostsCommand(program) {
|
package/dist/commands/import.js
CHANGED
|
@@ -263,7 +263,7 @@ When to use:
|
|
|
263
263
|
pointing at the existing install — nothing is copied or moved (except the
|
|
264
264
|
agent's config dir, which is moved into the version's home). Works for both
|
|
265
265
|
npm-style packages (claude, codex, gemini, opencode, openclaw) and
|
|
266
|
-
installScript-based agents (grok, antigravity, cursor, kiro, goose
|
|
266
|
+
installScript-based agents (grok, antigravity, cursor, kiro, goose).
|
|
267
267
|
`)
|
|
268
268
|
.action(runImport);
|
|
269
269
|
}
|
|
@@ -18,6 +18,8 @@ import { type PluginResourceGroup } from '../lib/plugins.js';
|
|
|
18
18
|
/** Resource kinds the inspect command can drill into. */
|
|
19
19
|
declare const DRILLABLE_KINDS: readonly ["commands", "skills", "hooks", "mcp", "rules", "plugins", "workflows", "subagents"];
|
|
20
20
|
type DrillableKind = typeof DRILLABLE_KINDS[number];
|
|
21
|
+
/** Wrap a separator-joined value list under `prefix`, preserving a hanging indent. */
|
|
22
|
+
export declare function wrapJoined(prefix: string, items: string[], sep: string, width: number): string[];
|
|
21
23
|
interface ResourceItem {
|
|
22
24
|
name: string;
|
|
23
25
|
source: string;
|
package/dist/commands/inspect.js
CHANGED
|
@@ -31,6 +31,7 @@ import { discoverPlugins, discoverPluginsInDir, pluginResourceGroups } from '../
|
|
|
31
31
|
import { PLUGIN_GROUP_COLORS } from './plugins.js';
|
|
32
32
|
import { countSessionsInScope } from '../lib/session/discover.js';
|
|
33
33
|
import { damerauLevenshtein } from '../lib/fuzzy.js';
|
|
34
|
+
import { terminalWidth, truncateToWidth, stringWidth, stripAnsi } from '../lib/session/width.js';
|
|
34
35
|
/** Resource kinds the inspect command can drill into. */
|
|
35
36
|
const DRILLABLE_KINDS = [
|
|
36
37
|
'commands',
|
|
@@ -68,6 +69,44 @@ const SINGULAR_DRILL_ALIASES = {
|
|
|
68
69
|
const CAPABILITY_NAMES = [
|
|
69
70
|
'hooks', 'mcp', 'skills', 'commands', 'subagents', 'plugins', 'workflows', 'rules', 'allowlist',
|
|
70
71
|
];
|
|
72
|
+
/** Wrap a separator-joined value list under `prefix`, preserving a hanging indent. */
|
|
73
|
+
export function wrapJoined(prefix, items, sep, width) {
|
|
74
|
+
if (items.length === 0)
|
|
75
|
+
return [];
|
|
76
|
+
const continuation = ' '.repeat(stringWidth(prefix));
|
|
77
|
+
const fitItem = (item, linePrefix) => {
|
|
78
|
+
const room = Math.max(1, width - stringWidth(linePrefix));
|
|
79
|
+
return stringWidth(item) > room ? truncateToWidth(item, room) : item;
|
|
80
|
+
};
|
|
81
|
+
const lines = [];
|
|
82
|
+
let linePrefix = prefix;
|
|
83
|
+
let line = prefix;
|
|
84
|
+
let hasItem = false;
|
|
85
|
+
for (const raw of items) {
|
|
86
|
+
const item = fitItem(raw, linePrefix);
|
|
87
|
+
const candidate = hasItem ? `${line}${sep}${item}` : `${line}${item}`;
|
|
88
|
+
if (hasItem && stringWidth(candidate) > width) {
|
|
89
|
+
lines.push(line);
|
|
90
|
+
linePrefix = continuation;
|
|
91
|
+
line = continuation + fitItem(raw, linePrefix);
|
|
92
|
+
hasItem = true;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
line = candidate;
|
|
96
|
+
hasItem = true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (hasItem)
|
|
100
|
+
lines.push(line);
|
|
101
|
+
return lines;
|
|
102
|
+
}
|
|
103
|
+
function printWrappedJoined(prefix, items, sep) {
|
|
104
|
+
for (const line of wrapJoined(prefix, items, sep, terminalWidth()))
|
|
105
|
+
console.log(line);
|
|
106
|
+
}
|
|
107
|
+
function truncateValueForPrefix(prefix, value) {
|
|
108
|
+
return truncateToWidth(stripAnsi(value), Math.max(1, terminalWidth() - stringWidth(prefix)));
|
|
109
|
+
}
|
|
71
110
|
// ─── Command registration ────────────────────────────────────────────────────
|
|
72
111
|
export function registerInspectCommand(program) {
|
|
73
112
|
const cmd = addHostOption(program.command('inspect <target>'))
|
|
@@ -425,7 +464,10 @@ function renderRepoSummary(repo, options) {
|
|
|
425
464
|
console.log(` ${'git'.padEnd(10)} ${git.branch}${dirty}${url}`);
|
|
426
465
|
if (git.lastCommit) {
|
|
427
466
|
const rel = git.lastCommit.relative ? ` ${chalk.gray(`(${git.lastCommit.relative})`)}` : '';
|
|
428
|
-
|
|
467
|
+
const prefix = ` ${''.padEnd(10)} ${chalk.gray('last'.padEnd(8))} `;
|
|
468
|
+
const sha = chalk.cyan(git.lastCommit.sha);
|
|
469
|
+
const subjectWidth = Math.max(8, terminalWidth() - stringWidth(prefix) - stringWidth(sha) - 2 - stringWidth(rel));
|
|
470
|
+
console.log(`${prefix}${sha} ${truncateToWidth(git.lastCommit.subject, subjectWidth)}${rel}`);
|
|
429
471
|
}
|
|
430
472
|
if (git.ahead !== null && git.behind !== null && (git.ahead > 0 || git.behind > 0)) {
|
|
431
473
|
sub('sync', `ahead ${git.ahead} ${chalk.gray('·')} behind ${git.behind}`);
|
|
@@ -440,10 +482,12 @@ function renderRepoSummary(repo, options) {
|
|
|
440
482
|
console.log(` ${'manifests'.padEnd(10)} ${manifests.join(', ')}`);
|
|
441
483
|
if (manifest) {
|
|
442
484
|
if (manifest.versions.length > 0) {
|
|
443
|
-
|
|
485
|
+
const prefix = ` ${''.padEnd(10)} ${chalk.gray('versions'.padEnd(8))} `;
|
|
486
|
+
printWrappedJoined(prefix, manifest.versions.map(v => `${v.agent} ${chalk.cyan(v.version)}`), chalk.gray(' · '));
|
|
444
487
|
}
|
|
445
488
|
if (manifest.strategies.length > 0) {
|
|
446
|
-
|
|
489
|
+
const prefix = ` ${''.padEnd(10)} ${chalk.gray('run'.padEnd(8))} `;
|
|
490
|
+
printWrappedJoined(prefix, manifest.strategies.map(s => `${s.agent}:${s.strategy}`), chalk.gray(' · '));
|
|
447
491
|
}
|
|
448
492
|
}
|
|
449
493
|
}
|
|
@@ -551,8 +595,10 @@ async function renderSummary(agent, version, versionHome, options) {
|
|
|
551
595
|
['alias', aliasPath],
|
|
552
596
|
['strategy', strategy],
|
|
553
597
|
];
|
|
554
|
-
for (const [k, v] of rows)
|
|
555
|
-
|
|
598
|
+
for (const [k, v] of rows) {
|
|
599
|
+
const prefix = ` ${k.padEnd(10)} `;
|
|
600
|
+
console.log(prefix + truncateValueForPrefix(prefix, v));
|
|
601
|
+
}
|
|
556
602
|
console.log('\n' + chalk.bold('Capabilities'));
|
|
557
603
|
for (const cap of CAPABILITY_NAMES) {
|
|
558
604
|
const res = capabilities[cap];
|
|
@@ -603,7 +649,8 @@ function renderItemList(header, jsonHead, kind, items, options) {
|
|
|
603
649
|
const tag = chalk.gray(`[${item.source}]`.padEnd(10));
|
|
604
650
|
console.log(` ${tag} ${termLink(chalk.cyan(item.name), item.linkTarget)}`);
|
|
605
651
|
if (item.description) {
|
|
606
|
-
|
|
652
|
+
const prefix = ' ';
|
|
653
|
+
console.log(prefix + chalk.gray(truncateToWidth(item.description, Math.max(1, terminalWidth() - stringWidth(prefix)))));
|
|
607
654
|
}
|
|
608
655
|
if (item.groups)
|
|
609
656
|
printGroupRows(item.groups);
|
|
@@ -1171,7 +1218,7 @@ function readFirstProseLine(p) {
|
|
|
1171
1218
|
}
|
|
1172
1219
|
// ─── OSC-8 + path helpers ────────────────────────────────────────────────────
|
|
1173
1220
|
function termLink(text, filePath) {
|
|
1174
|
-
if (!filePath)
|
|
1221
|
+
if (!filePath || !process.stdout.isTTY)
|
|
1175
1222
|
return text;
|
|
1176
1223
|
const url = `file://${filePath}`;
|
|
1177
1224
|
return `\x1b]8;;${url}\x1b\\${text}\x1b]8;;\x1b\\`;
|