@phnx-labs/agents-cli 1.20.73 → 1.20.76
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 +989 -0
- package/README.md +36 -10
- package/dist/bin/agents +0 -0
- package/dist/commands/apply.js +21 -3
- package/dist/commands/attach.d.ts +10 -0
- package/dist/commands/attach.js +41 -0
- package/dist/commands/computer.js +3 -3
- package/dist/commands/detach-core.d.ts +46 -0
- package/dist/commands/detach-core.js +61 -0
- package/dist/commands/detach.d.ts +5 -0
- package/dist/commands/detach.js +171 -0
- package/dist/commands/doctor.js +31 -1
- package/dist/commands/exec.js +31 -3
- package/dist/commands/export.d.ts +11 -0
- package/dist/commands/export.js +215 -0
- package/dist/commands/feed.d.ts +7 -1
- package/dist/commands/feed.js +47 -4
- package/dist/commands/go.d.ts +9 -2
- package/dist/commands/go.js +16 -6
- package/dist/commands/harness.d.ts +14 -0
- package/dist/commands/harness.js +145 -0
- package/dist/commands/import.js +87 -9
- package/dist/commands/inspect.js +5 -2
- package/dist/commands/profiles.d.ts +20 -0
- package/dist/commands/profiles.js +68 -21
- package/dist/commands/repo.js +14 -0
- package/dist/commands/routines.js +15 -2
- package/dist/commands/secrets.js +44 -21
- package/dist/commands/sessions-browser.js +1 -1
- package/dist/commands/sessions-export.js +19 -5
- package/dist/commands/sessions-migrate.d.ts +29 -0
- package/dist/commands/sessions-migrate.js +596 -0
- package/dist/commands/sessions-picker.js +16 -1
- package/dist/commands/sessions.d.ts +36 -13
- package/dist/commands/sessions.js +210 -40
- package/dist/commands/setup-computer.js +2 -2
- package/dist/commands/setup.js +6 -1
- package/dist/commands/ssh.js +71 -35
- package/dist/commands/status.js +10 -2
- package/dist/commands/versions.js +51 -11
- package/dist/commands/view.d.ts +5 -0
- package/dist/commands/view.js +99 -31
- package/dist/commands/watchdog.d.ts +7 -2
- package/dist/commands/watchdog.js +73 -57
- package/dist/index.js +59 -7
- package/dist/lib/activity.d.ts +14 -4
- package/dist/lib/activity.js +17 -5
- package/dist/lib/actor.d.ts +50 -0
- package/dist/lib/actor.js +166 -0
- package/dist/lib/agent-spec/provider.js +2 -1
- package/dist/lib/agent-spec/resolve.js +19 -5
- package/dist/lib/agent-spec/types.d.ts +9 -1
- package/dist/lib/agents.d.ts +56 -0
- package/dist/lib/agents.js +133 -7
- package/dist/lib/cloud/codex.d.ts +2 -0
- package/dist/lib/cloud/codex.js +14 -3
- package/dist/lib/cloud/session-index.d.ts +32 -0
- package/dist/lib/cloud/session-index.js +58 -0
- package/dist/lib/cloud/store.d.ts +7 -0
- package/dist/lib/cloud/store.js +25 -0
- package/dist/lib/config-transfer.d.ts +16 -0
- package/dist/lib/config-transfer.js +60 -0
- package/dist/lib/daemon.js +18 -2
- package/dist/lib/devices/connect.d.ts +15 -1
- package/dist/lib/devices/connect.js +15 -1
- package/dist/lib/devices/fleet.d.ts +21 -0
- package/dist/lib/devices/fleet.js +26 -0
- package/dist/lib/devices/health-report.d.ts +38 -0
- package/dist/lib/devices/health-report.js +214 -0
- package/dist/lib/devices/health.js +4 -1
- package/dist/lib/devices/reachability.d.ts +31 -0
- package/dist/lib/devices/reachability.js +40 -0
- package/dist/lib/devices/registry.d.ts +33 -0
- package/dist/lib/devices/registry.js +37 -0
- package/dist/lib/devices/resolve-target.d.ts +15 -27
- package/dist/lib/devices/resolve-target.js +63 -102
- package/dist/lib/devices/sync.d.ts +18 -0
- package/dist/lib/devices/sync.js +23 -1
- package/dist/lib/devices/tailscale.d.ts +3 -0
- package/dist/lib/devices/tailscale.js +1 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +66 -0
- package/dist/lib/exec.js +195 -6
- package/dist/lib/export.d.ts +72 -0
- package/dist/lib/export.js +269 -0
- package/dist/lib/feed-post.d.ts +63 -0
- package/dist/lib/feed-post.js +204 -0
- package/dist/lib/fleet/apply.d.ts +32 -2
- package/dist/lib/fleet/apply.js +97 -10
- package/dist/lib/fleet/types.d.ts +11 -0
- package/dist/lib/fs-walk.d.ts +13 -0
- package/dist/lib/fs-walk.js +16 -7
- package/dist/lib/hooks.d.ts +1 -0
- package/dist/lib/hooks.js +156 -0
- package/dist/lib/hosts/dispatch.d.ts +7 -0
- package/dist/lib/hosts/dispatch.js +2 -0
- package/dist/lib/hosts/passthrough.js +2 -0
- package/dist/lib/hosts/reconnect.d.ts +78 -0
- package/dist/lib/hosts/reconnect.js +127 -0
- package/dist/lib/hosts/registry.d.ts +75 -14
- package/dist/lib/hosts/registry.js +205 -30
- package/dist/lib/hosts/remote-cmd.js +1 -0
- package/dist/lib/hosts/run-target.d.ts +13 -5
- package/dist/lib/hosts/run-target.js +26 -10
- package/dist/lib/hosts/session-index.d.ts +18 -1
- package/dist/lib/hosts/session-index.js +37 -2
- package/dist/lib/hosts/session-marker.d.ts +33 -0
- package/dist/lib/hosts/session-marker.js +51 -0
- package/dist/lib/hosts/tasks.d.ts +8 -4
- package/dist/lib/import.d.ts +25 -0
- package/dist/lib/import.js +107 -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.d.ts +20 -0
- package/dist/lib/menubar/install-menubar.js +45 -1
- package/dist/lib/migrate.d.ts +16 -0
- package/dist/lib/migrate.js +36 -0
- package/dist/lib/models.d.ts +27 -0
- package/dist/lib/models.js +54 -1
- package/dist/lib/overdue.d.ts +7 -2
- package/dist/lib/overdue.js +18 -11
- package/dist/lib/picker.js +4 -1
- package/dist/lib/platform/process.d.ts +17 -0
- package/dist/lib/platform/process.js +70 -0
- package/dist/lib/profiles-presets.js +9 -7
- package/dist/lib/profiles.d.ts +31 -0
- package/dist/lib/profiles.js +70 -0
- package/dist/lib/project-resources.js +20 -5
- package/dist/lib/pty-server.d.ts +2 -10
- package/dist/lib/pty-server.js +4 -38
- package/dist/lib/registry.d.ts +1 -1
- package/dist/lib/registry.js +48 -8
- package/dist/lib/rotate.d.ts +18 -0
- package/dist/lib/rotate.js +28 -0
- package/dist/lib/routines.d.ts +26 -0
- package/dist/lib/routines.js +19 -2
- package/dist/lib/runner.js +104 -7
- package/dist/lib/secrets/agent.d.ts +66 -1
- package/dist/lib/secrets/agent.js +154 -90
- package/dist/lib/secrets/bundles.d.ts +34 -0
- package/dist/lib/secrets/bundles.js +179 -19
- package/dist/lib/secrets/index.d.ts +15 -1
- package/dist/lib/secrets/index.js +21 -3
- package/dist/lib/secrets/rc-hygiene.d.ts +55 -0
- package/dist/lib/secrets/rc-hygiene.js +154 -0
- package/dist/lib/secrets/remote.d.ts +7 -4
- package/dist/lib/secrets/remote.js +7 -4
- package/dist/lib/secrets/sync-commands.d.ts +21 -0
- package/dist/lib/secrets/sync-commands.js +21 -0
- package/dist/lib/self-update.d.ts +23 -2
- package/dist/lib/self-update.js +86 -5
- package/dist/lib/session/active.d.ts +114 -7
- package/dist/lib/session/active.js +323 -52
- package/dist/lib/session/cloud.js +2 -1
- package/dist/lib/session/db.d.ts +48 -0
- package/dist/lib/session/db.js +238 -14
- package/dist/lib/session/detached.d.ts +30 -0
- package/dist/lib/session/detached.js +92 -0
- package/dist/lib/session/discover.d.ts +465 -2
- package/dist/lib/session/discover.js +1648 -468
- package/dist/lib/session/fork.js +2 -1
- package/dist/lib/session/hook-sessions.d.ts +43 -0
- package/dist/lib/session/hook-sessions.js +135 -0
- package/dist/lib/session/linear.d.ts +6 -0
- package/dist/lib/session/linear.js +55 -0
- package/dist/lib/session/migrate-targets.d.ts +65 -0
- package/dist/lib/session/migrate-targets.js +94 -0
- package/dist/lib/session/migrations.d.ts +37 -0
- package/dist/lib/session/migrations.js +60 -0
- package/dist/lib/session/parse.d.ts +18 -0
- package/dist/lib/session/parse.js +130 -30
- package/dist/lib/session/pid-registry.d.ts +34 -2
- package/dist/lib/session/pid-registry.js +49 -2
- package/dist/lib/session/render.d.ts +7 -2
- package/dist/lib/session/render.js +22 -10
- package/dist/lib/session/short-id.d.ts +17 -0
- package/dist/lib/session/short-id.js +20 -0
- package/dist/lib/session/state.js +6 -2
- package/dist/lib/shims.d.ts +27 -1
- package/dist/lib/shims.js +88 -6
- package/dist/lib/sqlite.d.ts +3 -2
- package/dist/lib/sqlite.js +27 -4
- package/dist/lib/staleness/detectors/commands.js +1 -1
- package/dist/lib/staleness/detectors/workflows.js +13 -2
- package/dist/lib/staleness/writers/commands.js +7 -7
- package/dist/lib/staleness/writers/workflows.d.ts +4 -2
- package/dist/lib/startup/command-registry.d.ts +2 -0
- package/dist/lib/startup/command-registry.js +5 -0
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +15 -36
- package/dist/lib/teams/agents.d.ts +19 -10
- package/dist/lib/teams/agents.js +40 -39
- package/dist/lib/types.d.ts +48 -2
- package/dist/lib/uninstall.js +1 -45
- package/dist/lib/usage.d.ts +33 -1
- package/dist/lib/usage.js +172 -12
- package/dist/lib/versions.d.ts +22 -2
- package/dist/lib/versions.js +98 -10
- package/dist/lib/watchdog/log.d.ts +43 -0
- package/dist/lib/watchdog/log.js +69 -0
- package/dist/lib/watchdog/routine.d.ts +44 -0
- package/dist/lib/watchdog/routine.js +69 -0
- package/dist/lib/watchdog/runner.d.ts +51 -7
- package/dist/lib/watchdog/runner.js +239 -64
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdog.js +31 -16
- package/dist/lib/workflows.d.ts +16 -0
- package/dist/lib/workflows.js +110 -1
- package/package.json +1 -1
package/dist/commands/ssh.js
CHANGED
|
@@ -20,21 +20,22 @@ import { readAndResolveBundleEnv, isHeadlessSecretsContext } from '../lib/secret
|
|
|
20
20
|
import { machineId } from '../lib/session/sync/config.js';
|
|
21
21
|
import { registerFleetCaptureCommand } from './fleet-capture.js';
|
|
22
22
|
import { registerFleetApplyAlias } from './apply.js';
|
|
23
|
-
import { addIgnored, getDevice, loadDevices, loadIgnored, removeDevice, removeIgnored, upsertDevice, } from '../lib/devices/registry.js';
|
|
23
|
+
import { addIgnored, getDevice, loadDevices, loadIgnored, removeDevice, removeIgnored, upsertDevice, writeReachability, } from '../lib/devices/registry.js';
|
|
24
|
+
import { collectReachabilityWriteBacks, deviceOnlineState } from '../lib/devices/reachability.js';
|
|
24
25
|
import { addControlToken } from '../lib/serve/token.js';
|
|
25
26
|
import { DEFAULT_SERVE_PORT } from '../lib/serve/server.js';
|
|
26
27
|
import { nodeToDeviceInput, parseTailscaleStatus, tailscaleStatusJson, } from '../lib/devices/tailscale.js';
|
|
27
|
-
import { localLoginUser, planDeviceReconciliation, runDeviceSync, withDefaultUser } from '../lib/devices/sync.js';
|
|
28
|
+
import { defaultPickerChecked, localLoginUser, planDeviceReconciliation, runDeviceSync, withDefaultUser } from '../lib/devices/sync.js';
|
|
28
29
|
import { resolveDeviceTarget, splitUserHost } from '../lib/devices/resolve-target.js';
|
|
29
30
|
import { clearPendingSentinel } from '../lib/devices/pending.js';
|
|
30
31
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
31
32
|
import { hostNameFor, renderSshConfig } from '../lib/devices/ssh-config.js';
|
|
32
|
-
import { ASKPASS_BUNDLE_ENV, ASKPASS_KEY_ENV, buildSshInvocation, fleetDialTarget, writeAskpassShim, } from '../lib/devices/connect.js';
|
|
33
|
+
import { ASKPASS_BUNDLE_ENV, ASKPASS_KEY_ENV, ASKPASS_AGENT_ONLY_ENV, buildSshInvocation, fleetDialTarget, writeAskpassShim, } from '../lib/devices/connect.js';
|
|
33
34
|
import { ensureManagedKnownHostsDir, isHostPinned } from '../lib/devices/known-hosts.js';
|
|
34
35
|
import { shouldSyncTerminfo, syncTerminfoToDevice, terminfoHostKey } from '../lib/devices/terminfo.js';
|
|
35
|
-
import { fanOutDevices, planFleetTargets, remoteFleetTargets, runFleet, skipLabel, upgradeCommand, } from '../lib/devices/fleet.js';
|
|
36
|
+
import { fanOutDevices, fleetHealthSkip, planFleetTargets, remoteFleetTargets, runFleet, skipLabel, upgradeCommand, } from '../lib/devices/fleet.js';
|
|
36
37
|
import { fleetCapacity, fmtBytes, headroom, } from '../lib/devices/health.js';
|
|
37
|
-
import { buildFleetHealthReport, renderFleetMatrix, renderFleetWarnings, } from '../lib/devices/health-report.js';
|
|
38
|
+
import { buildFleetHealthReport, renderFleetMatrix, renderFleetSummary, renderFleetWarnings, } from '../lib/devices/health-report.js';
|
|
38
39
|
import { loadFleetStats, readStatsCache } from '../lib/devices/stats-cache.js';
|
|
39
40
|
import { checkSyncStatus, countOrphans } from '../lib/drift.js';
|
|
40
41
|
import { checkAllClis } from '../lib/teams/agents.js';
|
|
@@ -47,14 +48,17 @@ import { authCellColor, formatCheckedAge, isDeadVerdict, probeLocalFleetAuth, re
|
|
|
47
48
|
import { runFleetLogin } from '../lib/fleet/remote-login.js';
|
|
48
49
|
/** One-line summary of a device for `list`. `isSelf` marks the machine this
|
|
49
50
|
* command is running on so it stands out from the rest of the tailnet. */
|
|
50
|
-
function deviceSummary(d, isSelf = false) {
|
|
51
|
+
function deviceSummary(d, isSelf = false, stats) {
|
|
51
52
|
const addr = hostNameFor(d) ?? chalk.gray('no address');
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
// Prefer a fresh live verdict (this run's probe, else the written-back
|
|
54
|
+
// reachability) over the stale tailscale.online snapshot (RUSH-1965).
|
|
55
|
+
const state = deviceOnlineState(d, stats);
|
|
56
|
+
const online = state === 'online'
|
|
57
|
+
? chalk.green('online')
|
|
58
|
+
: state === 'offline'
|
|
59
|
+
? chalk.gray('offline')
|
|
60
|
+
: chalk.gray('unknown');
|
|
61
|
+
const reach = state === 'online' && d.tailscale && !d.tailscale.direct ? chalk.yellow(' (relayed)') : '';
|
|
58
62
|
const marker = isSelf ? chalk.cyan('▸ ') : ' ';
|
|
59
63
|
const name = isSelf ? chalk.bold.cyan(d.name.padEnd(16)) : chalk.bold(d.name.padEnd(16));
|
|
60
64
|
const here = isSelf ? chalk.cyan(' ← this machine') : '';
|
|
@@ -106,8 +110,11 @@ function renderDeviceTable(reg, names, self, statsMap, full = false) {
|
|
|
106
110
|
const marker = isSelf ? chalk.cyan('▸ ') : ' ';
|
|
107
111
|
const label = isSelf ? chalk.bold.cyan(name.padEnd(16)) : chalk.bold(name.padEnd(16));
|
|
108
112
|
const plat = String(d.platform).padEnd(8);
|
|
109
|
-
const offline = d.tailscale && !d.tailscale.online;
|
|
110
113
|
const stats = statsMap.get(name);
|
|
114
|
+
// Prefer this run's live probe, then the written-back verdict, over the
|
|
115
|
+
// stale tailscale.online snapshot — so a reachable box never renders
|
|
116
|
+
// "offline" while its live load/mem sit one column over (RUSH-1965).
|
|
117
|
+
const offline = deviceOnlineState(d, stats) === 'offline';
|
|
111
118
|
if (offline) {
|
|
112
119
|
lines.push(`${marker}${label}${plat} ${chalk.gray('offline')}`);
|
|
113
120
|
continue;
|
|
@@ -243,13 +250,16 @@ async function runInteractiveDeviceSync() {
|
|
|
243
250
|
// Everything not already dismissed starts checked, so pressing Enter keeps
|
|
244
251
|
// the fleet as-is (matching what auto-sync would register). Unchecking a
|
|
245
252
|
// device removes it AND dismisses it so auto-sync never re-adds it.
|
|
253
|
+
// Sharee nodes (shared in by another user) start unchecked unless already
|
|
254
|
+
// registered — registering one must be a deliberate check, never the
|
|
255
|
+
// default Enter.
|
|
246
256
|
message: 'Your fleet — uncheck a device to remove and stop suggesting it:',
|
|
247
257
|
pageSize: Math.min(nodes.length, 20),
|
|
248
258
|
choices: nodes.map((n) => {
|
|
249
|
-
const flags = [n.platform, n.online ? undefined : 'offline', ignored.has(n.name) ? 'ignored' : undefined]
|
|
259
|
+
const flags = [n.platform, n.online ? undefined : 'offline', n.sharee ? 'shared' : undefined, ignored.has(n.name) ? 'ignored' : undefined]
|
|
250
260
|
.filter(Boolean)
|
|
251
261
|
.join(', ');
|
|
252
|
-
return { value: n.name, name: `${n.name} ${chalk.gray(`(${flags})`)}`, checked:
|
|
262
|
+
return { value: n.name, name: `${n.name} ${chalk.gray(`(${flags})`)}`, checked: defaultPickerChecked(n, registered, ignored) };
|
|
253
263
|
}),
|
|
254
264
|
});
|
|
255
265
|
}
|
|
@@ -347,26 +357,23 @@ async function runFleetStatus(opts) {
|
|
|
347
357
|
const statsMap = opts.stats === false
|
|
348
358
|
? new Map()
|
|
349
359
|
: (await loadFleetStats(probeable, { forceRefresh, selfName: self })).stats;
|
|
360
|
+
// Persist the live probe's reachability verdict so the online/offline word is
|
|
361
|
+
// read from a fresh probe, not a stale tailscale snapshot (RUSH-1965).
|
|
362
|
+
// Best-effort: a registry write must never break the status render.
|
|
363
|
+
await writeReachability(collectReachabilityWriteBacks(reg, statsMap)).catch(() => { });
|
|
350
364
|
const rows = [localHealthRow(self, statsMap.get(self))];
|
|
351
365
|
const remoteTargets = remoteFleetTargets(planned, self)
|
|
352
366
|
.map((t) => ({
|
|
353
367
|
name: t.device.name,
|
|
354
368
|
platform: t.device.platform,
|
|
355
|
-
|
|
369
|
+
// Fail fast: gate the expensive version+doctor dials on the reachability
|
|
370
|
+
// verdict the cheap stats probe already computed one step earlier. A box
|
|
371
|
+
// it found unreachable skips straight to an `unreachable` row instead of
|
|
372
|
+
// burning 15s+30s per box — so one genuinely-offline device can't stall
|
|
373
|
+
// the matrix for ~60s (RUSH-1964). See {@link fleetHealthSkip} for why
|
|
374
|
+
// this is trusted on the default path, not just under `--refresh`.
|
|
375
|
+
skip: fleetHealthSkip(t.skip, statsMap.get(t.device.name)),
|
|
356
376
|
dialTarget: fleetDialTarget(t.device),
|
|
357
|
-
// Fail fast: the stats probe already tried this box with the same
|
|
358
|
-
// (registry) address. If it came back unreachable, don't spend another
|
|
359
|
-
// 15s+30s on version+doctor — skip straight to an unreachable row so one
|
|
360
|
-
// genuinely-offline device can't stall the whole matrix.
|
|
361
|
-
//
|
|
362
|
-
// Only trust this on a FRESH probe (`--refresh`/`--live`). loadFleetStats
|
|
363
|
-
// is cache-first (daemon-warmed ~3min), so a box that came back online
|
|
364
|
-
// since the last warm would otherwise be skipped to "unreachable" without
|
|
365
|
-
// a live attempt — a false negative. On a default run we still do the live
|
|
366
|
-
// probe (fast now that it dials the registry address).
|
|
367
|
-
...(forceRefresh && statsMap.get(t.device.name)?.reachable === false && !t.skip
|
|
368
|
-
? { skip: 'unreachable' }
|
|
369
|
-
: {}),
|
|
370
377
|
}));
|
|
371
378
|
const remote = await fanOutDevices(remoteTargets, probeRemoteHealth);
|
|
372
379
|
for (const result of remote) {
|
|
@@ -400,18 +407,32 @@ async function runFleetStatus(opts) {
|
|
|
400
407
|
for (const row of rows) {
|
|
401
408
|
if (!row.auth)
|
|
402
409
|
row.auth = summarizeHostAuth(authCache, row.name);
|
|
410
|
+
// Resolve the online/offline verdict and last-seen once (RUSH-1966) so the
|
|
411
|
+
// summary view reads one truth — the same `deviceOnlineState` ordering the
|
|
412
|
+
// registry write-back uses — instead of re-deriving it from error/skipped.
|
|
413
|
+
const profile = reg[row.name];
|
|
414
|
+
if (profile) {
|
|
415
|
+
row.online = deviceOnlineState(profile, statsMap.get(row.name));
|
|
416
|
+
row.lastSeen = profile.tailscale?.lastSeen ?? profile.reachability?.checkedAt;
|
|
417
|
+
}
|
|
403
418
|
}
|
|
404
419
|
const report = buildFleetHealthReport(rows);
|
|
405
420
|
if (opts.json) {
|
|
406
421
|
console.log(JSON.stringify(report, null, 2));
|
|
407
422
|
}
|
|
408
|
-
else {
|
|
423
|
+
else if (opts.verbose) {
|
|
424
|
+
// Full grid: the auth/CLI/sync/version columns and the warnings rollup.
|
|
409
425
|
for (const line of renderFleetWarnings(report))
|
|
410
426
|
console.log(line);
|
|
411
427
|
console.log();
|
|
412
428
|
for (const line of renderFleetMatrix(report))
|
|
413
429
|
console.log(line);
|
|
414
430
|
}
|
|
431
|
+
else {
|
|
432
|
+
// Default: rollup + NEEDS ATTENTION + OS-grouped quiet rows + footer.
|
|
433
|
+
for (const line of renderFleetSummary(report, { self }))
|
|
434
|
+
console.log(line);
|
|
435
|
+
}
|
|
415
436
|
if (opts.strict && report.hasWarnings)
|
|
416
437
|
process.exitCode = 1;
|
|
417
438
|
}
|
|
@@ -737,6 +758,10 @@ Typical workflow:
|
|
|
737
758
|
finally {
|
|
738
759
|
spinner?.stop();
|
|
739
760
|
}
|
|
761
|
+
// Write the live verdict back so this and every other consumer read one
|
|
762
|
+
// reachability truth instead of the stale tailscale snapshot (RUSH-1965).
|
|
763
|
+
if (statsMap)
|
|
764
|
+
await writeReachability(collectReachabilityWriteBacks(reg, statsMap)).catch(() => { });
|
|
740
765
|
}
|
|
741
766
|
console.log(chalk.bold(`Devices (${names.length})`));
|
|
742
767
|
for (const line of renderDeviceTable(reg, names, self, statsMap, opts.full))
|
|
@@ -766,14 +791,20 @@ Typical workflow:
|
|
|
766
791
|
.action(runList);
|
|
767
792
|
devicesCmd
|
|
768
793
|
.command('status')
|
|
769
|
-
.description('
|
|
794
|
+
.description('Fleet health at a glance: online/offline rollup, a NEEDS ATTENTION list (each with its fix command), and quiet per-device rows grouped by OS. Use --verbose for the full auth/CLI/sync grid.')
|
|
770
795
|
.option('--json', 'output machine-readable JSON')
|
|
771
796
|
.option('--strict', 'exit non-zero when any device has drift or is unreachable')
|
|
772
797
|
.option('--no-stats', 'skip the live resource probe')
|
|
773
798
|
.option('--refresh', 'force a live probe of every device, bypassing the cache')
|
|
774
799
|
.option('--live', 'alias of --refresh (shorter to type)')
|
|
775
|
-
.
|
|
776
|
-
|
|
800
|
+
.option('--verbose', 'show the full per-device auth/CLI/sync/version grid instead of the summary')
|
|
801
|
+
.action(async (opts, cmd) => {
|
|
802
|
+
// The root program also defines a global `--verbose`; commander binds a
|
|
803
|
+
// shared long flag to the program, not the leaf. Read the effective value
|
|
804
|
+
// from the merged globals so `fleet status --verbose` works at either level
|
|
805
|
+
// (same pattern as `fleet ping --verbose`).
|
|
806
|
+
const verbose = opts.verbose ?? Boolean(cmd.optsWithGlobals().verbose);
|
|
807
|
+
await runFleetStatus({ ...opts, verbose });
|
|
777
808
|
});
|
|
778
809
|
devicesCmd
|
|
779
810
|
.command('ping')
|
|
@@ -1014,7 +1045,7 @@ secrets bundle via an askpass shim — the password never touches argv.
|
|
|
1014
1045
|
// `user@device` (same device, login user overridden — dialed via its
|
|
1015
1046
|
// Tailscale route, not LAN DNS), or an ad-hoc `user@host`/`host` literal.
|
|
1016
1047
|
// A bare unregistered alias still errors as "Unknown device".
|
|
1017
|
-
const device = resolveDeviceTarget(name
|
|
1048
|
+
const device = await resolveDeviceTarget(name);
|
|
1018
1049
|
if (!device) {
|
|
1019
1050
|
// Not a registered device — it may be a leased crabbox box slug. ssh into
|
|
1020
1051
|
// it directly (crabbox@<tailnet|ip>:2222) before giving up.
|
|
@@ -1076,8 +1107,13 @@ async function runAskpass() {
|
|
|
1076
1107
|
console.error(`askpass: ${ASKPASS_BUNDLE_ENV} not set`);
|
|
1077
1108
|
process.exit(1);
|
|
1078
1109
|
}
|
|
1110
|
+
// A read-only stats probe sets ASKPASS_AGENT_ONLY_ENV to force a broker-only
|
|
1111
|
+
// resolve even under a TTY — so `agents devices` never pops Touch ID just to
|
|
1112
|
+
// render load/mem for an uncached password-auth device (RUSH-1970). Otherwise
|
|
1113
|
+
// fall back to the headless-context heuristic.
|
|
1114
|
+
const agentOnly = process.env[ASKPASS_AGENT_ONLY_ENV] === '1' || isHeadlessSecretsContext();
|
|
1079
1115
|
try {
|
|
1080
|
-
const { env } = readAndResolveBundleEnv(bundle, { caller: 'agents ssh', keys: [key], keyMode: 'storage', agentOnly
|
|
1116
|
+
const { env } = readAndResolveBundleEnv(bundle, { caller: 'agents ssh', keys: [key], keyMode: 'storage', agentOnly });
|
|
1081
1117
|
const value = env[key];
|
|
1082
1118
|
if (value === undefined) {
|
|
1083
1119
|
console.error(`askpass: key '${key}' not found in bundle '${bundle}'`);
|
package/dist/commands/status.js
CHANGED
|
@@ -13,6 +13,7 @@ import { AGENTS } from '../lib/agents.js';
|
|
|
13
13
|
import { setHelpSections } from '../lib/help.js';
|
|
14
14
|
import { computeSyncStatus } from '../lib/sync-status.js';
|
|
15
15
|
import { promptDriftSync } from '../lib/drift-sync.js';
|
|
16
|
+
import { resolveConfiguredModel } from '../lib/models.js';
|
|
16
17
|
import { resolveSurface } from './utils.js';
|
|
17
18
|
const agentName = (id) => AGENTS[id]?.name ?? id;
|
|
18
19
|
function versionSummary(v) {
|
|
@@ -75,8 +76,15 @@ export function registerStatusCommand(program) {
|
|
|
75
76
|
console.log(chalk.gray(' (no installed agent versions)'));
|
|
76
77
|
}
|
|
77
78
|
for (const v of status.agents) {
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
// Model sits right beside the version, same priority (no label).
|
|
80
|
+
const model = resolveConfiguredModel(v.agent, v.version)?.model;
|
|
81
|
+
const defaultTag = v.isDefault ? ' (default)' : '';
|
|
82
|
+
const plain = `${agentName(v.agent)}@${v.version}${model ? ` · ${model}` : ''}${defaultTag}`;
|
|
83
|
+
const shown = `${agentName(v.agent)}@${v.version}`
|
|
84
|
+
+ (model ? ` ${chalk.gray('·')} ${chalk.yellow(model)}` : '')
|
|
85
|
+
+ (v.isDefault ? chalk.gray(' (default)') : '');
|
|
86
|
+
const pad = ' '.repeat(Math.max(1, 34 - plain.length));
|
|
87
|
+
console.log(` ${shown}${pad}${versionSummary(v)}`);
|
|
80
88
|
}
|
|
81
89
|
if (status.totals.orphan > 0) {
|
|
82
90
|
console.log(chalk.gray(` (${status.totals.orphan} orphan${status.totals.orphan === 1 ? '' : 's'} — run \`agents prune cleanup\`)`));
|
|
@@ -7,10 +7,11 @@ import { select, confirm, checkbox } from '@inquirer/prompts';
|
|
|
7
7
|
import { AGENTS, ALL_AGENT_IDS, accountOrgBadge, getAccountEmail, getAccountInfo, agentLabel, warnAgentDeprecated, isSelfUpdatingAgent, } from '../lib/agents.js';
|
|
8
8
|
import { formatUsageSummary, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, } from '../lib/usage.js';
|
|
9
9
|
import { viewAction } from './view.js';
|
|
10
|
+
import { resolveConfiguredModel, formatAgentIdentity } from '../lib/models.js';
|
|
10
11
|
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';
|
|
12
|
+
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
13
|
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';
|
|
14
|
+
import { createShim, createVersionedAlias, supportsIsolatedInstall, isIsolationProtected, CONFIG_ENV_ISOLATED_AGENTS, removeShim, shimExists, getShimsDir, getShimPath, getPathShadowingExecutable, isShimsInPath, getPathSetupInstructions, addShimsToPath, switchConfigSymlink, switchHomeFileSymlinks, } from '../lib/shims.js';
|
|
14
15
|
import { isInteractiveTerminal, isPromptCancelled, requireInteractiveSelection } from './utils.js';
|
|
15
16
|
import { tryAutoPull } from '../lib/git.js';
|
|
16
17
|
import { getAgentsDir, getTrashVersionsDir } from '../lib/state.js';
|
|
@@ -342,6 +343,20 @@ export function registerVersionsCommands(program) {
|
|
|
342
343
|
// from the user's real ~/.<agent>. Agents without one isolate only by
|
|
343
344
|
// adopting ~/.<agent> (which --isolated skips), so an isolated copy
|
|
344
345
|
// would silently read/write the real config. Refuse rather than lie.
|
|
346
|
+
// A normal install of a currently isolated-only agent would adopt it: set the
|
|
347
|
+
// global default, create the bare shim, and repoint the real ~/.<agent>. The
|
|
348
|
+
// primitives refuse that outright, so catch it here — before spending a
|
|
349
|
+
// network install on something that cannot finish — and say what to do.
|
|
350
|
+
if (!isIsolated && isIsolationProtected(agent)) {
|
|
351
|
+
console.log(chalk.red(`${agentLabel(agentConfig.id)} is installed only as isolated copies.`));
|
|
352
|
+
console.log(chalk.gray(` A normal install would adopt ${agentConfig.configDir} and your ${agentConfig.cliCommand} launcher.`));
|
|
353
|
+
console.log(chalk.gray(` Keep the sandbox: agents add ${agent}@${version ?? 'latest'} --isolated`));
|
|
354
|
+
console.log(chalk.gray(' Or manage it normally by removing the isolated copies first:'));
|
|
355
|
+
for (const v of listInstalledVersions(agent)) {
|
|
356
|
+
console.log(chalk.gray(` agents remove ${agent}@${v} --isolated`));
|
|
357
|
+
}
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
345
360
|
if (isIsolated && !supportsIsolatedInstall(agent)) {
|
|
346
361
|
console.log(chalk.red(`${agentLabel(agentConfig.id)} does not support --isolated installs.`));
|
|
347
362
|
console.log(chalk.gray(` It has no config-directory env var, so it can only isolate by adopting ${agentConfig.configDir} — which --isolated deliberately avoids.`));
|
|
@@ -396,7 +411,10 @@ export function registerVersionsCommands(program) {
|
|
|
396
411
|
spinner.text = msg;
|
|
397
412
|
});
|
|
398
413
|
if (result.success) {
|
|
399
|
-
|
|
414
|
+
const installedVer = result.installedVersion || version;
|
|
415
|
+
const installedModel = resolveConfiguredModel(agentConfig.id, installedVer)?.model;
|
|
416
|
+
const installedIdentity = formatAgentIdentity(`${agentLabel(agentConfig.id)}@${result.installedVersion}`, installedModel ? chalk.yellow(installedModel) : null);
|
|
417
|
+
spinner.succeed(`Installed ${installedIdentity}`);
|
|
400
418
|
const installedVersion = result.installedVersion || version;
|
|
401
419
|
// Track the concrete version so a `--project` pin records it instead
|
|
402
420
|
// of the `latest`/`oldest` alias.
|
|
@@ -705,10 +723,27 @@ export function registerVersionsCommands(program) {
|
|
|
705
723
|
// Refuse for both the explicit `use <agent>@<isolated>` path (the picker
|
|
706
724
|
// above already filters isolated versions out of the interactive path).
|
|
707
725
|
// Isolated copies are launched explicitly via `agents run <agent>@<v>`.
|
|
726
|
+
// ...so `use` is scoped to the sandbox rather than refused. Setting the
|
|
727
|
+
// ISOLATED default records which copy a bare `agents run <agent>` should
|
|
728
|
+
// reach, and touches none of the five adopting side effects above. The
|
|
729
|
+
// pointer lives in `meta.isolatedAgents`, never in `meta.agents`, so
|
|
730
|
+
// `getGlobalDefault` still cannot return an isolated version.
|
|
708
731
|
if (isVersionIsolated(agentId, finalVersion)) {
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
732
|
+
if (options.project) {
|
|
733
|
+
console.log(chalk.yellow(`${agentLabel(agentConfig.id)}@${finalVersion} is an isolated install; --project pins are for shared versions.`));
|
|
734
|
+
console.log(chalk.gray(`Run it directly instead: agents run ${agentId}@${finalVersion}`));
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
setIsolatedDefault(agentId, finalVersion);
|
|
738
|
+
console.log(chalk.green(`Set ${agentLabel(agentConfig.id)}@${finalVersion} as your default ISOLATED copy.`));
|
|
739
|
+
console.log(chalk.gray(` agents run ${agentId} now reaches it (no @version needed).`));
|
|
740
|
+
const globalDefault = getGlobalDefault(agentId);
|
|
741
|
+
if (globalDefault) {
|
|
742
|
+
console.log(chalk.gray(` Your default ${agentConfig.cliCommand} is still ${globalDefault}; ${agentConfig.configDir} is untouched.`));
|
|
743
|
+
}
|
|
744
|
+
else {
|
|
745
|
+
console.log(chalk.gray(` ${agentConfig.configDir} and your ${agentConfig.cliCommand} launcher are untouched.`));
|
|
746
|
+
}
|
|
712
747
|
return;
|
|
713
748
|
}
|
|
714
749
|
if (options.project) {
|
|
@@ -725,8 +760,9 @@ export function registerVersionsCommands(program) {
|
|
|
725
760
|
manifest.agents[agentId] = finalVersion;
|
|
726
761
|
writeManifest(process.cwd(), manifest);
|
|
727
762
|
const projEmail = await getAccountEmail(agentId, getVersionHomePath(agentId, finalVersion));
|
|
728
|
-
const
|
|
729
|
-
|
|
763
|
+
const projModel = resolveConfiguredModel(agentId, finalVersion)?.model;
|
|
764
|
+
const projIdentity = formatAgentIdentity(chalk.green(`${agentLabel(agentConfig.id)}@${finalVersion}`), projModel ? chalk.yellow(projModel) : null, projEmail ? chalk.cyan(projEmail) : null);
|
|
765
|
+
console.log(`Set ${projIdentity} for this project`);
|
|
730
766
|
}
|
|
731
767
|
else {
|
|
732
768
|
// Smart resource detection: compare available vs ACTUALLY synced (source of truth: files, not tracking)
|
|
@@ -864,15 +900,19 @@ export function registerVersionsCommands(program) {
|
|
|
864
900
|
switchHomeFileSymlinks(agentId, finalVersion);
|
|
865
901
|
warnIfShimShadowed(agentId);
|
|
866
902
|
const useEmail = await getAccountEmail(agentId, getVersionHomePath(agentId, finalVersion));
|
|
867
|
-
const
|
|
903
|
+
const useModel = resolveConfiguredModel(agentId, finalVersion)?.model;
|
|
904
|
+
const useModelStr = useModel ? chalk.yellow(useModel) : null;
|
|
905
|
+
const useAcctStr = useEmail ? chalk.cyan(useEmail) : null;
|
|
868
906
|
// Self-updating agents are one binary; `use` only swaps the config
|
|
869
907
|
// symlink (the profile), it does not change which binary runs — so say
|
|
870
908
|
// "profile", not "version", to match what actually happened.
|
|
871
909
|
if (isSelfUpdatingAgent(agentId)) {
|
|
872
|
-
|
|
910
|
+
const identity = formatAgentIdentity(chalk.green(agentLabel(agentConfig.id)), useModelStr, useAcctStr);
|
|
911
|
+
console.log(`Switched ${identity} to config profile ${chalk.green(finalVersion)}`);
|
|
873
912
|
}
|
|
874
913
|
else {
|
|
875
|
-
|
|
914
|
+
const identity = formatAgentIdentity(chalk.green(`${agentLabel(agentConfig.id)}@${finalVersion}`), useModelStr, useAcctStr);
|
|
915
|
+
console.log(`Set ${identity} as global default`);
|
|
876
916
|
}
|
|
877
917
|
}
|
|
878
918
|
}
|
package/dist/commands/view.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { Command } from 'commander';
|
|
|
10
10
|
import { accountDisplayLabel } from '../lib/agents.js';
|
|
11
11
|
import type { AccountInfo } from '../lib/agents.js';
|
|
12
12
|
import type { AgentId } from '../lib/types.js';
|
|
13
|
+
import { type ConfiguredModelSource } from '../lib/models.js';
|
|
13
14
|
import { type ProfileSummary } from '../lib/profiles.js';
|
|
14
15
|
/** Shared account identity formatter, re-exported for the view-specific tests. */
|
|
15
16
|
export declare const accountColumnLabel: typeof accountDisplayLabel;
|
|
@@ -51,6 +52,10 @@ export interface ViewJsonVersion {
|
|
|
51
52
|
}>;
|
|
52
53
|
lastActive: string | null;
|
|
53
54
|
path: string;
|
|
55
|
+
configuredModel?: {
|
|
56
|
+
model: string;
|
|
57
|
+
source: ConfiguredModelSource;
|
|
58
|
+
} | null;
|
|
54
59
|
/** Present only when --resources / --detailed (or --json with a section
|
|
55
60
|
* flag) is passed: this version's resource inventory with git sync-state. */
|
|
56
61
|
resources?: VersionResourcesJson;
|