@phnx-labs/agents-cli 1.20.33 → 1.20.34
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 +7 -0
- package/README.md +28 -2
- package/dist/commands/computer.d.ts +23 -0
- package/dist/commands/computer.js +45 -3
- package/dist/commands/doctor.d.ts +10 -0
- package/dist/commands/doctor.js +49 -0
- package/dist/commands/import.js +1 -1
- package/dist/commands/rules.js +1 -1
- package/dist/commands/secrets-migrate.js +23 -11
- package/dist/commands/secrets.d.ts +20 -0
- package/dist/commands/secrets.js +53 -1
- package/dist/commands/status.d.ts +12 -0
- package/dist/commands/status.js +81 -0
- package/dist/commands/teams.js +70 -6
- package/dist/commands/versions.js +2 -1
- package/dist/commands/view.d.ts +39 -0
- package/dist/commands/view.js +194 -75
- package/dist/index.js +4 -2
- package/dist/lib/acp/harnesses.d.ts +1 -1
- package/dist/lib/acp/harnesses.js +2 -2
- package/dist/lib/agents.d.ts +12 -0
- package/dist/lib/agents.js +115 -32
- package/dist/lib/browser/chrome.js +20 -0
- package/dist/lib/browser/drivers/ssh.d.ts +19 -0
- package/dist/lib/browser/drivers/ssh.js +18 -3
- package/dist/lib/doctor-diff.js +29 -2
- package/dist/lib/drift-sync.d.ts +43 -0
- package/dist/lib/drift-sync.js +179 -0
- package/dist/lib/exec.d.ts +15 -0
- package/dist/lib/exec.js +21 -11
- package/dist/lib/platform/winpath.d.ts +31 -2
- package/dist/lib/platform/winpath.js +133 -24
- package/dist/lib/pwsh.d.ts +11 -0
- package/dist/lib/pwsh.js +13 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/agent.d.ts +42 -1
- package/dist/lib/secrets/agent.js +89 -11
- package/dist/lib/secrets/bundles.js +40 -9
- package/dist/lib/secrets/filestore.js +31 -1
- package/dist/lib/secrets/index.d.ts +33 -1
- package/dist/lib/secrets/index.js +90 -9
- package/dist/lib/secrets/windows.d.ts +74 -0
- package/dist/lib/secrets/windows.js +440 -0
- package/dist/lib/shims.d.ts +20 -0
- package/dist/lib/shims.js +53 -20
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +2 -0
- package/dist/lib/sync-status.d.ts +102 -0
- package/dist/lib/sync-status.js +135 -0
- package/dist/lib/teams/agents.d.ts +24 -0
- package/dist/lib/teams/agents.js +30 -1
- package/dist/lib/types.d.ts +20 -1
- package/dist/lib/usage.d.ts +30 -0
- package/dist/lib/usage.js +159 -2
- package/package.json +1 -1
package/dist/commands/teams.js
CHANGED
|
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import * as fs from 'fs/promises';
|
|
3
3
|
import { addHostOption } from '../lib/hosts/option.js';
|
|
4
4
|
import * as path from 'path';
|
|
5
|
-
import { AgentManager, checkAllClis, getAgentsDir, VALID_TASK_TYPES, } from '../lib/teams/agents.js';
|
|
5
|
+
import { AgentManager, checkAllClis, checkCliSignedIn, getAgentsDir, resolveSignInAdvisory, VALID_TASK_TYPES, } from '../lib/teams/agents.js';
|
|
6
6
|
import { resolveProvider } from '../lib/cloud/registry.js';
|
|
7
7
|
import { runSupervisor } from '../lib/teams/supervisor.js';
|
|
8
8
|
import { handleSpawn, handleStatus, handleStop, handleTasks, toTaskStatusSummary, } from '../lib/teams/api.js';
|
|
@@ -10,6 +10,7 @@ import { createTeam, ensureTeam, getTeam, loadTeams, removeTeam, teamExists, } f
|
|
|
10
10
|
import { setHelpSections } from '../lib/help.js';
|
|
11
11
|
import { createWorktree, isGitRepo, hasUncommittedChanges, removeWorktree, } from '../lib/teams/worktree.js';
|
|
12
12
|
import { isVersionInstalled, resolveVersionAlias, resolveVersionAliasLoose } from '../lib/versions.js';
|
|
13
|
+
import { AGENTS, warnAgentDeprecated } from '../lib/agents.js';
|
|
13
14
|
import { discoverSessions, parseTimeFilter, resolveSessionById } from '../lib/session/discover.js';
|
|
14
15
|
import { buildPreview as buildSessionPreview } from './sessions-picker.js';
|
|
15
16
|
import { parseExecEnv } from '../lib/exec.js';
|
|
@@ -133,7 +134,7 @@ function parseTeammate(spec) {
|
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
136
|
die(`Unknown teammate '${spec}'. Available agents: ${VALID_AGENTS.join(', ')}.\n` +
|
|
136
|
-
` Use 'claude', '
|
|
137
|
+
` Use 'claude', 'kimi@latest', 'kimi@0.19.2' (a version from 'agents view'), or a profile name.`);
|
|
137
138
|
}
|
|
138
139
|
function shortId(id) {
|
|
139
140
|
return id.slice(0, 8);
|
|
@@ -183,6 +184,31 @@ export function wireCloudDispatcher(mgr) {
|
|
|
183
184
|
return { cloudSessionId: cloudTask.id };
|
|
184
185
|
});
|
|
185
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Advisory: warn once per agent type of any still-pending (staged `--after`)
|
|
189
|
+
* teammate whose CLI may not be signed in. Warn-only — never blocks `start`.
|
|
190
|
+
* Local teammates only; cloud teammates authenticate through their provider.
|
|
191
|
+
*/
|
|
192
|
+
async function warnUnsignedTeammates(mgr, team) {
|
|
193
|
+
let pending;
|
|
194
|
+
try {
|
|
195
|
+
pending = (await mgr.listByTask(team)).filter((a) => a.status === 'pending' && !a.cloudProvider);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return; // team not loadable yet — nothing to warn about
|
|
199
|
+
}
|
|
200
|
+
const seen = new Set();
|
|
201
|
+
for (const a of pending) {
|
|
202
|
+
const agent = a.agentType;
|
|
203
|
+
if (seen.has(agent) || !AGENT_NAMES[agent])
|
|
204
|
+
continue;
|
|
205
|
+
seen.add(agent);
|
|
206
|
+
if (!(await checkCliSignedIn(agent))) {
|
|
207
|
+
console.error(chalk.yellow(`⚠ ${AGENT_NAMES[agent]} may not be signed in (detection is unreliable). Launching anyway.`) +
|
|
208
|
+
chalk.gray(`\n If it fails to start, run \`${AGENTS[agent].cliCommand}\` to log in, or pass --force to silence this.`));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
186
212
|
/** Single-wave start used by `teams start` without --watch. */
|
|
187
213
|
async function runOneWave(mgr, team, json) {
|
|
188
214
|
const launched = await mgr.startReady(team);
|
|
@@ -839,6 +865,7 @@ export function registerTeamsCommands(program) {
|
|
|
839
865
|
.option('--cloud <provider>', `Dispatch to cloud backend instead of local CLI: ${VALID_CLOUD_PROVIDERS.join('|')}`)
|
|
840
866
|
.option('--repo <owner/repo>', 'GitHub repository (required for --cloud rush)')
|
|
841
867
|
.option('--branch <name>', 'Target git branch for cloud dispatch')
|
|
868
|
+
.option('--force', "Skip the advisory 'may not be signed in' warning (detection is unreliable)")
|
|
842
869
|
.option('--json', 'Output machine-readable JSON')
|
|
843
870
|
.action(async (team, teammate, task, opts) => {
|
|
844
871
|
if (!VALID_MODES.includes(opts.mode)) {
|
|
@@ -865,10 +892,18 @@ export function registerTeamsCommands(program) {
|
|
|
865
892
|
}
|
|
866
893
|
}
|
|
867
894
|
const { agent, version, profileName } = parseTeammate(teammate);
|
|
895
|
+
warnAgentDeprecated(agent);
|
|
868
896
|
if (version && !isVersionInstalled(agent, version)) {
|
|
869
897
|
die(`${AGENT_NAMES[agent]} ${version} isn't installed.\n` +
|
|
870
898
|
` Install it: agents add ${agent}@${version}\n` +
|
|
871
|
-
` Or see what's installed: agents view ${agent}`);
|
|
899
|
+
` Or see what's installed (incl. @latest): agents view ${agent}`);
|
|
900
|
+
}
|
|
901
|
+
// Advisory sign-in check: warn but NEVER block. Detection is unreliable
|
|
902
|
+
// for opaque-cred agents, so a false negative must not stop a team. Cloud
|
|
903
|
+
// dispatch authenticates through the provider, not the local CLI — skip it.
|
|
904
|
+
if (!opts.force && !cloudProviderId && !(await checkCliSignedIn(agent))) {
|
|
905
|
+
console.error(chalk.yellow(`⚠ ${AGENT_NAMES[agent]} may not be signed in (detection is unreliable). Adding anyway.`) +
|
|
906
|
+
chalk.gray(`\n If it fails to start, run \`${AGENTS[agent].cliCommand}\` to log in, or pass --force to silence this.`));
|
|
872
907
|
}
|
|
873
908
|
if (opts.name !== undefined) {
|
|
874
909
|
if (!opts.name || !/^[A-Za-z0-9_-]+$/.test(opts.name)) {
|
|
@@ -1139,6 +1174,7 @@ export function registerTeamsCommands(program) {
|
|
|
1139
1174
|
.option('--watch', 'Keep running: poll every --interval seconds, fire new waves, exit when the DAG drains.')
|
|
1140
1175
|
.option('--interval <seconds>', 'Seconds between waves in --watch mode (default 8)', '8')
|
|
1141
1176
|
.option('--max-waves <n>', 'Safety cap on waves in --watch mode (default 1000)', '1000')
|
|
1177
|
+
.option('--force', "Skip the advisory 'may not be signed in' warning for staged teammates (detection is unreliable)")
|
|
1142
1178
|
.action(async (team, opts) => {
|
|
1143
1179
|
const mgr = mkManager();
|
|
1144
1180
|
wireCloudDispatcher(mgr);
|
|
@@ -1148,6 +1184,8 @@ export function registerTeamsCommands(program) {
|
|
|
1148
1184
|
return;
|
|
1149
1185
|
team = picked;
|
|
1150
1186
|
}
|
|
1187
|
+
if (!opts.force && !isJsonMode(opts))
|
|
1188
|
+
await warnUnsignedTeammates(mgr, team);
|
|
1151
1189
|
if (!opts.watch) {
|
|
1152
1190
|
await runOneWave(mgr, team, Boolean(opts.json));
|
|
1153
1191
|
return;
|
|
@@ -1446,19 +1484,45 @@ export function registerTeamsCommands(program) {
|
|
|
1446
1484
|
teams
|
|
1447
1485
|
.command('doctor')
|
|
1448
1486
|
.alias('dr')
|
|
1449
|
-
.description('Check which agents are installed and available to join a team. Verifies CLI paths.')
|
|
1487
|
+
.description('Check which agents are installed and available to join a team. Verifies CLI paths and shows an advisory sign-in hint.')
|
|
1450
1488
|
.option('--json', 'Output machine-readable JSON')
|
|
1451
1489
|
.action(async (opts) => {
|
|
1452
1490
|
const info = checkAllClis();
|
|
1491
|
+
// Advisory enrichment only. Sign-in detection is UNRELIABLE, so it never
|
|
1492
|
+
// changes the authoritative installed/ready column — it annotates. And an
|
|
1493
|
+
// agent that is actually running in a team is treated as signed in
|
|
1494
|
+
// regardless of the probe, so doctor never reports a working agent as
|
|
1495
|
+
// logged out ("don't show wrong stuff").
|
|
1496
|
+
const running = new Set();
|
|
1497
|
+
try {
|
|
1498
|
+
for (const a of await mkManager().listRunning())
|
|
1499
|
+
running.add(a.agentType);
|
|
1500
|
+
}
|
|
1501
|
+
catch { /* no teams yet — leave running empty */ }
|
|
1502
|
+
const auth = {};
|
|
1503
|
+
await Promise.all(Object.entries(info).map(async ([name, entry]) => {
|
|
1504
|
+
const isRunning = running.has(name);
|
|
1505
|
+
const probe = entry.installed && !isRunning ? await checkCliSignedIn(name) : false;
|
|
1506
|
+
auth[name] = resolveSignInAdvisory(entry.installed, isRunning, probe);
|
|
1507
|
+
}));
|
|
1453
1508
|
if (isJsonMode(opts)) {
|
|
1454
|
-
|
|
1509
|
+
const merged = {};
|
|
1510
|
+
for (const [name, entry] of Object.entries(info))
|
|
1511
|
+
merged[name] = { ...entry, ...auth[name] };
|
|
1512
|
+
console.log(JSON.stringify(merged, null, 2));
|
|
1455
1513
|
return;
|
|
1456
1514
|
}
|
|
1457
1515
|
console.log(chalk.bold('Who can join a team:'));
|
|
1458
1516
|
for (const [name, entry] of Object.entries(info)) {
|
|
1459
1517
|
const pretty = AGENT_NAMES[name] || name;
|
|
1460
1518
|
if (entry.installed) {
|
|
1461
|
-
|
|
1519
|
+
const { signedIn, running: isRunning } = auth[name];
|
|
1520
|
+
const hint = isRunning
|
|
1521
|
+
? chalk.gray('in use')
|
|
1522
|
+
: signedIn
|
|
1523
|
+
? chalk.gray('signed in')
|
|
1524
|
+
: chalk.gray('sign-in unverified');
|
|
1525
|
+
console.log(` ${chalk.green('ready')} ${pretty.padEnd(10)} ${chalk.gray(entry.path || '')} ${hint}`);
|
|
1462
1526
|
}
|
|
1463
1527
|
else {
|
|
1464
1528
|
console.log(` ${chalk.red('no ')} ${pretty.padEnd(10)} ${chalk.gray(entry.error || 'not installed')}`);
|
|
@@ -4,7 +4,7 @@ import ora from 'ora';
|
|
|
4
4
|
import * as fs from 'fs';
|
|
5
5
|
import * as path from 'path';
|
|
6
6
|
import { select, confirm, checkbox } from '@inquirer/prompts';
|
|
7
|
-
import { AGENTS, ALL_AGENT_IDS, getAccountEmail, getAccountInfo, agentLabel, } from '../lib/agents.js';
|
|
7
|
+
import { AGENTS, ALL_AGENT_IDS, getAccountEmail, getAccountInfo, agentLabel, warnAgentDeprecated, } from '../lib/agents.js';
|
|
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';
|
|
@@ -273,6 +273,7 @@ export function registerVersionsCommands(program) {
|
|
|
273
273
|
}
|
|
274
274
|
const { agent, version } = parsed;
|
|
275
275
|
const agentConfig = AGENTS[agent];
|
|
276
|
+
warnAgentDeprecated(agent);
|
|
276
277
|
if (!agentConfig.npmPackage && !agentConfig.installScript) {
|
|
277
278
|
console.log(chalk.yellow(`${agentLabel(agentConfig.id)} has no npm package. Install manually.`));
|
|
278
279
|
continue;
|
package/dist/commands/view.d.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import type { Command } from 'commander';
|
|
10
10
|
import type { AgentId } from '../lib/types.js';
|
|
11
11
|
import { type ProfileSummary } from '../lib/profiles.js';
|
|
12
|
+
type SyncState = 'synced' | 'new' | 'modified' | 'deleted';
|
|
12
13
|
/** Per-section filter flags. When any are true, only those sections render. */
|
|
13
14
|
export interface ViewSectionFilter {
|
|
14
15
|
commands?: boolean;
|
|
@@ -27,6 +28,7 @@ export interface ViewJsonVersion {
|
|
|
27
28
|
isDefault: boolean;
|
|
28
29
|
signedIn: boolean;
|
|
29
30
|
email: string | null;
|
|
31
|
+
accountId?: string | null;
|
|
30
32
|
plan: string | null;
|
|
31
33
|
usageStatus: 'available' | 'rate_limited' | 'out_of_credits' | null;
|
|
32
34
|
overageCredits?: {
|
|
@@ -40,6 +42,9 @@ export interface ViewJsonVersion {
|
|
|
40
42
|
}>;
|
|
41
43
|
lastActive: string | null;
|
|
42
44
|
path: string;
|
|
45
|
+
/** Present only when --resources / --detailed (or --json with a section
|
|
46
|
+
* flag) is passed: this version's resource inventory with git sync-state. */
|
|
47
|
+
resources?: VersionResourcesJson;
|
|
43
48
|
}
|
|
44
49
|
/** Machine-readable entry for one agent's installed versions. */
|
|
45
50
|
export interface ViewJsonAgent {
|
|
@@ -47,6 +52,37 @@ export interface ViewJsonAgent {
|
|
|
47
52
|
versions: ViewJsonVersion[];
|
|
48
53
|
profiles: ProfileSummary[];
|
|
49
54
|
}
|
|
55
|
+
/** Resource sections that --resources can include in --json output. */
|
|
56
|
+
export type ResourceSection = 'commands' | 'skills' | 'mcp' | 'memory' | 'hooks' | 'workflows' | 'plugins';
|
|
57
|
+
/** One resource entry in `--json --resources` output. */
|
|
58
|
+
export interface ResourceItemJson {
|
|
59
|
+
name: string;
|
|
60
|
+
scope?: 'user' | 'project';
|
|
61
|
+
/** Git sync-state vs ~/.agents. Omitted for project-scoped resources or when
|
|
62
|
+
* ~/.agents isn't a git repo. When queried via --host it reflects the
|
|
63
|
+
* remote's ~/.agents — i.e. per-host resource drift. */
|
|
64
|
+
syncState?: SyncState;
|
|
65
|
+
description?: string;
|
|
66
|
+
/** Skills only, when > 0. */
|
|
67
|
+
ruleCount?: number;
|
|
68
|
+
}
|
|
69
|
+
/** Per-version resource inventory; one key per requested section. */
|
|
70
|
+
export interface VersionResourcesJson {
|
|
71
|
+
commands?: ResourceItemJson[];
|
|
72
|
+
skills?: ResourceItemJson[];
|
|
73
|
+
mcp?: ResourceItemJson[];
|
|
74
|
+
memory?: ResourceItemJson[];
|
|
75
|
+
hooks?: ResourceItemJson[];
|
|
76
|
+
workflows?: ResourceItemJson[];
|
|
77
|
+
plugins?: ResourceItemJson[];
|
|
78
|
+
}
|
|
79
|
+
/** Build the set of resource sections to include in `--json` from the
|
|
80
|
+
* `--resources` / `--detailed` flags, plus (in --json mode) the per-section
|
|
81
|
+
* boolean filters (`--skills` etc.) that plain `--json` historically ignored. */
|
|
82
|
+
export declare function parseResourceSections(options: {
|
|
83
|
+
resources?: string | boolean;
|
|
84
|
+
detailed?: boolean;
|
|
85
|
+
} & ViewSectionFilter, jsonMode: boolean): Set<ResourceSection>;
|
|
50
86
|
/**
|
|
51
87
|
* Prune older installed versions that share an email with a newer installed
|
|
52
88
|
* version. Keeps the highest semver per email, skips the global default (with
|
|
@@ -66,6 +102,9 @@ export declare function viewAction(agentArg?: string, options?: {
|
|
|
66
102
|
prune?: boolean;
|
|
67
103
|
yes?: boolean;
|
|
68
104
|
dryRun?: boolean;
|
|
105
|
+
resources?: string | boolean;
|
|
106
|
+
detailed?: boolean;
|
|
69
107
|
} & ViewSectionFilter): Promise<void>;
|
|
70
108
|
/** Register the `agents view` command. */
|
|
71
109
|
export declare function registerViewCommand(program: Command): void;
|
|
110
|
+
export {};
|
package/dist/commands/view.js
CHANGED
|
@@ -24,8 +24,24 @@ import { loadManifest, isStale } from '../lib/staleness/index.js';
|
|
|
24
24
|
import { confirm } from '@inquirer/prompts';
|
|
25
25
|
import { formatPath, isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
26
26
|
// Shown in the email column for agents that are signed in but expose no email
|
|
27
|
-
// address locally (Antigravity
|
|
27
|
+
// address locally (Antigravity stores an opaque OAuth grant with no identity).
|
|
28
28
|
const SIGNED_IN_LABEL = 'signed in';
|
|
29
|
+
/**
|
|
30
|
+
* Text for the account (email) column. Prefers the real email; otherwise, for a
|
|
31
|
+
* signed-in agent whose credential carries no email but does carry an opaque
|
|
32
|
+
* account id (Kimi's user_id), show `id:<user_id>` so distinct accounts read
|
|
33
|
+
* distinctly instead of a generic "signed in". Falls back to "signed in" when we
|
|
34
|
+
* have neither (Antigravity), and empty when signed out.
|
|
35
|
+
*/
|
|
36
|
+
function accountColumnLabel(info) {
|
|
37
|
+
if (!info)
|
|
38
|
+
return '';
|
|
39
|
+
if (info.email)
|
|
40
|
+
return info.email;
|
|
41
|
+
if (info.signedIn)
|
|
42
|
+
return info.accountId ? `id:${info.accountId}` : SIGNED_IN_LABEL;
|
|
43
|
+
return '';
|
|
44
|
+
}
|
|
29
45
|
/**
|
|
30
46
|
* Group profile summaries by their host harness, optionally filtered to a
|
|
31
47
|
* single agent. Profile YAMLs that fail validation are silently skipped by
|
|
@@ -310,7 +326,10 @@ async function showInstalledVersions(filterAgentId) {
|
|
|
310
326
|
return info;
|
|
311
327
|
return {
|
|
312
328
|
...info,
|
|
313
|
-
plan
|
|
329
|
+
// Prefer a plan the live usage fetch surfaced (Kimi reports membership
|
|
330
|
+
// tier in /usages; its local auth file has none) and fall back to the
|
|
331
|
+
// account-derived plan (Claude's billingType).
|
|
332
|
+
plan: usageByKey.get(key)?.snapshot?.plan ?? canon.plan,
|
|
314
333
|
// Throttle state comes from the live usage windows, not the pay-as-you-go
|
|
315
334
|
// overage flag that AccountInfo.usageStatus used to carry. A maxed window
|
|
316
335
|
// means rate-limited; no snapshot means no badge. See
|
|
@@ -353,10 +372,9 @@ async function showInstalledVersions(filterAgentId) {
|
|
|
353
372
|
maxVerLabel = Math.max(maxVerLabel, label.length);
|
|
354
373
|
const rawInfo = infoMap.get(`${agentId}:${v}`);
|
|
355
374
|
const info = rawInfo ? mergeCanonical(rawInfo) : undefined;
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
maxEmail = Math.max(maxEmail, SIGNED_IN_LABEL.length);
|
|
375
|
+
const accountLabel = accountColumnLabel(info);
|
|
376
|
+
if (accountLabel)
|
|
377
|
+
maxEmail = Math.max(maxEmail, accountLabel.length);
|
|
360
378
|
if (info?.plan)
|
|
361
379
|
maxPlanWidth = Math.max(maxPlanWidth, info.plan.length);
|
|
362
380
|
}
|
|
@@ -432,9 +450,10 @@ async function showInstalledVersions(filterAgentId) {
|
|
|
432
450
|
}
|
|
433
451
|
else {
|
|
434
452
|
if (hasEmail || hasUsage || hasActive || signedIn) {
|
|
435
|
-
// Signed-in agents without a local email
|
|
436
|
-
// "signed in" placeholder so they
|
|
437
|
-
|
|
453
|
+
// Signed-in agents without a local email show their account id
|
|
454
|
+
// (Kimi's user_id) or a "signed in" placeholder (Antigravity) so they
|
|
455
|
+
// read as logged in, not blank.
|
|
456
|
+
const display = accountColumnLabel(vInfo);
|
|
438
457
|
const emailCol = display.padEnd(maxEmail);
|
|
439
458
|
parts.push(display ? chalk.cyan(emailCol) : ' '.repeat(maxEmail));
|
|
440
459
|
}
|
|
@@ -515,7 +534,7 @@ async function showInstalledVersions(filterAgentId) {
|
|
|
515
534
|
const gUsageStr = formatUsageSummary(gInfo?.plan || null, gUsage?.snapshot || null);
|
|
516
535
|
const gActiveStr = gInfo ? formatLastActive(gInfo.lastActive) : '';
|
|
517
536
|
if (gInfo?.email || gUsageStr || gActiveStr || gInfo?.signedIn) {
|
|
518
|
-
const gDisplay =
|
|
537
|
+
const gDisplay = accountColumnLabel(gInfo);
|
|
519
538
|
parts.push(gDisplay ? chalk.cyan(gDisplay) : '');
|
|
520
539
|
}
|
|
521
540
|
if (gUsageStr || gActiveStr)
|
|
@@ -686,53 +705,9 @@ async function showAgentResources(agentId, requestedVersion, filter) {
|
|
|
686
705
|
}
|
|
687
706
|
}
|
|
688
707
|
const home = getVersionHomePath(agentId, version);
|
|
689
|
-
//
|
|
690
|
-
|
|
691
|
-
const hasGitRepo =
|
|
692
|
-
const commandsSync = hasGitRepo ? await getGitSyncStatus(userAgentsDir, 'commands') : null;
|
|
693
|
-
const skillsSync = hasGitRepo ? await getGitSyncStatus(userAgentsDir, 'skills') : null;
|
|
694
|
-
const hooksSync = hasGitRepo ? await getGitSyncStatus(userAgentsDir, 'hooks') : null;
|
|
695
|
-
const memorySync = hasGitRepo ? await getGitSyncStatus(userAgentsDir, 'rules') : null;
|
|
696
|
-
// Helper to determine sync state for a resource
|
|
697
|
-
const getSyncState = (resourceName, resourceType, syncStatus) => {
|
|
698
|
-
if (!syncStatus)
|
|
699
|
-
return undefined;
|
|
700
|
-
let relativePath;
|
|
701
|
-
if (resourceType === 'commands') {
|
|
702
|
-
relativePath = `commands/${resourceName}.md`;
|
|
703
|
-
}
|
|
704
|
-
else if (resourceType === 'skills') {
|
|
705
|
-
relativePath = `skills/${resourceName}`;
|
|
706
|
-
}
|
|
707
|
-
else if (resourceType === 'hooks') {
|
|
708
|
-
relativePath = `hooks/${resourceName}`;
|
|
709
|
-
}
|
|
710
|
-
else {
|
|
711
|
-
// Rules files: map agent-specific name (CLAUDE.md) back to canonical (AGENTS.md)
|
|
712
|
-
const centralName = getCentralRulesFileName(agentId);
|
|
713
|
-
relativePath = `rules/${centralName}`;
|
|
714
|
-
}
|
|
715
|
-
const matchesPath = (f) => f === relativePath || f.startsWith(relativePath + '/');
|
|
716
|
-
const isNew = syncStatus.new.some(matchesPath);
|
|
717
|
-
const isStaged = syncStatus.staged.some(matchesPath);
|
|
718
|
-
const isModified = syncStatus.modified.some(matchesPath);
|
|
719
|
-
const isDeleted = syncStatus.deleted.some(matchesPath);
|
|
720
|
-
const isSynced = syncStatus.synced.some(matchesPath);
|
|
721
|
-
if (isNew || isStaged) {
|
|
722
|
-
return 'new';
|
|
723
|
-
}
|
|
724
|
-
if (isModified) {
|
|
725
|
-
return 'modified';
|
|
726
|
-
}
|
|
727
|
-
if (isDeleted) {
|
|
728
|
-
return 'deleted';
|
|
729
|
-
}
|
|
730
|
-
if (isSynced) {
|
|
731
|
-
return 'synced';
|
|
732
|
-
}
|
|
733
|
-
// Not in any array = local-only (untracked with no files)
|
|
734
|
-
return 'new';
|
|
735
|
-
};
|
|
708
|
+
// Git sync status if ~/.agents/ is a git repo (shared loader — see
|
|
709
|
+
// loadResourceSyncData / resolveResourceSyncState, reused by --json --resources).
|
|
710
|
+
const { hasGitRepo, commands: commandsSync, skills: skillsSync, hooks: hooksSync, memory: memorySync } = await loadResourceSyncData();
|
|
736
711
|
const resources = getAgentResources(agentId, {
|
|
737
712
|
cwd,
|
|
738
713
|
scope: 'all',
|
|
@@ -745,23 +720,23 @@ async function showAgentResources(agentId, requestedVersion, filter) {
|
|
|
745
720
|
version,
|
|
746
721
|
commands: resources.commands.map(r => ({
|
|
747
722
|
...r,
|
|
748
|
-
syncState: r.scope === 'project' ? undefined :
|
|
723
|
+
syncState: r.scope === 'project' ? undefined : resolveResourceSyncState(agentId, r.name, 'commands', commandsSync),
|
|
749
724
|
})),
|
|
750
725
|
skills: resources.skills.map(r => ({
|
|
751
726
|
...r,
|
|
752
727
|
// ruleCount of 0 is noise — every skill has 0 unless it ships subrules, which is rare.
|
|
753
728
|
ruleCount: r.ruleCount && r.ruleCount > 0 ? r.ruleCount : undefined,
|
|
754
|
-
syncState: r.scope === 'project' ? undefined :
|
|
729
|
+
syncState: r.scope === 'project' ? undefined : resolveResourceSyncState(agentId, r.name, 'skills', skillsSync),
|
|
755
730
|
})),
|
|
756
731
|
skillErrors: resources.skillErrors,
|
|
757
732
|
mcp: resources.mcp.map(r => ({ name: r.name, scope: r.scope, syncState: r.scope === 'project' ? undefined : 'synced' })),
|
|
758
733
|
memory: resources.memory.map(r => ({
|
|
759
734
|
...r,
|
|
760
|
-
syncState: r.scope === 'project' ? undefined :
|
|
735
|
+
syncState: r.scope === 'project' ? undefined : resolveResourceSyncState(agentId, r.name, 'memory', memorySync),
|
|
761
736
|
})),
|
|
762
737
|
hooks: resources.hooks.map(r => ({
|
|
763
738
|
...r,
|
|
764
|
-
syncState: r.scope === 'project' ? undefined :
|
|
739
|
+
syncState: r.scope === 'project' ? undefined : resolveResourceSyncState(agentId, r.name, 'hooks', hooksSync),
|
|
765
740
|
})),
|
|
766
741
|
workflows: resources.workflows.map(r => ({ name: r.name, path: r.path, scope: r.scope })),
|
|
767
742
|
};
|
|
@@ -827,13 +802,10 @@ async function showAgentResources(agentId, requestedVersion, filter) {
|
|
|
827
802
|
cliVersion: version,
|
|
828
803
|
info: accountInfo,
|
|
829
804
|
});
|
|
830
|
-
const
|
|
831
|
-
|
|
832
|
-
: accountInfo.signedIn
|
|
833
|
-
? chalk.cyan(` ${SIGNED_IN_LABEL}`)
|
|
834
|
-
: '';
|
|
805
|
+
const accountLabel = accountColumnLabel(accountInfo);
|
|
806
|
+
const emailStr = accountLabel ? chalk.cyan(` ${accountLabel}`) : '';
|
|
835
807
|
const status = chalk.green(version);
|
|
836
|
-
const usageStr = formatUsageSummary(accountInfo.plan, null);
|
|
808
|
+
const usageStr = formatUsageSummary(usageInfo.snapshot?.plan ?? accountInfo.plan, null);
|
|
837
809
|
const usagePart = usageStr ? ` ${usageStr}` : '';
|
|
838
810
|
console.log(` ${colorAgent(agentId)(AGENTS[agentId].name.padEnd(14))} ${status}${emailStr}${usagePart}`);
|
|
839
811
|
const usageLines = formatUsageSection(usageInfo);
|
|
@@ -973,14 +945,144 @@ async function showAgentResources(agentId, requestedVersion, filter) {
|
|
|
973
945
|
console.log(chalk.gray('Legend:'), chalk.green('Tracked'), chalk.blue('Local-only'), chalk.yellow('Modified'), chalk.red('Deleted'));
|
|
974
946
|
}
|
|
975
947
|
}
|
|
948
|
+
const ALL_RESOURCE_SECTIONS = ['commands', 'skills', 'mcp', 'memory', 'hooks', 'workflows', 'plugins'];
|
|
949
|
+
/** Load git sync-state for the tracked resource kinds. Shared by the human
|
|
950
|
+
* detail view (showAgentResources) and the `--json --resources` path. */
|
|
951
|
+
async function loadResourceSyncData() {
|
|
952
|
+
const userAgentsDir = getUserAgentsDir();
|
|
953
|
+
const hasGitRepo = isGitRepo(userAgentsDir);
|
|
954
|
+
return {
|
|
955
|
+
hasGitRepo,
|
|
956
|
+
commands: hasGitRepo ? await getGitSyncStatus(userAgentsDir, 'commands') : null,
|
|
957
|
+
skills: hasGitRepo ? await getGitSyncStatus(userAgentsDir, 'skills') : null,
|
|
958
|
+
hooks: hasGitRepo ? await getGitSyncStatus(userAgentsDir, 'hooks') : null,
|
|
959
|
+
memory: hasGitRepo ? await getGitSyncStatus(userAgentsDir, 'rules') : null,
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
/** Resolve one resource's git sync-state. Extracted from showAgentResources so
|
|
963
|
+
* the human view and the JSON path derive drift identically. */
|
|
964
|
+
function resolveResourceSyncState(agentId, resourceName, resourceType, syncStatus) {
|
|
965
|
+
if (!syncStatus)
|
|
966
|
+
return undefined;
|
|
967
|
+
let relativePath;
|
|
968
|
+
if (resourceType === 'commands') {
|
|
969
|
+
relativePath = `commands/${resourceName}.md`;
|
|
970
|
+
}
|
|
971
|
+
else if (resourceType === 'skills') {
|
|
972
|
+
relativePath = `skills/${resourceName}`;
|
|
973
|
+
}
|
|
974
|
+
else if (resourceType === 'hooks') {
|
|
975
|
+
relativePath = `hooks/${resourceName}`;
|
|
976
|
+
}
|
|
977
|
+
else {
|
|
978
|
+
// Rules files: map agent-specific name (CLAUDE.md) back to canonical (AGENTS.md)
|
|
979
|
+
const centralName = getCentralRulesFileName(agentId);
|
|
980
|
+
relativePath = `rules/${centralName}`;
|
|
981
|
+
}
|
|
982
|
+
const matchesPath = (f) => f === relativePath || f.startsWith(relativePath + '/');
|
|
983
|
+
if (syncStatus.new.some(matchesPath) || syncStatus.staged.some(matchesPath))
|
|
984
|
+
return 'new';
|
|
985
|
+
if (syncStatus.modified.some(matchesPath))
|
|
986
|
+
return 'modified';
|
|
987
|
+
if (syncStatus.deleted.some(matchesPath))
|
|
988
|
+
return 'deleted';
|
|
989
|
+
if (syncStatus.synced.some(matchesPath))
|
|
990
|
+
return 'synced';
|
|
991
|
+
// Not in any array = local-only (untracked with no files)
|
|
992
|
+
return 'new';
|
|
993
|
+
}
|
|
994
|
+
/** Collect one version's resources for `--json`, limited to `sections`. Scans
|
|
995
|
+
* the version's `home`, so per-version differences are reported accurately. */
|
|
996
|
+
function collectVersionResources(agentId, home, cwd, cliInstalled, sections, sync) {
|
|
997
|
+
const res = getAgentResources(agentId, { cwd, scope: 'all', cliInstalled, home });
|
|
998
|
+
const out = {};
|
|
999
|
+
const withSync = (r, type, syncStatus) => {
|
|
1000
|
+
const item = { name: r.name, scope: r.scope };
|
|
1001
|
+
if (r.scope !== 'project') {
|
|
1002
|
+
const s = resolveResourceSyncState(agentId, r.name, type, syncStatus);
|
|
1003
|
+
if (s)
|
|
1004
|
+
item.syncState = s;
|
|
1005
|
+
}
|
|
1006
|
+
if (r.description)
|
|
1007
|
+
item.description = r.description;
|
|
1008
|
+
return item;
|
|
1009
|
+
};
|
|
1010
|
+
if (sections.has('commands'))
|
|
1011
|
+
out.commands = res.commands.map((r) => withSync(r, 'commands', sync.commands));
|
|
1012
|
+
if (sections.has('skills')) {
|
|
1013
|
+
out.skills = res.skills.map((r) => {
|
|
1014
|
+
const item = withSync(r, 'skills', sync.skills);
|
|
1015
|
+
// ruleCount of 0 is noise — every skill has 0 unless it ships subrules.
|
|
1016
|
+
if (r.ruleCount && r.ruleCount > 0)
|
|
1017
|
+
item.ruleCount = r.ruleCount;
|
|
1018
|
+
return item;
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
if (sections.has('mcp')) {
|
|
1022
|
+
out.mcp = res.mcp.map((r) => {
|
|
1023
|
+
const item = { name: r.name, scope: r.scope };
|
|
1024
|
+
if (r.scope !== 'project')
|
|
1025
|
+
item.syncState = 'synced';
|
|
1026
|
+
return item;
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
if (sections.has('memory'))
|
|
1030
|
+
out.memory = res.memory.map((r) => withSync(r, 'memory', sync.memory));
|
|
1031
|
+
if (sections.has('hooks'))
|
|
1032
|
+
out.hooks = res.hooks.map((r) => withSync(r, 'hooks', sync.hooks));
|
|
1033
|
+
if (sections.has('workflows'))
|
|
1034
|
+
out.workflows = res.workflows.map((r) => ({ name: r.name, scope: r.scope }));
|
|
1035
|
+
if (sections.has('plugins')) {
|
|
1036
|
+
out.plugins = discoverPlugins()
|
|
1037
|
+
.filter((p) => pluginSupportsAgent(p, agentId))
|
|
1038
|
+
.map((p) => ({ name: p.name }));
|
|
1039
|
+
}
|
|
1040
|
+
return out;
|
|
1041
|
+
}
|
|
1042
|
+
/** Build the set of resource sections to include in `--json` from the
|
|
1043
|
+
* `--resources` / `--detailed` flags, plus (in --json mode) the per-section
|
|
1044
|
+
* boolean filters (`--skills` etc.) that plain `--json` historically ignored. */
|
|
1045
|
+
export function parseResourceSections(options, jsonMode) {
|
|
1046
|
+
const set = new Set();
|
|
1047
|
+
const addAll = () => ALL_RESOURCE_SECTIONS.forEach((s) => set.add(s));
|
|
1048
|
+
if (options.detailed)
|
|
1049
|
+
addAll();
|
|
1050
|
+
const rv = options.resources;
|
|
1051
|
+
if (rv !== undefined) {
|
|
1052
|
+
if (rv === true || String(rv).trim() === '' || String(rv).toLowerCase() === 'all') {
|
|
1053
|
+
addAll();
|
|
1054
|
+
}
|
|
1055
|
+
else {
|
|
1056
|
+
for (const raw of String(rv).split(',').map((s) => s.trim().toLowerCase()).filter(Boolean)) {
|
|
1057
|
+
const section = raw === 'rules' ? 'memory' : raw; // --rules is the memory file
|
|
1058
|
+
if (ALL_RESOURCE_SECTIONS.includes(section))
|
|
1059
|
+
set.add(section);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
if (jsonMode) {
|
|
1064
|
+
const flagToSection = [
|
|
1065
|
+
['commands', 'commands'], ['skills', 'skills'], ['mcp', 'mcp'],
|
|
1066
|
+
['workflows', 'workflows'], ['plugins', 'plugins'], ['hooks', 'hooks'], ['rules', 'memory'],
|
|
1067
|
+
];
|
|
1068
|
+
for (const [flag, section] of flagToSection)
|
|
1069
|
+
if (options[flag])
|
|
1070
|
+
set.add(section);
|
|
1071
|
+
}
|
|
1072
|
+
return set;
|
|
1073
|
+
}
|
|
976
1074
|
/**
|
|
977
1075
|
* Collect structured info for one or more agents without rendering to the
|
|
978
1076
|
* terminal. Used by `--json` output and any programmatic consumer (e.g. the
|
|
979
1077
|
* agents-cli extension's "resume current session in best available version"
|
|
980
1078
|
* command).
|
|
981
1079
|
*/
|
|
982
|
-
async function collectAgentsJson(filterAgentId) {
|
|
1080
|
+
async function collectAgentsJson(filterAgentId, resourceSections) {
|
|
983
1081
|
const agentsToShow = filterAgentId ? [filterAgentId] : ALL_AGENT_IDS;
|
|
1082
|
+
const wantResources = !!resourceSections && resourceSections.size > 0;
|
|
1083
|
+
// Only pay for the git-status + resource scans when resources were requested.
|
|
1084
|
+
const resourceSync = wantResources ? await loadResourceSyncData() : null;
|
|
1085
|
+
const cliStates = wantResources ? await getAllCliStates() : null;
|
|
984
1086
|
const infoFetches = [];
|
|
985
1087
|
for (const agentId of agentsToShow) {
|
|
986
1088
|
for (const ver of listInstalledVersions(agentId)) {
|
|
@@ -1004,7 +1106,10 @@ async function collectAgentsJson(filterAgentId) {
|
|
|
1004
1106
|
return info;
|
|
1005
1107
|
return {
|
|
1006
1108
|
...info,
|
|
1007
|
-
plan
|
|
1109
|
+
// Prefer a plan the live usage fetch surfaced (Kimi reports membership
|
|
1110
|
+
// tier in /usages; its local auth file has none) and fall back to the
|
|
1111
|
+
// account-derived plan (Claude's billingType).
|
|
1112
|
+
plan: usageByKey.get(key)?.snapshot?.plan ?? canon.plan,
|
|
1008
1113
|
// Throttle state comes from the live usage windows, not the pay-as-you-go
|
|
1009
1114
|
// overage flag that AccountInfo.usageStatus used to carry. A maxed window
|
|
1010
1115
|
// means rate-limited; no snapshot means no badge. See
|
|
@@ -1014,7 +1119,7 @@ async function collectAgentsJson(filterAgentId) {
|
|
|
1014
1119
|
};
|
|
1015
1120
|
};
|
|
1016
1121
|
const byAgent = new Map();
|
|
1017
|
-
for (const { agentId, version, info: rawInfo } of infoResults) {
|
|
1122
|
+
for (const { agentId, version, home, info: rawInfo } of infoResults) {
|
|
1018
1123
|
const info = mergeCanonical(rawInfo);
|
|
1019
1124
|
const globalDefault = getGlobalDefault(agentId);
|
|
1020
1125
|
const usageKey = getUsageLookupKey(info);
|
|
@@ -1025,6 +1130,7 @@ async function collectAgentsJson(filterAgentId) {
|
|
|
1025
1130
|
isDefault: version === globalDefault,
|
|
1026
1131
|
signedIn: info.signedIn,
|
|
1027
1132
|
email: info.email,
|
|
1133
|
+
accountId: info.accountId,
|
|
1028
1134
|
plan: info.plan,
|
|
1029
1135
|
usageStatus: info.usageStatus,
|
|
1030
1136
|
overageCredits: info.overageCredits,
|
|
@@ -1038,6 +1144,9 @@ async function collectAgentsJson(filterAgentId) {
|
|
|
1038
1144
|
lastActive: info.lastActive ? info.lastActive.toISOString() : null,
|
|
1039
1145
|
path: getVersionDir(agentId, version),
|
|
1040
1146
|
};
|
|
1147
|
+
if (wantResources && resourceSync) {
|
|
1148
|
+
entry.resources = collectVersionResources(agentId, home, process.cwd(), cliStates?.[agentId]?.installed ?? false, resourceSections, resourceSync);
|
|
1149
|
+
}
|
|
1041
1150
|
const existing = byAgent.get(agentId);
|
|
1042
1151
|
if (existing)
|
|
1043
1152
|
existing.push(entry);
|
|
@@ -1248,7 +1357,10 @@ export async function pruneDuplicates(filterAgentId, yes, dryRun) {
|
|
|
1248
1357
|
* Exported for use by deprecated aliases.
|
|
1249
1358
|
*/
|
|
1250
1359
|
export async function viewAction(agentArg, options) {
|
|
1251
|
-
|
|
1360
|
+
// --resources / --detailed imply --json (they only shape structured output).
|
|
1361
|
+
const explicitResources = options?.detailed === true || options?.resources !== undefined;
|
|
1362
|
+
const json = options?.json === true || explicitResources;
|
|
1363
|
+
const resourceSections = parseResourceSections(options ?? {}, json);
|
|
1252
1364
|
const prune = options?.prune === true;
|
|
1253
1365
|
const yes = options?.yes === true;
|
|
1254
1366
|
const dryRun = options?.dryRun === true;
|
|
@@ -1280,7 +1392,7 @@ export async function viewAction(agentArg, options) {
|
|
|
1280
1392
|
return;
|
|
1281
1393
|
}
|
|
1282
1394
|
if (json) {
|
|
1283
|
-
const data = await collectAgentsJson();
|
|
1395
|
+
const data = await collectAgentsJson(undefined, resourceSections);
|
|
1284
1396
|
console.log(JSON.stringify(data, null, 2));
|
|
1285
1397
|
return;
|
|
1286
1398
|
}
|
|
@@ -1329,9 +1441,9 @@ export async function viewAction(agentArg, options) {
|
|
|
1329
1441
|
return;
|
|
1330
1442
|
}
|
|
1331
1443
|
if (json) {
|
|
1332
|
-
// --json ignores the @version suffix
|
|
1333
|
-
//
|
|
1334
|
-
const data = await collectAgentsJson(agentId);
|
|
1444
|
+
// --json ignores the @version suffix, but --resources/--detailed (or a
|
|
1445
|
+
// section flag) now attach each version's resource inventory + sync-state.
|
|
1446
|
+
const data = await collectAgentsJson(agentId, resourceSections);
|
|
1335
1447
|
console.log(JSON.stringify(data[0] ?? { agent: agentId, versions: [], profiles: [] }, null, 2));
|
|
1336
1448
|
return;
|
|
1337
1449
|
}
|
|
@@ -1354,6 +1466,8 @@ export function registerViewCommand(program) {
|
|
|
1354
1466
|
addHostOption(program.command('view [agent]'))
|
|
1355
1467
|
.description('Show what agent CLIs are installed and which versions you have. Inspect resources when you pass agent@version.')
|
|
1356
1468
|
.option('--json', 'Emit machine-readable JSON (version list, usage, signed-in status).')
|
|
1469
|
+
.option('--resources [sections]', 'In --json mode, include each version\'s resources: "all" (default) or a comma list (skills,plugins,mcp,commands,workflows,memory,hooks). Implies --json.')
|
|
1470
|
+
.option('--detailed', 'Include all resources in --json output (alias for --resources all). Implies --json.')
|
|
1357
1471
|
.option('--prune', 'Remove older installed versions that share an account with a newer installed version. Skips the global default.')
|
|
1358
1472
|
.option('--dry-run', 'With --prune, show duplicate versions without deleting')
|
|
1359
1473
|
.option('-y, --yes', 'Skip the prune confirmation prompt.')
|
|
@@ -1381,6 +1495,11 @@ Examples:
|
|
|
1381
1495
|
# Machine-readable output (used by tools that pick a version programmatically)
|
|
1382
1496
|
agents view claude --json
|
|
1383
1497
|
|
|
1498
|
+
# One call: full inventory + what's synced on a host (installed agents,
|
|
1499
|
+
# versions, accounts, usage, and per-version resource sync-state)
|
|
1500
|
+
agents view claude --host yosemite-s0 --json --resources all
|
|
1501
|
+
agents view claude --json --resources skills,plugins # just those sections
|
|
1502
|
+
|
|
1384
1503
|
# Prune older versions that duplicate an account already used by a newer version
|
|
1385
1504
|
agents view --prune --dry-run
|
|
1386
1505
|
agents view claude --prune
|