@swarmify/agents-cli 1.0.0 → 1.2.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/README.md +120 -100
- package/dist/index.js +1190 -109
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-skills.d.ts +31 -0
- package/dist/lib/agent-skills.d.ts.map +1 -0
- package/dist/lib/agent-skills.js +338 -0
- package/dist/lib/agent-skills.js.map +1 -0
- package/dist/lib/agents.d.ts +20 -0
- package/dist/lib/agents.d.ts.map +1 -1
- package/dist/lib/agents.js +118 -6
- package/dist/lib/agents.js.map +1 -1
- package/dist/lib/commands.d.ts +47 -0
- package/dist/lib/commands.d.ts.map +1 -0
- package/dist/lib/commands.js +280 -0
- package/dist/lib/commands.js.map +1 -0
- package/dist/lib/git.d.ts +17 -0
- package/dist/lib/git.d.ts.map +1 -1
- package/dist/lib/git.js +98 -11
- package/dist/lib/git.js.map +1 -1
- package/dist/lib/hooks.d.ts +21 -0
- package/dist/lib/hooks.d.ts.map +1 -0
- package/dist/lib/hooks.js +234 -0
- package/dist/lib/hooks.js.map +1 -0
- package/dist/lib/registry.d.ts +28 -0
- package/dist/lib/registry.d.ts.map +1 -0
- package/dist/lib/registry.js +195 -0
- package/dist/lib/registry.js.map +1 -0
- package/dist/lib/skills.d.ts +35 -10
- package/dist/lib/skills.d.ts.map +1 -1
- package/dist/lib/skills.js +353 -101
- package/dist/lib/skills.js.map +1 -1
- package/dist/lib/state.d.ts +21 -5
- package/dist/lib/state.d.ts.map +1 -1
- package/dist/lib/state.js +131 -20
- package/dist/lib/state.js.map +1 -1
- package/dist/lib/types.d.ts +144 -8
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +18 -1
- package/dist/lib/types.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2,13 +2,62 @@
|
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import ora from 'ora';
|
|
5
|
-
import { checkbox, select } from '@inquirer/prompts';
|
|
6
|
-
import { AGENTS, ALL_AGENT_IDS, MCP_CAPABLE_AGENTS, getAllCliStates, isCliInstalled, isMcpRegistered, registerMcp, unregisterMcp, } from './lib/agents.js';
|
|
5
|
+
import { checkbox, confirm, select } from '@inquirer/prompts';
|
|
6
|
+
import { AGENTS, ALL_AGENT_IDS, MCP_CAPABLE_AGENTS, SKILLS_CAPABLE_AGENTS, HOOKS_CAPABLE_AGENTS, getAllCliStates, isCliInstalled, isMcpRegistered, registerMcp, unregisterMcp, listInstalledMcpsWithScope, promoteMcpToUser, } from './lib/agents.js';
|
|
7
7
|
import { readManifest, writeManifest, createDefaultManifest, MANIFEST_FILENAME, } from './lib/manifest.js';
|
|
8
|
-
import { readState,
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { readState, ensureAgentsDir, getRepoLocalPath, getScope, setScope, removeScope, getScopesByPriority, getScopePriority, } from './lib/state.js';
|
|
9
|
+
import { SCOPE_PRIORITIES, DEFAULT_SYSTEM_REPO } from './lib/types.js';
|
|
10
|
+
import { cloneRepo, parseSource } from './lib/git.js';
|
|
11
|
+
import { discoverCommands, resolveCommandSource, installCommand, uninstallCommand, listInstalledCommandsWithScope, promoteCommandToUser, } from './lib/commands.js';
|
|
12
|
+
import { discoverHooksFromRepo, installHooks, listInstalledHooksWithScope, promoteHookToUser, removeHook, } from './lib/hooks.js';
|
|
13
|
+
import { discoverSkillsFromRepo, installSkill, uninstallSkill, listInstalledSkillsWithScope, promoteSkillToUser, getSkillInfo, getSkillRules, } from './lib/skills.js';
|
|
14
|
+
import { DEFAULT_REGISTRIES } from './lib/types.js';
|
|
15
|
+
import { search as searchRegistries, getRegistries, setRegistry, removeRegistry, resolvePackage, } from './lib/registry.js';
|
|
11
16
|
const program = new Command();
|
|
17
|
+
/**
|
|
18
|
+
* Ensure at least one scope is configured.
|
|
19
|
+
* If not, automatically initialize the system scope from DEFAULT_SYSTEM_REPO.
|
|
20
|
+
* Returns the highest priority scope's source.
|
|
21
|
+
*/
|
|
22
|
+
async function ensureSource(scopeName) {
|
|
23
|
+
const meta = readState();
|
|
24
|
+
// If specific scope requested, check if it exists
|
|
25
|
+
if (scopeName) {
|
|
26
|
+
const scope = meta.scopes[scopeName];
|
|
27
|
+
if (scope?.source) {
|
|
28
|
+
return scope.source;
|
|
29
|
+
}
|
|
30
|
+
throw new Error(`Scope '${scopeName}' not configured. Run: agents repo add <source> --scope ${scopeName}`);
|
|
31
|
+
}
|
|
32
|
+
// Check if any scope is configured
|
|
33
|
+
const scopes = getScopesByPriority();
|
|
34
|
+
if (scopes.length > 0) {
|
|
35
|
+
// Return highest priority scope's source
|
|
36
|
+
return scopes[scopes.length - 1].config.source;
|
|
37
|
+
}
|
|
38
|
+
// No scopes configured - initialize system scope
|
|
39
|
+
console.log(chalk.gray(`No repo configured. Initializing system scope from ${DEFAULT_SYSTEM_REPO}...`));
|
|
40
|
+
const parsed = parseSource(DEFAULT_SYSTEM_REPO);
|
|
41
|
+
const { commit } = await cloneRepo(DEFAULT_SYSTEM_REPO);
|
|
42
|
+
setScope('system', {
|
|
43
|
+
source: DEFAULT_SYSTEM_REPO,
|
|
44
|
+
branch: parsed.ref || 'main',
|
|
45
|
+
commit,
|
|
46
|
+
lastSync: new Date().toISOString(),
|
|
47
|
+
priority: SCOPE_PRIORITIES.system,
|
|
48
|
+
readonly: true,
|
|
49
|
+
});
|
|
50
|
+
return DEFAULT_SYSTEM_REPO;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Get repo local path for a scope.
|
|
54
|
+
*/
|
|
55
|
+
function getScopeLocalPath(scopeName) {
|
|
56
|
+
const scope = getScope(scopeName);
|
|
57
|
+
if (!scope)
|
|
58
|
+
return null;
|
|
59
|
+
return getRepoLocalPath(scope.source);
|
|
60
|
+
}
|
|
12
61
|
program
|
|
13
62
|
.name('agents')
|
|
14
63
|
.description('Dotfiles manager for AI coding agents')
|
|
@@ -18,10 +67,11 @@ program
|
|
|
18
67
|
// =============================================================================
|
|
19
68
|
program
|
|
20
69
|
.command('status')
|
|
21
|
-
.description('Show sync status, CLI versions, installed
|
|
70
|
+
.description('Show sync status, CLI versions, installed commands and MCP servers')
|
|
22
71
|
.action(() => {
|
|
23
72
|
const state = readState();
|
|
24
73
|
const cliStates = getAllCliStates();
|
|
74
|
+
const cwd = process.cwd();
|
|
25
75
|
console.log(chalk.bold('\nAgent CLIs\n'));
|
|
26
76
|
for (const agentId of ALL_AGENT_IDS) {
|
|
27
77
|
const agent = AGENTS[agentId];
|
|
@@ -31,21 +81,75 @@ program
|
|
|
31
81
|
: chalk.gray('not installed');
|
|
32
82
|
console.log(` ${agent.name.padEnd(14)} ${status}`);
|
|
33
83
|
}
|
|
34
|
-
console.log(chalk.bold('\nInstalled
|
|
84
|
+
console.log(chalk.bold('\nInstalled Commands\n'));
|
|
35
85
|
for (const agentId of ALL_AGENT_IDS) {
|
|
36
86
|
const agent = AGENTS[agentId];
|
|
37
|
-
const
|
|
87
|
+
const commands = listInstalledCommandsWithScope(agentId, cwd);
|
|
88
|
+
const userCommands = commands.filter((c) => c.scope === 'user');
|
|
89
|
+
const projectCommands = commands.filter((c) => c.scope === 'project');
|
|
90
|
+
if (commands.length > 0) {
|
|
91
|
+
const parts = [];
|
|
92
|
+
if (userCommands.length > 0) {
|
|
93
|
+
parts.push(`${chalk.cyan(userCommands.length)} user`);
|
|
94
|
+
}
|
|
95
|
+
if (projectCommands.length > 0) {
|
|
96
|
+
parts.push(`${chalk.yellow(projectCommands.length)} project`);
|
|
97
|
+
}
|
|
98
|
+
console.log(` ${agent.name}: ${parts.join(', ')}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
console.log(chalk.bold('\nInstalled Skills\n'));
|
|
102
|
+
for (const agentId of SKILLS_CAPABLE_AGENTS) {
|
|
103
|
+
const agent = AGENTS[agentId];
|
|
104
|
+
const skills = listInstalledSkillsWithScope(agentId, cwd);
|
|
105
|
+
const userSkills = skills.filter((s) => s.scope === 'user');
|
|
106
|
+
const projectSkills = skills.filter((s) => s.scope === 'project');
|
|
38
107
|
if (skills.length > 0) {
|
|
39
|
-
|
|
108
|
+
const parts = [];
|
|
109
|
+
if (userSkills.length > 0) {
|
|
110
|
+
parts.push(`${chalk.cyan(userSkills.length)} user`);
|
|
111
|
+
}
|
|
112
|
+
if (projectSkills.length > 0) {
|
|
113
|
+
parts.push(`${chalk.yellow(projectSkills.length)} project`);
|
|
114
|
+
}
|
|
115
|
+
console.log(` ${agent.name}: ${parts.join(', ')}`);
|
|
40
116
|
}
|
|
41
117
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (
|
|
46
|
-
|
|
118
|
+
console.log(chalk.bold('\nInstalled MCP Servers\n'));
|
|
119
|
+
for (const agentId of MCP_CAPABLE_AGENTS) {
|
|
120
|
+
const agent = AGENTS[agentId];
|
|
121
|
+
if (!isCliInstalled(agentId))
|
|
122
|
+
continue;
|
|
123
|
+
const mcps = listInstalledMcpsWithScope(agentId, cwd);
|
|
124
|
+
const userMcps = mcps.filter((m) => m.scope === 'user');
|
|
125
|
+
const projectMcps = mcps.filter((m) => m.scope === 'project');
|
|
126
|
+
if (mcps.length > 0) {
|
|
127
|
+
const parts = [];
|
|
128
|
+
if (userMcps.length > 0) {
|
|
129
|
+
parts.push(`${chalk.cyan(userMcps.length)} user`);
|
|
130
|
+
}
|
|
131
|
+
if (projectMcps.length > 0) {
|
|
132
|
+
parts.push(`${chalk.yellow(projectMcps.length)} project`);
|
|
133
|
+
}
|
|
134
|
+
console.log(` ${agent.name}: ${parts.join(', ')}`);
|
|
47
135
|
}
|
|
48
136
|
}
|
|
137
|
+
const scopes = getScopesByPriority();
|
|
138
|
+
if (scopes.length > 0) {
|
|
139
|
+
console.log(chalk.bold('\nConfigured Scopes\n'));
|
|
140
|
+
for (const { name, config } of scopes) {
|
|
141
|
+
const readonlyTag = config.readonly ? chalk.gray(' (readonly)') : '';
|
|
142
|
+
const priorityTag = chalk.gray(` [priority: ${config.priority}]`);
|
|
143
|
+
console.log(` ${chalk.bold(name)}${readonlyTag}${priorityTag}`);
|
|
144
|
+
console.log(` ${config.source}`);
|
|
145
|
+
console.log(` Branch: ${config.branch} Commit: ${config.commit.substring(0, 8)}`);
|
|
146
|
+
console.log(` Last sync: ${new Date(config.lastSync).toLocaleString()}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
console.log(chalk.bold('\nNo scopes configured\n'));
|
|
151
|
+
console.log(chalk.gray(' Run: agents repo add <source>'));
|
|
152
|
+
}
|
|
49
153
|
console.log();
|
|
50
154
|
});
|
|
51
155
|
// =============================================================================
|
|
@@ -56,18 +160,29 @@ program
|
|
|
56
160
|
.description('Pull and sync from remote .agents repo')
|
|
57
161
|
.option('-y, --yes', 'Skip interactive prompts')
|
|
58
162
|
.option('-f, --force', 'Overwrite local changes')
|
|
163
|
+
.option('-s, --scope <scope>', 'Target scope (default: user)', 'user')
|
|
59
164
|
.option('--dry-run', 'Show what would change')
|
|
60
165
|
.option('--skip-clis', 'Skip CLI installation')
|
|
61
166
|
.option('--skip-mcp', 'Skip MCP registration')
|
|
62
167
|
.action(async (source, options) => {
|
|
63
|
-
const
|
|
64
|
-
const
|
|
168
|
+
const scopeName = options.scope;
|
|
169
|
+
const meta = readState();
|
|
170
|
+
const existingScope = meta.scopes[scopeName];
|
|
171
|
+
const targetSource = source || existingScope?.source;
|
|
65
172
|
if (!targetSource) {
|
|
66
|
-
console.log(chalk.red(
|
|
173
|
+
console.log(chalk.red(`No source specified for scope '${scopeName}'.`));
|
|
174
|
+
const scopeHint = scopeName === 'user' ? '' : ` --scope ${scopeName}`;
|
|
175
|
+
console.log(chalk.gray(` Usage: agents pull <source>${scopeHint}`));
|
|
67
176
|
console.log(chalk.gray(' Example: agents pull gh:username/.agents'));
|
|
68
177
|
process.exit(1);
|
|
69
178
|
}
|
|
70
|
-
|
|
179
|
+
// Prevent modification of readonly scopes
|
|
180
|
+
if (existingScope?.readonly && !options.force) {
|
|
181
|
+
console.log(chalk.red(`Scope '${scopeName}' is readonly. Use --force to override.`));
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
const parsed = parseSource(targetSource);
|
|
185
|
+
const spinner = ora(`Cloning repository for ${scopeName} scope...`).start();
|
|
71
186
|
try {
|
|
72
187
|
const { localPath, commit, isNew } = await cloneRepo(targetSource);
|
|
73
188
|
spinner.succeed(isNew ? 'Repository cloned' : 'Repository updated');
|
|
@@ -75,14 +190,14 @@ program
|
|
|
75
190
|
if (!manifest) {
|
|
76
191
|
console.log(chalk.yellow(`No ${MANIFEST_FILENAME} found in repository`));
|
|
77
192
|
}
|
|
78
|
-
const
|
|
79
|
-
console.log(chalk.bold(`\nFound ${
|
|
80
|
-
for (const
|
|
81
|
-
const
|
|
82
|
-
console.log(` ${chalk.cyan(
|
|
193
|
+
const commands = discoverCommands(localPath);
|
|
194
|
+
console.log(chalk.bold(`\nFound ${commands.length} commands\n`));
|
|
195
|
+
for (const command of commands.slice(0, 10)) {
|
|
196
|
+
const src = command.isShared ? 'shared' : command.agentSpecific;
|
|
197
|
+
console.log(` ${chalk.cyan(command.name.padEnd(20))} ${chalk.gray(src)}`);
|
|
83
198
|
}
|
|
84
|
-
if (
|
|
85
|
-
console.log(chalk.gray(` ... and ${
|
|
199
|
+
if (commands.length > 10) {
|
|
200
|
+
console.log(chalk.gray(` ... and ${commands.length - 10} more`));
|
|
86
201
|
}
|
|
87
202
|
if (options.dryRun) {
|
|
88
203
|
console.log(chalk.yellow('\nDry run - no changes made'));
|
|
@@ -114,24 +229,27 @@ program
|
|
|
114
229
|
});
|
|
115
230
|
}
|
|
116
231
|
const defaultAgents = selectedAgents;
|
|
117
|
-
const installSpinner = ora('Installing
|
|
232
|
+
const installSpinner = ora('Installing commands...').start();
|
|
118
233
|
let installed = 0;
|
|
119
|
-
for (const
|
|
234
|
+
for (const command of commands) {
|
|
120
235
|
for (const agentId of defaultAgents) {
|
|
121
236
|
if (!isCliInstalled(agentId) && agentId !== 'cursor')
|
|
122
237
|
continue;
|
|
123
|
-
const sourcePath =
|
|
238
|
+
const sourcePath = resolveCommandSource(localPath, command.name, agentId);
|
|
124
239
|
if (sourcePath) {
|
|
125
|
-
|
|
240
|
+
installCommand(sourcePath, agentId, command.name, method);
|
|
126
241
|
installed++;
|
|
127
242
|
}
|
|
128
243
|
}
|
|
129
244
|
}
|
|
130
|
-
installSpinner.succeed(`Installed ${installed}
|
|
245
|
+
installSpinner.succeed(`Installed ${installed} command instances`);
|
|
131
246
|
if (!options.skipMcp && manifest?.mcp) {
|
|
132
247
|
const mcpSpinner = ora('Registering MCP servers...').start();
|
|
133
248
|
let registered = 0;
|
|
134
249
|
for (const [name, config] of Object.entries(manifest.mcp)) {
|
|
250
|
+
// Skip HTTP transport MCPs for now (need different registration)
|
|
251
|
+
if (config.transport === 'http' || !config.command)
|
|
252
|
+
continue;
|
|
135
253
|
for (const agentId of config.agents) {
|
|
136
254
|
if (!isCliInstalled(agentId))
|
|
137
255
|
continue;
|
|
@@ -144,12 +262,17 @@ program
|
|
|
144
262
|
}
|
|
145
263
|
mcpSpinner.succeed(`Registered ${registered} MCP servers`);
|
|
146
264
|
}
|
|
147
|
-
|
|
148
|
-
|
|
265
|
+
// Update scope config
|
|
266
|
+
const priority = getScopePriority(scopeName);
|
|
267
|
+
setScope(scopeName, {
|
|
149
268
|
source: targetSource,
|
|
269
|
+
branch: parsed.ref || 'main',
|
|
270
|
+
commit,
|
|
150
271
|
lastSync: new Date().toISOString(),
|
|
272
|
+
priority,
|
|
273
|
+
readonly: scopeName === 'system',
|
|
151
274
|
});
|
|
152
|
-
console.log(chalk.green(
|
|
275
|
+
console.log(chalk.green(`\nSync complete. Updated scope: ${scopeName}`));
|
|
153
276
|
}
|
|
154
277
|
catch (err) {
|
|
155
278
|
spinner.fail('Failed to sync');
|
|
@@ -162,15 +285,23 @@ program
|
|
|
162
285
|
// =============================================================================
|
|
163
286
|
program
|
|
164
287
|
.command('push')
|
|
165
|
-
.description('Export local
|
|
166
|
-
.option('
|
|
288
|
+
.description('Export local configuration to .agents repo for manual commit')
|
|
289
|
+
.option('-s, --scope <scope>', 'Target scope (default: user)', 'user')
|
|
290
|
+
.option('--export-only', 'Only export, do not update manifest')
|
|
167
291
|
.action(async (options) => {
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
292
|
+
const scopeName = options.scope;
|
|
293
|
+
const scope = getScope(scopeName);
|
|
294
|
+
if (!scope) {
|
|
295
|
+
console.log(chalk.red(`Scope '${scopeName}' not configured.`));
|
|
296
|
+
const scopeHint = scopeName === 'user' ? '' : ` --scope ${scopeName}`;
|
|
297
|
+
console.log(chalk.gray(` Run: agents repo add <source>${scopeHint}`));
|
|
298
|
+
process.exit(1);
|
|
299
|
+
}
|
|
300
|
+
if (scope.readonly) {
|
|
301
|
+
console.log(chalk.red(`Scope '${scopeName}' is readonly. Cannot push.`));
|
|
171
302
|
process.exit(1);
|
|
172
303
|
}
|
|
173
|
-
const localPath = getRepoLocalPath(
|
|
304
|
+
const localPath = getRepoLocalPath(scope.source);
|
|
174
305
|
const manifest = readManifest(localPath) || createDefaultManifest();
|
|
175
306
|
console.log(chalk.bold('\nExporting local configuration...\n'));
|
|
176
307
|
const cliStates = getAllCliStates();
|
|
@@ -218,71 +349,89 @@ program
|
|
|
218
349
|
await program.commands.find((c) => c.name() === 'pull')?.parseAsync(args, { from: 'user' });
|
|
219
350
|
});
|
|
220
351
|
// =============================================================================
|
|
221
|
-
//
|
|
352
|
+
// COMMANDS COMMANDS
|
|
222
353
|
// =============================================================================
|
|
223
|
-
const
|
|
224
|
-
.command('
|
|
225
|
-
.description('Manage
|
|
226
|
-
|
|
354
|
+
const commandsCmd = program
|
|
355
|
+
.command('commands')
|
|
356
|
+
.description('Manage slash commands');
|
|
357
|
+
commandsCmd
|
|
227
358
|
.command('list')
|
|
228
|
-
.description('List installed
|
|
229
|
-
.option('-a, --agent <agent>', '
|
|
359
|
+
.description('List installed commands')
|
|
360
|
+
.option('-a, --agent <agent>', 'Filter by agent')
|
|
361
|
+
.option('-s, --scope <scope>', 'Filter by scope: user, project, or all', 'all')
|
|
230
362
|
.action((options) => {
|
|
231
|
-
console.log(chalk.bold('\nInstalled
|
|
363
|
+
console.log(chalk.bold('\nInstalled Commands\n'));
|
|
364
|
+
const cwd = process.cwd();
|
|
232
365
|
const agents = options.agent
|
|
233
366
|
? [options.agent]
|
|
234
367
|
: ALL_AGENT_IDS;
|
|
235
368
|
for (const agentId of agents) {
|
|
236
369
|
const agent = AGENTS[agentId];
|
|
237
|
-
|
|
238
|
-
if (
|
|
370
|
+
let commands = listInstalledCommandsWithScope(agentId, cwd);
|
|
371
|
+
if (options.scope !== 'all') {
|
|
372
|
+
commands = commands.filter((c) => c.scope === options.scope);
|
|
373
|
+
}
|
|
374
|
+
if (commands.length === 0) {
|
|
239
375
|
console.log(` ${chalk.bold(agent.name)}: ${chalk.gray('none')}`);
|
|
240
376
|
}
|
|
241
377
|
else {
|
|
242
378
|
console.log(` ${chalk.bold(agent.name)}:`);
|
|
243
|
-
|
|
244
|
-
|
|
379
|
+
const userCommands = commands.filter((c) => c.scope === 'user');
|
|
380
|
+
const projectCommands = commands.filter((c) => c.scope === 'project');
|
|
381
|
+
if (userCommands.length > 0 && (options.scope === 'all' || options.scope === 'user')) {
|
|
382
|
+
console.log(` ${chalk.gray('User:')}`);
|
|
383
|
+
for (const cmd of userCommands) {
|
|
384
|
+
const desc = cmd.description ? ` - ${chalk.gray(cmd.description)}` : '';
|
|
385
|
+
console.log(` ${chalk.cyan(cmd.name)}${desc}`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
if (projectCommands.length > 0 && (options.scope === 'all' || options.scope === 'project')) {
|
|
389
|
+
console.log(` ${chalk.gray('Project:')}`);
|
|
390
|
+
for (const cmd of projectCommands) {
|
|
391
|
+
const desc = cmd.description ? ` - ${chalk.gray(cmd.description)}` : '';
|
|
392
|
+
console.log(` ${chalk.yellow(cmd.name)}${desc}`);
|
|
393
|
+
}
|
|
245
394
|
}
|
|
246
395
|
}
|
|
247
396
|
console.log();
|
|
248
397
|
}
|
|
249
398
|
});
|
|
250
|
-
|
|
399
|
+
commandsCmd
|
|
251
400
|
.command('add <source>')
|
|
252
|
-
.description('Add
|
|
401
|
+
.description('Add commands from Git repo or local path')
|
|
253
402
|
.option('-a, --agents <list>', 'Comma-separated agents to install to')
|
|
254
403
|
.action(async (source, options) => {
|
|
255
|
-
const spinner = ora('Fetching
|
|
404
|
+
const spinner = ora('Fetching commands...').start();
|
|
256
405
|
try {
|
|
257
406
|
const { localPath } = await cloneRepo(source);
|
|
258
|
-
const
|
|
259
|
-
spinner.succeed(`Found ${
|
|
407
|
+
const commands = discoverCommands(localPath);
|
|
408
|
+
spinner.succeed(`Found ${commands.length} commands`);
|
|
260
409
|
const agents = options.agents
|
|
261
410
|
? options.agents.split(',')
|
|
262
411
|
: ['claude', 'codex', 'gemini'];
|
|
263
|
-
for (const
|
|
264
|
-
console.log(`\n ${chalk.cyan(
|
|
412
|
+
for (const command of commands) {
|
|
413
|
+
console.log(`\n ${chalk.cyan(command.name)}: ${command.description}`);
|
|
265
414
|
for (const agentId of agents) {
|
|
266
415
|
if (!isCliInstalled(agentId) && agentId !== 'cursor')
|
|
267
416
|
continue;
|
|
268
|
-
const sourcePath =
|
|
417
|
+
const sourcePath = resolveCommandSource(localPath, command.name, agentId);
|
|
269
418
|
if (sourcePath) {
|
|
270
|
-
|
|
419
|
+
installCommand(sourcePath, agentId, command.name, 'symlink');
|
|
271
420
|
console.log(` ${chalk.green('+')} ${AGENTS[agentId].name}`);
|
|
272
421
|
}
|
|
273
422
|
}
|
|
274
423
|
}
|
|
275
|
-
console.log(chalk.green('\
|
|
424
|
+
console.log(chalk.green('\nCommands installed.'));
|
|
276
425
|
}
|
|
277
426
|
catch (err) {
|
|
278
|
-
spinner.fail('Failed to add
|
|
427
|
+
spinner.fail('Failed to add commands');
|
|
279
428
|
console.error(chalk.red(err.message));
|
|
280
429
|
process.exit(1);
|
|
281
430
|
}
|
|
282
431
|
});
|
|
283
|
-
|
|
432
|
+
commandsCmd
|
|
284
433
|
.command('remove <name>')
|
|
285
|
-
.description('Remove a
|
|
434
|
+
.description('Remove a command from all agents')
|
|
286
435
|
.option('-a, --agents <list>', 'Comma-separated agents to remove from')
|
|
287
436
|
.action((name, options) => {
|
|
288
437
|
const agents = options.agents
|
|
@@ -290,18 +439,389 @@ skillsCmd
|
|
|
290
439
|
: ALL_AGENT_IDS;
|
|
291
440
|
let removed = 0;
|
|
292
441
|
for (const agentId of agents) {
|
|
293
|
-
if (
|
|
442
|
+
if (uninstallCommand(agentId, name)) {
|
|
294
443
|
console.log(` ${chalk.red('-')} ${AGENTS[agentId].name}`);
|
|
295
444
|
removed++;
|
|
296
445
|
}
|
|
297
446
|
}
|
|
298
447
|
if (removed === 0) {
|
|
299
|
-
console.log(chalk.yellow(`
|
|
448
|
+
console.log(chalk.yellow(`Command '${name}' not found`));
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
console.log(chalk.green(`\nRemoved from ${removed} agents.`));
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
commandsCmd
|
|
455
|
+
.command('push <name>')
|
|
456
|
+
.description('Save project-scoped command to user scope')
|
|
457
|
+
.option('-a, --agents <list>', 'Comma-separated agents to push for')
|
|
458
|
+
.action((name, options) => {
|
|
459
|
+
const cwd = process.cwd();
|
|
460
|
+
const agents = options.agents
|
|
461
|
+
? options.agents.split(',')
|
|
462
|
+
: ALL_AGENT_IDS;
|
|
463
|
+
let pushed = 0;
|
|
464
|
+
for (const agentId of agents) {
|
|
465
|
+
if (!isCliInstalled(agentId) && agentId !== 'cursor')
|
|
466
|
+
continue;
|
|
467
|
+
const result = promoteCommandToUser(agentId, name, cwd);
|
|
468
|
+
if (result.success) {
|
|
469
|
+
console.log(` ${chalk.green('+')} ${AGENTS[agentId].name}`);
|
|
470
|
+
pushed++;
|
|
471
|
+
}
|
|
472
|
+
else if (result.error && !result.error.includes('not found')) {
|
|
473
|
+
console.log(` ${chalk.red('x')} ${AGENTS[agentId].name}: ${result.error}`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (pushed === 0) {
|
|
477
|
+
console.log(chalk.yellow(`Project command '${name}' not found for any agent`));
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
console.log(chalk.green(`\nPushed to user scope for ${pushed} agents.`));
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
const hooksCmd = program.command('hooks').description('Manage hooks');
|
|
484
|
+
hooksCmd
|
|
485
|
+
.command('list')
|
|
486
|
+
.description('List installed hooks')
|
|
487
|
+
.option('-a, --agent <agent>', 'Filter by agent')
|
|
488
|
+
.option('-s, --scope <scope>', 'Filter by scope: user, project, or all', 'all')
|
|
489
|
+
.action((options) => {
|
|
490
|
+
console.log(chalk.bold('\nInstalled Hooks\n'));
|
|
491
|
+
const cwd = process.cwd();
|
|
492
|
+
const agents = options.agent
|
|
493
|
+
? [options.agent]
|
|
494
|
+
: Array.from(HOOKS_CAPABLE_AGENTS);
|
|
495
|
+
for (const agentId of agents) {
|
|
496
|
+
const agent = AGENTS[agentId];
|
|
497
|
+
if (!agent.supportsHooks) {
|
|
498
|
+
console.log(` ${chalk.bold(agent.name)}: ${chalk.gray('hooks not supported')}`);
|
|
499
|
+
console.log();
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
let hooks = listInstalledHooksWithScope(agentId, cwd);
|
|
503
|
+
if (options.scope !== 'all') {
|
|
504
|
+
hooks = hooks.filter((h) => h.scope === options.scope);
|
|
505
|
+
}
|
|
506
|
+
if (hooks.length === 0) {
|
|
507
|
+
console.log(` ${chalk.bold(agent.name)}: ${chalk.gray('none')}`);
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
console.log(` ${chalk.bold(agent.name)}:`);
|
|
511
|
+
const userHooks = hooks.filter((h) => h.scope === 'user');
|
|
512
|
+
const projectHooks = hooks.filter((h) => h.scope === 'project');
|
|
513
|
+
if (userHooks.length > 0 && (options.scope === 'all' || options.scope === 'user')) {
|
|
514
|
+
console.log(` ${chalk.gray('User:')}`);
|
|
515
|
+
for (const hook of userHooks) {
|
|
516
|
+
console.log(` ${chalk.cyan(hook.name)}`);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (projectHooks.length > 0 && (options.scope === 'all' || options.scope === 'project')) {
|
|
520
|
+
console.log(` ${chalk.gray('Project:')}`);
|
|
521
|
+
for (const hook of projectHooks) {
|
|
522
|
+
console.log(` ${chalk.yellow(hook.name)}`);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
console.log();
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
hooksCmd
|
|
530
|
+
.command('add <source>')
|
|
531
|
+
.description('Install hooks from git repo or local path')
|
|
532
|
+
.option('-a, --agent <agents>', 'Target agents (comma-separated)', 'claude,gemini')
|
|
533
|
+
.action(async (source, options) => {
|
|
534
|
+
const spinner = ora('Fetching hooks...').start();
|
|
535
|
+
try {
|
|
536
|
+
const { localPath } = await cloneRepo(source);
|
|
537
|
+
const hooks = discoverHooksFromRepo(localPath);
|
|
538
|
+
const hookNames = new Set();
|
|
539
|
+
for (const name of hooks.shared) {
|
|
540
|
+
hookNames.add(name);
|
|
541
|
+
}
|
|
542
|
+
for (const list of Object.values(hooks.agentSpecific)) {
|
|
543
|
+
for (const name of list) {
|
|
544
|
+
hookNames.add(name);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
spinner.succeed(`Found ${hookNames.size} hooks`);
|
|
548
|
+
const agents = options.agent
|
|
549
|
+
? options.agent.split(',')
|
|
550
|
+
: ['claude', 'gemini'];
|
|
551
|
+
const result = await installHooks(localPath, agents, { scope: 'user' });
|
|
552
|
+
const installedByHook = new Map();
|
|
553
|
+
for (const item of result.installed) {
|
|
554
|
+
const [name, agentId] = item.split(':');
|
|
555
|
+
const list = installedByHook.get(name) || [];
|
|
556
|
+
list.push(agentId);
|
|
557
|
+
installedByHook.set(name, list);
|
|
558
|
+
}
|
|
559
|
+
const orderedHooks = Array.from(installedByHook.keys()).sort((a, b) => a.localeCompare(b));
|
|
560
|
+
for (const name of orderedHooks) {
|
|
561
|
+
console.log(`\n ${chalk.cyan(name)}`);
|
|
562
|
+
const agentIds = installedByHook.get(name) || [];
|
|
563
|
+
agentIds.sort();
|
|
564
|
+
for (const agentId of agentIds) {
|
|
565
|
+
console.log(` ${AGENTS[agentId].name}`);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (result.errors.length > 0) {
|
|
569
|
+
console.log(chalk.red('\nErrors:'));
|
|
570
|
+
for (const error of result.errors) {
|
|
571
|
+
console.log(chalk.red(` ${error}`));
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (result.installed.length === 0) {
|
|
575
|
+
console.log(chalk.yellow('\nNo hooks installed.'));
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
console.log(chalk.green('\nHooks installed.'));
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
catch (err) {
|
|
582
|
+
spinner.fail('Failed to add hooks');
|
|
583
|
+
console.error(chalk.red(err.message));
|
|
584
|
+
process.exit(1);
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
hooksCmd
|
|
588
|
+
.command('remove <name>')
|
|
589
|
+
.description('Remove a hook')
|
|
590
|
+
.option('-a, --agent <agents>', 'Target agents (comma-separated)')
|
|
591
|
+
.action(async (name, options) => {
|
|
592
|
+
const agents = options.agent
|
|
593
|
+
? options.agent.split(',')
|
|
594
|
+
: Array.from(HOOKS_CAPABLE_AGENTS);
|
|
595
|
+
const result = await removeHook(name, agents);
|
|
596
|
+
let removed = 0;
|
|
597
|
+
for (const item of result.removed) {
|
|
598
|
+
const [, agentId] = item.split(':');
|
|
599
|
+
console.log(` ${AGENTS[agentId].name}`);
|
|
600
|
+
removed++;
|
|
601
|
+
}
|
|
602
|
+
if (result.errors.length > 0) {
|
|
603
|
+
console.log(chalk.red('\nErrors:'));
|
|
604
|
+
for (const error of result.errors) {
|
|
605
|
+
console.log(chalk.red(` ${error}`));
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
if (removed === 0) {
|
|
609
|
+
console.log(chalk.yellow(`Hook '${name}' not found`));
|
|
300
610
|
}
|
|
301
611
|
else {
|
|
302
612
|
console.log(chalk.green(`\nRemoved from ${removed} agents.`));
|
|
303
613
|
}
|
|
304
614
|
});
|
|
615
|
+
hooksCmd
|
|
616
|
+
.command('push <name>')
|
|
617
|
+
.description('Copy project-scoped hook to user scope')
|
|
618
|
+
.option('-a, --agent <agents>', 'Target agents (comma-separated)')
|
|
619
|
+
.action((name, options) => {
|
|
620
|
+
const cwd = process.cwd();
|
|
621
|
+
const agents = options.agent
|
|
622
|
+
? options.agent.split(',')
|
|
623
|
+
: Array.from(HOOKS_CAPABLE_AGENTS);
|
|
624
|
+
let pushed = 0;
|
|
625
|
+
for (const agentId of agents) {
|
|
626
|
+
const result = promoteHookToUser(agentId, name, cwd);
|
|
627
|
+
if (result.success) {
|
|
628
|
+
console.log(` ${AGENTS[agentId].name}`);
|
|
629
|
+
pushed++;
|
|
630
|
+
}
|
|
631
|
+
else if (result.error && !result.error.includes('not found')) {
|
|
632
|
+
console.log(` ${AGENTS[agentId].name}: ${result.error}`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if (pushed === 0) {
|
|
636
|
+
console.log(chalk.yellow(`Project hook '${name}' not found for any agent`));
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
console.log(chalk.green(`\nPushed to user scope for ${pushed} agents.`));
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
// =============================================================================
|
|
643
|
+
// SKILLS COMMANDS (Agent Skills)
|
|
644
|
+
// =============================================================================
|
|
645
|
+
const skillsCmd = program
|
|
646
|
+
.command('skills')
|
|
647
|
+
.description('Manage Agent Skills (SKILL.md + rules/)');
|
|
648
|
+
skillsCmd
|
|
649
|
+
.command('list')
|
|
650
|
+
.description('List installed Agent Skills')
|
|
651
|
+
.option('-a, --agent <agent>', 'Filter by agent')
|
|
652
|
+
.option('-s, --scope <scope>', 'Filter by scope: user, project, or all', 'all')
|
|
653
|
+
.action((options) => {
|
|
654
|
+
console.log(chalk.bold('\nInstalled Agent Skills\n'));
|
|
655
|
+
const cwd = process.cwd();
|
|
656
|
+
const agents = options.agent
|
|
657
|
+
? [options.agent]
|
|
658
|
+
: SKILLS_CAPABLE_AGENTS;
|
|
659
|
+
for (const agentId of agents) {
|
|
660
|
+
const agent = AGENTS[agentId];
|
|
661
|
+
if (!agent.capabilities.skills) {
|
|
662
|
+
console.log(` ${chalk.bold(agent.name)}: ${chalk.gray('skills not supported')}`);
|
|
663
|
+
console.log();
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
let skills = listInstalledSkillsWithScope(agentId, cwd);
|
|
667
|
+
if (options.scope !== 'all') {
|
|
668
|
+
skills = skills.filter((s) => s.scope === options.scope);
|
|
669
|
+
}
|
|
670
|
+
if (skills.length === 0) {
|
|
671
|
+
console.log(` ${chalk.bold(agent.name)}: ${chalk.gray('none')}`);
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
console.log(` ${chalk.bold(agent.name)}:`);
|
|
675
|
+
const userSkills = skills.filter((s) => s.scope === 'user');
|
|
676
|
+
const projectSkills = skills.filter((s) => s.scope === 'project');
|
|
677
|
+
if (userSkills.length > 0 && (options.scope === 'all' || options.scope === 'user')) {
|
|
678
|
+
console.log(` ${chalk.gray('User:')}`);
|
|
679
|
+
for (const skill of userSkills) {
|
|
680
|
+
const desc = skill.metadata.description ? ` - ${chalk.gray(skill.metadata.description)}` : '';
|
|
681
|
+
const ruleInfo = skill.ruleCount > 0 ? chalk.gray(` (${skill.ruleCount} rules)`) : '';
|
|
682
|
+
console.log(` ${chalk.cyan(skill.name)}${desc}${ruleInfo}`);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
if (projectSkills.length > 0 && (options.scope === 'all' || options.scope === 'project')) {
|
|
686
|
+
console.log(` ${chalk.gray('Project:')}`);
|
|
687
|
+
for (const skill of projectSkills) {
|
|
688
|
+
const desc = skill.metadata.description ? ` - ${chalk.gray(skill.metadata.description)}` : '';
|
|
689
|
+
const ruleInfo = skill.ruleCount > 0 ? chalk.gray(` (${skill.ruleCount} rules)`) : '';
|
|
690
|
+
console.log(` ${chalk.yellow(skill.name)}${desc}${ruleInfo}`);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
console.log();
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
skillsCmd
|
|
698
|
+
.command('add <source>')
|
|
699
|
+
.description('Add Agent Skills from Git repo or local path')
|
|
700
|
+
.option('-a, --agents <list>', 'Comma-separated agents to install to')
|
|
701
|
+
.action(async (source, options) => {
|
|
702
|
+
const spinner = ora('Fetching skills...').start();
|
|
703
|
+
try {
|
|
704
|
+
const { localPath } = await cloneRepo(source);
|
|
705
|
+
const skills = discoverSkillsFromRepo(localPath);
|
|
706
|
+
spinner.succeed(`Found ${skills.length} skills`);
|
|
707
|
+
if (skills.length === 0) {
|
|
708
|
+
console.log(chalk.yellow('No skills found (looking for SKILL.md files)'));
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
for (const skill of skills) {
|
|
712
|
+
console.log(`\n ${chalk.cyan(skill.name)}: ${skill.metadata.description || 'no description'}`);
|
|
713
|
+
if (skill.ruleCount > 0) {
|
|
714
|
+
console.log(` ${chalk.gray(`${skill.ruleCount} rules`)}`);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
const agents = options.agents
|
|
718
|
+
? options.agents.split(',')
|
|
719
|
+
: await checkbox({
|
|
720
|
+
message: 'Select agents to install skills to:',
|
|
721
|
+
choices: SKILLS_CAPABLE_AGENTS.filter((id) => isCliInstalled(id) || id === 'cursor').map((id) => ({
|
|
722
|
+
name: AGENTS[id].name,
|
|
723
|
+
value: id,
|
|
724
|
+
checked: ['claude', 'codex', 'gemini'].includes(id),
|
|
725
|
+
})),
|
|
726
|
+
});
|
|
727
|
+
if (agents.length === 0) {
|
|
728
|
+
console.log(chalk.yellow('\nNo agents selected.'));
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
const installSpinner = ora('Installing skills...').start();
|
|
732
|
+
let installed = 0;
|
|
733
|
+
for (const skill of skills) {
|
|
734
|
+
const result = installSkill(skill.path, skill.name, agents);
|
|
735
|
+
if (result.success) {
|
|
736
|
+
installed++;
|
|
737
|
+
}
|
|
738
|
+
else {
|
|
739
|
+
console.log(chalk.red(`\n Failed to install ${skill.name}: ${result.error}`));
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
installSpinner.succeed(`Installed ${installed} skills to ${agents.length} agents`);
|
|
743
|
+
console.log(chalk.green('\nSkills installed.'));
|
|
744
|
+
}
|
|
745
|
+
catch (err) {
|
|
746
|
+
spinner.fail('Failed to add skills');
|
|
747
|
+
console.error(chalk.red(err.message));
|
|
748
|
+
process.exit(1);
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
skillsCmd
|
|
752
|
+
.command('remove <name>')
|
|
753
|
+
.description('Remove an Agent Skill')
|
|
754
|
+
.action((name) => {
|
|
755
|
+
const result = uninstallSkill(name);
|
|
756
|
+
if (result.success) {
|
|
757
|
+
console.log(chalk.green(`Removed skill '${name}'`));
|
|
758
|
+
}
|
|
759
|
+
else {
|
|
760
|
+
console.log(chalk.red(result.error || 'Failed to remove skill'));
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
skillsCmd
|
|
764
|
+
.command('push <name>')
|
|
765
|
+
.description('Save project-scoped skill to user scope')
|
|
766
|
+
.option('-a, --agents <list>', 'Comma-separated agents to push for')
|
|
767
|
+
.action((name, options) => {
|
|
768
|
+
const cwd = process.cwd();
|
|
769
|
+
const agents = options.agents
|
|
770
|
+
? options.agents.split(',')
|
|
771
|
+
: SKILLS_CAPABLE_AGENTS;
|
|
772
|
+
let pushed = 0;
|
|
773
|
+
for (const agentId of agents) {
|
|
774
|
+
if (!AGENTS[agentId].capabilities.skills)
|
|
775
|
+
continue;
|
|
776
|
+
const result = promoteSkillToUser(agentId, name, cwd);
|
|
777
|
+
if (result.success) {
|
|
778
|
+
console.log(` ${chalk.green('+')} ${AGENTS[agentId].name}`);
|
|
779
|
+
pushed++;
|
|
780
|
+
}
|
|
781
|
+
else if (result.error && !result.error.includes('not found')) {
|
|
782
|
+
console.log(` ${chalk.red('x')} ${AGENTS[agentId].name}: ${result.error}`);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
if (pushed === 0) {
|
|
786
|
+
console.log(chalk.yellow(`Project skill '${name}' not found for any agent`));
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
console.log(chalk.green(`\nPushed to user scope for ${pushed} agents.`));
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
skillsCmd
|
|
793
|
+
.command('info <name>')
|
|
794
|
+
.description('Show detailed info about an installed skill')
|
|
795
|
+
.action((name) => {
|
|
796
|
+
const skill = getSkillInfo(name);
|
|
797
|
+
if (!skill) {
|
|
798
|
+
console.log(chalk.yellow(`Skill '${name}' not found`));
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
console.log(chalk.bold(`\n${skill.metadata.name}\n`));
|
|
802
|
+
if (skill.metadata.description) {
|
|
803
|
+
console.log(` ${skill.metadata.description}`);
|
|
804
|
+
}
|
|
805
|
+
console.log();
|
|
806
|
+
if (skill.metadata.author) {
|
|
807
|
+
console.log(` Author: ${skill.metadata.author}`);
|
|
808
|
+
}
|
|
809
|
+
if (skill.metadata.version) {
|
|
810
|
+
console.log(` Version: ${skill.metadata.version}`);
|
|
811
|
+
}
|
|
812
|
+
if (skill.metadata.license) {
|
|
813
|
+
console.log(` License: ${skill.metadata.license}`);
|
|
814
|
+
}
|
|
815
|
+
console.log(` Path: ${skill.path}`);
|
|
816
|
+
const rules = getSkillRules(name);
|
|
817
|
+
if (rules.length > 0) {
|
|
818
|
+
console.log(chalk.bold(`\n Rules (${rules.length}):\n`));
|
|
819
|
+
for (const rule of rules) {
|
|
820
|
+
console.log(` ${chalk.cyan(rule)}`);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
console.log();
|
|
824
|
+
});
|
|
305
825
|
// =============================================================================
|
|
306
826
|
// MCP COMMANDS
|
|
307
827
|
// =============================================================================
|
|
@@ -311,40 +831,102 @@ const mcpCmd = program
|
|
|
311
831
|
mcpCmd
|
|
312
832
|
.command('list')
|
|
313
833
|
.description('List MCP servers and registration status')
|
|
314
|
-
.
|
|
834
|
+
.option('-a, --agent <agent>', 'Filter by agent')
|
|
835
|
+
.option('-s, --scope <scope>', 'Filter by scope: user, project, or all', 'all')
|
|
836
|
+
.action((options) => {
|
|
315
837
|
console.log(chalk.bold('\nMCP Servers\n'));
|
|
316
|
-
|
|
838
|
+
const cwd = process.cwd();
|
|
839
|
+
const agents = options.agent
|
|
840
|
+
? [options.agent]
|
|
841
|
+
: MCP_CAPABLE_AGENTS;
|
|
842
|
+
for (const agentId of agents) {
|
|
317
843
|
const agent = AGENTS[agentId];
|
|
844
|
+
if (!agent.capabilities.mcp) {
|
|
845
|
+
console.log(` ${chalk.bold(agent.name)}: ${chalk.gray('mcp not supported')}`);
|
|
846
|
+
console.log();
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
318
849
|
if (!isCliInstalled(agentId)) {
|
|
319
850
|
console.log(` ${chalk.bold(agent.name)}: ${chalk.gray('CLI not installed')}`);
|
|
320
851
|
continue;
|
|
321
852
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
853
|
+
let mcps = listInstalledMcpsWithScope(agentId, cwd);
|
|
854
|
+
if (options.scope !== 'all') {
|
|
855
|
+
mcps = mcps.filter((m) => m.scope === options.scope);
|
|
856
|
+
}
|
|
857
|
+
if (mcps.length === 0) {
|
|
858
|
+
console.log(` ${chalk.bold(agent.name)}: ${chalk.gray('none')}`);
|
|
859
|
+
}
|
|
860
|
+
else {
|
|
861
|
+
console.log(` ${chalk.bold(agent.name)}:`);
|
|
862
|
+
const userMcps = mcps.filter((m) => m.scope === 'user');
|
|
863
|
+
const projectMcps = mcps.filter((m) => m.scope === 'project');
|
|
864
|
+
if (userMcps.length > 0 && (options.scope === 'all' || options.scope === 'user')) {
|
|
865
|
+
console.log(` ${chalk.gray('User:')}`);
|
|
866
|
+
for (const mcp of userMcps) {
|
|
867
|
+
console.log(` ${chalk.cyan(mcp.name)}`);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
if (projectMcps.length > 0 && (options.scope === 'all' || options.scope === 'project')) {
|
|
871
|
+
console.log(` ${chalk.gray('Project:')}`);
|
|
872
|
+
for (const mcp of projectMcps) {
|
|
873
|
+
console.log(` ${chalk.yellow(mcp.name)}`);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
325
877
|
console.log();
|
|
326
878
|
}
|
|
327
879
|
});
|
|
328
880
|
mcpCmd
|
|
329
|
-
.command('add <name>
|
|
330
|
-
.description('Add MCP server
|
|
331
|
-
.option('-a, --agents <list>', 'Comma-separated agents', '
|
|
881
|
+
.command('add <name> [command_or_url...]')
|
|
882
|
+
.description('Add MCP server (stdio: use -- before command, http: use URL)')
|
|
883
|
+
.option('-a, --agents <list>', 'Comma-separated agents', MCP_CAPABLE_AGENTS.join(','))
|
|
332
884
|
.option('-s, --scope <scope>', 'Scope: user or project', 'user')
|
|
333
|
-
.
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
885
|
+
.option('-t, --transport <type>', 'Transport: stdio or http', 'stdio')
|
|
886
|
+
.option('-H, --header <header>', 'HTTP header (name:value), can be repeated', (val, acc) => {
|
|
887
|
+
acc.push(val);
|
|
888
|
+
return acc;
|
|
889
|
+
}, [])
|
|
890
|
+
.action(async (name, commandOrUrl, options) => {
|
|
891
|
+
const transport = options.transport;
|
|
892
|
+
if (commandOrUrl.length === 0) {
|
|
893
|
+
console.error(chalk.red('Error: Command or URL required'));
|
|
894
|
+
console.log(chalk.gray('Stdio: agents mcp add <name> -- <command...>'));
|
|
895
|
+
console.log(chalk.gray('HTTP: agents mcp add <name> <url> --transport http'));
|
|
896
|
+
process.exit(1);
|
|
338
897
|
}
|
|
339
|
-
const
|
|
898
|
+
const source = await ensureSource();
|
|
899
|
+
const localPath = getRepoLocalPath(source);
|
|
340
900
|
const manifest = readManifest(localPath) || createDefaultManifest();
|
|
341
901
|
manifest.mcp = manifest.mcp || {};
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
902
|
+
if (transport === 'http') {
|
|
903
|
+
const url = commandOrUrl[0];
|
|
904
|
+
const headers = {};
|
|
905
|
+
if (options.header && options.header.length > 0) {
|
|
906
|
+
for (const h of options.header) {
|
|
907
|
+
const [key, ...valueParts] = h.split(':');
|
|
908
|
+
if (key && valueParts.length > 0) {
|
|
909
|
+
headers[key.trim()] = valueParts.join(':').trim();
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
manifest.mcp[name] = {
|
|
914
|
+
url,
|
|
915
|
+
transport: 'http',
|
|
916
|
+
scope: options.scope,
|
|
917
|
+
agents: options.agents.split(','),
|
|
918
|
+
...(Object.keys(headers).length > 0 && { headers }),
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
else {
|
|
922
|
+
const command = commandOrUrl.join(' ');
|
|
923
|
+
manifest.mcp[name] = {
|
|
924
|
+
command,
|
|
925
|
+
transport: 'stdio',
|
|
926
|
+
scope: options.scope,
|
|
927
|
+
agents: options.agents.split(','),
|
|
928
|
+
};
|
|
929
|
+
}
|
|
348
930
|
writeManifest(localPath, manifest);
|
|
349
931
|
console.log(chalk.green(`Added MCP server '${name}' to manifest`));
|
|
350
932
|
console.log(chalk.gray('Run: agents mcp register to apply'));
|
|
@@ -378,20 +960,21 @@ mcpCmd
|
|
|
378
960
|
.command('register [name]')
|
|
379
961
|
.description('Register MCP server(s) with agent CLIs')
|
|
380
962
|
.option('-a, --agents <list>', 'Comma-separated agents')
|
|
381
|
-
.action((name, options) => {
|
|
382
|
-
const state = readState();
|
|
963
|
+
.action(async (name, options) => {
|
|
383
964
|
if (!name) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
const localPath = getRepoLocalPath(state.source);
|
|
965
|
+
const source = await ensureSource();
|
|
966
|
+
const localPath = getRepoLocalPath(source);
|
|
389
967
|
const manifest = readManifest(localPath);
|
|
390
968
|
if (!manifest?.mcp) {
|
|
391
969
|
console.log(chalk.yellow('No MCP servers in manifest'));
|
|
392
970
|
return;
|
|
393
971
|
}
|
|
394
972
|
for (const [mcpName, config] of Object.entries(manifest.mcp)) {
|
|
973
|
+
// Skip HTTP transport MCPs for now (need different registration)
|
|
974
|
+
if (config.transport === 'http' || !config.command) {
|
|
975
|
+
console.log(`\n ${chalk.cyan(mcpName)}: ${chalk.yellow('HTTP transport not yet supported')}`);
|
|
976
|
+
continue;
|
|
977
|
+
}
|
|
395
978
|
console.log(`\n ${chalk.cyan(mcpName)}:`);
|
|
396
979
|
for (const agentId of config.agents) {
|
|
397
980
|
if (!isCliInstalled(agentId))
|
|
@@ -409,6 +992,35 @@ mcpCmd
|
|
|
409
992
|
}
|
|
410
993
|
console.log(chalk.yellow('Single MCP registration not yet implemented'));
|
|
411
994
|
});
|
|
995
|
+
mcpCmd
|
|
996
|
+
.command('push <name>')
|
|
997
|
+
.description('Save project-scoped MCP to user scope')
|
|
998
|
+
.option('-a, --agents <list>', 'Comma-separated agents to push for')
|
|
999
|
+
.action((name, options) => {
|
|
1000
|
+
const cwd = process.cwd();
|
|
1001
|
+
const agents = options.agents
|
|
1002
|
+
? options.agents.split(',')
|
|
1003
|
+
: MCP_CAPABLE_AGENTS;
|
|
1004
|
+
let pushed = 0;
|
|
1005
|
+
for (const agentId of agents) {
|
|
1006
|
+
if (!isCliInstalled(agentId))
|
|
1007
|
+
continue;
|
|
1008
|
+
const result = promoteMcpToUser(agentId, name, cwd);
|
|
1009
|
+
if (result.success) {
|
|
1010
|
+
console.log(` ${chalk.green('+')} ${AGENTS[agentId].name}`);
|
|
1011
|
+
pushed++;
|
|
1012
|
+
}
|
|
1013
|
+
else if (result.error && !result.error.includes('not found')) {
|
|
1014
|
+
console.log(` ${chalk.red('x')} ${AGENTS[agentId].name}: ${result.error}`);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
if (pushed === 0) {
|
|
1018
|
+
console.log(chalk.yellow(`Project MCP '${name}' not found for any agent`));
|
|
1019
|
+
}
|
|
1020
|
+
else {
|
|
1021
|
+
console.log(chalk.green(`\nPushed to user scope for ${pushed} agents.`));
|
|
1022
|
+
}
|
|
1023
|
+
});
|
|
412
1024
|
// =============================================================================
|
|
413
1025
|
// CLI COMMANDS
|
|
414
1026
|
// =============================================================================
|
|
@@ -440,19 +1052,15 @@ cliCmd
|
|
|
440
1052
|
.command('add <agent>')
|
|
441
1053
|
.description('Add agent CLI to manifest')
|
|
442
1054
|
.option('-v, --version <version>', 'Version to pin', 'latest')
|
|
443
|
-
.action((agent, options) => {
|
|
444
|
-
const state = readState();
|
|
445
|
-
if (!state.source) {
|
|
446
|
-
console.log(chalk.yellow('No .agents repo configured. Run: agents pull <source>'));
|
|
447
|
-
return;
|
|
448
|
-
}
|
|
1055
|
+
.action(async (agent, options) => {
|
|
449
1056
|
const agentId = agent.toLowerCase();
|
|
450
1057
|
if (!AGENTS[agentId]) {
|
|
451
1058
|
console.log(chalk.red(`Unknown agent: ${agent}`));
|
|
452
1059
|
console.log(chalk.gray(`Available: ${ALL_AGENT_IDS.join(', ')}`));
|
|
453
1060
|
return;
|
|
454
1061
|
}
|
|
455
|
-
const
|
|
1062
|
+
const source = await ensureSource();
|
|
1063
|
+
const localPath = getRepoLocalPath(source);
|
|
456
1064
|
const manifest = readManifest(localPath) || createDefaultManifest();
|
|
457
1065
|
manifest.clis = manifest.clis || {};
|
|
458
1066
|
manifest.clis[agentId] = {
|
|
@@ -465,14 +1073,10 @@ cliCmd
|
|
|
465
1073
|
cliCmd
|
|
466
1074
|
.command('remove <agent>')
|
|
467
1075
|
.description('Remove agent CLI from manifest')
|
|
468
|
-
.action((agent) => {
|
|
469
|
-
const
|
|
470
|
-
if (!state.source) {
|
|
471
|
-
console.log(chalk.yellow('No .agents repo configured. Run: agents pull <source>'));
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
1076
|
+
.action(async (agent) => {
|
|
1077
|
+
const source = await ensureSource();
|
|
474
1078
|
const agentId = agent.toLowerCase();
|
|
475
|
-
const localPath = getRepoLocalPath(
|
|
1079
|
+
const localPath = getRepoLocalPath(source);
|
|
476
1080
|
const manifest = readManifest(localPath);
|
|
477
1081
|
if (manifest?.clis?.[agentId]) {
|
|
478
1082
|
delete manifest.clis[agentId];
|
|
@@ -486,10 +1090,12 @@ cliCmd
|
|
|
486
1090
|
cliCmd
|
|
487
1091
|
.command('upgrade [agent]')
|
|
488
1092
|
.description('Upgrade agent CLI(s) to version in manifest')
|
|
1093
|
+
.option('-s, --scope <scope>', 'Target scope (default: user)', 'user')
|
|
489
1094
|
.option('--latest', 'Upgrade to latest version (ignore manifest)')
|
|
490
1095
|
.action(async (agent, options) => {
|
|
491
|
-
const
|
|
492
|
-
const
|
|
1096
|
+
const scopeName = options.scope;
|
|
1097
|
+
const scope = getScope(scopeName);
|
|
1098
|
+
const localPath = scope ? getRepoLocalPath(scope.source) : null;
|
|
493
1099
|
const manifest = localPath ? readManifest(localPath) : null;
|
|
494
1100
|
const agentsToUpgrade = agent
|
|
495
1101
|
? [agent.toLowerCase()]
|
|
@@ -520,6 +1126,145 @@ cliCmd
|
|
|
520
1126
|
}
|
|
521
1127
|
});
|
|
522
1128
|
// =============================================================================
|
|
1129
|
+
// REPO COMMANDS
|
|
1130
|
+
// =============================================================================
|
|
1131
|
+
const repoCmd = program
|
|
1132
|
+
.command('repo')
|
|
1133
|
+
.description('Manage .agents repository scopes');
|
|
1134
|
+
repoCmd
|
|
1135
|
+
.command('list')
|
|
1136
|
+
.description('List configured repository scopes')
|
|
1137
|
+
.action(() => {
|
|
1138
|
+
const scopes = getScopesByPriority();
|
|
1139
|
+
if (scopes.length === 0) {
|
|
1140
|
+
console.log(chalk.yellow('\nNo scopes configured.'));
|
|
1141
|
+
console.log(chalk.gray(' Run: agents repo add <source>'));
|
|
1142
|
+
console.log();
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
console.log(chalk.bold('\nConfigured Scopes\n'));
|
|
1146
|
+
console.log(chalk.gray(' Scopes are applied in priority order (higher overrides lower)\n'));
|
|
1147
|
+
for (const { name, config } of scopes) {
|
|
1148
|
+
const readonlyTag = config.readonly ? chalk.gray(' (readonly)') : '';
|
|
1149
|
+
console.log(` ${chalk.bold(name)}${readonlyTag}`);
|
|
1150
|
+
console.log(` Source: ${config.source}`);
|
|
1151
|
+
console.log(` Branch: ${config.branch}`);
|
|
1152
|
+
console.log(` Commit: ${config.commit.substring(0, 8)}`);
|
|
1153
|
+
console.log(` Priority: ${config.priority}`);
|
|
1154
|
+
console.log(` Synced: ${new Date(config.lastSync).toLocaleString()}`);
|
|
1155
|
+
console.log();
|
|
1156
|
+
}
|
|
1157
|
+
});
|
|
1158
|
+
repoCmd
|
|
1159
|
+
.command('add <source>')
|
|
1160
|
+
.description('Add a repository scope')
|
|
1161
|
+
.option('-s, --scope <scope>', 'Target scope (default: user)', 'user')
|
|
1162
|
+
.option('-y, --yes', 'Skip confirmation prompts')
|
|
1163
|
+
.action(async (source, options) => {
|
|
1164
|
+
const scopeName = options.scope;
|
|
1165
|
+
const existingScope = getScope(scopeName);
|
|
1166
|
+
if (existingScope && !options.yes) {
|
|
1167
|
+
const shouldOverwrite = await confirm({
|
|
1168
|
+
message: `Scope '${scopeName}' already exists (${existingScope.source}). Overwrite?`,
|
|
1169
|
+
default: false,
|
|
1170
|
+
});
|
|
1171
|
+
if (!shouldOverwrite) {
|
|
1172
|
+
console.log(chalk.yellow('Cancelled.'));
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
if (existingScope?.readonly && !options.yes) {
|
|
1177
|
+
console.log(chalk.red(`Scope '${scopeName}' is readonly. Cannot overwrite.`));
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
const parsed = parseSource(source);
|
|
1181
|
+
const spinner = ora(`Cloning repository for ${scopeName} scope...`).start();
|
|
1182
|
+
try {
|
|
1183
|
+
const { commit, isNew } = await cloneRepo(source);
|
|
1184
|
+
spinner.succeed(isNew ? 'Repository cloned' : 'Repository updated');
|
|
1185
|
+
const priority = getScopePriority(scopeName);
|
|
1186
|
+
setScope(scopeName, {
|
|
1187
|
+
source,
|
|
1188
|
+
branch: parsed.ref || 'main',
|
|
1189
|
+
commit,
|
|
1190
|
+
lastSync: new Date().toISOString(),
|
|
1191
|
+
priority,
|
|
1192
|
+
readonly: scopeName === 'system',
|
|
1193
|
+
});
|
|
1194
|
+
console.log(chalk.green(`\nAdded scope '${scopeName}' with priority ${priority}`));
|
|
1195
|
+
const scopeHint = scopeName === 'user' ? '' : ` --scope ${scopeName}`;
|
|
1196
|
+
console.log(chalk.gray(` Run: agents pull${scopeHint} to sync commands`));
|
|
1197
|
+
}
|
|
1198
|
+
catch (err) {
|
|
1199
|
+
spinner.fail('Failed to add scope');
|
|
1200
|
+
console.error(chalk.red(err.message));
|
|
1201
|
+
process.exit(1);
|
|
1202
|
+
}
|
|
1203
|
+
});
|
|
1204
|
+
repoCmd
|
|
1205
|
+
.command('remove <scope>')
|
|
1206
|
+
.description('Remove a repository scope')
|
|
1207
|
+
.option('-y, --yes', 'Skip confirmation prompts')
|
|
1208
|
+
.action(async (scopeName, options) => {
|
|
1209
|
+
const existingScope = getScope(scopeName);
|
|
1210
|
+
if (!existingScope) {
|
|
1211
|
+
console.log(chalk.yellow(`Scope '${scopeName}' not found.`));
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
if (existingScope.readonly) {
|
|
1215
|
+
console.log(chalk.red(`Scope '${scopeName}' is readonly. Cannot remove.`));
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
if (!options.yes) {
|
|
1219
|
+
const shouldRemove = await confirm({
|
|
1220
|
+
message: `Remove scope '${scopeName}' (${existingScope.source})?`,
|
|
1221
|
+
default: false,
|
|
1222
|
+
});
|
|
1223
|
+
if (!shouldRemove) {
|
|
1224
|
+
console.log(chalk.yellow('Cancelled.'));
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
const removed = removeScope(scopeName);
|
|
1229
|
+
if (removed) {
|
|
1230
|
+
console.log(chalk.green(`Removed scope '${scopeName}'`));
|
|
1231
|
+
}
|
|
1232
|
+
else {
|
|
1233
|
+
console.log(chalk.yellow(`Failed to remove scope '${scopeName}'`));
|
|
1234
|
+
}
|
|
1235
|
+
});
|
|
1236
|
+
repoCmd
|
|
1237
|
+
.command('sync [scope]')
|
|
1238
|
+
.description('Sync a specific scope or all scopes')
|
|
1239
|
+
.option('-y, --yes', 'Skip confirmation prompts')
|
|
1240
|
+
.action(async (scopeName, options) => {
|
|
1241
|
+
const scopes = scopeName ? [{ name: scopeName, config: getScope(scopeName) }].filter(s => s.config) : getScopesByPriority();
|
|
1242
|
+
if (scopes.length === 0) {
|
|
1243
|
+
console.log(chalk.yellow('No scopes to sync.'));
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
for (const { name, config } of scopes) {
|
|
1247
|
+
if (!config)
|
|
1248
|
+
continue;
|
|
1249
|
+
console.log(chalk.bold(`\nSyncing scope: ${name}`));
|
|
1250
|
+
const spinner = ora('Updating repository...').start();
|
|
1251
|
+
try {
|
|
1252
|
+
const { commit } = await cloneRepo(config.source);
|
|
1253
|
+
spinner.succeed('Repository updated');
|
|
1254
|
+
setScope(name, {
|
|
1255
|
+
...config,
|
|
1256
|
+
commit,
|
|
1257
|
+
lastSync: new Date().toISOString(),
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
catch (err) {
|
|
1261
|
+
spinner.fail(`Failed to sync ${name}`);
|
|
1262
|
+
console.error(chalk.gray(err.message));
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
console.log(chalk.green('\nSync complete.'));
|
|
1266
|
+
});
|
|
1267
|
+
// =============================================================================
|
|
523
1268
|
// INIT COMMAND
|
|
524
1269
|
// =============================================================================
|
|
525
1270
|
program
|
|
@@ -544,5 +1289,341 @@ program
|
|
|
544
1289
|
console.log(chalk.gray(' claude/hooks/'));
|
|
545
1290
|
console.log();
|
|
546
1291
|
});
|
|
1292
|
+
// =============================================================================
|
|
1293
|
+
// REGISTRY COMMANDS
|
|
1294
|
+
// =============================================================================
|
|
1295
|
+
const registryCmd = program
|
|
1296
|
+
.command('registry')
|
|
1297
|
+
.description('Manage package registries (MCP servers, skills)');
|
|
1298
|
+
registryCmd
|
|
1299
|
+
.command('list')
|
|
1300
|
+
.description('List configured registries')
|
|
1301
|
+
.option('-t, --type <type>', 'Filter by type: mcp or skill')
|
|
1302
|
+
.action((options) => {
|
|
1303
|
+
const types = options.type ? [options.type] : ['mcp', 'skill'];
|
|
1304
|
+
console.log(chalk.bold('\nConfigured Registries\n'));
|
|
1305
|
+
for (const type of types) {
|
|
1306
|
+
console.log(chalk.bold(` ${type.toUpperCase()}`));
|
|
1307
|
+
const registries = getRegistries(type);
|
|
1308
|
+
const entries = Object.entries(registries);
|
|
1309
|
+
if (entries.length === 0) {
|
|
1310
|
+
console.log(chalk.gray(' No registries configured'));
|
|
1311
|
+
}
|
|
1312
|
+
else {
|
|
1313
|
+
for (const [name, config] of entries) {
|
|
1314
|
+
const status = config.enabled ? chalk.green('enabled') : chalk.gray('disabled');
|
|
1315
|
+
const isDefault = DEFAULT_REGISTRIES[type]?.[name] ? chalk.gray(' (default)') : '';
|
|
1316
|
+
console.log(` ${name}${isDefault}: ${status}`);
|
|
1317
|
+
console.log(chalk.gray(` ${config.url}`));
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
console.log();
|
|
1321
|
+
}
|
|
1322
|
+
});
|
|
1323
|
+
registryCmd
|
|
1324
|
+
.command('add <type> <name> <url>')
|
|
1325
|
+
.description('Add a registry (type: mcp or skill)')
|
|
1326
|
+
.option('--api-key <key>', 'API key for authentication')
|
|
1327
|
+
.action((type, name, url, options) => {
|
|
1328
|
+
if (type !== 'mcp' && type !== 'skill') {
|
|
1329
|
+
console.log(chalk.red(`Invalid type '${type}'. Use 'mcp' or 'skill'.`));
|
|
1330
|
+
process.exit(1);
|
|
1331
|
+
}
|
|
1332
|
+
setRegistry(type, name, {
|
|
1333
|
+
url,
|
|
1334
|
+
enabled: true,
|
|
1335
|
+
apiKey: options.apiKey,
|
|
1336
|
+
});
|
|
1337
|
+
console.log(chalk.green(`Added ${type} registry '${name}'`));
|
|
1338
|
+
});
|
|
1339
|
+
registryCmd
|
|
1340
|
+
.command('remove <type> <name>')
|
|
1341
|
+
.description('Remove a registry')
|
|
1342
|
+
.action((type, name) => {
|
|
1343
|
+
if (type !== 'mcp' && type !== 'skill') {
|
|
1344
|
+
console.log(chalk.red(`Invalid type '${type}'. Use 'mcp' or 'skill'.`));
|
|
1345
|
+
process.exit(1);
|
|
1346
|
+
}
|
|
1347
|
+
// Check if it's a default registry
|
|
1348
|
+
if (DEFAULT_REGISTRIES[type]?.[name]) {
|
|
1349
|
+
console.log(chalk.yellow(`Cannot remove default registry '${name}'. Use 'agents registry disable' instead.`));
|
|
1350
|
+
process.exit(1);
|
|
1351
|
+
}
|
|
1352
|
+
if (removeRegistry(type, name)) {
|
|
1353
|
+
console.log(chalk.green(`Removed ${type} registry '${name}'`));
|
|
1354
|
+
}
|
|
1355
|
+
else {
|
|
1356
|
+
console.log(chalk.yellow(`Registry '${name}' not found`));
|
|
1357
|
+
}
|
|
1358
|
+
});
|
|
1359
|
+
registryCmd
|
|
1360
|
+
.command('enable <type> <name>')
|
|
1361
|
+
.description('Enable a registry')
|
|
1362
|
+
.action((type, name) => {
|
|
1363
|
+
if (type !== 'mcp' && type !== 'skill') {
|
|
1364
|
+
console.log(chalk.red(`Invalid type '${type}'. Use 'mcp' or 'skill'.`));
|
|
1365
|
+
process.exit(1);
|
|
1366
|
+
}
|
|
1367
|
+
const registries = getRegistries(type);
|
|
1368
|
+
if (!registries[name]) {
|
|
1369
|
+
console.log(chalk.yellow(`Registry '${name}' not found`));
|
|
1370
|
+
process.exit(1);
|
|
1371
|
+
}
|
|
1372
|
+
setRegistry(type, name, { enabled: true });
|
|
1373
|
+
console.log(chalk.green(`Enabled ${type} registry '${name}'`));
|
|
1374
|
+
});
|
|
1375
|
+
registryCmd
|
|
1376
|
+
.command('disable <type> <name>')
|
|
1377
|
+
.description('Disable a registry')
|
|
1378
|
+
.action((type, name) => {
|
|
1379
|
+
if (type !== 'mcp' && type !== 'skill') {
|
|
1380
|
+
console.log(chalk.red(`Invalid type '${type}'. Use 'mcp' or 'skill'.`));
|
|
1381
|
+
process.exit(1);
|
|
1382
|
+
}
|
|
1383
|
+
const registries = getRegistries(type);
|
|
1384
|
+
if (!registries[name]) {
|
|
1385
|
+
console.log(chalk.yellow(`Registry '${name}' not found`));
|
|
1386
|
+
process.exit(1);
|
|
1387
|
+
}
|
|
1388
|
+
setRegistry(type, name, { enabled: false });
|
|
1389
|
+
console.log(chalk.green(`Disabled ${type} registry '${name}'`));
|
|
1390
|
+
});
|
|
1391
|
+
registryCmd
|
|
1392
|
+
.command('config <type> <name>')
|
|
1393
|
+
.description('Configure a registry')
|
|
1394
|
+
.option('--api-key <key>', 'Set API key')
|
|
1395
|
+
.option('--url <url>', 'Update URL')
|
|
1396
|
+
.action((type, name, options) => {
|
|
1397
|
+
if (type !== 'mcp' && type !== 'skill') {
|
|
1398
|
+
console.log(chalk.red(`Invalid type '${type}'. Use 'mcp' or 'skill'.`));
|
|
1399
|
+
process.exit(1);
|
|
1400
|
+
}
|
|
1401
|
+
const registries = getRegistries(type);
|
|
1402
|
+
if (!registries[name]) {
|
|
1403
|
+
console.log(chalk.yellow(`Registry '${name}' not found`));
|
|
1404
|
+
process.exit(1);
|
|
1405
|
+
}
|
|
1406
|
+
const updates = {};
|
|
1407
|
+
if (options.apiKey)
|
|
1408
|
+
updates.apiKey = options.apiKey;
|
|
1409
|
+
if (options.url)
|
|
1410
|
+
updates.url = options.url;
|
|
1411
|
+
if (Object.keys(updates).length === 0) {
|
|
1412
|
+
console.log(chalk.yellow('No options provided. Use --api-key or --url.'));
|
|
1413
|
+
process.exit(1);
|
|
1414
|
+
}
|
|
1415
|
+
setRegistry(type, name, updates);
|
|
1416
|
+
console.log(chalk.green(`Updated ${type} registry '${name}'`));
|
|
1417
|
+
});
|
|
1418
|
+
// =============================================================================
|
|
1419
|
+
// SEARCH COMMAND
|
|
1420
|
+
// =============================================================================
|
|
1421
|
+
program
|
|
1422
|
+
.command('search <query>')
|
|
1423
|
+
.description('Search registries for packages (MCP servers, skills)')
|
|
1424
|
+
.option('-t, --type <type>', 'Filter by type: mcp or skill')
|
|
1425
|
+
.option('-r, --registry <name>', 'Search specific registry')
|
|
1426
|
+
.option('-l, --limit <n>', 'Max results', '20')
|
|
1427
|
+
.action(async (query, options) => {
|
|
1428
|
+
const spinner = ora('Searching registries...').start();
|
|
1429
|
+
try {
|
|
1430
|
+
const results = await searchRegistries(query, {
|
|
1431
|
+
type: options.type,
|
|
1432
|
+
registry: options.registry,
|
|
1433
|
+
limit: parseInt(options.limit, 10),
|
|
1434
|
+
});
|
|
1435
|
+
spinner.stop();
|
|
1436
|
+
if (results.length === 0) {
|
|
1437
|
+
console.log(chalk.yellow('\nNo packages found.'));
|
|
1438
|
+
if (!options.type) {
|
|
1439
|
+
console.log(chalk.gray('\nTip: skill registries not yet available. Use gh:user/repo for skills.'));
|
|
1440
|
+
}
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
console.log(chalk.bold(`\nFound ${results.length} packages\n`));
|
|
1444
|
+
// Group by type
|
|
1445
|
+
const mcpResults = results.filter((r) => r.type === 'mcp');
|
|
1446
|
+
const skillResults = results.filter((r) => r.type === 'skill');
|
|
1447
|
+
if (mcpResults.length > 0) {
|
|
1448
|
+
console.log(chalk.bold(' MCP Servers'));
|
|
1449
|
+
for (const result of mcpResults) {
|
|
1450
|
+
const desc = result.description
|
|
1451
|
+
? chalk.gray(` - ${result.description.slice(0, 50)}${result.description.length > 50 ? '...' : ''}`)
|
|
1452
|
+
: '';
|
|
1453
|
+
console.log(` ${chalk.cyan(result.name)}${desc}`);
|
|
1454
|
+
console.log(chalk.gray(` Registry: ${result.registry} Install: agents add mcp:${result.name}`));
|
|
1455
|
+
}
|
|
1456
|
+
console.log();
|
|
1457
|
+
}
|
|
1458
|
+
if (skillResults.length > 0) {
|
|
1459
|
+
console.log(chalk.bold(' Skills'));
|
|
1460
|
+
for (const result of skillResults) {
|
|
1461
|
+
const desc = result.description
|
|
1462
|
+
? chalk.gray(` - ${result.description.slice(0, 50)}${result.description.length > 50 ? '...' : ''}`)
|
|
1463
|
+
: '';
|
|
1464
|
+
console.log(` ${chalk.cyan(result.name)}${desc}`);
|
|
1465
|
+
console.log(chalk.gray(` Registry: ${result.registry} Install: agents add skill:${result.name}`));
|
|
1466
|
+
}
|
|
1467
|
+
console.log();
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
catch (err) {
|
|
1471
|
+
spinner.fail('Search failed');
|
|
1472
|
+
console.error(chalk.red(err.message));
|
|
1473
|
+
process.exit(1);
|
|
1474
|
+
}
|
|
1475
|
+
});
|
|
1476
|
+
// =============================================================================
|
|
1477
|
+
// ADD COMMAND (unified package installation)
|
|
1478
|
+
// =============================================================================
|
|
1479
|
+
program
|
|
1480
|
+
.command('add <identifier>')
|
|
1481
|
+
.description('Add a package (mcp:name, skill:user/repo, or gh:user/repo)')
|
|
1482
|
+
.option('-a, --agents <list>', 'Comma-separated agents to install to')
|
|
1483
|
+
.action(async (identifier, options) => {
|
|
1484
|
+
const spinner = ora('Resolving package...').start();
|
|
1485
|
+
try {
|
|
1486
|
+
const resolved = await resolvePackage(identifier);
|
|
1487
|
+
if (!resolved) {
|
|
1488
|
+
spinner.fail('Package not found');
|
|
1489
|
+
console.log(chalk.gray('\nTip: Use explicit prefix (mcp:, skill:, gh:) or check the identifier.'));
|
|
1490
|
+
process.exit(1);
|
|
1491
|
+
}
|
|
1492
|
+
spinner.succeed(`Found ${resolved.type} package`);
|
|
1493
|
+
if (resolved.type === 'mcp') {
|
|
1494
|
+
// Install MCP server
|
|
1495
|
+
const entry = resolved.mcpEntry;
|
|
1496
|
+
if (!entry) {
|
|
1497
|
+
console.log(chalk.red('Failed to get MCP server details'));
|
|
1498
|
+
process.exit(1);
|
|
1499
|
+
}
|
|
1500
|
+
console.log(chalk.bold(`\n${entry.name}`));
|
|
1501
|
+
if (entry.description) {
|
|
1502
|
+
console.log(chalk.gray(` ${entry.description}`));
|
|
1503
|
+
}
|
|
1504
|
+
if (entry.repository?.url) {
|
|
1505
|
+
console.log(chalk.gray(` ${entry.repository.url}`));
|
|
1506
|
+
}
|
|
1507
|
+
// Get package info
|
|
1508
|
+
const pkg = entry.packages?.[0];
|
|
1509
|
+
if (!pkg) {
|
|
1510
|
+
console.log(chalk.yellow('\nNo installable package found for this server.'));
|
|
1511
|
+
console.log(chalk.gray('You may need to install it manually.'));
|
|
1512
|
+
process.exit(1);
|
|
1513
|
+
}
|
|
1514
|
+
console.log(chalk.bold('\nPackage:'));
|
|
1515
|
+
console.log(` Name: ${pkg.name || pkg.registry_name}`);
|
|
1516
|
+
console.log(` Runtime: ${pkg.runtime || 'unknown'}`);
|
|
1517
|
+
console.log(` Transport: ${pkg.transport || 'stdio'}`);
|
|
1518
|
+
if (pkg.packageArguments && pkg.packageArguments.length > 0) {
|
|
1519
|
+
console.log(chalk.bold('\nRequired arguments:'));
|
|
1520
|
+
for (const arg of pkg.packageArguments) {
|
|
1521
|
+
const req = arg.required ? chalk.red('*') : '';
|
|
1522
|
+
console.log(` ${arg.name}${req}: ${arg.description || ''}`);
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
// Determine command based on runtime
|
|
1526
|
+
let command;
|
|
1527
|
+
if (pkg.runtime === 'node') {
|
|
1528
|
+
command = `npx -y ${pkg.name || pkg.registry_name}`;
|
|
1529
|
+
}
|
|
1530
|
+
else if (pkg.runtime === 'python') {
|
|
1531
|
+
command = `uvx ${pkg.name || pkg.registry_name}`;
|
|
1532
|
+
}
|
|
1533
|
+
else {
|
|
1534
|
+
command = pkg.name || pkg.registry_name;
|
|
1535
|
+
}
|
|
1536
|
+
const agents = options.agents
|
|
1537
|
+
? options.agents.split(',')
|
|
1538
|
+
: MCP_CAPABLE_AGENTS.filter((id) => isCliInstalled(id));
|
|
1539
|
+
if (agents.length === 0) {
|
|
1540
|
+
console.log(chalk.yellow('\nNo MCP-capable agents installed.'));
|
|
1541
|
+
process.exit(1);
|
|
1542
|
+
}
|
|
1543
|
+
console.log(chalk.bold('\nInstalling to agents...'));
|
|
1544
|
+
for (const agentId of agents) {
|
|
1545
|
+
if (!isCliInstalled(agentId))
|
|
1546
|
+
continue;
|
|
1547
|
+
const result = registerMcp(agentId, entry.name, command, 'user');
|
|
1548
|
+
if (result.success) {
|
|
1549
|
+
console.log(` ${chalk.green('+')} ${AGENTS[agentId].name}`);
|
|
1550
|
+
}
|
|
1551
|
+
else {
|
|
1552
|
+
console.log(` ${chalk.red('x')} ${AGENTS[agentId].name}: ${result.error}`);
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
console.log(chalk.green('\nMCP server installed.'));
|
|
1556
|
+
}
|
|
1557
|
+
else if (resolved.type === 'git' || resolved.type === 'skill') {
|
|
1558
|
+
// Install from git source (skills/commands/hooks)
|
|
1559
|
+
console.log(chalk.bold(`\nInstalling from ${resolved.source}`));
|
|
1560
|
+
const { localPath } = await cloneRepo(resolved.source);
|
|
1561
|
+
// Discover what's in the repo
|
|
1562
|
+
const commands = discoverCommands(localPath);
|
|
1563
|
+
const skills = discoverSkillsFromRepo(localPath);
|
|
1564
|
+
const hooks = discoverHooksFromRepo(localPath);
|
|
1565
|
+
const hasCommands = commands.length > 0;
|
|
1566
|
+
const hasSkills = skills.length > 0;
|
|
1567
|
+
const hasHooks = hooks.shared.length > 0 || Object.values(hooks.agentSpecific).some((h) => h.length > 0);
|
|
1568
|
+
if (!hasCommands && !hasSkills && !hasHooks) {
|
|
1569
|
+
console.log(chalk.yellow('No installable content found in repository.'));
|
|
1570
|
+
process.exit(1);
|
|
1571
|
+
}
|
|
1572
|
+
console.log(chalk.bold('\nFound:'));
|
|
1573
|
+
if (hasCommands)
|
|
1574
|
+
console.log(` ${commands.length} commands`);
|
|
1575
|
+
if (hasSkills)
|
|
1576
|
+
console.log(` ${skills.length} skills`);
|
|
1577
|
+
if (hasHooks)
|
|
1578
|
+
console.log(` ${hooks.shared.length + Object.values(hooks.agentSpecific).flat().length} hooks`);
|
|
1579
|
+
const agents = options.agents
|
|
1580
|
+
? options.agents.split(',')
|
|
1581
|
+
: ['claude', 'codex', 'gemini'];
|
|
1582
|
+
// Install commands
|
|
1583
|
+
if (hasCommands) {
|
|
1584
|
+
console.log(chalk.bold('\nInstalling commands...'));
|
|
1585
|
+
let installed = 0;
|
|
1586
|
+
for (const command of commands) {
|
|
1587
|
+
for (const agentId of agents) {
|
|
1588
|
+
if (!isCliInstalled(agentId) && agentId !== 'cursor')
|
|
1589
|
+
continue;
|
|
1590
|
+
const sourcePath = resolveCommandSource(localPath, command.name, agentId);
|
|
1591
|
+
if (sourcePath) {
|
|
1592
|
+
installCommand(sourcePath, agentId, command.name, 'symlink');
|
|
1593
|
+
installed++;
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
console.log(` Installed ${installed} command instances`);
|
|
1598
|
+
}
|
|
1599
|
+
// Install skills
|
|
1600
|
+
if (hasSkills) {
|
|
1601
|
+
console.log(chalk.bold('\nInstalling skills...'));
|
|
1602
|
+
for (const skill of skills) {
|
|
1603
|
+
const result = installSkill(skill.path, skill.name, agents);
|
|
1604
|
+
if (result.success) {
|
|
1605
|
+
console.log(` ${chalk.green('+')} ${skill.name}`);
|
|
1606
|
+
}
|
|
1607
|
+
else {
|
|
1608
|
+
console.log(` ${chalk.red('x')} ${skill.name}: ${result.error}`);
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
// Install hooks
|
|
1613
|
+
if (hasHooks) {
|
|
1614
|
+
console.log(chalk.bold('\nInstalling hooks...'));
|
|
1615
|
+
const hookAgents = agents.filter((id) => AGENTS[id].supportsHooks);
|
|
1616
|
+
const result = await installHooks(localPath, hookAgents, { scope: 'user' });
|
|
1617
|
+
console.log(` Installed ${result.installed.length} hooks`);
|
|
1618
|
+
}
|
|
1619
|
+
console.log(chalk.green('\nPackage installed.'));
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
catch (err) {
|
|
1623
|
+
spinner.fail('Installation failed');
|
|
1624
|
+
console.error(chalk.red(err.message));
|
|
1625
|
+
process.exit(1);
|
|
1626
|
+
}
|
|
1627
|
+
});
|
|
547
1628
|
program.parse();
|
|
548
1629
|
//# sourceMappingURL=index.js.map
|