@phnx-labs/agents-cli 1.20.12 → 1.20.14
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 +30 -0
- package/README.md +3 -0
- package/dist/commands/computer-actions.d.ts +3 -0
- package/dist/commands/computer-actions.js +16 -0
- package/dist/commands/doctor.js +51 -7
- package/dist/commands/exec.js +25 -4
- package/dist/commands/import.js +17 -6
- package/dist/commands/inspect.d.ts +28 -1
- package/dist/commands/inspect.js +330 -47
- package/dist/commands/mcp.js +3 -3
- package/dist/commands/plugins.d.ts +2 -0
- package/dist/commands/plugins.js +69 -26
- package/dist/commands/prune.js +8 -5
- package/dist/commands/sync.js +1 -1
- package/dist/commands/teams.js +1 -0
- package/dist/commands/trash.d.ts +11 -0
- package/dist/commands/trash.js +57 -41
- package/dist/commands/versions.js +68 -20
- package/dist/commands/view.d.ts +1 -0
- package/dist/commands/view.js +56 -12
- package/dist/commands/wallet.d.ts +14 -0
- package/dist/commands/wallet.js +199 -0
- package/dist/index.js +4 -1
- package/dist/lib/agents.js +70 -22
- package/dist/lib/browser/ipc.d.ts +7 -0
- package/dist/lib/browser/ipc.js +43 -27
- package/dist/lib/capabilities.js +7 -1
- package/dist/lib/command-skills.d.ts +1 -0
- package/dist/lib/command-skills.js +23 -7
- package/dist/lib/exec.d.ts +32 -1
- package/dist/lib/exec.js +79 -7
- package/dist/lib/hooks.d.ts +21 -1
- package/dist/lib/hooks.js +69 -7
- package/dist/lib/mcp.js +33 -0
- package/dist/lib/models.js +5 -0
- package/dist/lib/picker.d.ts +2 -0
- package/dist/lib/picker.js +96 -6
- package/dist/lib/platform/index.d.ts +1 -0
- package/dist/lib/platform/index.js +1 -0
- package/dist/lib/platform/winpath.d.ts +35 -0
- package/dist/lib/platform/winpath.js +86 -0
- package/dist/lib/plugins.d.ts +24 -0
- package/dist/lib/plugins.js +37 -2
- package/dist/lib/project-launch.js +110 -5
- package/dist/lib/registry.js +15 -2
- package/dist/lib/rotate.d.ts +7 -0
- package/dist/lib/rotate.js +17 -7
- package/dist/lib/runner.js +14 -0
- package/dist/lib/sandbox.js +5 -2
- package/dist/lib/settings-manifest.d.ts +39 -0
- package/dist/lib/settings-manifest.js +163 -0
- package/dist/lib/shims.d.ts +1 -1
- package/dist/lib/shims.js +16 -31
- package/dist/lib/staleness/detectors/subagents.js +16 -0
- package/dist/lib/staleness/writers/subagents.js +11 -3
- package/dist/lib/subagents.d.ts +9 -0
- package/dist/lib/subagents.js +33 -0
- package/dist/lib/teams/agents.js +1 -1
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/teams/parsers.js +6 -0
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/versions.d.ts +15 -3
- package/dist/lib/versions.js +88 -19
- package/dist/lib/wallet/index.d.ts +78 -0
- package/dist/lib/wallet/index.js +253 -0
- package/package.json +3 -3
- package/scripts/postinstall.js +35 -7
package/dist/commands/plugins.js
CHANGED
|
@@ -12,7 +12,7 @@ import { homeDir } from '../lib/platform/index.js';
|
|
|
12
12
|
import { input } from '@inquirer/prompts';
|
|
13
13
|
import { agentLabel } from '../lib/agents.js';
|
|
14
14
|
import { capableAgents, isCapable } from '../lib/capabilities.js';
|
|
15
|
-
import { discoverPlugins, getPlugin, pluginSupportsAgent, removePluginFromVersion, isPluginSynced, installPlugin, updatePlugin, loadUserConfig, saveUserConfig, checkPluginDependencies, hasPluginExecSurfaces, inspectPluginCapabilities, pluginCapabilityLabels, parseInstallSpec, syncPluginToVersion, } from '../lib/plugins.js';
|
|
15
|
+
import { discoverPlugins, getPlugin, pluginSupportsAgent, removePluginFromVersion, isPluginSynced, installPlugin, updatePlugin, loadUserConfig, saveUserConfig, checkPluginDependencies, hasPluginExecSurfaces, inspectPluginCapabilities, pluginCapabilityLabels, parseInstallSpec, syncPluginToVersion, pluginResourceGroups, } from '../lib/plugins.js';
|
|
16
16
|
import { listInstalledVersions, syncResourcesToVersion, getGlobalDefault, getVersionHomePath, } from '../lib/versions.js';
|
|
17
17
|
import { isPromptCancelled, isInteractiveTerminal, requireDestructiveArg, requireInteractiveSelection, promptRemovalTargets, } from './utils.js';
|
|
18
18
|
import { itemPicker } from '../lib/picker.js';
|
|
@@ -276,14 +276,17 @@ Examples:
|
|
|
276
276
|
// agents plugins sync <name> [agent]
|
|
277
277
|
pluginsCmd
|
|
278
278
|
.command('sync <name> [agent]')
|
|
279
|
-
.description('Apply a plugin to
|
|
279
|
+
.description('Apply a plugin to an agent. Syncs every installed version (pass agent@version to target one).')
|
|
280
280
|
.option('--allow-exec-surfaces', 'Enable the plugin even when it ships hooks/, .mcp.json, bin/, scripts/, settings.json, or permissions/')
|
|
281
281
|
.addHelpText('after', `
|
|
282
282
|
Examples:
|
|
283
|
-
# Sync a plugin to
|
|
283
|
+
# Sync a plugin to every installed version of an agent
|
|
284
284
|
agents plugins sync rush-toolkit claude
|
|
285
285
|
|
|
286
|
-
# Sync to
|
|
286
|
+
# Sync to one specific version (parity with 'agents sync')
|
|
287
|
+
agents plugins sync rush-toolkit claude@2.1.142
|
|
288
|
+
|
|
289
|
+
# Sync to all supported agents (every installed version of each)
|
|
287
290
|
agents plugins sync rush-toolkit
|
|
288
291
|
|
|
289
292
|
# Re-affirm consent for a hooks-bearing plugin
|
|
@@ -295,12 +298,22 @@ Examples:
|
|
|
295
298
|
console.log(chalk.red(`Plugin '${name}' not found`));
|
|
296
299
|
process.exit(1);
|
|
297
300
|
}
|
|
301
|
+
// Accept the same "agent@version" form as `agents sync`. Splitting here
|
|
302
|
+
// also means an unknown spec is reported cleanly rather than crashing
|
|
303
|
+
// isCapable() with a bare "claude@2.1.168".
|
|
304
|
+
let versionArg;
|
|
305
|
+
let agentName = agentArg;
|
|
306
|
+
if (agentArg && agentArg.includes('@')) {
|
|
307
|
+
const at = agentArg.lastIndexOf('@');
|
|
308
|
+
agentName = agentArg.slice(0, at);
|
|
309
|
+
versionArg = agentArg.slice(at + 1);
|
|
310
|
+
}
|
|
298
311
|
// Determine target agents
|
|
299
312
|
let targetAgents;
|
|
300
|
-
if (
|
|
301
|
-
const agentId =
|
|
313
|
+
if (agentName) {
|
|
314
|
+
const agentId = agentName;
|
|
302
315
|
if (!isCapable(agentId, 'plugins')) {
|
|
303
|
-
console.log(chalk.red(`Agent '${
|
|
316
|
+
console.log(chalk.red(`Agent '${agentName}' does not support plugins`));
|
|
304
317
|
process.exit(1);
|
|
305
318
|
}
|
|
306
319
|
if (!pluginSupportsAgent(plugin, agentId)) {
|
|
@@ -310,6 +323,10 @@ Examples:
|
|
|
310
323
|
targetAgents = [agentId];
|
|
311
324
|
}
|
|
312
325
|
else {
|
|
326
|
+
if (versionArg) {
|
|
327
|
+
console.log(chalk.red(`A version (@${versionArg}) requires naming the agent, e.g. claude@${versionArg}`));
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
313
330
|
targetAgents = capableAgents('plugins').filter(a => pluginSupportsAgent(plugin, a));
|
|
314
331
|
}
|
|
315
332
|
const allowExec = options.allowExecSurfaces === true;
|
|
@@ -317,8 +334,21 @@ Examples:
|
|
|
317
334
|
const versions = listInstalledVersions(agentId);
|
|
318
335
|
if (versions.length === 0)
|
|
319
336
|
continue;
|
|
320
|
-
|
|
321
|
-
|
|
337
|
+
// Default to EVERY installed version. The previous behaviour synced only
|
|
338
|
+
// the global default, which silently skipped non-default versions used
|
|
339
|
+
// by balanced rotation -- so a rotated version would lack the plugin's
|
|
340
|
+
// slash commands. An explicit agent@version narrows back to one.
|
|
341
|
+
let targetVersions;
|
|
342
|
+
if (versionArg) {
|
|
343
|
+
if (!versions.includes(versionArg)) {
|
|
344
|
+
console.log(chalk.red(`${agentLabel(agentId)} has no installed version ${versionArg} (installed: ${versions.join(', ')})`));
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
targetVersions = [versionArg];
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
targetVersions = versions;
|
|
351
|
+
}
|
|
322
352
|
for (const version of targetVersions) {
|
|
323
353
|
const didSync = allowExec
|
|
324
354
|
? syncPluginToVersion(plugin, agentId, getVersionHomePath(agentId, version), { allowExecSurfaces: true, version }).success
|
|
@@ -711,6 +741,32 @@ function buildPluginRows(plugins) {
|
|
|
711
741
|
});
|
|
712
742
|
return rows;
|
|
713
743
|
}
|
|
744
|
+
/** Per-category color for a plugin resource breakdown (shared with `agents inspect`). */
|
|
745
|
+
export const PLUGIN_GROUP_COLORS = {
|
|
746
|
+
skills: chalk.cyan,
|
|
747
|
+
commands: chalk.cyan,
|
|
748
|
+
subagents: chalk.magenta,
|
|
749
|
+
hooks: chalk.yellow,
|
|
750
|
+
mcp: chalk.green,
|
|
751
|
+
lsp: chalk.green,
|
|
752
|
+
monitors: chalk.blue,
|
|
753
|
+
bin: chalk.white,
|
|
754
|
+
scripts: chalk.white,
|
|
755
|
+
settings: chalk.gray,
|
|
756
|
+
};
|
|
757
|
+
/** Human-readable section header per category, used by the picker detail pane. */
|
|
758
|
+
const PLUGIN_GROUP_TITLES = {
|
|
759
|
+
skills: 'Skills',
|
|
760
|
+
commands: 'Commands',
|
|
761
|
+
subagents: 'Subagents',
|
|
762
|
+
hooks: 'Hooks',
|
|
763
|
+
mcp: 'MCP Servers',
|
|
764
|
+
lsp: 'LSP Servers',
|
|
765
|
+
monitors: 'Monitors',
|
|
766
|
+
bin: 'Bin',
|
|
767
|
+
scripts: 'Scripts',
|
|
768
|
+
settings: 'Settings',
|
|
769
|
+
};
|
|
714
770
|
/** Build the multi-line detail pane shown when a plugin is selected in the picker. */
|
|
715
771
|
function formatPluginDetail(plugin, targets) {
|
|
716
772
|
const lines = [];
|
|
@@ -728,24 +784,11 @@ function formatPluginDetail(plugin, targets) {
|
|
|
728
784
|
lines.push(' ' + chalk.gray('Supports: ') + supported.join(chalk.gray(' · ')));
|
|
729
785
|
}
|
|
730
786
|
lines.push(' ' + chalk.gray(formatPath(plugin.root)));
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
return;
|
|
787
|
+
for (const group of pluginResourceGroups(plugin)) {
|
|
788
|
+
const colorFn = PLUGIN_GROUP_COLORS[group.label] ?? chalk.white;
|
|
734
789
|
lines.push('');
|
|
735
|
-
lines.push(chalk.bold(` ${label}`));
|
|
736
|
-
lines.push(' ' + items.map(colorFn).join(chalk.gray(', ')));
|
|
737
|
-
};
|
|
738
|
-
section('Skills', plugin.skills.map((s) => `/${plugin.name}:${s}`), chalk.cyan);
|
|
739
|
-
section('Commands', plugin.commands.map((c) => `/${plugin.name}:${c}`), chalk.cyan);
|
|
740
|
-
section('Subagents', plugin.agentDefs, chalk.magenta);
|
|
741
|
-
section('Hooks', plugin.hooks, chalk.yellow);
|
|
742
|
-
section('MCP Servers', plugin.mcpServers, chalk.green);
|
|
743
|
-
section('LSP Servers', plugin.lspServers, chalk.green);
|
|
744
|
-
section('Monitors', plugin.monitors, chalk.blue);
|
|
745
|
-
section('Bin', plugin.bin, chalk.white);
|
|
746
|
-
section('Scripts', plugin.scripts, chalk.white);
|
|
747
|
-
if (plugin.hasSettings) {
|
|
748
|
-
section('Settings', ['settings.json'], chalk.gray);
|
|
790
|
+
lines.push(chalk.bold(` ${PLUGIN_GROUP_TITLES[group.label] ?? group.label}`));
|
|
791
|
+
lines.push(' ' + group.items.map((s) => colorFn(s)).join(chalk.gray(', ')));
|
|
749
792
|
}
|
|
750
793
|
if (targets.length > 0) {
|
|
751
794
|
lines.push('');
|
package/dist/commands/prune.js
CHANGED
|
@@ -26,7 +26,7 @@ import chalk from 'chalk';
|
|
|
26
26
|
import { confirm } from '@inquirer/prompts';
|
|
27
27
|
import { diffVersionCommands, iterCommandsCapableVersions, removeCommandFromVersion, } from '../lib/commands.js';
|
|
28
28
|
import { diffVersionSkills, iterSkillsCapableVersions, removeSkillFromVersion, } from '../lib/skills.js';
|
|
29
|
-
import {
|
|
29
|
+
import { listUnmanagedHooksInVersionHome, iterHooksCapableVersions, removeHookFromVersion, } from '../lib/hooks.js';
|
|
30
30
|
import { diffVersionPlugins, iterPluginsCapableVersions, removePluginSkillFromVersion, } from '../lib/plugins.js';
|
|
31
31
|
import { diffVersionSubagents, iterSubagentsCapableVersions, removeSubagentFromVersion, } from '../lib/subagents.js';
|
|
32
32
|
import { getGlobalDefault } from '../lib/versions.js';
|
|
@@ -64,9 +64,12 @@ function collectOrphans(types, all) {
|
|
|
64
64
|
}
|
|
65
65
|
if (types.includes('hooks')) {
|
|
66
66
|
for (const { agent, version } of scopePairs(iterHooksCapableVersions(), all)) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
// Orphan hooks = scripts present in the version home that no
|
|
68
|
+
// agents.yaml/hooks.yaml entry registers, so they never fire. Same
|
|
69
|
+
// definition the doctor overview reports.
|
|
70
|
+
const orphans = listUnmanagedHooksInVersionHome(agent, version);
|
|
71
|
+
if (orphans.length > 0) {
|
|
72
|
+
groups.push({ type: 'hooks', agent, version, orphans });
|
|
70
73
|
}
|
|
71
74
|
}
|
|
72
75
|
}
|
|
@@ -210,7 +213,7 @@ async function runOrphanPrune(resourceTypes, options) {
|
|
|
210
213
|
return;
|
|
211
214
|
}
|
|
212
215
|
const total = groups.reduce((n, g) => n + g.orphans.length, 0);
|
|
213
|
-
console.log(chalk.bold('Orphans (in version home,
|
|
216
|
+
console.log(chalk.bold('Orphans (in version home, unmanaged by any source)\n'));
|
|
214
217
|
for (const g of groups) {
|
|
215
218
|
const label = `${g.type} · ${g.agent}@${g.version}`;
|
|
216
219
|
console.log(` ${chalk.cyan(label)} ${g.orphans.join(', ')}`);
|
package/dist/commands/sync.js
CHANGED
package/dist/commands/teams.js
CHANGED
package/dist/commands/trash.d.ts
CHANGED
|
@@ -7,4 +7,15 @@
|
|
|
7
7
|
* `rm -rf ~/.agents/.history/trash/` removes bytes from disk.
|
|
8
8
|
*/
|
|
9
9
|
import type { Command } from 'commander';
|
|
10
|
+
/**
|
|
11
|
+
* Restore a soft-deleted version back into ~/.agents/.history/versions/.
|
|
12
|
+
* Shared by `agents trash restore` and the top-level `agents restore` alias.
|
|
13
|
+
* Exits the process with a non-zero code on any failure.
|
|
14
|
+
*/
|
|
15
|
+
export declare function restoreVersion(target: string): void;
|
|
16
|
+
/**
|
|
17
|
+
* Register the top-level `agents restore` command — a shorthand for
|
|
18
|
+
* `agents trash restore` so users can undo a `remove`/`prune` directly.
|
|
19
|
+
*/
|
|
20
|
+
export declare function registerRestoreCommand(program: Command): void;
|
|
10
21
|
export declare function registerTrashCommands(program: Command): void;
|
package/dist/commands/trash.js
CHANGED
|
@@ -110,6 +110,61 @@ function humanSize(bytes) {
|
|
|
110
110
|
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
111
111
|
return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`;
|
|
112
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Restore a soft-deleted version back into ~/.agents/.history/versions/.
|
|
115
|
+
* Shared by `agents trash restore` and the top-level `agents restore` alias.
|
|
116
|
+
* Exits the process with a non-zero code on any failure.
|
|
117
|
+
*/
|
|
118
|
+
export function restoreVersion(target) {
|
|
119
|
+
const parsed = parseAgentVersion(target);
|
|
120
|
+
if (!parsed) {
|
|
121
|
+
console.error(chalk.red(`Expected <agent>@<version>, got: ${target}`));
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
const { agent, version } = parsed;
|
|
125
|
+
const entries = listTrashEntries(agent);
|
|
126
|
+
const entry = pickLatest(entries, agent, version);
|
|
127
|
+
if (!entry) {
|
|
128
|
+
console.error(chalk.red(`No trashed copy found for ${agent}@${version}`));
|
|
129
|
+
console.error(chalk.gray('Run `agents trash list` to see what exists.'));
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
const dest = getVersionDir(agent, version);
|
|
133
|
+
if (fs.existsSync(dest)) {
|
|
134
|
+
console.error(chalk.red(`Cannot restore: ${dest} already exists.`));
|
|
135
|
+
console.error(chalk.gray('Move or remove the existing dir first, then re-run restore.'));
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true, mode: 0o700 });
|
|
140
|
+
fs.renameSync(entry.trashPath, dest);
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
console.error(chalk.red(`Restore failed: ${err.message}`));
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
// Best-effort cleanup of empty stamp/version parents in trash.
|
|
147
|
+
try {
|
|
148
|
+
const verDir = path.dirname(entry.trashPath);
|
|
149
|
+
if (fs.readdirSync(verDir).length === 0)
|
|
150
|
+
fs.rmdirSync(verDir);
|
|
151
|
+
const agentDir = path.dirname(verDir);
|
|
152
|
+
if (fs.readdirSync(agentDir).length === 0)
|
|
153
|
+
fs.rmdirSync(agentDir);
|
|
154
|
+
}
|
|
155
|
+
catch { /* best-effort */ }
|
|
156
|
+
console.log(chalk.green(`Restored ${agentLabel(agent)}@${version} to ${dest}`));
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Register the top-level `agents restore` command — a shorthand for
|
|
160
|
+
* `agents trash restore` so users can undo a `remove`/`prune` directly.
|
|
161
|
+
*/
|
|
162
|
+
export function registerRestoreCommand(program) {
|
|
163
|
+
program
|
|
164
|
+
.command('restore <target>')
|
|
165
|
+
.description('Restore a soft-deleted agent version (e.g. "codex@0.141.0") removed via prune/remove')
|
|
166
|
+
.action((target) => restoreVersion(target));
|
|
167
|
+
}
|
|
113
168
|
export function registerTrashCommands(program) {
|
|
114
169
|
const trash = program
|
|
115
170
|
.command('trash')
|
|
@@ -139,49 +194,10 @@ export function registerTrashCommands(program) {
|
|
|
139
194
|
chalk.gray(`${e.stamp} ${size} ${e.trashPath}`));
|
|
140
195
|
}
|
|
141
196
|
console.log();
|
|
142
|
-
console.log(chalk.gray('Restore with: agents
|
|
197
|
+
console.log(chalk.gray('Restore with: agents restore <agent>@<version>'));
|
|
143
198
|
});
|
|
144
199
|
trash
|
|
145
200
|
.command('restore <target>')
|
|
146
201
|
.description('Restore a soft-deleted version (e.g. "claude@2.1.110") back to ~/.agents/.history/versions/')
|
|
147
|
-
.action((target) =>
|
|
148
|
-
const parsed = parseAgentVersion(target);
|
|
149
|
-
if (!parsed) {
|
|
150
|
-
console.error(chalk.red(`Expected <agent>@<version>, got: ${target}`));
|
|
151
|
-
process.exit(1);
|
|
152
|
-
}
|
|
153
|
-
const { agent, version } = parsed;
|
|
154
|
-
const entries = listTrashEntries(agent);
|
|
155
|
-
const entry = pickLatest(entries, agent, version);
|
|
156
|
-
if (!entry) {
|
|
157
|
-
console.error(chalk.red(`No trashed copy found for ${agent}@${version}`));
|
|
158
|
-
console.error(chalk.gray('Run `agents trash list` to see what exists.'));
|
|
159
|
-
process.exit(1);
|
|
160
|
-
}
|
|
161
|
-
const dest = getVersionDir(agent, version);
|
|
162
|
-
if (fs.existsSync(dest)) {
|
|
163
|
-
console.error(chalk.red(`Cannot restore: ${dest} already exists.`));
|
|
164
|
-
console.error(chalk.gray('Move or remove the existing dir first, then re-run restore.'));
|
|
165
|
-
process.exit(1);
|
|
166
|
-
}
|
|
167
|
-
try {
|
|
168
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true, mode: 0o700 });
|
|
169
|
-
fs.renameSync(entry.trashPath, dest);
|
|
170
|
-
}
|
|
171
|
-
catch (err) {
|
|
172
|
-
console.error(chalk.red(`Restore failed: ${err.message}`));
|
|
173
|
-
process.exit(1);
|
|
174
|
-
}
|
|
175
|
-
// Best-effort cleanup of empty stamp/version parents in trash.
|
|
176
|
-
try {
|
|
177
|
-
const verDir = path.dirname(entry.trashPath);
|
|
178
|
-
if (fs.readdirSync(verDir).length === 0)
|
|
179
|
-
fs.rmdirSync(verDir);
|
|
180
|
-
const agentDir = path.dirname(verDir);
|
|
181
|
-
if (fs.readdirSync(agentDir).length === 0)
|
|
182
|
-
fs.rmdirSync(agentDir);
|
|
183
|
-
}
|
|
184
|
-
catch { /* best-effort */ }
|
|
185
|
-
console.log(chalk.green(`Restored ${agentLabel(agent)}@${version} to ${dest}`));
|
|
186
|
-
});
|
|
202
|
+
.action((target) => restoreVersion(target));
|
|
187
203
|
}
|
|
@@ -7,7 +7,8 @@ import { AGENTS, ALL_AGENT_IDS, getAccountEmail, getAccountInfo, agentLabel, } f
|
|
|
7
7
|
import { formatUsageSummary, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, } from '../lib/usage.js';
|
|
8
8
|
import { viewAction } from './view.js';
|
|
9
9
|
import { readManifest, writeManifest, createDefaultManifest } from '../lib/manifest.js';
|
|
10
|
-
import { installVersion, removeVersion, listInstalledVersions, isVersionInstalled, isLatestInstalled, getGlobalDefault, setGlobalDefault, getVersionHomePath, getVersionDir, syncResourcesToVersion, parseAgentSpec, promptResourceSelection, promptNewResourceSelection, getAvailableResources, getActuallySyncedResources, getNewResources, getProjectOnlyResources, hasNewResources, printTrashFooter, } from '../lib/versions.js';
|
|
10
|
+
import { installVersion, removeVersion, listInstalledVersions, isVersionInstalled, isLatestInstalled, isOldestInstalled, getGlobalDefault, setGlobalDefault, getVersionHomePath, getVersionDir, syncResourcesToVersion, parseAgentSpec, promptResourceSelection, promptNewResourceSelection, getAvailableResources, getActuallySyncedResources, getNewResources, getProjectOnlyResources, hasNewResources, printTrashFooter, } from '../lib/versions.js';
|
|
11
|
+
import { carryForwardSettings } from '../lib/settings-manifest.js';
|
|
11
12
|
import { createShim, createVersionedAlias, removeShim, shimExists, getShimsDir, getShimPath, getPathShadowingExecutable, isShimsInPath, getPathSetupInstructions, addShimsToPath, switchConfigSymlink, switchHomeFileSymlinks, } from '../lib/shims.js';
|
|
12
13
|
import { isInteractiveTerminal, isPromptCancelled, requireInteractiveSelection } from './utils.js';
|
|
13
14
|
import { tryAutoPull } from '../lib/git.js';
|
|
@@ -28,17 +29,6 @@ function fixSessionFilePaths(agent, version, oldVersionDir) {
|
|
|
28
29
|
const trashPath = path.join(trashAgentDir, stamps[0]);
|
|
29
30
|
updateSessionFilePaths(oldVersionDir, trashPath);
|
|
30
31
|
}
|
|
31
|
-
/**
|
|
32
|
-
* Helper to get actual installed version for an agent.
|
|
33
|
-
* Returns the latest installed version, or throws if none installed.
|
|
34
|
-
*/
|
|
35
|
-
async function getInstalledVersionForAgent(agent) {
|
|
36
|
-
const versions = listInstalledVersions(agent);
|
|
37
|
-
if (versions.length > 0) {
|
|
38
|
-
return versions[versions.length - 1];
|
|
39
|
-
}
|
|
40
|
-
throw new Error(`No versions of ${agent} installed`);
|
|
41
|
-
}
|
|
42
32
|
function formatAccountHint(info, usage) {
|
|
43
33
|
const parts = [];
|
|
44
34
|
if (info.email)
|
|
@@ -91,7 +81,17 @@ function warnIfShimShadowed(agent) {
|
|
|
91
81
|
}
|
|
92
82
|
console.log(chalk.yellow(` Warning: ${AGENTS[agent].cliCommand} currently resolves to ${shadowedBy}`));
|
|
93
83
|
console.log(chalk.gray(` Managed shim: ${getShimPath(agent)}`));
|
|
94
|
-
|
|
84
|
+
const result = addShimsToPath();
|
|
85
|
+
if (!result.success) {
|
|
86
|
+
console.log(chalk.gray(` ${getPathSetupInstructions().split('\n').join('\n ')}`));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (result.alreadyPresent) {
|
|
90
|
+
console.log(chalk.gray(` Shim PATH entry already set — ${AGENTS[agent].cliCommand} is shadowed by another binary. Remove or reorder it so ${getShimPath(agent)} takes priority.`));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
console.log(chalk.green(` Added shim directory to ${result.location}.`));
|
|
94
|
+
console.log(chalk.gray(` ${result.reloadHint}`));
|
|
95
95
|
}
|
|
96
96
|
async function versionPruneAction(specs, options, commandName) {
|
|
97
97
|
const isProject = options.project;
|
|
@@ -105,7 +105,7 @@ async function versionPruneAction(specs, options, commandName) {
|
|
|
105
105
|
}
|
|
106
106
|
const { agent, version } = parsed;
|
|
107
107
|
const agentConfig = AGENTS[agent];
|
|
108
|
-
if (version === 'latest' || !spec.includes('@')) {
|
|
108
|
+
if (version === 'latest' || version === 'oldest' || !spec.includes('@')) {
|
|
109
109
|
const versions = listInstalledVersions(agent);
|
|
110
110
|
if (versions.length === 0) {
|
|
111
111
|
console.log(chalk.gray(`No versions of ${agentLabel(agentConfig.id)} installed`));
|
|
@@ -139,7 +139,11 @@ async function versionPruneAction(specs, options, commandName) {
|
|
|
139
139
|
}
|
|
140
140
|
for (const v of toRemove) {
|
|
141
141
|
const versionDir = getVersionDir(agent, v);
|
|
142
|
-
removeVersion(agent, v);
|
|
142
|
+
const removed = removeVersion(agent, v);
|
|
143
|
+
if (!removed) {
|
|
144
|
+
console.log(chalk.red(`Failed to move ${agentLabel(agentConfig.id)}@${v} to trash — a file may be locked by a running process. Close any active sessions and try again.`));
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
143
147
|
fixSessionFilePaths(agent, v, versionDir);
|
|
144
148
|
console.log(chalk.green(`Moved ${agentLabel(agentConfig.id)}@${v} to trash`));
|
|
145
149
|
moved.push({ agent, version: v });
|
|
@@ -166,7 +170,11 @@ async function versionPruneAction(specs, options, commandName) {
|
|
|
166
170
|
}
|
|
167
171
|
else {
|
|
168
172
|
const versionDir = getVersionDir(agent, version);
|
|
169
|
-
removeVersion(agent, version);
|
|
173
|
+
const removed = removeVersion(agent, version);
|
|
174
|
+
if (!removed) {
|
|
175
|
+
console.log(chalk.red(`Failed to move ${agentLabel(agentConfig.id)}@${version} to trash — a file may be locked by a running process. Close any active sessions and try again.`));
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
170
178
|
fixSessionFilePaths(agent, version, versionDir);
|
|
171
179
|
console.log(chalk.green(`Moved ${agentLabel(agentConfig.id)}@${version} to trash`));
|
|
172
180
|
moved.push({ agent, version });
|
|
@@ -228,6 +236,9 @@ export function registerVersionsCommands(program) {
|
|
|
228
236
|
# Install the latest version of an agent
|
|
229
237
|
agents add claude@latest
|
|
230
238
|
|
|
239
|
+
# Install the oldest published version of an agent
|
|
240
|
+
agents add claude@oldest
|
|
241
|
+
|
|
231
242
|
# Install a specific version (reproducibility)
|
|
232
243
|
agents add claude@2.1.112
|
|
233
244
|
|
|
@@ -259,7 +270,7 @@ export function registerVersionsCommands(program) {
|
|
|
259
270
|
console.log(chalk.yellow(`${agentLabel(agentConfig.id)} has no npm package. Install manually.`));
|
|
260
271
|
continue;
|
|
261
272
|
}
|
|
262
|
-
// Check if already installed (
|
|
273
|
+
// Check if already installed (resolve 'latest'/'oldest' against npm first)
|
|
263
274
|
let alreadyInstalled = false;
|
|
264
275
|
let installedAsVersion = version;
|
|
265
276
|
if (version === 'latest') {
|
|
@@ -269,6 +280,13 @@ export function registerVersionsCommands(program) {
|
|
|
269
280
|
installedAsVersion = latestCheck.version;
|
|
270
281
|
}
|
|
271
282
|
}
|
|
283
|
+
else if (version === 'oldest') {
|
|
284
|
+
const oldestCheck = await isOldestInstalled(agent);
|
|
285
|
+
if (oldestCheck.installed && oldestCheck.version) {
|
|
286
|
+
alreadyInstalled = true;
|
|
287
|
+
installedAsVersion = oldestCheck.version;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
272
290
|
else {
|
|
273
291
|
alreadyInstalled = isVersionInstalled(agent, version);
|
|
274
292
|
}
|
|
@@ -290,6 +308,19 @@ export function registerVersionsCommands(program) {
|
|
|
290
308
|
console.log(chalk.gray(` Created shim: ${getShimsDir()}/${agentConfig.cliCommand}`));
|
|
291
309
|
}
|
|
292
310
|
const installedVersion = result.installedVersion || version;
|
|
311
|
+
// Track the concrete version so a `--project` pin records it instead
|
|
312
|
+
// of the `latest`/`oldest` alias.
|
|
313
|
+
installedAsVersion = installedVersion;
|
|
314
|
+
// Seed the fresh version home with user settings from the current
|
|
315
|
+
// default version (settings.json, keybindings, codex config/auth).
|
|
316
|
+
// Gap-filling only — never overwrites what the new home has.
|
|
317
|
+
const carrySource = getGlobalDefault(agent);
|
|
318
|
+
if (carrySource && carrySource !== installedVersion) {
|
|
319
|
+
const carried = carryForwardSettings(agent, getVersionHomePath(agent, carrySource), getVersionHomePath(agent, installedVersion));
|
|
320
|
+
if (carried.applied.length > 0) {
|
|
321
|
+
console.log(chalk.gray(` Carried settings from ${agent}@${carrySource}: ${carried.applied.map(r => path.basename(r)).join(', ')}`));
|
|
322
|
+
}
|
|
323
|
+
}
|
|
293
324
|
// Smart resource detection: compare available vs ACTUALLY synced (source of truth: files)
|
|
294
325
|
const available = getAvailableResources();
|
|
295
326
|
const actuallySynced = getActuallySyncedResources(agent, installedVersion);
|
|
@@ -428,14 +459,19 @@ export function registerVersionsCommands(program) {
|
|
|
428
459
|
? readManifest(process.cwd()) || createDefaultManifest()
|
|
429
460
|
: createDefaultManifest();
|
|
430
461
|
manifest.agents = manifest.agents || {};
|
|
431
|
-
manifest.agents[agent] = version === 'latest'
|
|
462
|
+
manifest.agents[agent] = (version === 'latest' || version === 'oldest')
|
|
463
|
+
? installedAsVersion
|
|
464
|
+
: version;
|
|
432
465
|
writeManifest(process.cwd(), manifest);
|
|
433
466
|
console.log(chalk.green(` Pinned ${agentLabel(agentConfig.id)}@${version} in .agents/agents.yaml`));
|
|
434
467
|
}
|
|
435
468
|
}
|
|
436
469
|
});
|
|
437
470
|
configureVersionPruneCommand(program.command('prune <specs...>'), 'prune');
|
|
438
|
-
|
|
471
|
+
// `rm` and `purge` are commander aliases for `remove` (which is itself an
|
|
472
|
+
// alias for `prune`). Native `.aliases()` keeps them in lockstep — same
|
|
473
|
+
// action, same options, no duplicate registration.
|
|
474
|
+
configureVersionPruneCommand(program.command('remove <specs...>', { hidden: true }).aliases(['rm', 'purge']), 'remove');
|
|
439
475
|
const useCmd = program
|
|
440
476
|
.command('use <agent> [version]')
|
|
441
477
|
.description('Switch the active version for an agent. This is the only command that sets the default.')
|
|
@@ -478,7 +514,7 @@ export function registerVersionsCommands(program) {
|
|
|
478
514
|
return;
|
|
479
515
|
}
|
|
480
516
|
agent = parsed.agent;
|
|
481
|
-
version = parsed.version === 'latest' ? undefined : parsed.version;
|
|
517
|
+
version = (parsed.version === 'latest' || parsed.version === 'oldest') ? undefined : parsed.version;
|
|
482
518
|
}
|
|
483
519
|
else {
|
|
484
520
|
const agentLower = agentArg.toLowerCase();
|
|
@@ -679,6 +715,18 @@ export function registerVersionsCommands(program) {
|
|
|
679
715
|
}
|
|
680
716
|
}
|
|
681
717
|
const previousDefault = getGlobalDefault(agentId);
|
|
718
|
+
// Carry user settings from the outgoing default into the target
|
|
719
|
+
// version home before switching. Gap-filling only, so versions that
|
|
720
|
+
// already have their own settings are left untouched.
|
|
721
|
+
if (previousDefault && previousDefault !== finalVersion) {
|
|
722
|
+
const carried = carryForwardSettings(agentId, getVersionHomePath(agentId, previousDefault), getVersionHomePath(agentId, finalVersion));
|
|
723
|
+
if (carried.applied.length > 0) {
|
|
724
|
+
console.log(chalk.gray(`Carried settings from ${agentId}@${previousDefault}: ${carried.applied.map(r => path.basename(r)).join(', ')}`));
|
|
725
|
+
if (carried.backupDir) {
|
|
726
|
+
console.log(chalk.gray(` Pre-merge backup: ${carried.backupDir}`));
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
682
730
|
// Set global default
|
|
683
731
|
setGlobalDefault(agentId, finalVersion);
|
|
684
732
|
// Regenerate shim so it uses the latest script format
|
package/dist/commands/view.d.ts
CHANGED
package/dist/commands/view.js
CHANGED
|
@@ -6,8 +6,9 @@ import { AGENTS, ALL_AGENT_IDS, getAllCliStates, getAccountInfo, resolveAgentNam
|
|
|
6
6
|
import { formatUsageSection, formatUsageSummary, formatUsageStatusBadge, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, } from '../lib/usage.js';
|
|
7
7
|
import { readManifest } from '../lib/manifest.js';
|
|
8
8
|
import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, resolveVersionAlias, getAvailableResources, getActuallySyncedResources, getNewResources, getProjectOnlyResources, hasNewResources, promptNewResourceSelection, syncResourcesToVersion, removeVersion, printTrashFooter, } from '../lib/versions.js';
|
|
9
|
-
import {
|
|
9
|
+
import { ensureVersionedAliasCurrent, removeShim, } from '../lib/shims.js';
|
|
10
10
|
import { getAgentResources } from '../lib/resources.js';
|
|
11
|
+
import { listCliStatus } from '../lib/cli-resources.js';
|
|
11
12
|
import { isCapable } from '../lib/capabilities.js';
|
|
12
13
|
import { discoverPlugins, pluginSupportsAgent } from '../lib/plugins.js';
|
|
13
14
|
import { getAgentsDir, getUserAgentsDir, getEffectivePromptcutsPath, readMergedPromptcuts } from '../lib/state.js';
|
|
@@ -114,7 +115,7 @@ function getProjectVersionFromCwd(agent) {
|
|
|
114
115
|
return null;
|
|
115
116
|
}
|
|
116
117
|
}
|
|
117
|
-
const SECTION_KEYS = ['commands', 'skills', 'mcp', 'workflows', 'plugins', 'rules', 'hooks', 'promptcuts'];
|
|
118
|
+
const SECTION_KEYS = ['commands', 'skills', 'mcp', 'workflows', 'plugins', 'rules', 'hooks', 'promptcuts', 'cli'];
|
|
118
119
|
/**
|
|
119
120
|
* Decide whether a section should render given the filter. If no flags are set,
|
|
120
121
|
* everything renders (current behavior). If any flag is set, only those sections
|
|
@@ -167,6 +168,51 @@ function renderProfilesSection(profiles) {
|
|
|
167
168
|
* Show installed versions for one or all agents.
|
|
168
169
|
* Called when: `agents view` or `agents view claude`
|
|
169
170
|
*/
|
|
171
|
+
/** Color the source-layer tag for a host CLI, matching the rules-section convention. */
|
|
172
|
+
function hostCliSourceTag(source) {
|
|
173
|
+
if (source === 'project')
|
|
174
|
+
return chalk.blue('[project]');
|
|
175
|
+
if (source === 'user')
|
|
176
|
+
return chalk.cyan('[user]');
|
|
177
|
+
if (source === 'system')
|
|
178
|
+
return chalk.gray('[system]');
|
|
179
|
+
// Anything else is an extra repo, tagged by its alias.
|
|
180
|
+
return chalk.magenta(`[${source}]`);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Render the host-CLI section. Host CLIs are host-global: declared in any
|
|
184
|
+
* DotAgents repo's `cli/` (project > user > system > extras), installed to PATH
|
|
185
|
+
* rather than copied into a version home. They render identically in the overview
|
|
186
|
+
* and in a per-agent detail view because every agent on the host shares them.
|
|
187
|
+
* The source tag shows which repo layer declared each — so user-level and
|
|
188
|
+
* extra-repo manifests are visibly supported.
|
|
189
|
+
*/
|
|
190
|
+
function renderHostClisSection(cwd) {
|
|
191
|
+
const { statuses, errors } = listCliStatus(cwd);
|
|
192
|
+
console.log(chalk.bold('\nHost CLIs\n'));
|
|
193
|
+
if (statuses.length === 0) {
|
|
194
|
+
console.log(` ${chalk.gray('none declared')} ${chalk.gray('— add one with `agents cli add <name>`')}`);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
const nameWidth = Math.max(...statuses.map((s) => s.manifest.name.length));
|
|
198
|
+
let anyMissing = false;
|
|
199
|
+
for (const { manifest, installed } of statuses) {
|
|
200
|
+
if (!installed)
|
|
201
|
+
anyMissing = true;
|
|
202
|
+
const status = installed ? chalk.green('installed') : chalk.red('missing ');
|
|
203
|
+
const linkedName = termLink(manifest.name.padEnd(nameWidth), linkTarget(manifest.path));
|
|
204
|
+
const tag = hostCliSourceTag(manifest.source);
|
|
205
|
+
const desc = manifest.description ? chalk.gray(` ${summarizeDescription(manifest.description, 60)}`) : '';
|
|
206
|
+
console.log(` ${status} ${chalk.cyan(linkedName)} ${tag}${desc}`);
|
|
207
|
+
}
|
|
208
|
+
if (anyMissing) {
|
|
209
|
+
console.log(chalk.gray(' Install missing with `agents cli install`'));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
for (const err of errors) {
|
|
213
|
+
console.log(` ${chalk.red('error')} ${chalk.gray(err.file)}: ${chalk.gray(err.reason)}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
170
216
|
async function showInstalledVersions(filterAgentId) {
|
|
171
217
|
const spinnerText = filterAgentId
|
|
172
218
|
? `Checking ${agentLabel(filterAgentId)} agents...`
|
|
@@ -537,16 +583,9 @@ async function showInstalledVersions(filterAgentId) {
|
|
|
537
583
|
console.log(chalk.gray(' Run: agents add claude@latest'));
|
|
538
584
|
console.log();
|
|
539
585
|
}
|
|
540
|
-
//
|
|
541
|
-
if (
|
|
542
|
-
|
|
543
|
-
if (isShimsInPath()) {
|
|
544
|
-
console.log(chalk.gray(`Shims: ${shimsDir} (in PATH)`));
|
|
545
|
-
}
|
|
546
|
-
else {
|
|
547
|
-
console.log(chalk.yellow(`Shims: ${shimsDir} (not in PATH)`));
|
|
548
|
-
console.log(chalk.gray('Add to PATH for automatic version switching'));
|
|
549
|
-
}
|
|
586
|
+
// Host CLIs are host-global, not per-agent — show them once in the overview.
|
|
587
|
+
if (!filterAgentId) {
|
|
588
|
+
renderHostClisSection(process.cwd());
|
|
550
589
|
}
|
|
551
590
|
// Check for new resources when viewing a specific agent
|
|
552
591
|
if (filterAgentId && versionManaged.length > 0) {
|
|
@@ -903,6 +942,9 @@ async function showAgentResources(agentId, requestedVersion, filter) {
|
|
|
903
942
|
if (shouldRenderSection('promptcuts', filter)) {
|
|
904
943
|
renderPromptcuts();
|
|
905
944
|
}
|
|
945
|
+
if (shouldRenderSection('cli', filter)) {
|
|
946
|
+
renderHostClisSection(cwd);
|
|
947
|
+
}
|
|
906
948
|
// Show legend at the end if git repo exists and we showed all sections.
|
|
907
949
|
// Filtered single-section views skip it — noise for promptcuts or plugins.
|
|
908
950
|
if (hasGitRepo && !anyFilterSet) {
|
|
@@ -1194,6 +1236,7 @@ export async function viewAction(agentArg, options) {
|
|
|
1194
1236
|
rules: options?.rules,
|
|
1195
1237
|
hooks: options?.hooks,
|
|
1196
1238
|
promptcuts: options?.promptcuts,
|
|
1239
|
+
cli: options?.cli,
|
|
1197
1240
|
};
|
|
1198
1241
|
const filterIsSet = SECTION_KEYS.some((k) => filter[k]);
|
|
1199
1242
|
if (!agentArg) {
|
|
@@ -1274,6 +1317,7 @@ export function registerViewCommand(program) {
|
|
|
1274
1317
|
.option('--rules', 'Show only rules in the detail view.')
|
|
1275
1318
|
.option('--hooks', 'Show only hooks in the detail view.')
|
|
1276
1319
|
.option('--promptcuts', 'Show only promptcuts in the detail view.')
|
|
1320
|
+
.option('--cli', 'Show only host CLIs (declared in cli/, installed to PATH).')
|
|
1277
1321
|
.addHelpText('after', `
|
|
1278
1322
|
Examples:
|
|
1279
1323
|
# Show all installed agents with versions, accounts, and usage
|