@swarmify/agents-cli 1.0.0 → 1.1.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 +117 -100
- package/dist/index.js +1152 -102
- 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 +140 -6
- 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(', ')}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
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(', ')}`);
|
|
40
135
|
}
|
|
41
136
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
console.log(
|
|
45
|
-
|
|
46
|
-
|
|
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()}`);
|
|
47
147
|
}
|
|
48
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,20 +229,20 @@ 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;
|
|
@@ -144,12 +259,17 @@ program
|
|
|
144
259
|
}
|
|
145
260
|
mcpSpinner.succeed(`Registered ${registered} MCP servers`);
|
|
146
261
|
}
|
|
147
|
-
|
|
148
|
-
|
|
262
|
+
// Update scope config
|
|
263
|
+
const priority = getScopePriority(scopeName);
|
|
264
|
+
setScope(scopeName, {
|
|
149
265
|
source: targetSource,
|
|
266
|
+
branch: parsed.ref || 'main',
|
|
267
|
+
commit,
|
|
150
268
|
lastSync: new Date().toISOString(),
|
|
269
|
+
priority,
|
|
270
|
+
readonly: scopeName === 'system',
|
|
151
271
|
});
|
|
152
|
-
console.log(chalk.green(
|
|
272
|
+
console.log(chalk.green(`\nSync complete. Updated scope: ${scopeName}`));
|
|
153
273
|
}
|
|
154
274
|
catch (err) {
|
|
155
275
|
spinner.fail('Failed to sync');
|
|
@@ -162,15 +282,23 @@ program
|
|
|
162
282
|
// =============================================================================
|
|
163
283
|
program
|
|
164
284
|
.command('push')
|
|
165
|
-
.description('Export local
|
|
166
|
-
.option('
|
|
285
|
+
.description('Export local configuration to .agents repo for manual commit')
|
|
286
|
+
.option('-s, --scope <scope>', 'Target scope (default: user)', 'user')
|
|
287
|
+
.option('--export-only', 'Only export, do not update manifest')
|
|
167
288
|
.action(async (options) => {
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
289
|
+
const scopeName = options.scope;
|
|
290
|
+
const scope = getScope(scopeName);
|
|
291
|
+
if (!scope) {
|
|
292
|
+
console.log(chalk.red(`Scope '${scopeName}' not configured.`));
|
|
293
|
+
const scopeHint = scopeName === 'user' ? '' : ` --scope ${scopeName}`;
|
|
294
|
+
console.log(chalk.gray(` Run: agents repo add <source>${scopeHint}`));
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
if (scope.readonly) {
|
|
298
|
+
console.log(chalk.red(`Scope '${scopeName}' is readonly. Cannot push.`));
|
|
171
299
|
process.exit(1);
|
|
172
300
|
}
|
|
173
|
-
const localPath = getRepoLocalPath(
|
|
301
|
+
const localPath = getRepoLocalPath(scope.source);
|
|
174
302
|
const manifest = readManifest(localPath) || createDefaultManifest();
|
|
175
303
|
console.log(chalk.bold('\nExporting local configuration...\n'));
|
|
176
304
|
const cliStates = getAllCliStates();
|
|
@@ -218,71 +346,89 @@ program
|
|
|
218
346
|
await program.commands.find((c) => c.name() === 'pull')?.parseAsync(args, { from: 'user' });
|
|
219
347
|
});
|
|
220
348
|
// =============================================================================
|
|
221
|
-
//
|
|
349
|
+
// COMMANDS COMMANDS
|
|
222
350
|
// =============================================================================
|
|
223
|
-
const
|
|
224
|
-
.command('
|
|
225
|
-
.description('Manage
|
|
226
|
-
|
|
351
|
+
const commandsCmd = program
|
|
352
|
+
.command('commands')
|
|
353
|
+
.description('Manage slash commands');
|
|
354
|
+
commandsCmd
|
|
227
355
|
.command('list')
|
|
228
|
-
.description('List installed
|
|
229
|
-
.option('-a, --agent <agent>', '
|
|
356
|
+
.description('List installed commands')
|
|
357
|
+
.option('-a, --agent <agent>', 'Filter by agent')
|
|
358
|
+
.option('-s, --scope <scope>', 'Filter by scope: user, project, or all', 'all')
|
|
230
359
|
.action((options) => {
|
|
231
|
-
console.log(chalk.bold('\nInstalled
|
|
360
|
+
console.log(chalk.bold('\nInstalled Commands\n'));
|
|
361
|
+
const cwd = process.cwd();
|
|
232
362
|
const agents = options.agent
|
|
233
363
|
? [options.agent]
|
|
234
364
|
: ALL_AGENT_IDS;
|
|
235
365
|
for (const agentId of agents) {
|
|
236
366
|
const agent = AGENTS[agentId];
|
|
237
|
-
|
|
238
|
-
if (
|
|
367
|
+
let commands = listInstalledCommandsWithScope(agentId, cwd);
|
|
368
|
+
if (options.scope !== 'all') {
|
|
369
|
+
commands = commands.filter((c) => c.scope === options.scope);
|
|
370
|
+
}
|
|
371
|
+
if (commands.length === 0) {
|
|
239
372
|
console.log(` ${chalk.bold(agent.name)}: ${chalk.gray('none')}`);
|
|
240
373
|
}
|
|
241
374
|
else {
|
|
242
375
|
console.log(` ${chalk.bold(agent.name)}:`);
|
|
243
|
-
|
|
244
|
-
|
|
376
|
+
const userCommands = commands.filter((c) => c.scope === 'user');
|
|
377
|
+
const projectCommands = commands.filter((c) => c.scope === 'project');
|
|
378
|
+
if (userCommands.length > 0 && (options.scope === 'all' || options.scope === 'user')) {
|
|
379
|
+
console.log(` ${chalk.gray('User:')}`);
|
|
380
|
+
for (const cmd of userCommands) {
|
|
381
|
+
const desc = cmd.description ? ` - ${chalk.gray(cmd.description)}` : '';
|
|
382
|
+
console.log(` ${chalk.cyan(cmd.name)}${desc}`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (projectCommands.length > 0 && (options.scope === 'all' || options.scope === 'project')) {
|
|
386
|
+
console.log(` ${chalk.gray('Project:')}`);
|
|
387
|
+
for (const cmd of projectCommands) {
|
|
388
|
+
const desc = cmd.description ? ` - ${chalk.gray(cmd.description)}` : '';
|
|
389
|
+
console.log(` ${chalk.yellow(cmd.name)}${desc}`);
|
|
390
|
+
}
|
|
245
391
|
}
|
|
246
392
|
}
|
|
247
393
|
console.log();
|
|
248
394
|
}
|
|
249
395
|
});
|
|
250
|
-
|
|
396
|
+
commandsCmd
|
|
251
397
|
.command('add <source>')
|
|
252
|
-
.description('Add
|
|
398
|
+
.description('Add commands from Git repo or local path')
|
|
253
399
|
.option('-a, --agents <list>', 'Comma-separated agents to install to')
|
|
254
400
|
.action(async (source, options) => {
|
|
255
|
-
const spinner = ora('Fetching
|
|
401
|
+
const spinner = ora('Fetching commands...').start();
|
|
256
402
|
try {
|
|
257
403
|
const { localPath } = await cloneRepo(source);
|
|
258
|
-
const
|
|
259
|
-
spinner.succeed(`Found ${
|
|
404
|
+
const commands = discoverCommands(localPath);
|
|
405
|
+
spinner.succeed(`Found ${commands.length} commands`);
|
|
260
406
|
const agents = options.agents
|
|
261
407
|
? options.agents.split(',')
|
|
262
408
|
: ['claude', 'codex', 'gemini'];
|
|
263
|
-
for (const
|
|
264
|
-
console.log(`\n ${chalk.cyan(
|
|
409
|
+
for (const command of commands) {
|
|
410
|
+
console.log(`\n ${chalk.cyan(command.name)}: ${command.description}`);
|
|
265
411
|
for (const agentId of agents) {
|
|
266
412
|
if (!isCliInstalled(agentId) && agentId !== 'cursor')
|
|
267
413
|
continue;
|
|
268
|
-
const sourcePath =
|
|
414
|
+
const sourcePath = resolveCommandSource(localPath, command.name, agentId);
|
|
269
415
|
if (sourcePath) {
|
|
270
|
-
|
|
416
|
+
installCommand(sourcePath, agentId, command.name, 'symlink');
|
|
271
417
|
console.log(` ${chalk.green('+')} ${AGENTS[agentId].name}`);
|
|
272
418
|
}
|
|
273
419
|
}
|
|
274
420
|
}
|
|
275
|
-
console.log(chalk.green('\
|
|
421
|
+
console.log(chalk.green('\nCommands installed.'));
|
|
276
422
|
}
|
|
277
423
|
catch (err) {
|
|
278
|
-
spinner.fail('Failed to add
|
|
424
|
+
spinner.fail('Failed to add commands');
|
|
279
425
|
console.error(chalk.red(err.message));
|
|
280
426
|
process.exit(1);
|
|
281
427
|
}
|
|
282
428
|
});
|
|
283
|
-
|
|
429
|
+
commandsCmd
|
|
284
430
|
.command('remove <name>')
|
|
285
|
-
.description('Remove a
|
|
431
|
+
.description('Remove a command from all agents')
|
|
286
432
|
.option('-a, --agents <list>', 'Comma-separated agents to remove from')
|
|
287
433
|
.action((name, options) => {
|
|
288
434
|
const agents = options.agents
|
|
@@ -290,18 +436,389 @@ skillsCmd
|
|
|
290
436
|
: ALL_AGENT_IDS;
|
|
291
437
|
let removed = 0;
|
|
292
438
|
for (const agentId of agents) {
|
|
293
|
-
if (
|
|
439
|
+
if (uninstallCommand(agentId, name)) {
|
|
294
440
|
console.log(` ${chalk.red('-')} ${AGENTS[agentId].name}`);
|
|
295
441
|
removed++;
|
|
296
442
|
}
|
|
297
443
|
}
|
|
298
444
|
if (removed === 0) {
|
|
299
|
-
console.log(chalk.yellow(`
|
|
445
|
+
console.log(chalk.yellow(`Command '${name}' not found`));
|
|
300
446
|
}
|
|
301
447
|
else {
|
|
302
448
|
console.log(chalk.green(`\nRemoved from ${removed} agents.`));
|
|
303
449
|
}
|
|
304
450
|
});
|
|
451
|
+
commandsCmd
|
|
452
|
+
.command('push <name>')
|
|
453
|
+
.description('Save project-scoped command to user scope')
|
|
454
|
+
.option('-a, --agents <list>', 'Comma-separated agents to push for')
|
|
455
|
+
.action((name, options) => {
|
|
456
|
+
const cwd = process.cwd();
|
|
457
|
+
const agents = options.agents
|
|
458
|
+
? options.agents.split(',')
|
|
459
|
+
: ALL_AGENT_IDS;
|
|
460
|
+
let pushed = 0;
|
|
461
|
+
for (const agentId of agents) {
|
|
462
|
+
if (!isCliInstalled(agentId) && agentId !== 'cursor')
|
|
463
|
+
continue;
|
|
464
|
+
const result = promoteCommandToUser(agentId, name, cwd);
|
|
465
|
+
if (result.success) {
|
|
466
|
+
console.log(` ${chalk.green('+')} ${AGENTS[agentId].name}`);
|
|
467
|
+
pushed++;
|
|
468
|
+
}
|
|
469
|
+
else if (result.error && !result.error.includes('not found')) {
|
|
470
|
+
console.log(` ${chalk.red('x')} ${AGENTS[agentId].name}: ${result.error}`);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (pushed === 0) {
|
|
474
|
+
console.log(chalk.yellow(`Project command '${name}' not found for any agent`));
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
console.log(chalk.green(`\nPushed to user scope for ${pushed} agents.`));
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
const hooksCmd = program.command('hooks').description('Manage hooks');
|
|
481
|
+
hooksCmd
|
|
482
|
+
.command('list')
|
|
483
|
+
.description('List installed hooks')
|
|
484
|
+
.option('-a, --agent <agent>', 'Filter by agent')
|
|
485
|
+
.option('-s, --scope <scope>', 'Filter by scope: user, project, or all', 'all')
|
|
486
|
+
.action((options) => {
|
|
487
|
+
console.log(chalk.bold('\nInstalled Hooks\n'));
|
|
488
|
+
const cwd = process.cwd();
|
|
489
|
+
const agents = options.agent
|
|
490
|
+
? [options.agent]
|
|
491
|
+
: Array.from(HOOKS_CAPABLE_AGENTS);
|
|
492
|
+
for (const agentId of agents) {
|
|
493
|
+
const agent = AGENTS[agentId];
|
|
494
|
+
if (!agent.supportsHooks) {
|
|
495
|
+
console.log(` ${chalk.bold(agent.name)}: ${chalk.gray('hooks not supported')}`);
|
|
496
|
+
console.log();
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
let hooks = listInstalledHooksWithScope(agentId, cwd);
|
|
500
|
+
if (options.scope !== 'all') {
|
|
501
|
+
hooks = hooks.filter((h) => h.scope === options.scope);
|
|
502
|
+
}
|
|
503
|
+
if (hooks.length === 0) {
|
|
504
|
+
console.log(` ${chalk.bold(agent.name)}: ${chalk.gray('none')}`);
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
console.log(` ${chalk.bold(agent.name)}:`);
|
|
508
|
+
const userHooks = hooks.filter((h) => h.scope === 'user');
|
|
509
|
+
const projectHooks = hooks.filter((h) => h.scope === 'project');
|
|
510
|
+
if (userHooks.length > 0 && (options.scope === 'all' || options.scope === 'user')) {
|
|
511
|
+
console.log(` ${chalk.gray('User:')}`);
|
|
512
|
+
for (const hook of userHooks) {
|
|
513
|
+
console.log(` ${chalk.cyan(hook.name)}`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (projectHooks.length > 0 && (options.scope === 'all' || options.scope === 'project')) {
|
|
517
|
+
console.log(` ${chalk.gray('Project:')}`);
|
|
518
|
+
for (const hook of projectHooks) {
|
|
519
|
+
console.log(` ${chalk.yellow(hook.name)}`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
console.log();
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
hooksCmd
|
|
527
|
+
.command('add <source>')
|
|
528
|
+
.description('Install hooks from git repo or local path')
|
|
529
|
+
.option('-a, --agent <agents>', 'Target agents (comma-separated)', 'claude,gemini')
|
|
530
|
+
.action(async (source, options) => {
|
|
531
|
+
const spinner = ora('Fetching hooks...').start();
|
|
532
|
+
try {
|
|
533
|
+
const { localPath } = await cloneRepo(source);
|
|
534
|
+
const hooks = discoverHooksFromRepo(localPath);
|
|
535
|
+
const hookNames = new Set();
|
|
536
|
+
for (const name of hooks.shared) {
|
|
537
|
+
hookNames.add(name);
|
|
538
|
+
}
|
|
539
|
+
for (const list of Object.values(hooks.agentSpecific)) {
|
|
540
|
+
for (const name of list) {
|
|
541
|
+
hookNames.add(name);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
spinner.succeed(`Found ${hookNames.size} hooks`);
|
|
545
|
+
const agents = options.agent
|
|
546
|
+
? options.agent.split(',')
|
|
547
|
+
: ['claude', 'gemini'];
|
|
548
|
+
const result = await installHooks(localPath, agents, { scope: 'user' });
|
|
549
|
+
const installedByHook = new Map();
|
|
550
|
+
for (const item of result.installed) {
|
|
551
|
+
const [name, agentId] = item.split(':');
|
|
552
|
+
const list = installedByHook.get(name) || [];
|
|
553
|
+
list.push(agentId);
|
|
554
|
+
installedByHook.set(name, list);
|
|
555
|
+
}
|
|
556
|
+
const orderedHooks = Array.from(installedByHook.keys()).sort((a, b) => a.localeCompare(b));
|
|
557
|
+
for (const name of orderedHooks) {
|
|
558
|
+
console.log(`\n ${chalk.cyan(name)}`);
|
|
559
|
+
const agentIds = installedByHook.get(name) || [];
|
|
560
|
+
agentIds.sort();
|
|
561
|
+
for (const agentId of agentIds) {
|
|
562
|
+
console.log(` ${AGENTS[agentId].name}`);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (result.errors.length > 0) {
|
|
566
|
+
console.log(chalk.red('\nErrors:'));
|
|
567
|
+
for (const error of result.errors) {
|
|
568
|
+
console.log(chalk.red(` ${error}`));
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
if (result.installed.length === 0) {
|
|
572
|
+
console.log(chalk.yellow('\nNo hooks installed.'));
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
console.log(chalk.green('\nHooks installed.'));
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
catch (err) {
|
|
579
|
+
spinner.fail('Failed to add hooks');
|
|
580
|
+
console.error(chalk.red(err.message));
|
|
581
|
+
process.exit(1);
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
hooksCmd
|
|
585
|
+
.command('remove <name>')
|
|
586
|
+
.description('Remove a hook')
|
|
587
|
+
.option('-a, --agent <agents>', 'Target agents (comma-separated)')
|
|
588
|
+
.action(async (name, options) => {
|
|
589
|
+
const agents = options.agent
|
|
590
|
+
? options.agent.split(',')
|
|
591
|
+
: Array.from(HOOKS_CAPABLE_AGENTS);
|
|
592
|
+
const result = await removeHook(name, agents);
|
|
593
|
+
let removed = 0;
|
|
594
|
+
for (const item of result.removed) {
|
|
595
|
+
const [, agentId] = item.split(':');
|
|
596
|
+
console.log(` ${AGENTS[agentId].name}`);
|
|
597
|
+
removed++;
|
|
598
|
+
}
|
|
599
|
+
if (result.errors.length > 0) {
|
|
600
|
+
console.log(chalk.red('\nErrors:'));
|
|
601
|
+
for (const error of result.errors) {
|
|
602
|
+
console.log(chalk.red(` ${error}`));
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (removed === 0) {
|
|
606
|
+
console.log(chalk.yellow(`Hook '${name}' not found`));
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
console.log(chalk.green(`\nRemoved from ${removed} agents.`));
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
hooksCmd
|
|
613
|
+
.command('push <name>')
|
|
614
|
+
.description('Copy project-scoped hook to user scope')
|
|
615
|
+
.option('-a, --agent <agents>', 'Target agents (comma-separated)')
|
|
616
|
+
.action((name, options) => {
|
|
617
|
+
const cwd = process.cwd();
|
|
618
|
+
const agents = options.agent
|
|
619
|
+
? options.agent.split(',')
|
|
620
|
+
: Array.from(HOOKS_CAPABLE_AGENTS);
|
|
621
|
+
let pushed = 0;
|
|
622
|
+
for (const agentId of agents) {
|
|
623
|
+
const result = promoteHookToUser(agentId, name, cwd);
|
|
624
|
+
if (result.success) {
|
|
625
|
+
console.log(` ${AGENTS[agentId].name}`);
|
|
626
|
+
pushed++;
|
|
627
|
+
}
|
|
628
|
+
else if (result.error && !result.error.includes('not found')) {
|
|
629
|
+
console.log(` ${AGENTS[agentId].name}: ${result.error}`);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
if (pushed === 0) {
|
|
633
|
+
console.log(chalk.yellow(`Project hook '${name}' not found for any agent`));
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
console.log(chalk.green(`\nPushed to user scope for ${pushed} agents.`));
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
// =============================================================================
|
|
640
|
+
// SKILLS COMMANDS (Agent Skills)
|
|
641
|
+
// =============================================================================
|
|
642
|
+
const skillsCmd = program
|
|
643
|
+
.command('skills')
|
|
644
|
+
.description('Manage Agent Skills (SKILL.md + rules/)');
|
|
645
|
+
skillsCmd
|
|
646
|
+
.command('list')
|
|
647
|
+
.description('List installed Agent Skills')
|
|
648
|
+
.option('-a, --agent <agent>', 'Filter by agent')
|
|
649
|
+
.option('-s, --scope <scope>', 'Filter by scope: user, project, or all', 'all')
|
|
650
|
+
.action((options) => {
|
|
651
|
+
console.log(chalk.bold('\nInstalled Agent Skills\n'));
|
|
652
|
+
const cwd = process.cwd();
|
|
653
|
+
const agents = options.agent
|
|
654
|
+
? [options.agent]
|
|
655
|
+
: SKILLS_CAPABLE_AGENTS;
|
|
656
|
+
for (const agentId of agents) {
|
|
657
|
+
const agent = AGENTS[agentId];
|
|
658
|
+
if (!agent.capabilities.skills) {
|
|
659
|
+
console.log(` ${chalk.bold(agent.name)}: ${chalk.gray('skills not supported')}`);
|
|
660
|
+
console.log();
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
let skills = listInstalledSkillsWithScope(agentId, cwd);
|
|
664
|
+
if (options.scope !== 'all') {
|
|
665
|
+
skills = skills.filter((s) => s.scope === options.scope);
|
|
666
|
+
}
|
|
667
|
+
if (skills.length === 0) {
|
|
668
|
+
console.log(` ${chalk.bold(agent.name)}: ${chalk.gray('none')}`);
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
console.log(` ${chalk.bold(agent.name)}:`);
|
|
672
|
+
const userSkills = skills.filter((s) => s.scope === 'user');
|
|
673
|
+
const projectSkills = skills.filter((s) => s.scope === 'project');
|
|
674
|
+
if (userSkills.length > 0 && (options.scope === 'all' || options.scope === 'user')) {
|
|
675
|
+
console.log(` ${chalk.gray('User:')}`);
|
|
676
|
+
for (const skill of userSkills) {
|
|
677
|
+
const desc = skill.metadata.description ? ` - ${chalk.gray(skill.metadata.description)}` : '';
|
|
678
|
+
const ruleInfo = skill.ruleCount > 0 ? chalk.gray(` (${skill.ruleCount} rules)`) : '';
|
|
679
|
+
console.log(` ${chalk.cyan(skill.name)}${desc}${ruleInfo}`);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
if (projectSkills.length > 0 && (options.scope === 'all' || options.scope === 'project')) {
|
|
683
|
+
console.log(` ${chalk.gray('Project:')}`);
|
|
684
|
+
for (const skill of projectSkills) {
|
|
685
|
+
const desc = skill.metadata.description ? ` - ${chalk.gray(skill.metadata.description)}` : '';
|
|
686
|
+
const ruleInfo = skill.ruleCount > 0 ? chalk.gray(` (${skill.ruleCount} rules)`) : '';
|
|
687
|
+
console.log(` ${chalk.yellow(skill.name)}${desc}${ruleInfo}`);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
console.log();
|
|
692
|
+
}
|
|
693
|
+
});
|
|
694
|
+
skillsCmd
|
|
695
|
+
.command('add <source>')
|
|
696
|
+
.description('Add Agent Skills from Git repo or local path')
|
|
697
|
+
.option('-a, --agents <list>', 'Comma-separated agents to install to')
|
|
698
|
+
.action(async (source, options) => {
|
|
699
|
+
const spinner = ora('Fetching skills...').start();
|
|
700
|
+
try {
|
|
701
|
+
const { localPath } = await cloneRepo(source);
|
|
702
|
+
const skills = discoverSkillsFromRepo(localPath);
|
|
703
|
+
spinner.succeed(`Found ${skills.length} skills`);
|
|
704
|
+
if (skills.length === 0) {
|
|
705
|
+
console.log(chalk.yellow('No skills found (looking for SKILL.md files)'));
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
for (const skill of skills) {
|
|
709
|
+
console.log(`\n ${chalk.cyan(skill.name)}: ${skill.metadata.description || 'no description'}`);
|
|
710
|
+
if (skill.ruleCount > 0) {
|
|
711
|
+
console.log(` ${chalk.gray(`${skill.ruleCount} rules`)}`);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
const agents = options.agents
|
|
715
|
+
? options.agents.split(',')
|
|
716
|
+
: await checkbox({
|
|
717
|
+
message: 'Select agents to install skills to:',
|
|
718
|
+
choices: SKILLS_CAPABLE_AGENTS.filter((id) => isCliInstalled(id) || id === 'cursor').map((id) => ({
|
|
719
|
+
name: AGENTS[id].name,
|
|
720
|
+
value: id,
|
|
721
|
+
checked: ['claude', 'codex', 'gemini'].includes(id),
|
|
722
|
+
})),
|
|
723
|
+
});
|
|
724
|
+
if (agents.length === 0) {
|
|
725
|
+
console.log(chalk.yellow('\nNo agents selected.'));
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const installSpinner = ora('Installing skills...').start();
|
|
729
|
+
let installed = 0;
|
|
730
|
+
for (const skill of skills) {
|
|
731
|
+
const result = installSkill(skill.path, skill.name, agents);
|
|
732
|
+
if (result.success) {
|
|
733
|
+
installed++;
|
|
734
|
+
}
|
|
735
|
+
else {
|
|
736
|
+
console.log(chalk.red(`\n Failed to install ${skill.name}: ${result.error}`));
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
installSpinner.succeed(`Installed ${installed} skills to ${agents.length} agents`);
|
|
740
|
+
console.log(chalk.green('\nSkills installed.'));
|
|
741
|
+
}
|
|
742
|
+
catch (err) {
|
|
743
|
+
spinner.fail('Failed to add skills');
|
|
744
|
+
console.error(chalk.red(err.message));
|
|
745
|
+
process.exit(1);
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
skillsCmd
|
|
749
|
+
.command('remove <name>')
|
|
750
|
+
.description('Remove an Agent Skill')
|
|
751
|
+
.action((name) => {
|
|
752
|
+
const result = uninstallSkill(name);
|
|
753
|
+
if (result.success) {
|
|
754
|
+
console.log(chalk.green(`Removed skill '${name}'`));
|
|
755
|
+
}
|
|
756
|
+
else {
|
|
757
|
+
console.log(chalk.red(result.error || 'Failed to remove skill'));
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
skillsCmd
|
|
761
|
+
.command('push <name>')
|
|
762
|
+
.description('Save project-scoped skill to user scope')
|
|
763
|
+
.option('-a, --agents <list>', 'Comma-separated agents to push for')
|
|
764
|
+
.action((name, options) => {
|
|
765
|
+
const cwd = process.cwd();
|
|
766
|
+
const agents = options.agents
|
|
767
|
+
? options.agents.split(',')
|
|
768
|
+
: SKILLS_CAPABLE_AGENTS;
|
|
769
|
+
let pushed = 0;
|
|
770
|
+
for (const agentId of agents) {
|
|
771
|
+
if (!AGENTS[agentId].capabilities.skills)
|
|
772
|
+
continue;
|
|
773
|
+
const result = promoteSkillToUser(agentId, name, cwd);
|
|
774
|
+
if (result.success) {
|
|
775
|
+
console.log(` ${chalk.green('+')} ${AGENTS[agentId].name}`);
|
|
776
|
+
pushed++;
|
|
777
|
+
}
|
|
778
|
+
else if (result.error && !result.error.includes('not found')) {
|
|
779
|
+
console.log(` ${chalk.red('x')} ${AGENTS[agentId].name}: ${result.error}`);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
if (pushed === 0) {
|
|
783
|
+
console.log(chalk.yellow(`Project skill '${name}' not found for any agent`));
|
|
784
|
+
}
|
|
785
|
+
else {
|
|
786
|
+
console.log(chalk.green(`\nPushed to user scope for ${pushed} agents.`));
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
skillsCmd
|
|
790
|
+
.command('info <name>')
|
|
791
|
+
.description('Show detailed info about an installed skill')
|
|
792
|
+
.action((name) => {
|
|
793
|
+
const skill = getSkillInfo(name);
|
|
794
|
+
if (!skill) {
|
|
795
|
+
console.log(chalk.yellow(`Skill '${name}' not found`));
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
console.log(chalk.bold(`\n${skill.metadata.name}\n`));
|
|
799
|
+
if (skill.metadata.description) {
|
|
800
|
+
console.log(` ${skill.metadata.description}`);
|
|
801
|
+
}
|
|
802
|
+
console.log();
|
|
803
|
+
if (skill.metadata.author) {
|
|
804
|
+
console.log(` Author: ${skill.metadata.author}`);
|
|
805
|
+
}
|
|
806
|
+
if (skill.metadata.version) {
|
|
807
|
+
console.log(` Version: ${skill.metadata.version}`);
|
|
808
|
+
}
|
|
809
|
+
if (skill.metadata.license) {
|
|
810
|
+
console.log(` License: ${skill.metadata.license}`);
|
|
811
|
+
}
|
|
812
|
+
console.log(` Path: ${skill.path}`);
|
|
813
|
+
const rules = getSkillRules(name);
|
|
814
|
+
if (rules.length > 0) {
|
|
815
|
+
console.log(chalk.bold(`\n Rules (${rules.length}):\n`));
|
|
816
|
+
for (const rule of rules) {
|
|
817
|
+
console.log(` ${chalk.cyan(rule)}`);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
console.log();
|
|
821
|
+
});
|
|
305
822
|
// =============================================================================
|
|
306
823
|
// MCP COMMANDS
|
|
307
824
|
// =============================================================================
|
|
@@ -311,32 +828,71 @@ const mcpCmd = program
|
|
|
311
828
|
mcpCmd
|
|
312
829
|
.command('list')
|
|
313
830
|
.description('List MCP servers and registration status')
|
|
314
|
-
.
|
|
831
|
+
.option('-a, --agent <agent>', 'Filter by agent')
|
|
832
|
+
.option('-s, --scope <scope>', 'Filter by scope: user, project, or all', 'all')
|
|
833
|
+
.action((options) => {
|
|
315
834
|
console.log(chalk.bold('\nMCP Servers\n'));
|
|
316
|
-
|
|
835
|
+
const cwd = process.cwd();
|
|
836
|
+
const agents = options.agent
|
|
837
|
+
? [options.agent]
|
|
838
|
+
: MCP_CAPABLE_AGENTS;
|
|
839
|
+
for (const agentId of agents) {
|
|
317
840
|
const agent = AGENTS[agentId];
|
|
841
|
+
if (!agent.capabilities.mcp) {
|
|
842
|
+
console.log(` ${chalk.bold(agent.name)}: ${chalk.gray('mcp not supported')}`);
|
|
843
|
+
console.log();
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
318
846
|
if (!isCliInstalled(agentId)) {
|
|
319
847
|
console.log(` ${chalk.bold(agent.name)}: ${chalk.gray('CLI not installed')}`);
|
|
320
848
|
continue;
|
|
321
849
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
850
|
+
let mcps = listInstalledMcpsWithScope(agentId, cwd);
|
|
851
|
+
if (options.scope !== 'all') {
|
|
852
|
+
mcps = mcps.filter((m) => m.scope === options.scope);
|
|
853
|
+
}
|
|
854
|
+
if (mcps.length === 0) {
|
|
855
|
+
console.log(` ${chalk.bold(agent.name)}: ${chalk.gray('none')}`);
|
|
856
|
+
}
|
|
857
|
+
else {
|
|
858
|
+
console.log(` ${chalk.bold(agent.name)}:`);
|
|
859
|
+
const userMcps = mcps.filter((m) => m.scope === 'user');
|
|
860
|
+
const projectMcps = mcps.filter((m) => m.scope === 'project');
|
|
861
|
+
if (userMcps.length > 0 && (options.scope === 'all' || options.scope === 'user')) {
|
|
862
|
+
console.log(` ${chalk.gray('User:')}`);
|
|
863
|
+
for (const mcp of userMcps) {
|
|
864
|
+
console.log(` ${chalk.cyan(mcp.name)}`);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
if (projectMcps.length > 0 && (options.scope === 'all' || options.scope === 'project')) {
|
|
868
|
+
console.log(` ${chalk.gray('Project:')}`);
|
|
869
|
+
for (const mcp of projectMcps) {
|
|
870
|
+
console.log(` ${chalk.yellow(mcp.name)}`);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
325
874
|
console.log();
|
|
326
875
|
}
|
|
327
876
|
});
|
|
328
877
|
mcpCmd
|
|
329
|
-
.command('add <name>
|
|
330
|
-
.description('Add MCP server to manifest')
|
|
878
|
+
.command('add <name>')
|
|
879
|
+
.description('Add MCP server to manifest (use -- before command)')
|
|
331
880
|
.option('-a, --agents <list>', 'Comma-separated agents', 'claude,codex,gemini')
|
|
332
881
|
.option('-s, --scope <scope>', 'Scope: user or project', 'user')
|
|
333
|
-
.
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
882
|
+
.allowUnknownOption(true)
|
|
883
|
+
.action(async (name, options, cmd) => {
|
|
884
|
+
// Get everything after -- as the command
|
|
885
|
+
const rawArgs = cmd.args || [];
|
|
886
|
+
const commandParts = rawArgs.slice(rawArgs.indexOf(name) + 1);
|
|
887
|
+
if (commandParts.length === 0) {
|
|
888
|
+
console.error(chalk.red('Error: Command required after --'));
|
|
889
|
+
console.log(chalk.gray('Usage: agents mcp add <name> -- <command...>'));
|
|
890
|
+
console.log(chalk.gray('Example: agents mcp add swarm -- npx @swarmify/agents-mcp'));
|
|
891
|
+
process.exit(1);
|
|
338
892
|
}
|
|
339
|
-
const
|
|
893
|
+
const command = commandParts.join(' ');
|
|
894
|
+
const source = await ensureSource();
|
|
895
|
+
const localPath = getRepoLocalPath(source);
|
|
340
896
|
const manifest = readManifest(localPath) || createDefaultManifest();
|
|
341
897
|
manifest.mcp = manifest.mcp || {};
|
|
342
898
|
manifest.mcp[name] = {
|
|
@@ -378,14 +934,10 @@ mcpCmd
|
|
|
378
934
|
.command('register [name]')
|
|
379
935
|
.description('Register MCP server(s) with agent CLIs')
|
|
380
936
|
.option('-a, --agents <list>', 'Comma-separated agents')
|
|
381
|
-
.action((name, options) => {
|
|
382
|
-
const state = readState();
|
|
937
|
+
.action(async (name, options) => {
|
|
383
938
|
if (!name) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
const localPath = getRepoLocalPath(state.source);
|
|
939
|
+
const source = await ensureSource();
|
|
940
|
+
const localPath = getRepoLocalPath(source);
|
|
389
941
|
const manifest = readManifest(localPath);
|
|
390
942
|
if (!manifest?.mcp) {
|
|
391
943
|
console.log(chalk.yellow('No MCP servers in manifest'));
|
|
@@ -409,6 +961,35 @@ mcpCmd
|
|
|
409
961
|
}
|
|
410
962
|
console.log(chalk.yellow('Single MCP registration not yet implemented'));
|
|
411
963
|
});
|
|
964
|
+
mcpCmd
|
|
965
|
+
.command('push <name>')
|
|
966
|
+
.description('Save project-scoped MCP to user scope')
|
|
967
|
+
.option('-a, --agents <list>', 'Comma-separated agents to push for')
|
|
968
|
+
.action((name, options) => {
|
|
969
|
+
const cwd = process.cwd();
|
|
970
|
+
const agents = options.agents
|
|
971
|
+
? options.agents.split(',')
|
|
972
|
+
: MCP_CAPABLE_AGENTS;
|
|
973
|
+
let pushed = 0;
|
|
974
|
+
for (const agentId of agents) {
|
|
975
|
+
if (!isCliInstalled(agentId))
|
|
976
|
+
continue;
|
|
977
|
+
const result = promoteMcpToUser(agentId, name, cwd);
|
|
978
|
+
if (result.success) {
|
|
979
|
+
console.log(` ${chalk.green('+')} ${AGENTS[agentId].name}`);
|
|
980
|
+
pushed++;
|
|
981
|
+
}
|
|
982
|
+
else if (result.error && !result.error.includes('not found')) {
|
|
983
|
+
console.log(` ${chalk.red('x')} ${AGENTS[agentId].name}: ${result.error}`);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
if (pushed === 0) {
|
|
987
|
+
console.log(chalk.yellow(`Project MCP '${name}' not found for any agent`));
|
|
988
|
+
}
|
|
989
|
+
else {
|
|
990
|
+
console.log(chalk.green(`\nPushed to user scope for ${pushed} agents.`));
|
|
991
|
+
}
|
|
992
|
+
});
|
|
412
993
|
// =============================================================================
|
|
413
994
|
// CLI COMMANDS
|
|
414
995
|
// =============================================================================
|
|
@@ -440,19 +1021,15 @@ cliCmd
|
|
|
440
1021
|
.command('add <agent>')
|
|
441
1022
|
.description('Add agent CLI to manifest')
|
|
442
1023
|
.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
|
-
}
|
|
1024
|
+
.action(async (agent, options) => {
|
|
449
1025
|
const agentId = agent.toLowerCase();
|
|
450
1026
|
if (!AGENTS[agentId]) {
|
|
451
1027
|
console.log(chalk.red(`Unknown agent: ${agent}`));
|
|
452
1028
|
console.log(chalk.gray(`Available: ${ALL_AGENT_IDS.join(', ')}`));
|
|
453
1029
|
return;
|
|
454
1030
|
}
|
|
455
|
-
const
|
|
1031
|
+
const source = await ensureSource();
|
|
1032
|
+
const localPath = getRepoLocalPath(source);
|
|
456
1033
|
const manifest = readManifest(localPath) || createDefaultManifest();
|
|
457
1034
|
manifest.clis = manifest.clis || {};
|
|
458
1035
|
manifest.clis[agentId] = {
|
|
@@ -465,14 +1042,10 @@ cliCmd
|
|
|
465
1042
|
cliCmd
|
|
466
1043
|
.command('remove <agent>')
|
|
467
1044
|
.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
|
-
}
|
|
1045
|
+
.action(async (agent) => {
|
|
1046
|
+
const source = await ensureSource();
|
|
474
1047
|
const agentId = agent.toLowerCase();
|
|
475
|
-
const localPath = getRepoLocalPath(
|
|
1048
|
+
const localPath = getRepoLocalPath(source);
|
|
476
1049
|
const manifest = readManifest(localPath);
|
|
477
1050
|
if (manifest?.clis?.[agentId]) {
|
|
478
1051
|
delete manifest.clis[agentId];
|
|
@@ -486,10 +1059,12 @@ cliCmd
|
|
|
486
1059
|
cliCmd
|
|
487
1060
|
.command('upgrade [agent]')
|
|
488
1061
|
.description('Upgrade agent CLI(s) to version in manifest')
|
|
1062
|
+
.option('-s, --scope <scope>', 'Target scope (default: user)', 'user')
|
|
489
1063
|
.option('--latest', 'Upgrade to latest version (ignore manifest)')
|
|
490
1064
|
.action(async (agent, options) => {
|
|
491
|
-
const
|
|
492
|
-
const
|
|
1065
|
+
const scopeName = options.scope;
|
|
1066
|
+
const scope = getScope(scopeName);
|
|
1067
|
+
const localPath = scope ? getRepoLocalPath(scope.source) : null;
|
|
493
1068
|
const manifest = localPath ? readManifest(localPath) : null;
|
|
494
1069
|
const agentsToUpgrade = agent
|
|
495
1070
|
? [agent.toLowerCase()]
|
|
@@ -520,6 +1095,145 @@ cliCmd
|
|
|
520
1095
|
}
|
|
521
1096
|
});
|
|
522
1097
|
// =============================================================================
|
|
1098
|
+
// REPO COMMANDS
|
|
1099
|
+
// =============================================================================
|
|
1100
|
+
const repoCmd = program
|
|
1101
|
+
.command('repo')
|
|
1102
|
+
.description('Manage .agents repository scopes');
|
|
1103
|
+
repoCmd
|
|
1104
|
+
.command('list')
|
|
1105
|
+
.description('List configured repository scopes')
|
|
1106
|
+
.action(() => {
|
|
1107
|
+
const scopes = getScopesByPriority();
|
|
1108
|
+
if (scopes.length === 0) {
|
|
1109
|
+
console.log(chalk.yellow('\nNo scopes configured.'));
|
|
1110
|
+
console.log(chalk.gray(' Run: agents repo add <source>'));
|
|
1111
|
+
console.log();
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
console.log(chalk.bold('\nConfigured Scopes\n'));
|
|
1115
|
+
console.log(chalk.gray(' Scopes are applied in priority order (higher overrides lower)\n'));
|
|
1116
|
+
for (const { name, config } of scopes) {
|
|
1117
|
+
const readonlyTag = config.readonly ? chalk.gray(' (readonly)') : '';
|
|
1118
|
+
console.log(` ${chalk.bold(name)}${readonlyTag}`);
|
|
1119
|
+
console.log(` Source: ${config.source}`);
|
|
1120
|
+
console.log(` Branch: ${config.branch}`);
|
|
1121
|
+
console.log(` Commit: ${config.commit.substring(0, 8)}`);
|
|
1122
|
+
console.log(` Priority: ${config.priority}`);
|
|
1123
|
+
console.log(` Synced: ${new Date(config.lastSync).toLocaleString()}`);
|
|
1124
|
+
console.log();
|
|
1125
|
+
}
|
|
1126
|
+
});
|
|
1127
|
+
repoCmd
|
|
1128
|
+
.command('add <source>')
|
|
1129
|
+
.description('Add a repository scope')
|
|
1130
|
+
.option('-s, --scope <scope>', 'Target scope (default: user)', 'user')
|
|
1131
|
+
.option('-y, --yes', 'Skip confirmation prompts')
|
|
1132
|
+
.action(async (source, options) => {
|
|
1133
|
+
const scopeName = options.scope;
|
|
1134
|
+
const existingScope = getScope(scopeName);
|
|
1135
|
+
if (existingScope && !options.yes) {
|
|
1136
|
+
const shouldOverwrite = await confirm({
|
|
1137
|
+
message: `Scope '${scopeName}' already exists (${existingScope.source}). Overwrite?`,
|
|
1138
|
+
default: false,
|
|
1139
|
+
});
|
|
1140
|
+
if (!shouldOverwrite) {
|
|
1141
|
+
console.log(chalk.yellow('Cancelled.'));
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
if (existingScope?.readonly && !options.yes) {
|
|
1146
|
+
console.log(chalk.red(`Scope '${scopeName}' is readonly. Cannot overwrite.`));
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
const parsed = parseSource(source);
|
|
1150
|
+
const spinner = ora(`Cloning repository for ${scopeName} scope...`).start();
|
|
1151
|
+
try {
|
|
1152
|
+
const { commit, isNew } = await cloneRepo(source);
|
|
1153
|
+
spinner.succeed(isNew ? 'Repository cloned' : 'Repository updated');
|
|
1154
|
+
const priority = getScopePriority(scopeName);
|
|
1155
|
+
setScope(scopeName, {
|
|
1156
|
+
source,
|
|
1157
|
+
branch: parsed.ref || 'main',
|
|
1158
|
+
commit,
|
|
1159
|
+
lastSync: new Date().toISOString(),
|
|
1160
|
+
priority,
|
|
1161
|
+
readonly: scopeName === 'system',
|
|
1162
|
+
});
|
|
1163
|
+
console.log(chalk.green(`\nAdded scope '${scopeName}' with priority ${priority}`));
|
|
1164
|
+
const scopeHint = scopeName === 'user' ? '' : ` --scope ${scopeName}`;
|
|
1165
|
+
console.log(chalk.gray(` Run: agents pull${scopeHint} to sync commands`));
|
|
1166
|
+
}
|
|
1167
|
+
catch (err) {
|
|
1168
|
+
spinner.fail('Failed to add scope');
|
|
1169
|
+
console.error(chalk.red(err.message));
|
|
1170
|
+
process.exit(1);
|
|
1171
|
+
}
|
|
1172
|
+
});
|
|
1173
|
+
repoCmd
|
|
1174
|
+
.command('remove <scope>')
|
|
1175
|
+
.description('Remove a repository scope')
|
|
1176
|
+
.option('-y, --yes', 'Skip confirmation prompts')
|
|
1177
|
+
.action(async (scopeName, options) => {
|
|
1178
|
+
const existingScope = getScope(scopeName);
|
|
1179
|
+
if (!existingScope) {
|
|
1180
|
+
console.log(chalk.yellow(`Scope '${scopeName}' not found.`));
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
if (existingScope.readonly) {
|
|
1184
|
+
console.log(chalk.red(`Scope '${scopeName}' is readonly. Cannot remove.`));
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
if (!options.yes) {
|
|
1188
|
+
const shouldRemove = await confirm({
|
|
1189
|
+
message: `Remove scope '${scopeName}' (${existingScope.source})?`,
|
|
1190
|
+
default: false,
|
|
1191
|
+
});
|
|
1192
|
+
if (!shouldRemove) {
|
|
1193
|
+
console.log(chalk.yellow('Cancelled.'));
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
const removed = removeScope(scopeName);
|
|
1198
|
+
if (removed) {
|
|
1199
|
+
console.log(chalk.green(`Removed scope '${scopeName}'`));
|
|
1200
|
+
}
|
|
1201
|
+
else {
|
|
1202
|
+
console.log(chalk.yellow(`Failed to remove scope '${scopeName}'`));
|
|
1203
|
+
}
|
|
1204
|
+
});
|
|
1205
|
+
repoCmd
|
|
1206
|
+
.command('sync [scope]')
|
|
1207
|
+
.description('Sync a specific scope or all scopes')
|
|
1208
|
+
.option('-y, --yes', 'Skip confirmation prompts')
|
|
1209
|
+
.action(async (scopeName, options) => {
|
|
1210
|
+
const scopes = scopeName ? [{ name: scopeName, config: getScope(scopeName) }].filter(s => s.config) : getScopesByPriority();
|
|
1211
|
+
if (scopes.length === 0) {
|
|
1212
|
+
console.log(chalk.yellow('No scopes to sync.'));
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
for (const { name, config } of scopes) {
|
|
1216
|
+
if (!config)
|
|
1217
|
+
continue;
|
|
1218
|
+
console.log(chalk.bold(`\nSyncing scope: ${name}`));
|
|
1219
|
+
const spinner = ora('Updating repository...').start();
|
|
1220
|
+
try {
|
|
1221
|
+
const { commit } = await cloneRepo(config.source);
|
|
1222
|
+
spinner.succeed('Repository updated');
|
|
1223
|
+
setScope(name, {
|
|
1224
|
+
...config,
|
|
1225
|
+
commit,
|
|
1226
|
+
lastSync: new Date().toISOString(),
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
catch (err) {
|
|
1230
|
+
spinner.fail(`Failed to sync ${name}`);
|
|
1231
|
+
console.error(chalk.gray(err.message));
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
console.log(chalk.green('\nSync complete.'));
|
|
1235
|
+
});
|
|
1236
|
+
// =============================================================================
|
|
523
1237
|
// INIT COMMAND
|
|
524
1238
|
// =============================================================================
|
|
525
1239
|
program
|
|
@@ -544,5 +1258,341 @@ program
|
|
|
544
1258
|
console.log(chalk.gray(' claude/hooks/'));
|
|
545
1259
|
console.log();
|
|
546
1260
|
});
|
|
1261
|
+
// =============================================================================
|
|
1262
|
+
// REGISTRY COMMANDS
|
|
1263
|
+
// =============================================================================
|
|
1264
|
+
const registryCmd = program
|
|
1265
|
+
.command('registry')
|
|
1266
|
+
.description('Manage package registries (MCP servers, skills)');
|
|
1267
|
+
registryCmd
|
|
1268
|
+
.command('list')
|
|
1269
|
+
.description('List configured registries')
|
|
1270
|
+
.option('-t, --type <type>', 'Filter by type: mcp or skill')
|
|
1271
|
+
.action((options) => {
|
|
1272
|
+
const types = options.type ? [options.type] : ['mcp', 'skill'];
|
|
1273
|
+
console.log(chalk.bold('\nConfigured Registries\n'));
|
|
1274
|
+
for (const type of types) {
|
|
1275
|
+
console.log(chalk.bold(` ${type.toUpperCase()}`));
|
|
1276
|
+
const registries = getRegistries(type);
|
|
1277
|
+
const entries = Object.entries(registries);
|
|
1278
|
+
if (entries.length === 0) {
|
|
1279
|
+
console.log(chalk.gray(' No registries configured'));
|
|
1280
|
+
}
|
|
1281
|
+
else {
|
|
1282
|
+
for (const [name, config] of entries) {
|
|
1283
|
+
const status = config.enabled ? chalk.green('enabled') : chalk.gray('disabled');
|
|
1284
|
+
const isDefault = DEFAULT_REGISTRIES[type]?.[name] ? chalk.gray(' (default)') : '';
|
|
1285
|
+
console.log(` ${name}${isDefault}: ${status}`);
|
|
1286
|
+
console.log(chalk.gray(` ${config.url}`));
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
console.log();
|
|
1290
|
+
}
|
|
1291
|
+
});
|
|
1292
|
+
registryCmd
|
|
1293
|
+
.command('add <type> <name> <url>')
|
|
1294
|
+
.description('Add a registry (type: mcp or skill)')
|
|
1295
|
+
.option('--api-key <key>', 'API key for authentication')
|
|
1296
|
+
.action((type, name, url, options) => {
|
|
1297
|
+
if (type !== 'mcp' && type !== 'skill') {
|
|
1298
|
+
console.log(chalk.red(`Invalid type '${type}'. Use 'mcp' or 'skill'.`));
|
|
1299
|
+
process.exit(1);
|
|
1300
|
+
}
|
|
1301
|
+
setRegistry(type, name, {
|
|
1302
|
+
url,
|
|
1303
|
+
enabled: true,
|
|
1304
|
+
apiKey: options.apiKey,
|
|
1305
|
+
});
|
|
1306
|
+
console.log(chalk.green(`Added ${type} registry '${name}'`));
|
|
1307
|
+
});
|
|
1308
|
+
registryCmd
|
|
1309
|
+
.command('remove <type> <name>')
|
|
1310
|
+
.description('Remove a registry')
|
|
1311
|
+
.action((type, name) => {
|
|
1312
|
+
if (type !== 'mcp' && type !== 'skill') {
|
|
1313
|
+
console.log(chalk.red(`Invalid type '${type}'. Use 'mcp' or 'skill'.`));
|
|
1314
|
+
process.exit(1);
|
|
1315
|
+
}
|
|
1316
|
+
// Check if it's a default registry
|
|
1317
|
+
if (DEFAULT_REGISTRIES[type]?.[name]) {
|
|
1318
|
+
console.log(chalk.yellow(`Cannot remove default registry '${name}'. Use 'agents registry disable' instead.`));
|
|
1319
|
+
process.exit(1);
|
|
1320
|
+
}
|
|
1321
|
+
if (removeRegistry(type, name)) {
|
|
1322
|
+
console.log(chalk.green(`Removed ${type} registry '${name}'`));
|
|
1323
|
+
}
|
|
1324
|
+
else {
|
|
1325
|
+
console.log(chalk.yellow(`Registry '${name}' not found`));
|
|
1326
|
+
}
|
|
1327
|
+
});
|
|
1328
|
+
registryCmd
|
|
1329
|
+
.command('enable <type> <name>')
|
|
1330
|
+
.description('Enable a registry')
|
|
1331
|
+
.action((type, name) => {
|
|
1332
|
+
if (type !== 'mcp' && type !== 'skill') {
|
|
1333
|
+
console.log(chalk.red(`Invalid type '${type}'. Use 'mcp' or 'skill'.`));
|
|
1334
|
+
process.exit(1);
|
|
1335
|
+
}
|
|
1336
|
+
const registries = getRegistries(type);
|
|
1337
|
+
if (!registries[name]) {
|
|
1338
|
+
console.log(chalk.yellow(`Registry '${name}' not found`));
|
|
1339
|
+
process.exit(1);
|
|
1340
|
+
}
|
|
1341
|
+
setRegistry(type, name, { enabled: true });
|
|
1342
|
+
console.log(chalk.green(`Enabled ${type} registry '${name}'`));
|
|
1343
|
+
});
|
|
1344
|
+
registryCmd
|
|
1345
|
+
.command('disable <type> <name>')
|
|
1346
|
+
.description('Disable a registry')
|
|
1347
|
+
.action((type, name) => {
|
|
1348
|
+
if (type !== 'mcp' && type !== 'skill') {
|
|
1349
|
+
console.log(chalk.red(`Invalid type '${type}'. Use 'mcp' or 'skill'.`));
|
|
1350
|
+
process.exit(1);
|
|
1351
|
+
}
|
|
1352
|
+
const registries = getRegistries(type);
|
|
1353
|
+
if (!registries[name]) {
|
|
1354
|
+
console.log(chalk.yellow(`Registry '${name}' not found`));
|
|
1355
|
+
process.exit(1);
|
|
1356
|
+
}
|
|
1357
|
+
setRegistry(type, name, { enabled: false });
|
|
1358
|
+
console.log(chalk.green(`Disabled ${type} registry '${name}'`));
|
|
1359
|
+
});
|
|
1360
|
+
registryCmd
|
|
1361
|
+
.command('config <type> <name>')
|
|
1362
|
+
.description('Configure a registry')
|
|
1363
|
+
.option('--api-key <key>', 'Set API key')
|
|
1364
|
+
.option('--url <url>', 'Update URL')
|
|
1365
|
+
.action((type, name, options) => {
|
|
1366
|
+
if (type !== 'mcp' && type !== 'skill') {
|
|
1367
|
+
console.log(chalk.red(`Invalid type '${type}'. Use 'mcp' or 'skill'.`));
|
|
1368
|
+
process.exit(1);
|
|
1369
|
+
}
|
|
1370
|
+
const registries = getRegistries(type);
|
|
1371
|
+
if (!registries[name]) {
|
|
1372
|
+
console.log(chalk.yellow(`Registry '${name}' not found`));
|
|
1373
|
+
process.exit(1);
|
|
1374
|
+
}
|
|
1375
|
+
const updates = {};
|
|
1376
|
+
if (options.apiKey)
|
|
1377
|
+
updates.apiKey = options.apiKey;
|
|
1378
|
+
if (options.url)
|
|
1379
|
+
updates.url = options.url;
|
|
1380
|
+
if (Object.keys(updates).length === 0) {
|
|
1381
|
+
console.log(chalk.yellow('No options provided. Use --api-key or --url.'));
|
|
1382
|
+
process.exit(1);
|
|
1383
|
+
}
|
|
1384
|
+
setRegistry(type, name, updates);
|
|
1385
|
+
console.log(chalk.green(`Updated ${type} registry '${name}'`));
|
|
1386
|
+
});
|
|
1387
|
+
// =============================================================================
|
|
1388
|
+
// SEARCH COMMAND
|
|
1389
|
+
// =============================================================================
|
|
1390
|
+
program
|
|
1391
|
+
.command('search <query>')
|
|
1392
|
+
.description('Search registries for packages (MCP servers, skills)')
|
|
1393
|
+
.option('-t, --type <type>', 'Filter by type: mcp or skill')
|
|
1394
|
+
.option('-r, --registry <name>', 'Search specific registry')
|
|
1395
|
+
.option('-l, --limit <n>', 'Max results', '20')
|
|
1396
|
+
.action(async (query, options) => {
|
|
1397
|
+
const spinner = ora('Searching registries...').start();
|
|
1398
|
+
try {
|
|
1399
|
+
const results = await searchRegistries(query, {
|
|
1400
|
+
type: options.type,
|
|
1401
|
+
registry: options.registry,
|
|
1402
|
+
limit: parseInt(options.limit, 10),
|
|
1403
|
+
});
|
|
1404
|
+
spinner.stop();
|
|
1405
|
+
if (results.length === 0) {
|
|
1406
|
+
console.log(chalk.yellow('\nNo packages found.'));
|
|
1407
|
+
if (!options.type) {
|
|
1408
|
+
console.log(chalk.gray('\nTip: skill registries not yet available. Use gh:user/repo for skills.'));
|
|
1409
|
+
}
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
console.log(chalk.bold(`\nFound ${results.length} packages\n`));
|
|
1413
|
+
// Group by type
|
|
1414
|
+
const mcpResults = results.filter((r) => r.type === 'mcp');
|
|
1415
|
+
const skillResults = results.filter((r) => r.type === 'skill');
|
|
1416
|
+
if (mcpResults.length > 0) {
|
|
1417
|
+
console.log(chalk.bold(' MCP Servers'));
|
|
1418
|
+
for (const result of mcpResults) {
|
|
1419
|
+
const desc = result.description
|
|
1420
|
+
? chalk.gray(` - ${result.description.slice(0, 50)}${result.description.length > 50 ? '...' : ''}`)
|
|
1421
|
+
: '';
|
|
1422
|
+
console.log(` ${chalk.cyan(result.name)}${desc}`);
|
|
1423
|
+
console.log(chalk.gray(` Registry: ${result.registry} Install: agents add mcp:${result.name}`));
|
|
1424
|
+
}
|
|
1425
|
+
console.log();
|
|
1426
|
+
}
|
|
1427
|
+
if (skillResults.length > 0) {
|
|
1428
|
+
console.log(chalk.bold(' Skills'));
|
|
1429
|
+
for (const result of skillResults) {
|
|
1430
|
+
const desc = result.description
|
|
1431
|
+
? chalk.gray(` - ${result.description.slice(0, 50)}${result.description.length > 50 ? '...' : ''}`)
|
|
1432
|
+
: '';
|
|
1433
|
+
console.log(` ${chalk.cyan(result.name)}${desc}`);
|
|
1434
|
+
console.log(chalk.gray(` Registry: ${result.registry} Install: agents add skill:${result.name}`));
|
|
1435
|
+
}
|
|
1436
|
+
console.log();
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
catch (err) {
|
|
1440
|
+
spinner.fail('Search failed');
|
|
1441
|
+
console.error(chalk.red(err.message));
|
|
1442
|
+
process.exit(1);
|
|
1443
|
+
}
|
|
1444
|
+
});
|
|
1445
|
+
// =============================================================================
|
|
1446
|
+
// ADD COMMAND (unified package installation)
|
|
1447
|
+
// =============================================================================
|
|
1448
|
+
program
|
|
1449
|
+
.command('add <identifier>')
|
|
1450
|
+
.description('Add a package (mcp:name, skill:user/repo, or gh:user/repo)')
|
|
1451
|
+
.option('-a, --agents <list>', 'Comma-separated agents to install to')
|
|
1452
|
+
.action(async (identifier, options) => {
|
|
1453
|
+
const spinner = ora('Resolving package...').start();
|
|
1454
|
+
try {
|
|
1455
|
+
const resolved = await resolvePackage(identifier);
|
|
1456
|
+
if (!resolved) {
|
|
1457
|
+
spinner.fail('Package not found');
|
|
1458
|
+
console.log(chalk.gray('\nTip: Use explicit prefix (mcp:, skill:, gh:) or check the identifier.'));
|
|
1459
|
+
process.exit(1);
|
|
1460
|
+
}
|
|
1461
|
+
spinner.succeed(`Found ${resolved.type} package`);
|
|
1462
|
+
if (resolved.type === 'mcp') {
|
|
1463
|
+
// Install MCP server
|
|
1464
|
+
const entry = resolved.mcpEntry;
|
|
1465
|
+
if (!entry) {
|
|
1466
|
+
console.log(chalk.red('Failed to get MCP server details'));
|
|
1467
|
+
process.exit(1);
|
|
1468
|
+
}
|
|
1469
|
+
console.log(chalk.bold(`\n${entry.name}`));
|
|
1470
|
+
if (entry.description) {
|
|
1471
|
+
console.log(chalk.gray(` ${entry.description}`));
|
|
1472
|
+
}
|
|
1473
|
+
if (entry.repository?.url) {
|
|
1474
|
+
console.log(chalk.gray(` ${entry.repository.url}`));
|
|
1475
|
+
}
|
|
1476
|
+
// Get package info
|
|
1477
|
+
const pkg = entry.packages?.[0];
|
|
1478
|
+
if (!pkg) {
|
|
1479
|
+
console.log(chalk.yellow('\nNo installable package found for this server.'));
|
|
1480
|
+
console.log(chalk.gray('You may need to install it manually.'));
|
|
1481
|
+
process.exit(1);
|
|
1482
|
+
}
|
|
1483
|
+
console.log(chalk.bold('\nPackage:'));
|
|
1484
|
+
console.log(` Name: ${pkg.name || pkg.registry_name}`);
|
|
1485
|
+
console.log(` Runtime: ${pkg.runtime || 'unknown'}`);
|
|
1486
|
+
console.log(` Transport: ${pkg.transport || 'stdio'}`);
|
|
1487
|
+
if (pkg.packageArguments && pkg.packageArguments.length > 0) {
|
|
1488
|
+
console.log(chalk.bold('\nRequired arguments:'));
|
|
1489
|
+
for (const arg of pkg.packageArguments) {
|
|
1490
|
+
const req = arg.required ? chalk.red('*') : '';
|
|
1491
|
+
console.log(` ${arg.name}${req}: ${arg.description || ''}`);
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
// Determine command based on runtime
|
|
1495
|
+
let command;
|
|
1496
|
+
if (pkg.runtime === 'node') {
|
|
1497
|
+
command = `npx -y ${pkg.name || pkg.registry_name}`;
|
|
1498
|
+
}
|
|
1499
|
+
else if (pkg.runtime === 'python') {
|
|
1500
|
+
command = `uvx ${pkg.name || pkg.registry_name}`;
|
|
1501
|
+
}
|
|
1502
|
+
else {
|
|
1503
|
+
command = pkg.name || pkg.registry_name;
|
|
1504
|
+
}
|
|
1505
|
+
const agents = options.agents
|
|
1506
|
+
? options.agents.split(',')
|
|
1507
|
+
: MCP_CAPABLE_AGENTS.filter((id) => isCliInstalled(id));
|
|
1508
|
+
if (agents.length === 0) {
|
|
1509
|
+
console.log(chalk.yellow('\nNo MCP-capable agents installed.'));
|
|
1510
|
+
process.exit(1);
|
|
1511
|
+
}
|
|
1512
|
+
console.log(chalk.bold('\nInstalling to agents...'));
|
|
1513
|
+
for (const agentId of agents) {
|
|
1514
|
+
if (!isCliInstalled(agentId))
|
|
1515
|
+
continue;
|
|
1516
|
+
const result = registerMcp(agentId, entry.name, command, 'user');
|
|
1517
|
+
if (result.success) {
|
|
1518
|
+
console.log(` ${chalk.green('+')} ${AGENTS[agentId].name}`);
|
|
1519
|
+
}
|
|
1520
|
+
else {
|
|
1521
|
+
console.log(` ${chalk.red('x')} ${AGENTS[agentId].name}: ${result.error}`);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
console.log(chalk.green('\nMCP server installed.'));
|
|
1525
|
+
}
|
|
1526
|
+
else if (resolved.type === 'git' || resolved.type === 'skill') {
|
|
1527
|
+
// Install from git source (skills/commands/hooks)
|
|
1528
|
+
console.log(chalk.bold(`\nInstalling from ${resolved.source}`));
|
|
1529
|
+
const { localPath } = await cloneRepo(resolved.source);
|
|
1530
|
+
// Discover what's in the repo
|
|
1531
|
+
const commands = discoverCommands(localPath);
|
|
1532
|
+
const skills = discoverSkillsFromRepo(localPath);
|
|
1533
|
+
const hooks = discoverHooksFromRepo(localPath);
|
|
1534
|
+
const hasCommands = commands.length > 0;
|
|
1535
|
+
const hasSkills = skills.length > 0;
|
|
1536
|
+
const hasHooks = hooks.shared.length > 0 || Object.values(hooks.agentSpecific).some((h) => h.length > 0);
|
|
1537
|
+
if (!hasCommands && !hasSkills && !hasHooks) {
|
|
1538
|
+
console.log(chalk.yellow('No installable content found in repository.'));
|
|
1539
|
+
process.exit(1);
|
|
1540
|
+
}
|
|
1541
|
+
console.log(chalk.bold('\nFound:'));
|
|
1542
|
+
if (hasCommands)
|
|
1543
|
+
console.log(` ${commands.length} commands`);
|
|
1544
|
+
if (hasSkills)
|
|
1545
|
+
console.log(` ${skills.length} skills`);
|
|
1546
|
+
if (hasHooks)
|
|
1547
|
+
console.log(` ${hooks.shared.length + Object.values(hooks.agentSpecific).flat().length} hooks`);
|
|
1548
|
+
const agents = options.agents
|
|
1549
|
+
? options.agents.split(',')
|
|
1550
|
+
: ['claude', 'codex', 'gemini'];
|
|
1551
|
+
// Install commands
|
|
1552
|
+
if (hasCommands) {
|
|
1553
|
+
console.log(chalk.bold('\nInstalling commands...'));
|
|
1554
|
+
let installed = 0;
|
|
1555
|
+
for (const command of commands) {
|
|
1556
|
+
for (const agentId of agents) {
|
|
1557
|
+
if (!isCliInstalled(agentId) && agentId !== 'cursor')
|
|
1558
|
+
continue;
|
|
1559
|
+
const sourcePath = resolveCommandSource(localPath, command.name, agentId);
|
|
1560
|
+
if (sourcePath) {
|
|
1561
|
+
installCommand(sourcePath, agentId, command.name, 'symlink');
|
|
1562
|
+
installed++;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
console.log(` Installed ${installed} command instances`);
|
|
1567
|
+
}
|
|
1568
|
+
// Install skills
|
|
1569
|
+
if (hasSkills) {
|
|
1570
|
+
console.log(chalk.bold('\nInstalling skills...'));
|
|
1571
|
+
for (const skill of skills) {
|
|
1572
|
+
const result = installSkill(skill.path, skill.name, agents);
|
|
1573
|
+
if (result.success) {
|
|
1574
|
+
console.log(` ${chalk.green('+')} ${skill.name}`);
|
|
1575
|
+
}
|
|
1576
|
+
else {
|
|
1577
|
+
console.log(` ${chalk.red('x')} ${skill.name}: ${result.error}`);
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
// Install hooks
|
|
1582
|
+
if (hasHooks) {
|
|
1583
|
+
console.log(chalk.bold('\nInstalling hooks...'));
|
|
1584
|
+
const hookAgents = agents.filter((id) => AGENTS[id].supportsHooks);
|
|
1585
|
+
const result = await installHooks(localPath, hookAgents, { scope: 'user' });
|
|
1586
|
+
console.log(` Installed ${result.installed.length} hooks`);
|
|
1587
|
+
}
|
|
1588
|
+
console.log(chalk.green('\nPackage installed.'));
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
catch (err) {
|
|
1592
|
+
spinner.fail('Installation failed');
|
|
1593
|
+
console.error(chalk.red(err.message));
|
|
1594
|
+
process.exit(1);
|
|
1595
|
+
}
|
|
1596
|
+
});
|
|
547
1597
|
program.parse();
|
|
548
1598
|
//# sourceMappingURL=index.js.map
|