@phnx-labs/agents-cli 1.15.0 → 1.16.0
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 +78 -39
- package/README.md +6 -6
- package/dist/commands/alias.js +2 -2
- package/dist/commands/browser-picker.d.ts +21 -0
- package/dist/commands/browser-picker.js +114 -0
- package/dist/commands/browser.js +546 -75
- package/dist/commands/commands.js +72 -22
- package/dist/commands/daemon.js +2 -2
- package/dist/commands/fork.js +2 -2
- package/dist/commands/hooks.js +71 -26
- package/dist/commands/mcp.js +81 -39
- package/dist/commands/plugins.js +48 -15
- package/dist/commands/prune.js +23 -1
- package/dist/commands/pull.js +3 -3
- package/dist/commands/repo.js +1 -1
- package/dist/commands/routines.js +2 -2
- package/dist/commands/secrets.js +37 -1
- package/dist/commands/sessions.js +62 -19
- package/dist/commands/{init.d.ts → setup.d.ts} +7 -6
- package/dist/commands/{init.js → setup.js} +22 -21
- package/dist/commands/skills.js +60 -19
- package/dist/commands/subagents.js +41 -13
- package/dist/commands/utils.d.ts +16 -0
- package/dist/commands/utils.js +32 -0
- package/dist/commands/view.js +61 -16
- package/dist/index.d.ts +1 -1
- package/dist/index.js +17 -20
- package/dist/lib/agents.js +2 -2
- package/dist/lib/auto-pull-worker.js +2 -3
- package/dist/lib/auto-pull.js +2 -2
- package/dist/lib/browser/cdp.d.ts +7 -1
- package/dist/lib/browser/cdp.js +29 -1
- package/dist/lib/browser/chrome.js +5 -2
- package/dist/lib/browser/devices.d.ts +4 -0
- package/dist/lib/browser/devices.js +27 -0
- package/dist/lib/browser/drivers/local.js +9 -4
- package/dist/lib/browser/drivers/ssh.js +9 -2
- package/dist/lib/browser/ipc.js +144 -23
- package/dist/lib/browser/profiles.d.ts +5 -2
- package/dist/lib/browser/profiles.js +77 -37
- package/dist/lib/browser/service.d.ts +81 -13
- package/dist/lib/browser/service.js +738 -131
- package/dist/lib/browser/types.d.ts +81 -3
- package/dist/lib/browser/types.js +16 -0
- package/dist/lib/cloud/rush.js +2 -2
- package/dist/lib/cloud/store.js +2 -2
- package/dist/lib/commands.d.ts +1 -0
- package/dist/lib/commands.js +6 -2
- package/dist/lib/daemon.js +2 -3
- package/dist/lib/doctor-diff.js +4 -4
- package/dist/lib/events.js +2 -2
- package/dist/lib/hooks.d.ts +11 -7
- package/dist/lib/hooks.js +125 -49
- package/dist/lib/migrate.d.ts +1 -1
- package/dist/lib/migrate.js +1178 -21
- package/dist/lib/models.js +2 -2
- package/dist/lib/permissions.d.ts +8 -8
- package/dist/lib/permissions.js +8 -8
- package/dist/lib/plugins.d.ts +30 -1
- package/dist/lib/plugins.js +75 -3
- package/dist/lib/pty-server.js +9 -10
- package/dist/lib/resources/hooks.d.ts +5 -1
- package/dist/lib/resources/hooks.js +21 -4
- package/dist/lib/rotate.js +3 -4
- package/dist/lib/session/active.d.ts +3 -0
- package/dist/lib/session/active.js +92 -6
- package/dist/lib/session/cloud.js +2 -2
- package/dist/lib/session/db.js +8 -3
- package/dist/lib/session/discover.js +30 -15
- package/dist/lib/session/team-filter.js +2 -2
- package/dist/lib/shims.d.ts +2 -2
- package/dist/lib/shims.js +6 -6
- package/dist/lib/skills.js +6 -2
- package/dist/lib/state.d.ts +86 -14
- package/dist/lib/state.js +150 -23
- package/dist/lib/subagents.d.ts +28 -0
- package/dist/lib/subagents.js +98 -1
- package/dist/lib/sync-manifest.d.ts +1 -1
- package/dist/lib/sync-manifest.js +3 -3
- package/dist/lib/teams/persistence.js +15 -5
- package/dist/lib/teams/registry.js +2 -2
- package/dist/lib/types.d.ts +32 -3
- package/dist/lib/types.js +3 -3
- package/dist/lib/usage.js +2 -2
- package/dist/lib/versions.js +20 -21
- package/package.json +1 -1
- package/scripts/postinstall.js +1 -1
|
@@ -571,8 +571,8 @@ Examples:
|
|
|
571
571
|
.action(async (options) => {
|
|
572
572
|
if (options.follow) {
|
|
573
573
|
const { exec: execCb } = await import('child_process');
|
|
574
|
-
const {
|
|
575
|
-
const logPath = path.join(
|
|
574
|
+
const { getDaemonDir } = await import('../lib/state.js');
|
|
575
|
+
const logPath = path.join(getDaemonDir(), 'logs.jsonl');
|
|
576
576
|
const child = execCb(`tail -f "${logPath}"`);
|
|
577
577
|
child.stdout?.pipe(process.stdout);
|
|
578
578
|
child.stderr?.pipe(process.stderr);
|
package/dist/commands/secrets.js
CHANGED
|
@@ -280,6 +280,10 @@ Examples:
|
|
|
280
280
|
# Eval the bundle into your current shell
|
|
281
281
|
eval "$(agents secrets export prod --plaintext)"
|
|
282
282
|
|
|
283
|
+
# Run a command with secrets injected
|
|
284
|
+
agents secrets exec prod -- ./deploy.sh
|
|
285
|
+
agents secrets exec hetzner.com -- crabbox list
|
|
286
|
+
|
|
283
287
|
# Remove one key (purges the keychain item by default)
|
|
284
288
|
agents secrets remove prod STRIPE_API_KEY
|
|
285
289
|
|
|
@@ -297,7 +301,7 @@ Examples:
|
|
|
297
301
|
registerCommandGroups(cmd, [
|
|
298
302
|
{ title: 'Bundle commands', names: ['list', 'view', 'create', 'delete'] },
|
|
299
303
|
{ title: 'Secret commands', names: ['add', 'rotate', 'remove', 'import', 'export'] },
|
|
300
|
-
{ title: 'Utilities', names: ['generate'] },
|
|
304
|
+
{ title: 'Utilities', names: ['exec', 'generate'] },
|
|
301
305
|
]);
|
|
302
306
|
cmd
|
|
303
307
|
.command('list')
|
|
@@ -711,6 +715,38 @@ Examples:
|
|
|
711
715
|
process.exit(1);
|
|
712
716
|
}
|
|
713
717
|
});
|
|
718
|
+
cmd
|
|
719
|
+
.command('exec <bundle> [command...]')
|
|
720
|
+
.description('Run a command with the bundle\'s secrets injected into the environment')
|
|
721
|
+
.allowUnknownOption()
|
|
722
|
+
.action(async (bundleName, commandParts) => {
|
|
723
|
+
try {
|
|
724
|
+
if (commandParts.length === 0) {
|
|
725
|
+
console.error(chalk.red('Usage: agents secrets exec <bundle> -- <command...>'));
|
|
726
|
+
process.exit(1);
|
|
727
|
+
}
|
|
728
|
+
const { resolveBundleEnv } = await import('../lib/secrets/bundles.js');
|
|
729
|
+
const bundle = readBundle(bundleName);
|
|
730
|
+
const secretEnv = resolveBundleEnv(bundle);
|
|
731
|
+
const { spawn } = await import('child_process');
|
|
732
|
+
const [cmd, ...args] = commandParts;
|
|
733
|
+
const proc = spawn(cmd, args, {
|
|
734
|
+
stdio: 'inherit',
|
|
735
|
+
env: { ...process.env, ...secretEnv },
|
|
736
|
+
});
|
|
737
|
+
proc.on('close', (code) => process.exit(code ?? 0));
|
|
738
|
+
proc.on('error', (err) => {
|
|
739
|
+
console.error(chalk.red(`Failed to run '${cmd}': ${err.message}`));
|
|
740
|
+
process.exit(1);
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
catch (err) {
|
|
744
|
+
if (isPromptCancelled(err))
|
|
745
|
+
return;
|
|
746
|
+
console.error(chalk.red(err.message));
|
|
747
|
+
process.exit(1);
|
|
748
|
+
}
|
|
749
|
+
});
|
|
714
750
|
cmd
|
|
715
751
|
.command('generate [length]')
|
|
716
752
|
.description('Generate a random password')
|
|
@@ -160,6 +160,26 @@ function formatStartedAt(startedAtMs) {
|
|
|
160
160
|
return '-';
|
|
161
161
|
return formatRelativeTime(new Date(startedAtMs).toISOString());
|
|
162
162
|
}
|
|
163
|
+
/** Build a display-friendly description for an active session (label or topic). */
|
|
164
|
+
function buildSessionDescription(s) {
|
|
165
|
+
if (s.context === 'cloud') {
|
|
166
|
+
return `${s.cloudProvider ?? ''}${s.cloudTaskId ? ` · ${s.cloudTaskId.slice(0, 12)}` : ''}`;
|
|
167
|
+
}
|
|
168
|
+
if (s.context === 'teams') {
|
|
169
|
+
const parts = [s.teamName];
|
|
170
|
+
if (s.label)
|
|
171
|
+
parts.push(s.label);
|
|
172
|
+
else if (s.topic)
|
|
173
|
+
parts.push(s.topic);
|
|
174
|
+
return parts.filter(Boolean).join(' · ');
|
|
175
|
+
}
|
|
176
|
+
// Terminal or headless: prefer label, then topic
|
|
177
|
+
if (s.label)
|
|
178
|
+
return s.label;
|
|
179
|
+
if (s.topic)
|
|
180
|
+
return s.topic;
|
|
181
|
+
return '';
|
|
182
|
+
}
|
|
163
183
|
/** Render the unified active-session view. */
|
|
164
184
|
async function renderActiveSessions(asJson) {
|
|
165
185
|
const sessions = await getActiveSessions();
|
|
@@ -171,26 +191,49 @@ async function renderActiveSessions(asJson) {
|
|
|
171
191
|
console.log(chalk.gray('No active agent sessions.'));
|
|
172
192
|
return;
|
|
173
193
|
}
|
|
194
|
+
// Group sessions by workspace (cwd), with cloud/undefined grouped separately
|
|
195
|
+
const byWorkspace = new Map();
|
|
174
196
|
for (const s of sessions) {
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
197
|
+
const key = s.cwd ?? (s.context === 'cloud' ? '__cloud__' : '__unknown__');
|
|
198
|
+
const list = byWorkspace.get(key) || [];
|
|
199
|
+
list.push(s);
|
|
200
|
+
byWorkspace.set(key, list);
|
|
201
|
+
}
|
|
202
|
+
// Sort workspaces: most sessions first, then alphabetically
|
|
203
|
+
const sortedKeys = Array.from(byWorkspace.keys()).sort((a, b) => {
|
|
204
|
+
const aCount = byWorkspace.get(a).length;
|
|
205
|
+
const bCount = byWorkspace.get(b).length;
|
|
206
|
+
if (aCount !== bCount)
|
|
207
|
+
return bCount - aCount;
|
|
208
|
+
return a.localeCompare(b);
|
|
209
|
+
});
|
|
210
|
+
let first = true;
|
|
211
|
+
for (const key of sortedKeys) {
|
|
212
|
+
const group = byWorkspace.get(key);
|
|
213
|
+
if (!first)
|
|
214
|
+
console.log();
|
|
215
|
+
first = false;
|
|
216
|
+
// Print workspace header
|
|
217
|
+
const header = key === '__cloud__'
|
|
218
|
+
? chalk.magenta.bold('cloud')
|
|
219
|
+
: key === '__unknown__'
|
|
220
|
+
? chalk.gray.bold('unknown')
|
|
221
|
+
: chalk.cyan.bold(shortCwd(key));
|
|
222
|
+
console.log(`${header} ${chalk.gray(`(${group.length})`)}`);
|
|
223
|
+
// Print each session in this workspace
|
|
224
|
+
for (const s of group) {
|
|
225
|
+
const kindCol = colorAgent(s.kind)(padRight(truncate(s.kind, 8), 9));
|
|
226
|
+
const hostCol = chalk.gray(padRight(truncate(s.host ?? '-', 8), 9));
|
|
227
|
+
const statusCol = statusColor(s.status)(padRight(truncate(s.status, 7), 8));
|
|
228
|
+
const pidCol = chalk.yellow(padRight(s.pid ? String(s.pid) : '-', 7));
|
|
229
|
+
const desc = buildSessionDescription(s);
|
|
230
|
+
console.log(' ' +
|
|
231
|
+
pidCol +
|
|
232
|
+
kindCol +
|
|
233
|
+
hostCol +
|
|
234
|
+
statusCol +
|
|
235
|
+
chalk.white(truncate(desc || '-', 50)));
|
|
236
|
+
}
|
|
194
237
|
}
|
|
195
238
|
const runningCount = sessions.filter(s => s.status === 'running').length;
|
|
196
239
|
const idleCount = sessions.filter(s => s.status === 'idle').length;
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* First-run
|
|
2
|
+
* First-run setup command.
|
|
3
3
|
*
|
|
4
|
-
* Registers the `agents
|
|
4
|
+
* Registers the `agents setup` command which clones the system repo into
|
|
5
5
|
* ~/.agents-system/ and installs agent CLIs with resource syncing.
|
|
6
6
|
*/
|
|
7
7
|
import type { Command } from 'commander';
|
|
8
8
|
/** First-run setup. Clones ~/.agents-system/ from the system repo if needed. */
|
|
9
|
-
export declare function
|
|
9
|
+
export declare function runSetup(program: Command, options?: {
|
|
10
10
|
force?: boolean;
|
|
11
|
+
suppressFooter?: boolean;
|
|
11
12
|
}): Promise<void>;
|
|
12
13
|
/**
|
|
13
14
|
* Ensure the system repo exists before running a command that needs it.
|
|
14
15
|
* If ~/.agents-system/ is not a git repo AND we're in an interactive TTY,
|
|
15
|
-
* prompt the user to run
|
|
16
|
+
* prompt the user to run setup now. In non-interactive mode, print a clear
|
|
16
17
|
* error and exit.
|
|
17
18
|
*/
|
|
18
19
|
export declare function ensureInitialized(program: Command): Promise<void>;
|
|
19
|
-
/** Register the `agents
|
|
20
|
-
export declare function
|
|
20
|
+
/** Register the `agents setup` command. */
|
|
21
|
+
export declare function registerSetupCommand(program: Command): void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* First-run
|
|
2
|
+
* First-run setup command.
|
|
3
3
|
*
|
|
4
|
-
* Registers the `agents
|
|
4
|
+
* Registers the `agents setup` command which clones the system repo into
|
|
5
5
|
* ~/.agents-system/ and installs agent CLIs with resource syncing.
|
|
6
6
|
*/
|
|
7
7
|
import chalk from 'chalk';
|
|
@@ -52,13 +52,13 @@ async function importAgent(agentId, version) {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
/** First-run setup. Clones ~/.agents-system/ from the system repo if needed. */
|
|
55
|
-
export async function
|
|
55
|
+
export async function runSetup(program, options = {}) {
|
|
56
56
|
const agentsDir = getAgentsDir();
|
|
57
57
|
const alreadyConfigured = isGitRepo(agentsDir);
|
|
58
58
|
if (alreadyConfigured && !options.force) {
|
|
59
59
|
console.log(chalk.gray('~/.agents-system/ is already set up.'));
|
|
60
60
|
console.log(chalk.gray('\nTo sync updates: agents repo pull system'));
|
|
61
|
-
console.log(chalk.gray('To re-
|
|
61
|
+
console.log(chalk.gray('To re-run setup: agents setup --force'));
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
64
|
// Detect existing installations BEFORE cloning (they won't exist after if we import)
|
|
@@ -76,7 +76,7 @@ export async function runInit(program, options = {}) {
|
|
|
76
76
|
const result = await pullRepo(agentsDir);
|
|
77
77
|
if (!result.success) {
|
|
78
78
|
spinner.fail(`Pull failed: ${result.error}`);
|
|
79
|
-
console.log(chalk.gray('Fix the issue and re-run: agents
|
|
79
|
+
console.log(chalk.gray('Fix the issue and re-run: agents setup --force'));
|
|
80
80
|
process.exit(1);
|
|
81
81
|
}
|
|
82
82
|
spinner.succeed(`Updated to ${result.commit}`);
|
|
@@ -95,7 +95,7 @@ export async function runInit(program, options = {}) {
|
|
|
95
95
|
const result = await cloneIntoExisting(DEFAULT_SYSTEM_REPO, agentsDir);
|
|
96
96
|
if (!result.success) {
|
|
97
97
|
spinner.fail(`Clone failed: ${result.error}`);
|
|
98
|
-
console.log(chalk.gray('Fix the issue and re-run: agents
|
|
98
|
+
console.log(chalk.gray('Fix the issue and re-run: agents setup --force'));
|
|
99
99
|
process.exit(1);
|
|
100
100
|
}
|
|
101
101
|
spinner.succeed(`Cloned ${systemRepoSlug(DEFAULT_SYSTEM_REPO)} (${result.commit})`);
|
|
@@ -156,6 +156,8 @@ export async function runInit(program, options = {}) {
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
|
+
if (options.suppressFooter)
|
|
160
|
+
return;
|
|
159
161
|
console.log(chalk.bold('\nSetup complete. Try:'));
|
|
160
162
|
console.log(chalk.cyan(' agents view ') + chalk.gray(' # see what\'s installed'));
|
|
161
163
|
console.log(chalk.cyan(' agents run <agent> "hello" ') + chalk.gray(' # run an agent'));
|
|
@@ -165,7 +167,7 @@ export async function runInit(program, options = {}) {
|
|
|
165
167
|
/**
|
|
166
168
|
* Ensure the system repo exists before running a command that needs it.
|
|
167
169
|
* If ~/.agents-system/ is not a git repo AND we're in an interactive TTY,
|
|
168
|
-
* prompt the user to run
|
|
170
|
+
* prompt the user to run setup now. In non-interactive mode, print a clear
|
|
169
171
|
* error and exit.
|
|
170
172
|
*/
|
|
171
173
|
export async function ensureInitialized(program) {
|
|
@@ -173,34 +175,33 @@ export async function ensureInitialized(program) {
|
|
|
173
175
|
if (isGitRepo(agentsDir))
|
|
174
176
|
return;
|
|
175
177
|
if (!isInteractiveTerminal()) {
|
|
176
|
-
console.error(chalk.red('agents-cli is not
|
|
178
|
+
console.error(chalk.red('agents-cli is not set up. Run: agents setup'));
|
|
177
179
|
process.exit(1);
|
|
178
180
|
}
|
|
179
181
|
console.log(chalk.yellow('\nagents-cli has not been set up yet.'));
|
|
180
182
|
const proceed = await confirm({
|
|
181
|
-
message: 'Run `agents
|
|
183
|
+
message: 'Run `agents setup` now?',
|
|
182
184
|
default: true,
|
|
183
185
|
}).catch(() => false);
|
|
184
186
|
if (!proceed) {
|
|
185
|
-
console.log(chalk.gray('Skipped. Run `agents
|
|
187
|
+
console.log(chalk.gray('Skipped. Run `agents setup` when ready.'));
|
|
186
188
|
process.exit(0);
|
|
187
189
|
}
|
|
188
|
-
await
|
|
189
|
-
process.exit(0);
|
|
190
|
+
await runSetup(program, { suppressFooter: true });
|
|
190
191
|
}
|
|
191
|
-
/** Register the `agents
|
|
192
|
-
export function
|
|
192
|
+
/** Register the `agents setup` command. */
|
|
193
|
+
export function registerSetupCommand(program) {
|
|
193
194
|
program
|
|
194
|
-
.command('
|
|
195
|
+
.command('setup')
|
|
195
196
|
.description('Set up agents-cli for the first time. Clones a config repo and installs agent CLIs.')
|
|
196
|
-
.option('-f, --force', '
|
|
197
|
+
.option('-f, --force', 'Re-run setup even if ~/.agents-system/ already exists (use with caution)')
|
|
197
198
|
.addHelpText('after', `
|
|
198
199
|
Examples:
|
|
199
200
|
# First-time setup (clones the system repo into ~/.agents-system/)
|
|
200
|
-
agents
|
|
201
|
+
agents setup
|
|
201
202
|
|
|
202
|
-
# Re-
|
|
203
|
-
agents
|
|
203
|
+
# Re-run setup after corruption
|
|
204
|
+
agents setup --force
|
|
204
205
|
|
|
205
206
|
When to use:
|
|
206
207
|
- First time running agents-cli: this is your starting point
|
|
@@ -213,12 +214,12 @@ What it does:
|
|
|
213
214
|
3. Syncs commands, skills, hooks, and MCP servers to each version
|
|
214
215
|
|
|
215
216
|
Non-interactive alternative:
|
|
216
|
-
Skip '
|
|
217
|
+
Skip 'setup' and run:
|
|
217
218
|
agents pull
|
|
218
219
|
`)
|
|
219
220
|
.action(async (options) => {
|
|
220
221
|
try {
|
|
221
|
-
await
|
|
222
|
+
await runSetup(program, options);
|
|
222
223
|
}
|
|
223
224
|
catch (err) {
|
|
224
225
|
if (isPromptCancelled(err)) {
|
package/dist/commands/skills.js
CHANGED
|
@@ -4,12 +4,12 @@ import * as fs from 'fs';
|
|
|
4
4
|
import * as os from 'os';
|
|
5
5
|
import * as path from 'path';
|
|
6
6
|
import { select, checkbox } from '@inquirer/prompts';
|
|
7
|
-
import { SKILLS_CAPABLE_AGENTS, resolveAgentName, formatAgentError, agentLabel, } from '../lib/agents.js';
|
|
7
|
+
import { AGENTS, SKILLS_CAPABLE_AGENTS, resolveAgentName, formatAgentError, agentLabel, } from '../lib/agents.js';
|
|
8
8
|
import { cloneRepo } from '../lib/git.js';
|
|
9
|
-
import { discoverSkillsFromRepo, installSkillCentrally,
|
|
10
|
-
import { getGlobalDefault, resolveVersionAlias, syncResourcesToVersion, promptAgentVersionSelection, resolveAgentVersionTargets, } from '../lib/versions.js';
|
|
9
|
+
import { discoverSkillsFromRepo, installSkillCentrally, listInstalledSkills, listInstalledSkillsWithScope, getSkillInfo, getSkillRules, getSkillsDir, countSkillFiles, tryParseSkillMetadata, diffVersionSkills, iterSkillsCapableVersions, removeSkillFromVersion, } from '../lib/skills.js';
|
|
10
|
+
import { getGlobalDefault, resolveVersionAlias, syncResourcesToVersion, promptAgentVersionSelection, getVersionHomePath, resolveAgentVersionTargets, } from '../lib/versions.js';
|
|
11
11
|
import { recordVersionResources } from '../lib/state.js';
|
|
12
|
-
import { isPromptCancelled, isInteractiveTerminal, parseCommaSeparatedList, printWithPager, requireInteractiveSelection, } from './utils.js';
|
|
12
|
+
import { isPromptCancelled, isInteractiveTerminal, parseCommaSeparatedList, printWithPager, requireInteractiveSelection, promptRemovalTargets, } from './utils.js';
|
|
13
13
|
import { showResourceList, buildTargetsSection, } from './resource-view.js';
|
|
14
14
|
/** Register the `agents skills` command tree (list, add, remove, sync, prune, view). */
|
|
15
15
|
export function registerSkillsCommands(program) {
|
|
@@ -294,15 +294,29 @@ Examples:
|
|
|
294
294
|
agents skills remove
|
|
295
295
|
`)
|
|
296
296
|
.action(async (name) => {
|
|
297
|
+
const skillTargetMap = new Map();
|
|
298
|
+
for (const { agent, version } of iterSkillsCapableVersions()) {
|
|
299
|
+
const home = getVersionHomePath(agent, version);
|
|
300
|
+
const skills = listInstalledSkillsWithScope(agent, process.cwd(), { home });
|
|
301
|
+
for (const skill of skills) {
|
|
302
|
+
if (skill.scope !== 'user')
|
|
303
|
+
continue;
|
|
304
|
+
const existing = skillTargetMap.get(skill.name);
|
|
305
|
+
if (existing) {
|
|
306
|
+
existing.targets.push({ agent, version });
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
skillTargetMap.set(skill.name, { name: skill.name, targets: [{ agent, version }] });
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
297
313
|
let skillsToRemove;
|
|
298
314
|
if (name) {
|
|
299
315
|
skillsToRemove = [name];
|
|
300
316
|
}
|
|
301
317
|
else {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
if (installedSkills.size === 0) {
|
|
305
|
-
console.log(chalk.yellow('No skills installed.'));
|
|
318
|
+
if (skillTargetMap.size === 0) {
|
|
319
|
+
console.log(chalk.yellow('No skills installed in any version.'));
|
|
306
320
|
return;
|
|
307
321
|
}
|
|
308
322
|
if (!isInteractiveTerminal()) {
|
|
@@ -311,12 +325,13 @@ Examples:
|
|
|
311
325
|
]);
|
|
312
326
|
}
|
|
313
327
|
try {
|
|
314
|
-
const choices = Array.from(
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
:
|
|
319
|
-
|
|
328
|
+
const choices = Array.from(skillTargetMap.values()).map((skill) => {
|
|
329
|
+
const agents = [...new Set(skill.targets.map((t) => AGENTS[t.agent].name))];
|
|
330
|
+
return {
|
|
331
|
+
value: skill.name,
|
|
332
|
+
name: `${skill.name} (${agents.join(', ')})`,
|
|
333
|
+
};
|
|
334
|
+
});
|
|
320
335
|
const selected = await checkbox({
|
|
321
336
|
message: 'Select skills to remove',
|
|
322
337
|
choices,
|
|
@@ -335,14 +350,40 @@ Examples:
|
|
|
335
350
|
throw err;
|
|
336
351
|
}
|
|
337
352
|
}
|
|
353
|
+
let removed = 0;
|
|
338
354
|
for (const skillName of skillsToRemove) {
|
|
339
|
-
const
|
|
340
|
-
if (
|
|
341
|
-
console.log(chalk.
|
|
355
|
+
const skillInfo = skillTargetMap.get(skillName);
|
|
356
|
+
if (!skillInfo || skillInfo.targets.length === 0) {
|
|
357
|
+
console.log(chalk.yellow(` Skill '${skillName}' not found in any version.`));
|
|
358
|
+
continue;
|
|
342
359
|
}
|
|
343
|
-
|
|
344
|
-
|
|
360
|
+
const removalTargets = skillInfo.targets.map((t) => ({
|
|
361
|
+
agent: t.agent,
|
|
362
|
+
version: t.version,
|
|
363
|
+
label: `${agentLabel(t.agent)}@${t.version}`,
|
|
364
|
+
}));
|
|
365
|
+
const selectedTargets = await promptRemovalTargets(skillName, removalTargets);
|
|
366
|
+
if (selectedTargets.length === 0) {
|
|
367
|
+
console.log(chalk.gray(` Skipped '${skillName}'.`));
|
|
368
|
+
continue;
|
|
345
369
|
}
|
|
370
|
+
for (const target of selectedTargets) {
|
|
371
|
+
const result = removeSkillFromVersion(target.agent, target.version, skillName);
|
|
372
|
+
if (result.success) {
|
|
373
|
+
console.log(` ${chalk.red('-')} ${target.label}: ${skillName}`);
|
|
374
|
+
removed++;
|
|
375
|
+
}
|
|
376
|
+
else if (result.error) {
|
|
377
|
+
console.log(` ${chalk.yellow('!')} ${target.label}: ${result.error}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (removed === 0) {
|
|
382
|
+
console.log(chalk.yellow('No skills removed.'));
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
console.log(chalk.green(`\nRemoved ${removed} skill(s) from version homes.`));
|
|
386
|
+
console.log(chalk.gray('Central source unchanged. Skills will re-sync on next agent launch.'));
|
|
346
387
|
}
|
|
347
388
|
});
|
|
348
389
|
// `skills sync` is gone — sync runs automatically when the agent launches.
|
|
@@ -12,10 +12,10 @@ import * as path from 'path';
|
|
|
12
12
|
import { checkbox } from '@inquirer/prompts';
|
|
13
13
|
import { AGENTS, agentLabel } from '../lib/agents.js';
|
|
14
14
|
import { cloneRepo } from '../lib/git.js';
|
|
15
|
-
import { discoverSubagentsFromRepo, installSubagentCentrally,
|
|
15
|
+
import { discoverSubagentsFromRepo, installSubagentCentrally, listInstalledSubagents, getInstalledSubagent, listSubagentsForAgent, SUBAGENT_CAPABLE_AGENTS, iterSubagentsCapableVersions, removeSubagentFromVersion, } from '../lib/subagents.js';
|
|
16
16
|
import { listInstalledVersions, syncResourcesToVersion, getGlobalDefault, getVersionHomePath, } from '../lib/versions.js';
|
|
17
17
|
import { getSubagentsDir } from '../lib/state.js';
|
|
18
|
-
import { isInteractiveTerminal, isPromptCancelled, requireInteractiveSelection, requireDestructiveArg, } from './utils.js';
|
|
18
|
+
import { isInteractiveTerminal, isPromptCancelled, requireInteractiveSelection, requireDestructiveArg, promptRemovalTargets, } from './utils.js';
|
|
19
19
|
import { showResourceList, buildTargetsSection, } from './resource-view.js';
|
|
20
20
|
/** Replace the home directory prefix with ~ for display. */
|
|
21
21
|
function formatPath(p) {
|
|
@@ -240,20 +240,48 @@ Examples:
|
|
|
240
240
|
console.log(chalk.red(`Subagent '${name}' not found`));
|
|
241
241
|
process.exit(1);
|
|
242
242
|
}
|
|
243
|
-
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
243
|
+
// Build list of targets that have this subagent synced
|
|
244
|
+
const availableTargets = [];
|
|
245
|
+
for (const { agent, version } of iterSubagentsCapableVersions()) {
|
|
246
|
+
const home = getVersionHomePath(agent, version);
|
|
247
|
+
const installed = listSubagentsForAgent(agent, home).some((s) => s.name === name);
|
|
248
|
+
if (installed) {
|
|
249
|
+
availableTargets.push({ agent, version });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (availableTargets.length === 0) {
|
|
253
|
+
console.log(chalk.yellow(`Subagent '${name}' not synced to any version.`));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
// Show multi-select picker for targets
|
|
257
|
+
const removalTargets = availableTargets.map((t) => ({
|
|
258
|
+
agent: t.agent,
|
|
259
|
+
version: t.version,
|
|
260
|
+
label: `${agentLabel(t.agent)}@${t.version}`,
|
|
261
|
+
}));
|
|
262
|
+
const selectedTargets = await promptRemovalTargets(name, removalTargets);
|
|
263
|
+
if (selectedTargets.length === 0) {
|
|
264
|
+
console.log(chalk.gray('Cancelled.'));
|
|
265
|
+
return;
|
|
248
266
|
}
|
|
249
|
-
|
|
250
|
-
for (const
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
267
|
+
let removed = 0;
|
|
268
|
+
for (const target of selectedTargets) {
|
|
269
|
+
const result = removeSubagentFromVersion(target.agent, target.version, name);
|
|
270
|
+
if (result.success) {
|
|
271
|
+
console.log(` ${chalk.red('-')} ${target.label}: ${name}`);
|
|
272
|
+
removed++;
|
|
273
|
+
}
|
|
274
|
+
else if (result.error) {
|
|
275
|
+
console.log(` ${chalk.yellow('!')} ${target.label}: ${result.error}`);
|
|
254
276
|
}
|
|
255
277
|
}
|
|
256
|
-
|
|
278
|
+
if (removed === 0) {
|
|
279
|
+
console.log(chalk.yellow('No subagents removed.'));
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
console.log(chalk.green(`\nRemoved ${removed} subagent(s) from version homes.`));
|
|
283
|
+
console.log(chalk.gray('Central source unchanged. Subagents will re-sync on next agent launch.'));
|
|
284
|
+
}
|
|
257
285
|
});
|
|
258
286
|
}
|
|
259
287
|
/** Every (agent, version) that supports subagents and is installed. */
|
package/dist/commands/utils.d.ts
CHANGED
|
@@ -38,6 +38,22 @@ export declare function printWithPager(output: string, lineCount: number): void;
|
|
|
38
38
|
* Parse a comma-separated CLI list, trimming whitespace and dropping empties.
|
|
39
39
|
*/
|
|
40
40
|
export declare function parseCommaSeparatedList(value: string | undefined): string[];
|
|
41
|
+
/**
|
|
42
|
+
* A target for resource removal: agent + version.
|
|
43
|
+
*/
|
|
44
|
+
export interface RemovalTarget {
|
|
45
|
+
agent: string;
|
|
46
|
+
version: string;
|
|
47
|
+
label: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Prompt user to select which agent/version targets to remove a resource from.
|
|
51
|
+
* If only one target, returns it without prompting. If multiple, shows checkbox.
|
|
52
|
+
* Returns empty array if user cancels or selects nothing.
|
|
53
|
+
*/
|
|
54
|
+
export declare function promptRemovalTargets(resourceName: string, targets: RemovalTarget[], options?: {
|
|
55
|
+
skipPrompt?: boolean;
|
|
56
|
+
}): Promise<RemovalTarget[]>;
|
|
41
57
|
/**
|
|
42
58
|
* Format a path for display, using ~ for home directory
|
|
43
59
|
*/
|
package/dist/commands/utils.js
CHANGED
|
@@ -89,6 +89,38 @@ export function parseCommaSeparatedList(value) {
|
|
|
89
89
|
.map((item) => item.trim())
|
|
90
90
|
.filter(Boolean);
|
|
91
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Prompt user to select which agent/version targets to remove a resource from.
|
|
94
|
+
* If only one target, returns it without prompting. If multiple, shows checkbox.
|
|
95
|
+
* Returns empty array if user cancels or selects nothing.
|
|
96
|
+
*/
|
|
97
|
+
export async function promptRemovalTargets(resourceName, targets, options) {
|
|
98
|
+
if (targets.length === 0)
|
|
99
|
+
return [];
|
|
100
|
+
if (targets.length === 1 || options?.skipPrompt)
|
|
101
|
+
return targets;
|
|
102
|
+
if (!isInteractiveTerminal()) {
|
|
103
|
+
return targets;
|
|
104
|
+
}
|
|
105
|
+
const { checkbox } = await import('@inquirer/prompts');
|
|
106
|
+
try {
|
|
107
|
+
const selected = await checkbox({
|
|
108
|
+
message: `Select targets to remove '${resourceName}' from`,
|
|
109
|
+
choices: targets.map((t) => ({
|
|
110
|
+
value: t,
|
|
111
|
+
name: t.label,
|
|
112
|
+
checked: true,
|
|
113
|
+
})),
|
|
114
|
+
});
|
|
115
|
+
return selected;
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
if (isPromptCancelled(err)) {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
throw err;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
92
124
|
/**
|
|
93
125
|
* Format a path for display, using ~ for home directory
|
|
94
126
|
*/
|