@phnx-labs/agents-cli 1.20.72 → 1.20.74
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 +395 -0
- package/README.md +40 -4
- package/dist/bin/agents +0 -0
- package/dist/commands/activity.d.ts +12 -0
- package/dist/commands/activity.js +68 -0
- package/dist/commands/apply.d.ts +13 -0
- package/dist/commands/apply.js +28 -3
- package/dist/commands/browser.js +1 -1
- package/dist/commands/cloud.js +21 -11
- package/dist/commands/doctor.js +21 -5
- package/dist/commands/events.d.ts +11 -10
- package/dist/commands/events.js +30 -21
- package/dist/commands/exec.d.ts +34 -0
- package/dist/commands/exec.js +337 -39
- package/dist/commands/export.d.ts +11 -0
- package/dist/commands/export.js +215 -0
- package/dist/commands/feed.d.ts +13 -0
- package/dist/commands/feed.js +130 -29
- package/dist/commands/hq.d.ts +2 -0
- package/dist/commands/hq.js +58 -0
- package/dist/commands/import.js +51 -3
- package/dist/commands/lease.d.ts +19 -0
- package/dist/commands/lease.js +138 -11
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +123 -0
- package/dist/commands/logs.js +1 -1
- package/dist/commands/mine.d.ts +27 -0
- package/dist/commands/mine.js +207 -0
- package/dist/commands/monitors.js +79 -49
- package/dist/commands/plugins.js +1 -0
- package/dist/commands/profiles.js +158 -0
- package/dist/commands/repo.js +2 -60
- package/dist/commands/resources.d.ts +5 -0
- package/dist/commands/resources.js +95 -0
- package/dist/commands/routines.d.ts +2 -0
- package/dist/commands/routines.js +139 -37
- package/dist/commands/secrets.js +234 -47
- package/dist/commands/send.d.ts +15 -0
- package/dist/commands/send.js +62 -0
- package/dist/commands/sessions-tail.d.ts +1 -0
- package/dist/commands/sessions-tail.js +20 -9
- package/dist/commands/sessions.js +18 -3
- package/dist/commands/setup-mine.d.ts +18 -0
- package/dist/commands/setup-mine.js +106 -0
- package/dist/commands/setup-share.js +2 -2
- package/dist/commands/setup.js +9 -2
- package/dist/commands/share.d.ts +8 -1
- package/dist/commands/share.js +122 -37
- package/dist/commands/ssh.d.ts +9 -0
- package/dist/commands/ssh.js +196 -26
- package/dist/commands/sync.js +1 -1
- package/dist/commands/teams.js +11 -2
- package/dist/commands/versions.js +36 -5
- package/dist/commands/view.js +75 -27
- package/dist/index.js +80 -10
- package/dist/lib/activity.d.ts +124 -0
- package/dist/lib/activity.js +542 -0
- package/dist/lib/agents.d.ts +35 -0
- package/dist/lib/agents.js +79 -6
- package/dist/lib/artifact-actions.d.ts +1 -1
- package/dist/lib/artifact-actions.js +1 -1
- package/dist/lib/ask-classifier.d.ts +2 -1
- package/dist/lib/ask-classifier.js +3 -3
- package/dist/lib/auth-health.d.ts +32 -4
- package/dist/lib/auth-health.js +40 -1
- package/dist/lib/auto-pull.js +8 -1
- package/dist/lib/brand.d.ts +40 -0
- package/dist/lib/brand.js +122 -0
- package/dist/lib/browser/drivers/ssh.js +4 -7
- package/dist/lib/browser/service.js +6 -8
- package/dist/lib/channels/providers/index.d.ts +2 -0
- package/dist/lib/channels/providers/index.js +20 -0
- package/dist/lib/channels/providers/mailbox.d.ts +2 -0
- package/dist/lib/channels/providers/mailbox.js +26 -0
- package/dist/lib/channels/providers/openclaw-telegram.d.ts +2 -0
- package/dist/lib/channels/providers/openclaw-telegram.js +34 -0
- package/dist/lib/channels/providers/rush.d.ts +6 -0
- package/dist/lib/channels/providers/rush.js +55 -0
- package/dist/lib/channels/registry.d.ts +42 -0
- package/dist/lib/channels/registry.js +20 -0
- package/dist/lib/channels/resolve.d.ts +11 -0
- package/dist/lib/channels/resolve.js +12 -0
- package/dist/lib/cli-resources.d.ts +18 -0
- package/dist/lib/cli-resources.js +53 -4
- package/dist/lib/cloud/codex.js +6 -3
- package/dist/lib/cloud/factory.d.ts +1 -0
- package/dist/lib/cloud/factory.js +10 -4
- package/dist/lib/cloud/rush.d.ts +3 -1
- package/dist/lib/cloud/rush.js +6 -1
- package/dist/lib/cloud/types.d.ts +2 -0
- package/dist/lib/codex-home.d.ts +35 -0
- package/dist/lib/codex-home.js +136 -0
- package/dist/lib/config-transfer.d.ts +16 -0
- package/dist/lib/config-transfer.js +56 -0
- package/dist/lib/crabbox/cli.d.ts +44 -0
- package/dist/lib/crabbox/cli.js +120 -20
- package/dist/lib/crabbox/lease.d.ts +52 -1
- package/dist/lib/crabbox/lease.js +117 -16
- package/dist/lib/crabbox/progress.d.ts +31 -0
- package/dist/lib/crabbox/progress.js +76 -0
- package/dist/lib/crabbox/runtimes.d.ts +4 -0
- package/dist/lib/crabbox/runtimes.js +33 -8
- package/dist/lib/crabbox/setup-copy.d.ts +87 -0
- package/dist/lib/crabbox/setup-copy.js +127 -0
- package/dist/lib/daemon.d.ts +5 -5
- package/dist/lib/daemon.js +9 -9
- package/dist/lib/drive-sync.js +6 -3
- package/dist/lib/event-stream.d.ts +36 -0
- package/dist/lib/event-stream.js +68 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +11 -0
- package/dist/lib/exec.js +23 -2
- package/dist/lib/export.d.ts +72 -0
- package/dist/lib/export.js +269 -0
- package/dist/lib/feed-policy.d.ts +1 -1
- package/dist/lib/feed-policy.js +14 -9
- package/dist/lib/feed-ranking.d.ts +32 -0
- package/dist/lib/feed-ranking.js +224 -0
- package/dist/lib/feed.d.ts +35 -2
- package/dist/lib/feed.js +66 -3
- package/dist/lib/fleet/auth-sync.d.ts +53 -0
- package/dist/lib/fleet/auth-sync.js +92 -0
- package/dist/lib/fleet/remote-login.d.ts +187 -0
- package/dist/lib/fleet/remote-login.js +556 -0
- package/dist/lib/format.d.ts +30 -3
- package/dist/lib/format.js +34 -5
- package/dist/lib/fs-atomic.d.ts +7 -2
- package/dist/lib/fs-atomic.js +8 -12
- package/dist/lib/git.d.ts +16 -0
- package/dist/lib/git.js +79 -2
- package/dist/lib/heal.js +11 -3
- package/dist/lib/hosts/progress.d.ts +28 -10
- package/dist/lib/hosts/progress.js +79 -22
- package/dist/lib/hosts/remote-cmd.js +4 -0
- package/dist/lib/hq/floor.d.ts +87 -0
- package/dist/lib/hq/floor.js +226 -0
- package/dist/lib/import.d.ts +23 -0
- package/dist/lib/import.js +74 -2
- package/dist/lib/isolation-boundary-report.d.ts +10 -0
- package/dist/lib/isolation-boundary-report.js +35 -0
- package/dist/lib/menubar/install-menubar.js +14 -20
- package/dist/lib/notify.d.ts +1 -0
- package/dist/lib/notify.js +3 -3
- package/dist/lib/open-url.d.ts +2 -0
- package/dist/lib/open-url.js +19 -0
- package/dist/lib/openclaw-keychain.d.ts +56 -0
- package/dist/lib/openclaw-keychain.js +236 -0
- package/dist/lib/overdue.js +10 -0
- package/dist/lib/permissions.d.ts +44 -1
- package/dist/lib/permissions.js +284 -7
- package/dist/lib/project-launch.d.ts +6 -12
- package/dist/lib/project-launch.js +13 -228
- package/dist/lib/project-resources.d.ts +7 -0
- package/dist/lib/project-resources.js +306 -0
- package/dist/lib/pty-client.d.ts +27 -0
- package/dist/lib/pty-client.js +136 -10
- package/dist/lib/refresh.js +14 -10
- package/dist/lib/resource-profiles.d.ts +26 -0
- package/dist/lib/resource-profiles.js +157 -0
- package/dist/lib/resources/permissions.js +7 -1
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/resources.d.ts +1 -1
- package/dist/lib/resources.js +32 -3
- package/dist/lib/routines.d.ts +11 -0
- package/dist/lib/routines.js +56 -15
- package/dist/lib/runner.d.ts +1 -0
- package/dist/lib/runner.js +66 -5
- package/dist/lib/secrets/bundles.d.ts +16 -3
- package/dist/lib/secrets/bundles.js +206 -37
- package/dist/lib/secrets/icloud-import.d.ts +2 -2
- package/dist/lib/secrets/icloud-import.js +9 -6
- package/dist/lib/secrets/mcp.js +1 -1
- package/dist/lib/secrets/vault-age-helper.d.ts +1 -0
- package/dist/lib/secrets/vault-age-helper.js +34 -0
- package/dist/lib/secrets/vault.d.ts +49 -0
- package/dist/lib/secrets/vault.js +397 -0
- package/dist/lib/self-heal/checks/path.js +3 -1
- package/dist/lib/self-heal/checks/shadowing.js +10 -2
- package/dist/lib/self-heal/checks/shims.js +19 -11
- package/dist/lib/session/cloud.d.ts +2 -2
- package/dist/lib/session/cloud.js +2 -2
- package/dist/lib/session/db.d.ts +4 -0
- package/dist/lib/session/db.js +44 -7
- package/dist/lib/session/discover.d.ts +2 -0
- package/dist/lib/session/discover.js +114 -3
- package/dist/lib/session/stream-render.d.ts +3 -0
- package/dist/lib/session/stream-render.js +130 -0
- package/dist/lib/session/types.d.ts +6 -0
- package/dist/lib/share/analytics.d.ts +13 -0
- package/dist/lib/share/analytics.js +45 -0
- package/dist/lib/share/config.d.ts +19 -5
- package/dist/lib/share/config.js +44 -8
- package/dist/lib/share/provision.d.ts +58 -6
- package/dist/lib/share/provision.js +97 -22
- package/dist/lib/share/publish.d.ts +36 -13
- package/dist/lib/share/publish.js +55 -8
- package/dist/lib/share/worker-template.js +83 -17
- package/dist/lib/shims.d.ts +35 -0
- package/dist/lib/shims.js +159 -13
- package/dist/lib/ssh-exec.d.ts +14 -0
- package/dist/lib/ssh-exec.js +57 -0
- package/dist/lib/staleness/detectors/hooks.js +25 -1
- package/dist/lib/staleness/detectors/permissions.js +66 -0
- package/dist/lib/staleness/detectors/workflows.js +20 -0
- package/dist/lib/staleness/writers/hooks.js +58 -4
- package/dist/lib/staleness/writers/permissions.js +2 -2
- package/dist/lib/staleness/writers/skills.js +1 -1
- package/dist/lib/staleness/writers/sources.d.ts +10 -1
- package/dist/lib/staleness/writers/sources.js +68 -1
- package/dist/lib/star-nudge.d.ts +45 -0
- package/dist/lib/star-nudge.js +91 -0
- package/dist/lib/startup/command-registry.d.ts +7 -1
- package/dist/lib/startup/command-registry.js +19 -3
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.js +17 -4
- package/dist/lib/subagents-registry.d.ts +2 -0
- package/dist/lib/subagents-registry.js +3 -0
- package/dist/lib/teams/parsers.js +214 -6
- package/dist/lib/teams/supervisor.d.ts +7 -0
- package/dist/lib/teams/supervisor.js +2 -1
- package/dist/lib/template.d.ts +1 -1
- package/dist/lib/template.js +1 -1
- package/dist/lib/triggers/webhook.js +36 -3
- package/dist/lib/types.d.ts +84 -0
- package/dist/lib/uninstall.js +1 -45
- package/dist/lib/version.d.ts +40 -0
- package/dist/lib/version.js +94 -16
- package/dist/lib/versions.d.ts +24 -0
- package/dist/lib/versions.js +269 -78
- package/dist/lib/workflows.d.ts +2 -0
- package/dist/lib/workflows.js +88 -0
- package/package.json +2 -1
- package/dist/commands/daemon.d.ts +0 -10
- package/dist/commands/daemon.js +0 -121
package/dist/commands/ssh.js
CHANGED
|
@@ -19,6 +19,7 @@ import { getCliVersion } from '../lib/version.js';
|
|
|
19
19
|
import { readAndResolveBundleEnv, isHeadlessSecretsContext } from '../lib/secrets/bundles.js';
|
|
20
20
|
import { machineId } from '../lib/session/sync/config.js';
|
|
21
21
|
import { registerFleetCaptureCommand } from './fleet-capture.js';
|
|
22
|
+
import { registerFleetApplyAlias } from './apply.js';
|
|
22
23
|
import { addIgnored, getDevice, loadDevices, loadIgnored, removeDevice, removeIgnored, upsertDevice, } from '../lib/devices/registry.js';
|
|
23
24
|
import { addControlToken } from '../lib/serve/token.js';
|
|
24
25
|
import { DEFAULT_SERVE_PORT } from '../lib/serve/server.js';
|
|
@@ -40,7 +41,10 @@ import { checkAllClis } from '../lib/teams/agents.js';
|
|
|
40
41
|
import { buildRemoteAgentsInvocation } from '../lib/hosts/remote-cmd.js';
|
|
41
42
|
import { sshExecAsync } from '../lib/ssh-exec.js';
|
|
42
43
|
import { ALL_AGENT_IDS } from '../lib/agents.js';
|
|
43
|
-
import {
|
|
44
|
+
import { crabboxList, crabboxFind, crabboxSshArgv } from '../lib/crabbox/cli.js';
|
|
45
|
+
import { boxAddress, boxStatus, fmtIdleShort, fmtExpiresShort } from './lease.js';
|
|
46
|
+
import { authCellColor, formatCheckedAge, isDeadVerdict, probeLocalFleetAuth, readAuthHealthCache, summarizeHostAuth, summarizeVerdicts, verdictColor, verdictLabel, writeFleetAuthRows, } from '../lib/auth-health.js';
|
|
47
|
+
import { runFleetLogin } from '../lib/fleet/remote-login.js';
|
|
44
48
|
/** One-line summary of a device for `list`. `isSelf` marks the machine this
|
|
45
49
|
* command is running on so it stands out from the rest of the tailnet. */
|
|
46
50
|
function deviceSummary(d, isSelf = false) {
|
|
@@ -130,6 +134,74 @@ function renderDeviceTable(reg, names, self, statsMap, full = false) {
|
|
|
130
134
|
}
|
|
131
135
|
return lines;
|
|
132
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Live "Leased boxes" section for `agents devices` (F4, RUSH-1923), computed
|
|
139
|
+
* from `crabboxList()` — these are ephemeral crabbox leases, NEVER written into
|
|
140
|
+
* the device registry. Returns [] when crabbox is unavailable / has no creds /
|
|
141
|
+
* reports no boxes, so the section is simply omitted. `nowSecs` is injected so
|
|
142
|
+
* the row formatting is deterministic in tests.
|
|
143
|
+
*/
|
|
144
|
+
export function renderLeasedBoxesSection(boxes, nowSecs) {
|
|
145
|
+
if (boxes.length === 0)
|
|
146
|
+
return [];
|
|
147
|
+
const lines = [];
|
|
148
|
+
lines.push('');
|
|
149
|
+
lines.push(chalk.bold('Leased boxes') + chalk.gray(' (ephemeral · via crabbox)'));
|
|
150
|
+
lines.push(' ' +
|
|
151
|
+
chalk.gray('box'.padEnd(16)) +
|
|
152
|
+
chalk.gray('class'.padEnd(10)) +
|
|
153
|
+
chalk.gray('address'.padEnd(24)) +
|
|
154
|
+
chalk.gray('status'.padEnd(9)) +
|
|
155
|
+
chalk.gray('idle'.padEnd(12)) +
|
|
156
|
+
chalk.gray('expires'));
|
|
157
|
+
for (const b of boxes) {
|
|
158
|
+
const addr = boxAddress(b) ?? '—';
|
|
159
|
+
lines.push(' ' +
|
|
160
|
+
chalk.cyan(b.slug.padEnd(16)) +
|
|
161
|
+
(b.class ?? '?').padEnd(10) +
|
|
162
|
+
addr.padEnd(24) +
|
|
163
|
+
boxStatus(b).padEnd(9) +
|
|
164
|
+
chalk.gray(fmtIdleShort(b, nowSecs).padEnd(12)) +
|
|
165
|
+
chalk.gray(fmtExpiresShort(b, nowSecs)));
|
|
166
|
+
}
|
|
167
|
+
lines.push(chalk.gray(' Reuse a box with `agents run --box <slug>` · stop with `agents lease stop <slug>`'));
|
|
168
|
+
return lines;
|
|
169
|
+
}
|
|
170
|
+
/** The leased-box rows for the devices list, or [] when crabbox can't be read. */
|
|
171
|
+
function loadLeasedBoxesSection() {
|
|
172
|
+
try {
|
|
173
|
+
const boxes = crabboxList({ secretsBundle: process.env.AGENTS_LEASE_SECRETS_BUNDLE, timeoutMs: 5000 });
|
|
174
|
+
return renderLeasedBoxesSection(boxes, Math.floor(Date.now() / 1000));
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return []; // crabbox not installed / no provider creds — omit the section
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* `agents ssh <slug>` targeting a leased crabbox box. crabbox provisions a
|
|
182
|
+
* per-lease identity key, so we ssh via crabbox's OWN emitted invocation
|
|
183
|
+
* (`crabboxSshArgv`) — a raw `ssh crabbox@ip` fails publickey. Returns false when
|
|
184
|
+
* `name` is not a known crabbox slug so the caller can fall through to the normal
|
|
185
|
+
* "Unknown device" error.
|
|
186
|
+
*/
|
|
187
|
+
function trySshLeasedBox(name, cmd) {
|
|
188
|
+
let box;
|
|
189
|
+
try {
|
|
190
|
+
box = crabboxFind(name, { secretsBundle: process.env.AGENTS_LEASE_SECRETS_BUNDLE, timeoutMs: 5000 });
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return false; // crabbox unavailable — not a leased-box target
|
|
194
|
+
}
|
|
195
|
+
if (!box)
|
|
196
|
+
return false;
|
|
197
|
+
const sshArgv = crabboxSshArgv(name, { secretsBundle: process.env.AGENTS_LEASE_SECRETS_BUNDLE, timeoutMs: 8000 });
|
|
198
|
+
if (!sshArgv) {
|
|
199
|
+
console.error(chalk.red(`Leased box '${name}' is not reachable yet (status: ${boxStatus(box)}).`));
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
const res = spawnSync(sshArgv[0], [...sshArgv.slice(1), ...cmd], { stdio: 'inherit' });
|
|
203
|
+
process.exit(res.status ?? 1);
|
|
204
|
+
}
|
|
133
205
|
/** Resolve a device or exit with a clear error. */
|
|
134
206
|
async function mustGetDevice(name) {
|
|
135
207
|
const d = await getDevice(name);
|
|
@@ -423,17 +495,28 @@ async function runFleetPing(opts) {
|
|
|
423
495
|
if (opts.strict && anyBad)
|
|
424
496
|
process.exitCode = 1;
|
|
425
497
|
}
|
|
426
|
-
/**
|
|
498
|
+
/** Resolve an {@link AuthCellColor} to a chalk painter. Single map for cells + labels. */
|
|
499
|
+
const CELL_PAINT = {
|
|
500
|
+
green: chalk.green,
|
|
501
|
+
yellow: chalk.yellow,
|
|
502
|
+
red: chalk.red,
|
|
503
|
+
gray: chalk.gray,
|
|
504
|
+
dim: chalk.dim,
|
|
505
|
+
};
|
|
506
|
+
/**
|
|
507
|
+
* Color a per-host×agent cell. The numerator counts accounts that are usable
|
|
508
|
+
* right now — live-verified PLUS signed-in-but-unverifiable (codex/grok) — over
|
|
509
|
+
* the total, so a logged-in codex fleet reads "1/1", not a scary "0/1". Color is
|
|
510
|
+
* the shared {@link authCellColor}: red only for revoked (re-login), yellow for
|
|
511
|
+
* soft/expired (self-refreshes), gray for present-but-unverifiable, green when
|
|
512
|
+
* all live.
|
|
513
|
+
*/
|
|
427
514
|
function authCell(summary, width) {
|
|
428
515
|
if (summary.total === 0)
|
|
429
516
|
return chalk.dim('·'.padEnd(width));
|
|
430
|
-
const
|
|
431
|
-
const padded =
|
|
432
|
-
|
|
433
|
-
return chalk.red(padded);
|
|
434
|
-
if (summary.warn > 0)
|
|
435
|
-
return chalk.yellow(padded);
|
|
436
|
-
return chalk.green(padded);
|
|
517
|
+
const ok = summary.live + summary.present;
|
|
518
|
+
const padded = `${ok}/${summary.total}`.padEnd(width);
|
|
519
|
+
return CELL_PAINT[authCellColor(summary)](padded);
|
|
437
520
|
}
|
|
438
521
|
/** Render the fleet auth matrix (device rows × agent columns) plus an optional per-account breakdown. */
|
|
439
522
|
function renderAuthMatrix(results, opts) {
|
|
@@ -466,7 +549,7 @@ function renderAuthMatrix(results, opts) {
|
|
|
466
549
|
lines.push(` ${r.host.padEnd(nameW)} ${cells.join(' ')}${note}`);
|
|
467
550
|
}
|
|
468
551
|
lines.push('');
|
|
469
|
-
lines.push(chalk.gray(' cell =
|
|
552
|
+
lines.push(chalk.gray(' cell = signed-in/total accounts · green live · gray signed-in (unverifiable: codex/grok) · yellow expired (self-refreshes) · red revoked (re-login)'));
|
|
470
553
|
if (opts?.verbose) {
|
|
471
554
|
lines.push('');
|
|
472
555
|
lines.push(chalk.bold('Accounts'));
|
|
@@ -475,9 +558,7 @@ function renderAuthMatrix(results, opts) {
|
|
|
475
558
|
continue;
|
|
476
559
|
for (const row of r.rows.slice().sort((x, y) => (x.agent + x.version).localeCompare(y.agent + y.version))) {
|
|
477
560
|
const v = row.health.verdict;
|
|
478
|
-
const label = v
|
|
479
|
-
: (v === 'revoked' || v === 'expired') ? chalk.red(verdictLabel(v))
|
|
480
|
-
: chalk.yellow(verdictLabel(v));
|
|
561
|
+
const label = CELL_PAINT[verdictColor(v)](verdictLabel(v));
|
|
481
562
|
const acctRaw = row.account ?? '—';
|
|
482
563
|
const acct = row.account ? chalk.cyan(acctRaw.padEnd(28)) : chalk.dim(acctRaw.padEnd(28));
|
|
483
564
|
const detail = row.health.detail ? chalk.dim(` ${row.health.detail}`) : '';
|
|
@@ -488,6 +569,46 @@ function renderAuthMatrix(results, opts) {
|
|
|
488
569
|
}
|
|
489
570
|
return lines;
|
|
490
571
|
}
|
|
572
|
+
/** Render the final `fleet login` result summary in the house auth-matrix style. */
|
|
573
|
+
function renderLoginMatrix(results) {
|
|
574
|
+
const lines = [chalk.bold('Fleet login')];
|
|
575
|
+
if (results.length === 0) {
|
|
576
|
+
lines.push(chalk.gray(' Nothing pending — every requested account is already logged in.'));
|
|
577
|
+
return lines;
|
|
578
|
+
}
|
|
579
|
+
const nameW = Math.max(6, ...results.map((r) => `${r.agent}@${r.device}`.length));
|
|
580
|
+
for (const r of results) {
|
|
581
|
+
const who = `${r.agent}@${r.device}`.padEnd(nameW);
|
|
582
|
+
let badge;
|
|
583
|
+
switch (r.state) {
|
|
584
|
+
case 'authorized':
|
|
585
|
+
badge = chalk.green('authorized');
|
|
586
|
+
break;
|
|
587
|
+
case 'ready':
|
|
588
|
+
badge = chalk.cyan('code ready');
|
|
589
|
+
break;
|
|
590
|
+
case 'driving':
|
|
591
|
+
badge = chalk.yellow('driving');
|
|
592
|
+
break;
|
|
593
|
+
case 'skipped':
|
|
594
|
+
badge = chalk.gray('not remotable');
|
|
595
|
+
break;
|
|
596
|
+
case 'error':
|
|
597
|
+
badge = chalk.red('error');
|
|
598
|
+
break;
|
|
599
|
+
default:
|
|
600
|
+
badge = chalk.gray(r.state);
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
const note = r.detail ? chalk.dim(` ${r.detail}`) : (r.reason && !r.remotable ? chalk.dim(` ${r.reason}`) : '');
|
|
604
|
+
lines.push(` ${who} ${badge}${note}`);
|
|
605
|
+
}
|
|
606
|
+
const auth = results.filter((r) => r.state === 'authorized').length;
|
|
607
|
+
const remotable = results.filter((r) => r.remotable).length;
|
|
608
|
+
lines.push('');
|
|
609
|
+
lines.push(chalk.gray(` ${auth}/${remotable} remotable logins authorized`));
|
|
610
|
+
return lines;
|
|
611
|
+
}
|
|
491
612
|
/** Register the `agents devices` command tree (also aliased as `fleet`). */
|
|
492
613
|
function registerDevicesCommands(program) {
|
|
493
614
|
const devicesCmd = program
|
|
@@ -529,6 +650,9 @@ Typical workflow:
|
|
|
529
650
|
});
|
|
530
651
|
// `agents fleet capture` — snapshot live state into agents.yaml fleet:.
|
|
531
652
|
registerFleetCaptureCommand(devicesCmd);
|
|
653
|
+
// `agents fleet apply` — same reconcile engine as the top-level `agents apply`,
|
|
654
|
+
// surfaced under the fleet tree for discoverability.
|
|
655
|
+
registerFleetApplyAlias(devicesCmd);
|
|
532
656
|
devicesCmd
|
|
533
657
|
.command('register <name>')
|
|
534
658
|
.description('Register a discovered (pending) node by name — used by the menu-bar "NEW DEVICES → Register" action.')
|
|
@@ -576,16 +700,7 @@ Typical workflow:
|
|
|
576
700
|
}
|
|
577
701
|
console.log(chalk.green(`No longer ignoring '${name}'`) + chalk.gray(' — run `agents devices sync` to register it.'));
|
|
578
702
|
});
|
|
579
|
-
|
|
580
|
-
.command('list')
|
|
581
|
-
.alias('ls')
|
|
582
|
-
.description('List registered devices with platform, address, reachability, and live resource headroom.')
|
|
583
|
-
.option('--json', 'output the registry as a JSON array (for scripts and hooks)')
|
|
584
|
-
.option('--no-stats', 'skip the live resource probe (instant; names/addresses only)')
|
|
585
|
-
.option('--refresh', 'force a live probe of every device, bypassing the cache')
|
|
586
|
-
.option('--live', 'alias of --refresh (shorter to type)')
|
|
587
|
-
.option('-f, --full', 'full mode: add per-device core count and free/total memory')
|
|
588
|
-
.action(async (opts) => {
|
|
703
|
+
const runList = async (opts = {}) => {
|
|
589
704
|
const reg = await loadDevices();
|
|
590
705
|
const names = Object.keys(reg).sort();
|
|
591
706
|
if (opts.json) {
|
|
@@ -629,7 +744,26 @@ Typical workflow:
|
|
|
629
744
|
if (freshness?.servedFromCache && freshness.oldestFetchedAt != null) {
|
|
630
745
|
console.log(chalk.gray(` updated ${formatCheckedAge(freshness.oldestFetchedAt)} — pass --refresh (--live) for a live probe`));
|
|
631
746
|
}
|
|
632
|
-
|
|
747
|
+
// Ephemeral crabbox leases live alongside the registered fleet but are never
|
|
748
|
+
// written into the registry — surface them as their own live section. This is
|
|
749
|
+
// a live provider call, so honor --no-stats (the explicit "instant, no probes"
|
|
750
|
+
// opt-out) and bound it so a slow provider can't hang `agents devices`.
|
|
751
|
+
if (opts.stats !== false) {
|
|
752
|
+
for (const line of loadLeasedBoxesSection())
|
|
753
|
+
console.log(line);
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
devicesCmd.action(runList);
|
|
757
|
+
devicesCmd
|
|
758
|
+
.command('list')
|
|
759
|
+
.alias('ls')
|
|
760
|
+
.description('List registered devices with platform, address, reachability, and live resource headroom.')
|
|
761
|
+
.option('--json', 'output the registry as a JSON array (for scripts and hooks)')
|
|
762
|
+
.option('--no-stats', 'skip the live resource probe (instant; names/addresses only)')
|
|
763
|
+
.option('--refresh', 'force a live probe of every device, bypassing the cache')
|
|
764
|
+
.option('--live', 'alias of --refresh (shorter to type)')
|
|
765
|
+
.option('-f, --full', 'full mode: add per-device core count and free/total memory')
|
|
766
|
+
.action(runList);
|
|
633
767
|
devicesCmd
|
|
634
768
|
.command('status')
|
|
635
769
|
.description('Show fleet health: warnings rollup, per-device sync drift, CLI readiness, version skew, and resource headroom.')
|
|
@@ -648,8 +782,41 @@ Typical workflow:
|
|
|
648
782
|
.option('--local', 'probe only this host (used internally for fan-out)')
|
|
649
783
|
.option('--verbose', 'show a per-account breakdown, not just the per-host rollup')
|
|
650
784
|
.option('--strict', 'exit non-zero when any account is revoked (expired is soft — it self-refreshes)')
|
|
785
|
+
.action(async (opts, cmd) => {
|
|
786
|
+
// The root program also defines a global `--verbose` (startup self-heal
|
|
787
|
+
// detail), and commander binds a shared long flag to the program, not the
|
|
788
|
+
// leaf — so `fleet ping --verbose` never set opts.verbose and the
|
|
789
|
+
// per-account breakdown was silently unreachable. Read the effective value
|
|
790
|
+
// from the merged globals so the flag works at either level.
|
|
791
|
+
const verbose = opts.verbose ?? Boolean(cmd.optsWithGlobals().verbose);
|
|
792
|
+
await runFleetPing({ ...opts, verbose });
|
|
793
|
+
});
|
|
794
|
+
devicesCmd
|
|
795
|
+
.command('login')
|
|
796
|
+
.description('Log agent CLIs into fleet boxes over SSH: drive each box\'s device-code OAuth, scrape the URL + code, and surface every pending login in one local browser page. Default drives all codes at once; --interactive walks one box at a time (codes requested just-in-time so they don\'t expire).')
|
|
797
|
+
.option('--agents <csv>', 'only these agents (comma-separated); default: every agent with a device-code flow')
|
|
798
|
+
.option('--devices <csv>', 'only these devices (comma-separated); default: every online box')
|
|
799
|
+
.option('--all', 'target every device-code pair regardless of cached login state (cold cache / forced re-login)')
|
|
800
|
+
.option('--interactive', 'guided one-box-at-a-time wizard (codes requested just-in-time)')
|
|
801
|
+
.option('--json', 'output the final result matrix as JSON')
|
|
651
802
|
.action(async (opts) => {
|
|
652
|
-
|
|
803
|
+
const csv = (s) => (s ? s.split(',').map((x) => x.trim()).filter(Boolean) : undefined);
|
|
804
|
+
const results = await runFleetLogin({
|
|
805
|
+
agents: csv(opts.agents),
|
|
806
|
+
devices: csv(opts.devices),
|
|
807
|
+
all: opts.all,
|
|
808
|
+
interactive: opts.interactive,
|
|
809
|
+
json: opts.json,
|
|
810
|
+
});
|
|
811
|
+
if (opts.json) {
|
|
812
|
+
console.log(JSON.stringify(results, null, 2));
|
|
813
|
+
}
|
|
814
|
+
else {
|
|
815
|
+
for (const line of renderLoginMatrix(results))
|
|
816
|
+
console.log(line);
|
|
817
|
+
}
|
|
818
|
+
if (results.some((r) => r.remotable && r.state !== 'authorized'))
|
|
819
|
+
process.exitCode = 1;
|
|
653
820
|
});
|
|
654
821
|
devicesCmd
|
|
655
822
|
.command('show <name>')
|
|
@@ -849,6 +1016,9 @@ secrets bundle via an askpass shim — the password never touches argv.
|
|
|
849
1016
|
// A bare unregistered alias still errors as "Unknown device".
|
|
850
1017
|
const device = resolveDeviceTarget(name, await loadDevices());
|
|
851
1018
|
if (!device) {
|
|
1019
|
+
// Not a registered device — it may be a leased crabbox box slug. ssh into
|
|
1020
|
+
// it directly (crabbox@<tailnet|ip>:2222) before giving up.
|
|
1021
|
+
trySshLeasedBox(name, cmd); // exits the process on a match
|
|
852
1022
|
console.error(chalk.red(`Unknown device '${name}'. See 'agents devices list'.`));
|
|
853
1023
|
process.exit(1);
|
|
854
1024
|
}
|
|
@@ -907,7 +1077,7 @@ async function runAskpass() {
|
|
|
907
1077
|
process.exit(1);
|
|
908
1078
|
}
|
|
909
1079
|
try {
|
|
910
|
-
const { env } = readAndResolveBundleEnv(bundle, { caller: 'agents ssh', agentOnly: isHeadlessSecretsContext() });
|
|
1080
|
+
const { env } = readAndResolveBundleEnv(bundle, { caller: 'agents ssh', keys: [key], keyMode: 'storage', agentOnly: isHeadlessSecretsContext() });
|
|
911
1081
|
const value = env[key];
|
|
912
1082
|
if (value === undefined) {
|
|
913
1083
|
console.error(`askpass: key '${key}' not found in bundle '${bundle}'`);
|
package/dist/commands/sync.js
CHANGED
|
@@ -544,7 +544,7 @@ function runLaunchMode(agent, version, cwd, quiet) {
|
|
|
544
544
|
if (result.rulesCompiled)
|
|
545
545
|
bits.push('rules');
|
|
546
546
|
if (result.workspaceLinks > 0)
|
|
547
|
-
bits.push(`${result.workspaceLinks}
|
|
547
|
+
bits.push(`${result.workspaceLinks} project resource(s)`);
|
|
548
548
|
const mpCount = Object.keys(result.marketplaces).length;
|
|
549
549
|
if (mpCount > 0) {
|
|
550
550
|
const pluginCount = Object.values(result.marketplaces).reduce((acc, names) => acc + names.length, 0);
|
package/dist/commands/teams.js
CHANGED
|
@@ -7,6 +7,8 @@ import { AgentManager, AgentStatus, checkCliSignedIn, collectTeamsDoctorData, ge
|
|
|
7
7
|
import { mailboxDir, enqueue } from '../lib/mailbox.js';
|
|
8
8
|
import { resolveProvider } from '../lib/cloud/registry.js';
|
|
9
9
|
import { emit } from '../lib/events.js';
|
|
10
|
+
import { maybeShowStarNudge } from '../lib/star-nudge.js';
|
|
11
|
+
import { shareRuntimeEnv } from '../lib/share/config.js';
|
|
10
12
|
import { runSupervisor } from '../lib/teams/supervisor.js';
|
|
11
13
|
import { debug } from '../lib/teams/debug.js';
|
|
12
14
|
import { runPrWatch, DEFAULT_MAX_WAVES, } from '../lib/teams/pr-watch.js';
|
|
@@ -49,8 +51,6 @@ const VALID_AGENTS = Object.keys(AGENT_NAMES);
|
|
|
49
51
|
const VALID_MODES = ['plan', 'edit', 'auto', 'skip', 'full'];
|
|
50
52
|
const VALID_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max', 'auto'];
|
|
51
53
|
const VALID_CLOUD_PROVIDERS = ['rush', 'codex', 'factory'];
|
|
52
|
-
// Auto-enable JSON mode when piped / not a TTY so AI agent consumers get
|
|
53
|
-
// parseable output by default.
|
|
54
54
|
function statusColor(status) {
|
|
55
55
|
switch (status) {
|
|
56
56
|
case 'pending': return chalk.blue;
|
|
@@ -1352,6 +1352,7 @@ export function registerTeamsCommands(program) {
|
|
|
1352
1352
|
repo: opts.repo,
|
|
1353
1353
|
branch: opts.branch,
|
|
1354
1354
|
model: a.model ?? undefined,
|
|
1355
|
+
env: shareRuntimeEnv({ agentOnly: true }),
|
|
1355
1356
|
};
|
|
1356
1357
|
const cloudTask = await prov.dispatch(dispatchOpts);
|
|
1357
1358
|
return { cloudSessionId: cloudTask.id };
|
|
@@ -1369,6 +1370,7 @@ export function registerTeamsCommands(program) {
|
|
|
1369
1370
|
repo: opts.repo,
|
|
1370
1371
|
branch: opts.branch,
|
|
1371
1372
|
model: opts.model,
|
|
1373
|
+
env: shareRuntimeEnv({ agentOnly: true }),
|
|
1372
1374
|
};
|
|
1373
1375
|
try {
|
|
1374
1376
|
const cloudTask = await prov.dispatch(dispatchOpts);
|
|
@@ -1594,6 +1596,13 @@ export function registerTeamsCommands(program) {
|
|
|
1594
1596
|
emit('teams.complete', { module: 'teams', team, stoppedBy: result.stoppedBy, waves: result.waves, durationMs: result.elapsed_ms });
|
|
1595
1597
|
if (result.stoppedBy === 'drained') {
|
|
1596
1598
|
console.log(chalk.green(`Factory drained in ${elapsed}s (${result.waves} waves).`));
|
|
1599
|
+
// First-successful-team star nudge (one-time, non-nagging). "drained"
|
|
1600
|
+
// only means nothing is pending/running — teammates may have failed — so
|
|
1601
|
+
// gate on a clean drain (failed === 0). A team where every teammate
|
|
1602
|
+
// failed is not the success this nudge celebrates.
|
|
1603
|
+
if ((result.failed ?? 0) === 0) {
|
|
1604
|
+
maybeShowStarNudge({ quiet: opts.json });
|
|
1605
|
+
}
|
|
1597
1606
|
}
|
|
1598
1607
|
else if (result.stoppedBy === 'max-waves') {
|
|
1599
1608
|
console.error(chalk.yellow(`Hit --max-waves=${maxWaves}; stopping. Re-run to continue.`));
|
|
@@ -8,9 +8,9 @@ import { AGENTS, ALL_AGENT_IDS, accountOrgBadge, getAccountEmail, getAccountInfo
|
|
|
8
8
|
import { formatUsageSummary, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, } from '../lib/usage.js';
|
|
9
9
|
import { viewAction } from './view.js';
|
|
10
10
|
import { readManifest, writeManifest, createDefaultManifest } from '../lib/manifest.js';
|
|
11
|
-
import { installVersion, removeVersion, listInstalledVersions, isVersionInstalled, isLatestInstalled, isOldestInstalled, getGlobalDefault, setGlobalDefault, markVersionIsolated, isVersionIsolated, getVersionHomePath, getVersionDir, syncResourcesToVersion, parseAgentSpec, promptResourceSelection, promptNewResourceSelection, getAvailableResources, getActuallySyncedResources, getNewResources, getProjectOnlyResources, hasNewResources, printTrashFooter, } from '../lib/versions.js';
|
|
11
|
+
import { installVersion, removeVersion, listInstalledVersions, isVersionInstalled, isLatestInstalled, isOldestInstalled, getGlobalDefault, setGlobalDefault, markVersionIsolated, setIsolatedDefault, isVersionIsolated, getVersionHomePath, getVersionDir, syncResourcesToVersion, parseAgentSpec, promptResourceSelection, promptNewResourceSelection, getAvailableResources, getActuallySyncedResources, getNewResources, getProjectOnlyResources, hasNewResources, printTrashFooter, } from '../lib/versions.js';
|
|
12
12
|
import { carryForwardSettings } from '../lib/settings-manifest.js';
|
|
13
|
-
import { createShim, createVersionedAlias, supportsIsolatedInstall, CONFIG_ENV_ISOLATED_AGENTS, removeShim, shimExists, getShimsDir, getShimPath, getPathShadowingExecutable, isShimsInPath, getPathSetupInstructions, addShimsToPath, switchConfigSymlink, switchHomeFileSymlinks, } from '../lib/shims.js';
|
|
13
|
+
import { createShim, createVersionedAlias, supportsIsolatedInstall, isIsolationProtected, CONFIG_ENV_ISOLATED_AGENTS, removeShim, shimExists, getShimsDir, getShimPath, getPathShadowingExecutable, isShimsInPath, getPathSetupInstructions, addShimsToPath, switchConfigSymlink, switchHomeFileSymlinks, } from '../lib/shims.js';
|
|
14
14
|
import { isInteractiveTerminal, isPromptCancelled, requireInteractiveSelection } from './utils.js';
|
|
15
15
|
import { tryAutoPull } from '../lib/git.js';
|
|
16
16
|
import { getAgentsDir, getTrashVersionsDir } from '../lib/state.js';
|
|
@@ -342,6 +342,20 @@ export function registerVersionsCommands(program) {
|
|
|
342
342
|
// from the user's real ~/.<agent>. Agents without one isolate only by
|
|
343
343
|
// adopting ~/.<agent> (which --isolated skips), so an isolated copy
|
|
344
344
|
// would silently read/write the real config. Refuse rather than lie.
|
|
345
|
+
// A normal install of a currently isolated-only agent would adopt it: set the
|
|
346
|
+
// global default, create the bare shim, and repoint the real ~/.<agent>. The
|
|
347
|
+
// primitives refuse that outright, so catch it here — before spending a
|
|
348
|
+
// network install on something that cannot finish — and say what to do.
|
|
349
|
+
if (!isIsolated && isIsolationProtected(agent)) {
|
|
350
|
+
console.log(chalk.red(`${agentLabel(agentConfig.id)} is installed only as isolated copies.`));
|
|
351
|
+
console.log(chalk.gray(` A normal install would adopt ${agentConfig.configDir} and your ${agentConfig.cliCommand} launcher.`));
|
|
352
|
+
console.log(chalk.gray(` Keep the sandbox: agents add ${agent}@${version ?? 'latest'} --isolated`));
|
|
353
|
+
console.log(chalk.gray(' Or manage it normally by removing the isolated copies first:'));
|
|
354
|
+
for (const v of listInstalledVersions(agent)) {
|
|
355
|
+
console.log(chalk.gray(` agents remove ${agent}@${v} --isolated`));
|
|
356
|
+
}
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
345
359
|
if (isIsolated && !supportsIsolatedInstall(agent)) {
|
|
346
360
|
console.log(chalk.red(`${agentLabel(agentConfig.id)} does not support --isolated installs.`));
|
|
347
361
|
console.log(chalk.gray(` It has no config-directory env var, so it can only isolate by adopting ${agentConfig.configDir} — which --isolated deliberately avoids.`));
|
|
@@ -705,10 +719,27 @@ export function registerVersionsCommands(program) {
|
|
|
705
719
|
// Refuse for both the explicit `use <agent>@<isolated>` path (the picker
|
|
706
720
|
// above already filters isolated versions out of the interactive path).
|
|
707
721
|
// Isolated copies are launched explicitly via `agents run <agent>@<v>`.
|
|
722
|
+
// ...so `use` is scoped to the sandbox rather than refused. Setting the
|
|
723
|
+
// ISOLATED default records which copy a bare `agents run <agent>` should
|
|
724
|
+
// reach, and touches none of the five adopting side effects above. The
|
|
725
|
+
// pointer lives in `meta.isolatedAgents`, never in `meta.agents`, so
|
|
726
|
+
// `getGlobalDefault` still cannot return an isolated version.
|
|
708
727
|
if (isVersionIsolated(agentId, finalVersion)) {
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
728
|
+
if (options.project) {
|
|
729
|
+
console.log(chalk.yellow(`${agentLabel(agentConfig.id)}@${finalVersion} is an isolated install; --project pins are for shared versions.`));
|
|
730
|
+
console.log(chalk.gray(`Run it directly instead: agents run ${agentId}@${finalVersion}`));
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
setIsolatedDefault(agentId, finalVersion);
|
|
734
|
+
console.log(chalk.green(`Set ${agentLabel(agentConfig.id)}@${finalVersion} as your default ISOLATED copy.`));
|
|
735
|
+
console.log(chalk.gray(` agents run ${agentId} now reaches it (no @version needed).`));
|
|
736
|
+
const globalDefault = getGlobalDefault(agentId);
|
|
737
|
+
if (globalDefault) {
|
|
738
|
+
console.log(chalk.gray(` Your default ${agentConfig.cliCommand} is still ${globalDefault}; ${agentConfig.configDir} is untouched.`));
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
console.log(chalk.gray(` ${agentConfig.configDir} and your ${agentConfig.cliCommand} launcher are untouched.`));
|
|
742
|
+
}
|
|
712
743
|
return;
|
|
713
744
|
}
|
|
714
745
|
if (options.project) {
|
package/dist/commands/view.js
CHANGED
|
@@ -4,13 +4,13 @@ import { visibleWidth, termLink } from '../lib/format.js';
|
|
|
4
4
|
import ora from 'ora';
|
|
5
5
|
import * as fs from 'fs';
|
|
6
6
|
import * as path from 'path';
|
|
7
|
-
import { AGENTS, ALL_AGENT_IDS, accountDisplayLabel, getAllCliStates, getAccountInfo, resolveAgentName, formatAgentError, agentLabel, colorAgent, } from '../lib/agents.js';
|
|
7
|
+
import { AGENTS, ALL_AGENT_IDS, accountDisplayLabel, getAllCliStates, getUnmanagedCliState, getAccountInfo, resolveAgentName, formatAgentError, agentLabel, colorAgent, } from '../lib/agents.js';
|
|
8
8
|
import { loginHint } from '../lib/signin-badge.js';
|
|
9
9
|
import { machineId } from '../lib/machine-id.js';
|
|
10
10
|
import { authCacheKey, formatCheckedAge, readAuthHealthCache } from '../lib/auth-health.js';
|
|
11
11
|
import { agentReportsUsage, deriveUsageStatusFromSnapshot, formatUsageSection, formatUsageSummary, formatUsageStatusBadge, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, } from '../lib/usage.js';
|
|
12
12
|
import { readManifest } from '../lib/manifest.js';
|
|
13
|
-
import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, getAvailableResources, getActuallySyncedResources, getNewResources, getProjectOnlyResources, hasNewResources, promptNewResourceSelection, syncResourcesToVersion, removeVersion, printTrashFooter, reconcileStaleLatestForAgent, isGlobalBinaryAgent, getLiveVersion, } from '../lib/versions.js';
|
|
13
|
+
import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, getAvailableResources, getActuallySyncedResources, getNewResources, getProjectOnlyResources, hasNewResources, promptNewResourceSelection, syncResourcesToVersion, removeVersion, printTrashFooter, reconcileStaleLatestForAgent, isGlobalBinaryAgent, getLiveVersion, isVersionIsolated, getIsolatedDefault, } from '../lib/versions.js';
|
|
14
14
|
import { ensureVersionedAliasCurrent, removeShim, } from '../lib/shims.js';
|
|
15
15
|
import { getAgentResources } from '../lib/resources.js';
|
|
16
16
|
import { resolveVersionFilter, AgentSpecError } from '../lib/agent-spec/index.js';
|
|
@@ -243,9 +243,27 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
243
243
|
? `Checking ${agentLabel(filterAgentId)} agents...`
|
|
244
244
|
: 'Checking installed agents...';
|
|
245
245
|
const spinner = ora({ text: spinnerText, isSilent: !process.stdout.isTTY }).start();
|
|
246
|
-
const cliStates = await getAllCliStates();
|
|
247
|
-
spinner.stop();
|
|
248
246
|
const agentsToShow = filterAgentId ? [filterAgentId] : ALL_AGENT_IDS;
|
|
247
|
+
// A globally-installed CLI is superseded only by a NORMAL managed version — that
|
|
248
|
+
// is when agents-cli owns the launcher and a "global" row would just be our own
|
|
249
|
+
// shim reported back. `--isolated` promises the opposite: no default, no bare
|
|
250
|
+
// shim, no adopted launcher, the user's own `~/.<agent>` untouched. So an
|
|
251
|
+
// isolated-only install must not make that still-live global CLI disappear from
|
|
252
|
+
// `agents view` — the two are genuinely separate installs and both get listed.
|
|
253
|
+
const hasNonIsolatedVersion = (agentId) => listInstalledVersions(agentId).some((v) => !isVersionIsolated(agentId, v));
|
|
254
|
+
// Every `cliStates` read in this function feeds the "Not Managed by Agents CLI"
|
|
255
|
+
// block, so resolve it the way that block means it: the user's own CLI on PATH.
|
|
256
|
+
// `getCliState` would answer with a version-dir install — including an isolated
|
|
257
|
+
// copy that is deliberately absent from PATH — and print it as "(global)".
|
|
258
|
+
//
|
|
259
|
+
// Resolved only for agents that can actually reach that block. `getCliState`
|
|
260
|
+
// deliberately avoids subprocesses for a version-managed agent, and PATH
|
|
261
|
+
// resolution costs a `<cli> --version` spawn on a cold cache — so probing an
|
|
262
|
+
// agent whose global row is suppressed anyway would be pure added latency.
|
|
263
|
+
const cliStates = Object.fromEntries(await Promise.all(agentsToShow
|
|
264
|
+
.filter((agentId) => !hasNonIsolatedVersion(agentId))
|
|
265
|
+
.map(async (agentId) => [agentId, await getUnmanagedCliState(agentId)])));
|
|
266
|
+
spinner.stop();
|
|
249
267
|
const showPaths = !!filterAgentId;
|
|
250
268
|
const profilesByAgent = getProfilesByAgent(filterAgentId);
|
|
251
269
|
const profileSummaries = [...profilesByAgent.values()].flat();
|
|
@@ -276,19 +294,18 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
276
294
|
const infoFetches = [];
|
|
277
295
|
const globalInfoFetches = [];
|
|
278
296
|
for (const agentId of agentsToShow) {
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
info,
|
|
288
|
-
})));
|
|
289
|
-
}
|
|
297
|
+
for (const ver of listInstalledVersions(agentId)) {
|
|
298
|
+
const home = getVersionHomePath(agentId, ver);
|
|
299
|
+
infoFetches.push(getAccountInfo(agentId, home).then((info) => ({
|
|
300
|
+
agentId,
|
|
301
|
+
version: ver,
|
|
302
|
+
home,
|
|
303
|
+
info,
|
|
304
|
+
})));
|
|
290
305
|
}
|
|
291
|
-
|
|
306
|
+
// Mirrors the classification below: fetch the global account whenever the
|
|
307
|
+
// global install will still be rendered (no versions at all, or isolated-only).
|
|
308
|
+
if (!hasNonIsolatedVersion(agentId)) {
|
|
292
309
|
globalInfoFetches.push(getAccountInfo(agentId).then((info) => ({
|
|
293
310
|
agentId,
|
|
294
311
|
cliVersion: cliStates[agentId]?.version || null,
|
|
@@ -356,10 +373,12 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
356
373
|
if (versions.length > 0) {
|
|
357
374
|
versionManaged.push(agentId);
|
|
358
375
|
}
|
|
359
|
-
|
|
360
|
-
|
|
376
|
+
if (cliState?.installed) {
|
|
377
|
+
// Isolated-only installs sit alongside the global CLI rather than replacing it.
|
|
378
|
+
if (!hasNonIsolatedVersion(agentId))
|
|
379
|
+
globallyInstalled.push(agentId);
|
|
361
380
|
}
|
|
362
|
-
else if (hasProfiles) {
|
|
381
|
+
else if (versions.length === 0 && hasProfiles) {
|
|
363
382
|
profileOnly.push(agentId);
|
|
364
383
|
}
|
|
365
384
|
}
|
|
@@ -376,6 +395,22 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
376
395
|
liveVersionByAgent.set(agentId, live);
|
|
377
396
|
}));
|
|
378
397
|
const displayVersion = (agentId, dirVersion) => liveVersionByAgent.get(agentId) ?? dirVersion;
|
|
398
|
+
// Uncolored row label, shared by the width pass and the render so padding lines
|
|
399
|
+
// up. An isolated copy is never the global default (installing one deliberately
|
|
400
|
+
// records no default), so the two tags can't collide.
|
|
401
|
+
const versionRowLabel = (agentId, version, globalDefault) => {
|
|
402
|
+
const shown = displayVersion(agentId, version);
|
|
403
|
+
if (version === globalDefault)
|
|
404
|
+
return `${shown} (default)`;
|
|
405
|
+
if (isVersionIsolated(agentId, version)) {
|
|
406
|
+
// The isolated default is what a bare `agents run <agent>` reaches, so it is
|
|
407
|
+
// worth distinguishing from the other isolated copies sitting beside it.
|
|
408
|
+
return getIsolatedDefault(agentId) === version
|
|
409
|
+
? `${shown} (isolated default)`
|
|
410
|
+
: `${shown} (isolated)`;
|
|
411
|
+
}
|
|
412
|
+
return shown;
|
|
413
|
+
};
|
|
379
414
|
// Show version-managed agents
|
|
380
415
|
if (versionManaged.length > 0) {
|
|
381
416
|
// Calculate column widths across all agents for alignment
|
|
@@ -388,9 +423,7 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
388
423
|
const versions = listInstalledVersions(agentId);
|
|
389
424
|
const globalDefault = getGlobalDefault(agentId);
|
|
390
425
|
for (const v of versions) {
|
|
391
|
-
|
|
392
|
-
const label = v === globalDefault ? `${shown} (default)` : shown;
|
|
393
|
-
maxVerLabel = Math.max(maxVerLabel, label.length);
|
|
426
|
+
maxVerLabel = Math.max(maxVerLabel, versionRowLabel(agentId, v, globalDefault).length);
|
|
394
427
|
const rawInfo = infoMap.get(`${agentId}:${v}`);
|
|
395
428
|
const info = rawInfo ? mergeCanonical(rawInfo) : undefined;
|
|
396
429
|
const accountLabel = accountColumnLabel(info);
|
|
@@ -431,7 +464,14 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
431
464
|
const globalDefault = getGlobalDefault(agentId);
|
|
432
465
|
const runStrategy = getConfiguredRunStrategy(agentId);
|
|
433
466
|
const strategyLabel = chalk.gray(` (${runStrategy})`);
|
|
434
|
-
|
|
467
|
+
// `(no default)` is a nudge to go set one. It would read as a contradiction
|
|
468
|
+
// directly above a row tagged `(isolated default)`, and it would be bad
|
|
469
|
+
// advice besides: for an isolated-only agent the pointer below IS how a
|
|
470
|
+
// bare `agents run <agent>` resolves, and setting a global default is
|
|
471
|
+
// precisely what `--isolated` exists to avoid.
|
|
472
|
+
const noDefaultLabel = !globalDefault && !getIsolatedDefault(agentId)
|
|
473
|
+
? chalk.yellow(' (no default)')
|
|
474
|
+
: '';
|
|
435
475
|
console.log(` ${chalk.bold(agentLabel(agentId))}${strategyLabel}${noDefaultLabel}`);
|
|
436
476
|
// Sort versions with default first, then by semver descending
|
|
437
477
|
const sortedVersions = [...versions].sort((a, b) => {
|
|
@@ -443,10 +483,16 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
443
483
|
});
|
|
444
484
|
for (const version of sortedVersions) {
|
|
445
485
|
const isDefault = version === globalDefault;
|
|
486
|
+
const isolated = !isDefault && isVersionIsolated(agentId, version);
|
|
487
|
+
const isolatedTag = getIsolatedDefault(agentId) === version ? ' (isolated default)' : ' (isolated)';
|
|
446
488
|
const shown = displayVersion(agentId, version);
|
|
447
|
-
const base =
|
|
448
|
-
const
|
|
449
|
-
const label = isDefault
|
|
489
|
+
const base = versionRowLabel(agentId, version, globalDefault);
|
|
490
|
+
const tagPad = ' '.repeat(maxVerLabel - base.length);
|
|
491
|
+
const label = isDefault
|
|
492
|
+
? `${shown}${chalk.green(' (default)')}${tagPad}`
|
|
493
|
+
: isolated
|
|
494
|
+
? `${shown}${chalk.gray(isolatedTag)}${tagPad}`
|
|
495
|
+
: base.padEnd(maxVerLabel);
|
|
450
496
|
const rawInfo = infoMap.get(`${agentId}:${version}`);
|
|
451
497
|
const vInfo = rawInfo ? mergeCanonical(rawInfo) : undefined;
|
|
452
498
|
const usageKey = getUsageLookupKey(vInfo);
|
|
@@ -580,7 +626,9 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
580
626
|
// Profile rows under a globally-installed harness. Use a simpler
|
|
581
627
|
// alignment here since this section doesn't share column state with
|
|
582
628
|
// the version-managed block.
|
|
583
|
-
|
|
629
|
+
// An isolated-only agent now appears in BOTH blocks; its profiles already
|
|
630
|
+
// rendered under the version-managed one, so don't print them twice.
|
|
631
|
+
const profilesHere = versionManaged.includes(agentId) ? [] : (profilesByAgent.get(agentId) ?? []);
|
|
584
632
|
if (profilesHere.length > 0) {
|
|
585
633
|
const nameWidth = Math.max(globalMaxVerLabel, ...profilesHere.map((p) => p.name.length));
|
|
586
634
|
const authWidth = Math.max(...profilesHere.map((p) => p.auth.length));
|