@phnx-labs/agents-cli 1.20.71 → 1.20.73
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 +325 -1
- 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/commands.js +2 -0
- package/dist/commands/doctor.js +8 -4
- 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/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/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 +39 -10
- package/dist/commands/mcp.js +2 -0
- 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/resource-view.d.ts +2 -0
- package/dist/commands/resource-view.js +8 -0
- 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.d.ts +6 -0
- package/dist/commands/sessions.js +26 -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 +3 -1
- package/dist/commands/share.d.ts +8 -1
- package/dist/commands/share.js +122 -37
- package/dist/commands/skills.js +2 -0
- package/dist/commands/ssh.d.ts +9 -0
- package/dist/commands/ssh.js +196 -26
- package/dist/commands/status.js +8 -2
- package/dist/commands/subagents.js +3 -1
- package/dist/commands/sync.js +1 -1
- package/dist/commands/teams.js +11 -2
- package/dist/commands/uninstall.d.ts +11 -0
- package/dist/commands/uninstall.js +158 -0
- package/dist/commands/utils.d.ts +29 -0
- package/dist/commands/utils.js +24 -0
- package/dist/commands/versions.js +120 -11
- package/dist/index.js +83 -12
- package/dist/lib/activity.d.ts +124 -0
- package/dist/lib/activity.js +542 -0
- package/dist/lib/agents.js +16 -5
- 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/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 +14 -5
- package/dist/lib/daemon.js +52 -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/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/logs.d.ts +12 -0
- package/dist/lib/hosts/logs.js +18 -0
- 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/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 +291 -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 +36 -0
- package/dist/lib/shims.js +120 -16
- 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 +3 -0
- 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 +73 -0
- package/dist/lib/uninstall.d.ts +84 -0
- package/dist/lib/uninstall.js +347 -0
- package/dist/lib/usage.d.ts +13 -1
- package/dist/lib/usage.js +32 -14
- package/dist/lib/version.d.ts +40 -0
- package/dist/lib/version.js +94 -16
- package/dist/lib/versions.d.ts +27 -0
- package/dist/lib/versions.js +248 -79
- 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/skills.js
CHANGED
|
@@ -45,6 +45,7 @@ When to use:
|
|
|
45
45
|
.command('list [agent]')
|
|
46
46
|
.description('Show which skills are installed and which agent versions they are synced to')
|
|
47
47
|
.option('-a, --agent <agent>', 'Filter to a specific agent (alternative to positional arg)')
|
|
48
|
+
.option('--json', 'Emit machine-readable JSON instead of the table/picker')
|
|
48
49
|
.action(async (agentArg, options) => {
|
|
49
50
|
const spinner = ora({ text: 'Loading...', isSilent: !process.stdout.isTTY }).start();
|
|
50
51
|
const agentInput = agentArg || options.agent;
|
|
@@ -74,6 +75,7 @@ When to use:
|
|
|
74
75
|
centralPath: getSkillsDir(),
|
|
75
76
|
filterAgent,
|
|
76
77
|
filterVersion,
|
|
78
|
+
json: options.json,
|
|
77
79
|
});
|
|
78
80
|
});
|
|
79
81
|
skillsCmd
|
package/dist/commands/ssh.d.ts
CHANGED
|
@@ -10,5 +10,14 @@
|
|
|
10
10
|
* rsync / `agents sessions --host` resolve the same logical names.
|
|
11
11
|
*/
|
|
12
12
|
import type { Command } from 'commander';
|
|
13
|
+
import { type CrabboxBox } from '../lib/crabbox/cli.js';
|
|
14
|
+
/**
|
|
15
|
+
* Live "Leased boxes" section for `agents devices` (F4, RUSH-1923), computed
|
|
16
|
+
* from `crabboxList()` — these are ephemeral crabbox leases, NEVER written into
|
|
17
|
+
* the device registry. Returns [] when crabbox is unavailable / has no creds /
|
|
18
|
+
* reports no boxes, so the section is simply omitted. `nowSecs` is injected so
|
|
19
|
+
* the row formatting is deterministic in tests.
|
|
20
|
+
*/
|
|
21
|
+
export declare function renderLeasedBoxesSection(boxes: CrabboxBox[], nowSecs: number): string[];
|
|
13
22
|
/** Register both `agents ssh` and `agents devices`. */
|
|
14
23
|
export declare function registerSshCommands(program: Command): void;
|
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/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 { resolveSurface } from './utils.js';
|
|
16
17
|
const agentName = (id) => AGENTS[id]?.name ?? id;
|
|
17
18
|
function versionSummary(v) {
|
|
18
19
|
if (!v.everSynced)
|
|
@@ -46,9 +47,14 @@ export function registerStatusCommand(program) {
|
|
|
46
47
|
agents status --yes
|
|
47
48
|
`,
|
|
48
49
|
});
|
|
49
|
-
cmd.action(async (opts) => {
|
|
50
|
+
cmd.action(async (opts, command) => {
|
|
51
|
+
// Centralized surface read (the human/agent split in one place). Note we still
|
|
52
|
+
// pass the *raw* `opts.yes` to promptDriftSync below — it distinguishes an
|
|
53
|
+
// explicit `--yes` (act) from a non-TTY shell (report only), so `surface.assumeYes`
|
|
54
|
+
// (which conflates the two) would wrongly auto-reconcile in a plain pipe.
|
|
55
|
+
const surface = resolveSurface(command);
|
|
50
56
|
const cwd = opts.cwd ?? process.cwd();
|
|
51
|
-
if (
|
|
57
|
+
if (surface.json) {
|
|
52
58
|
const status = await computeSyncStatus({ cwd });
|
|
53
59
|
console.log(JSON.stringify(status, null, 2));
|
|
54
60
|
return;
|
|
@@ -53,7 +53,7 @@ When to use:
|
|
|
53
53
|
- Team sharing: distribute subagent definitions via GitHub repos
|
|
54
54
|
`);
|
|
55
55
|
// Shared list implementation, registered as `list` and hidden `view` alias.
|
|
56
|
-
const runList = async () => {
|
|
56
|
+
const runList = async (opts) => {
|
|
57
57
|
const rows = buildSubagentRows();
|
|
58
58
|
await showResourceList({
|
|
59
59
|
resourcePlural: 'subagents',
|
|
@@ -62,12 +62,14 @@ When to use:
|
|
|
62
62
|
rows,
|
|
63
63
|
emptyMessage: 'No subagents in ~/.agents/subagents/. Add one with: agents subagents add gh:user/repo',
|
|
64
64
|
centralPath: getSubagentsDir(),
|
|
65
|
+
json: opts?.json,
|
|
65
66
|
});
|
|
66
67
|
};
|
|
67
68
|
// agents subagents list
|
|
68
69
|
subagentsCmd
|
|
69
70
|
.command('list')
|
|
70
71
|
.description('Show subagents in a table with sync status across agent versions')
|
|
72
|
+
.option('--json', 'Emit machine-readable JSON instead of the table/picker')
|
|
71
73
|
.addHelpText('after', `
|
|
72
74
|
Examples:
|
|
73
75
|
# Interactive picker (TTY) or sync-status table (piped)
|
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.`));
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents uninstall` — completely remove agents-cli and restore the user's
|
|
3
|
+
* original agent configs. The reverse of `agents setup`.
|
|
4
|
+
*
|
|
5
|
+
* Thin command layer: the restore/teardown logic lives in `lib/uninstall.ts`
|
|
6
|
+
* (planUninstall / executeUninstall) so it can be tested against a real temp
|
|
7
|
+
* HOME without the CLI wrapper.
|
|
8
|
+
*/
|
|
9
|
+
import type { Command } from 'commander';
|
|
10
|
+
/** Register `agents uninstall`. */
|
|
11
|
+
export declare function registerUninstallCommands(program: Command): void;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { confirm } from '@inquirer/prompts';
|
|
3
|
+
import { setHelpSections } from '../lib/help.js';
|
|
4
|
+
import { planUninstall, executeUninstall } from '../lib/uninstall.js';
|
|
5
|
+
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
6
|
+
/** Render the read-only plan so the user sees exactly what will change. */
|
|
7
|
+
function printPlan(plan, purge) {
|
|
8
|
+
const restores = plan.configs.filter((c) => c.kind === 'restore-backup' || c.kind === 'restore-version-home');
|
|
9
|
+
const dangling = plan.configs.filter((c) => c.kind === 'remove-dangling');
|
|
10
|
+
const untouched = plan.configs.filter((c) => c.kind === 'leave-real' || c.kind === 'leave-foreign');
|
|
11
|
+
console.log(chalk.bold('\nagents uninstall — planned changes\n'));
|
|
12
|
+
if (restores.length > 0) {
|
|
13
|
+
console.log(chalk.green('Restore your original config (adopted by agents-cli):'));
|
|
14
|
+
for (const c of restores) {
|
|
15
|
+
const how = c.kind === 'restore-backup' ? 'from backup' : 'from version home';
|
|
16
|
+
console.log(chalk.gray(` ${c.realPath} ${chalk.dim(`(${how})`)}`));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (dangling.length > 0) {
|
|
20
|
+
console.log(chalk.yellow('Remove dangling symlink (no original found to restore):'));
|
|
21
|
+
for (const c of dangling)
|
|
22
|
+
console.log(chalk.gray(` ${c.realPath}`));
|
|
23
|
+
}
|
|
24
|
+
if (untouched.length > 0) {
|
|
25
|
+
console.log(chalk.cyan('Left untouched (real, un-adopted configs — never modified):'));
|
|
26
|
+
for (const c of untouched)
|
|
27
|
+
console.log(chalk.gray(` ${c.realPath}`));
|
|
28
|
+
}
|
|
29
|
+
if (plan.homeFiles.length > 0) {
|
|
30
|
+
console.log(chalk.green('Restore home files:'));
|
|
31
|
+
for (const hf of plan.homeFiles)
|
|
32
|
+
console.log(chalk.gray(` ${hf.realPath}`));
|
|
33
|
+
}
|
|
34
|
+
if (plan.launchers.length > 0) {
|
|
35
|
+
console.log(chalk.green('Release adopted launchers (restore native binaries on PATH):'));
|
|
36
|
+
console.log(chalk.gray(` ${plan.launchers.join(', ')}`));
|
|
37
|
+
}
|
|
38
|
+
if (plan.rcFiles.length > 0) {
|
|
39
|
+
console.log(chalk.green('Remove the shim directory from PATH in:'));
|
|
40
|
+
for (const rc of plan.rcFiles)
|
|
41
|
+
console.log(chalk.gray(` ${rc}`));
|
|
42
|
+
}
|
|
43
|
+
console.log(chalk.bold('\nData:'));
|
|
44
|
+
if (purge) {
|
|
45
|
+
console.log(chalk.red(` Permanently delete ${plan.agentsDir} (installed versions, session history, secrets metadata).`));
|
|
46
|
+
if (plan.legacySymlink)
|
|
47
|
+
console.log(chalk.red(` Permanently delete ${plan.legacySymlink}.`));
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
console.log(chalk.gray(` Move ${plan.agentsDir} aside to ${plan.agentsDir}.removed-<timestamp> (recoverable).`));
|
|
51
|
+
if (plan.legacySymlink)
|
|
52
|
+
console.log(chalk.gray(` Remove the legacy symlink ${plan.legacySymlink}.`));
|
|
53
|
+
console.log(chalk.gray(' Use --purge to hard-delete instead.'));
|
|
54
|
+
}
|
|
55
|
+
console.log();
|
|
56
|
+
}
|
|
57
|
+
/** Report what actually happened, then the final manual npm step. */
|
|
58
|
+
function printResult(result, cleanedPath) {
|
|
59
|
+
for (const r of result.restoredConfigs)
|
|
60
|
+
console.log(chalk.green(`Restored ${r.realPath}`));
|
|
61
|
+
for (const r of result.removedDanglingConfigs)
|
|
62
|
+
console.log(chalk.yellow(`Removed dangling symlink ${r.realPath}`));
|
|
63
|
+
for (const f of result.restoredHomeFiles)
|
|
64
|
+
console.log(chalk.green(`Restored ${f}`));
|
|
65
|
+
if (result.releasedLaunchers.length > 0)
|
|
66
|
+
console.log(chalk.green(`Released launchers: ${result.releasedLaunchers.join(', ')}`));
|
|
67
|
+
for (const rc of result.cleanedRcFiles)
|
|
68
|
+
console.log(chalk.green(`Cleaned shim PATH entry from ${rc}`));
|
|
69
|
+
if (result.agentsDir.disposition === 'moved') {
|
|
70
|
+
console.log(chalk.gray(`Moved ${result.agentsDir.path} to ${result.agentsDir.movedTo}`));
|
|
71
|
+
}
|
|
72
|
+
else if (result.agentsDir.disposition === 'purged') {
|
|
73
|
+
console.log(chalk.gray(`Deleted ${result.agentsDir.path}`));
|
|
74
|
+
}
|
|
75
|
+
for (const e of result.errors)
|
|
76
|
+
console.log(chalk.red(` ! ${e}`));
|
|
77
|
+
if (result.purgeDowngraded) {
|
|
78
|
+
console.log(chalk.yellow(`\n--purge was downgraded to move-aside because a restore step errored — ${result.agentsDir.path} was kept so nothing is lost. Resolve the errors above, then delete ${result.agentsDir.movedTo} manually.`));
|
|
79
|
+
}
|
|
80
|
+
console.log(chalk.bold('\nFinish by removing the CLI package:'));
|
|
81
|
+
console.log(chalk.gray(' npm uninstall -g @phnx-labs/agents-cli # or: bun remove -g @phnx-labs/agents-cli'));
|
|
82
|
+
if (cleanedPath) {
|
|
83
|
+
console.log(chalk.gray('Open a new shell (or re-source your rc file) so the removed PATH entry takes effect.'));
|
|
84
|
+
}
|
|
85
|
+
if (result.agentsDir.disposition === 'moved') {
|
|
86
|
+
console.log(chalk.gray(`Your data is still at ${result.agentsDir.movedTo} — delete it once you are sure.`));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** Register `agents uninstall`. */
|
|
90
|
+
export function registerUninstallCommands(program) {
|
|
91
|
+
const cmd = program
|
|
92
|
+
.command('uninstall')
|
|
93
|
+
.description('Completely remove agents-cli and restore your original agent configs. Reverses `agents setup`.')
|
|
94
|
+
.option('--purge', 'Hard-delete ~/.agents (installed versions, sessions, secrets metadata) instead of moving it aside')
|
|
95
|
+
.option('--dry-run', 'Show exactly what would change without modifying anything')
|
|
96
|
+
.option('-y, --yes', 'Skip the confirmation prompt (required to run non-interactively)');
|
|
97
|
+
setHelpSections(cmd, {
|
|
98
|
+
examples: `
|
|
99
|
+
# Preview everything that would change, without touching anything
|
|
100
|
+
agents uninstall --dry-run
|
|
101
|
+
|
|
102
|
+
# Restore your configs and move ~/.agents aside (recoverable)
|
|
103
|
+
agents uninstall
|
|
104
|
+
|
|
105
|
+
# Same, but hard-delete ~/.agents (no recovery)
|
|
106
|
+
agents uninstall --purge
|
|
107
|
+
`,
|
|
108
|
+
notes: `
|
|
109
|
+
- Restores every ~/.<agent> that agents-cli adopted; a real config it never adopted is left untouched.
|
|
110
|
+
- Releases adopted launchers and strips the shim directory from your shell PATH.
|
|
111
|
+
- Without --purge, ~/.agents is moved to ~/.agents.removed-<timestamp> so nothing is lost.
|
|
112
|
+
- The CLI cannot delete its own running binary; it prints the final 'npm uninstall -g' step for you.
|
|
113
|
+
`,
|
|
114
|
+
});
|
|
115
|
+
cmd.action(async (options) => {
|
|
116
|
+
// We are tearing ~/.agents down. Silence the JSONL audit log for the rest
|
|
117
|
+
// of this process so a late emit() (its events path is memoized to the old
|
|
118
|
+
// location) can't re-create ~/.agents after we move it aside.
|
|
119
|
+
process.env.AGENTS_DISABLE_EVENT_LOG = '1';
|
|
120
|
+
const plan = planUninstall();
|
|
121
|
+
if (!plan.isInstalled) {
|
|
122
|
+
console.log(chalk.gray('agents-cli is not set up (no ~/.agents directory) — nothing to uninstall.'));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
printPlan(plan, !!options.purge);
|
|
126
|
+
if (options.dryRun) {
|
|
127
|
+
console.log(chalk.gray('Dry run — nothing was changed.'));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (!options.yes) {
|
|
131
|
+
if (!isInteractiveTerminal()) {
|
|
132
|
+
console.log(chalk.red('Refusing to uninstall non-interactively. Re-run with --yes to confirm.'));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const ok = await confirm({
|
|
137
|
+
message: options.purge
|
|
138
|
+
? 'Permanently remove agents-cli and restore your original configs?'
|
|
139
|
+
: 'Remove agents-cli (data moved to a recoverable directory) and restore your original configs?',
|
|
140
|
+
default: false,
|
|
141
|
+
});
|
|
142
|
+
if (!ok) {
|
|
143
|
+
console.log(chalk.gray('Cancelled.'));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
if (isPromptCancelled(err)) {
|
|
149
|
+
console.log(chalk.gray('Cancelled.'));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const result = executeUninstall(plan, { purge: options.purge, timestamp: Date.now() });
|
|
156
|
+
printResult(result, result.cleanedRcFiles.length > 0);
|
|
157
|
+
});
|
|
158
|
+
}
|