@swarmify/agents-cli 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -0
- package/README.md +33 -11
- package/dist/index.js +464 -216
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -2,7 +2,20 @@
|
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import ora from 'ora';
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
5
8
|
import { checkbox, confirm, select } from '@inquirer/prompts';
|
|
9
|
+
// Get version from package.json
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
|
12
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
13
|
+
const VERSION = packageJson.version;
|
|
14
|
+
function isPromptCancelled(err) {
|
|
15
|
+
return err instanceof Error && (err.name === 'ExitPromptError' ||
|
|
16
|
+
err.message.includes('force closed') ||
|
|
17
|
+
err.message.includes('User force closed'));
|
|
18
|
+
}
|
|
6
19
|
import { AGENTS, ALL_AGENT_IDS, MCP_CAPABLE_AGENTS, SKILLS_CAPABLE_AGENTS, HOOKS_CAPABLE_AGENTS, getAllCliStates, isCliInstalled, getCliVersion, isMcpRegistered, registerMcp, unregisterMcp, listInstalledMcpsWithScope, promoteMcpToUser, } from './lib/agents.js';
|
|
7
20
|
import { readManifest, writeManifest, createDefaultManifest, MANIFEST_FILENAME, } from './lib/manifest.js';
|
|
8
21
|
import { readState, ensureAgentsDir, getRepoLocalPath, getScope, setScope, removeScope, getScopesByPriority, getScopePriority, } from './lib/state.js';
|
|
@@ -61,7 +74,35 @@ function getScopeLocalPath(scopeName) {
|
|
|
61
74
|
program
|
|
62
75
|
.name('agents')
|
|
63
76
|
.description('Dotfiles manager for AI coding agents')
|
|
64
|
-
.version(
|
|
77
|
+
.version(VERSION);
|
|
78
|
+
// Check for updates (non-blocking, shows hint if update available)
|
|
79
|
+
async function checkForUpdates() {
|
|
80
|
+
try {
|
|
81
|
+
const response = await fetch('https://registry.npmjs.org/@swarmify/agents-cli/latest', {
|
|
82
|
+
signal: AbortSignal.timeout(2000), // 2s timeout
|
|
83
|
+
});
|
|
84
|
+
if (!response.ok)
|
|
85
|
+
return;
|
|
86
|
+
const data = (await response.json());
|
|
87
|
+
const latestVersion = data.version;
|
|
88
|
+
if (latestVersion !== VERSION && compareVersions(latestVersion, VERSION) > 0) {
|
|
89
|
+
console.log(chalk.yellow(`\nUpdate available: ${VERSION} -> ${latestVersion}`));
|
|
90
|
+
console.log(chalk.gray(`Run: agents upgrade\n`));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Silently ignore - don't block CLI usage
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Run update check after command completes (non-blocking)
|
|
98
|
+
program.hook('postAction', async () => {
|
|
99
|
+
// Don't check on upgrade command itself
|
|
100
|
+
const args = process.argv.slice(2);
|
|
101
|
+
if (args[0] === 'upgrade' || args[0] === '--version' || args[0] === '-V' || args[0] === '--help' || args[0] === '-h') {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
await checkForUpdates();
|
|
105
|
+
});
|
|
65
106
|
// =============================================================================
|
|
66
107
|
// STATUS COMMAND
|
|
67
108
|
// =============================================================================
|
|
@@ -195,24 +236,65 @@ program
|
|
|
195
236
|
}
|
|
196
237
|
console.log();
|
|
197
238
|
});
|
|
239
|
+
// =============================================================================
|
|
240
|
+
// PULL COMMAND
|
|
241
|
+
// =============================================================================
|
|
242
|
+
// Agent name aliases for flexible input
|
|
243
|
+
const AGENT_NAME_ALIASES = {
|
|
244
|
+
claude: 'claude',
|
|
245
|
+
'claude-code': 'claude',
|
|
246
|
+
cc: 'claude',
|
|
247
|
+
codex: 'codex',
|
|
248
|
+
'openai-codex': 'codex',
|
|
249
|
+
cx: 'codex',
|
|
250
|
+
gemini: 'gemini',
|
|
251
|
+
'gemini-cli': 'gemini',
|
|
252
|
+
gx: 'gemini',
|
|
253
|
+
cursor: 'cursor',
|
|
254
|
+
'cursor-agent': 'cursor',
|
|
255
|
+
cr: 'cursor',
|
|
256
|
+
opencode: 'opencode',
|
|
257
|
+
oc: 'opencode',
|
|
258
|
+
};
|
|
259
|
+
function resolveAgentName(input) {
|
|
260
|
+
return AGENT_NAME_ALIASES[input.toLowerCase()] || null;
|
|
261
|
+
}
|
|
262
|
+
function isAgentName(input) {
|
|
263
|
+
return resolveAgentName(input) !== null;
|
|
264
|
+
}
|
|
198
265
|
program
|
|
199
|
-
.command('pull [source]')
|
|
266
|
+
.command('pull [source] [agent]')
|
|
200
267
|
.description('Pull and sync from remote .agents repo')
|
|
201
|
-
.option('-y, --yes', '
|
|
202
|
-
.option('-f, --force', '
|
|
268
|
+
.option('-y, --yes', 'Auto-confirm and skip all conflicts')
|
|
269
|
+
.option('-f, --force', 'Auto-confirm and overwrite all conflicts')
|
|
203
270
|
.option('-s, --scope <scope>', 'Target scope (default: user)', 'user')
|
|
204
271
|
.option('--dry-run', 'Show what would change')
|
|
205
272
|
.option('--skip-clis', 'Skip CLI version sync')
|
|
206
273
|
.option('--skip-mcp', 'Skip MCP registration')
|
|
207
|
-
.action(async (
|
|
274
|
+
.action(async (arg1, arg2, options) => {
|
|
275
|
+
// Parse source and agent filter from positional args
|
|
276
|
+
let targetSource;
|
|
277
|
+
let agentFilter;
|
|
278
|
+
if (arg1) {
|
|
279
|
+
if (isAgentName(arg1)) {
|
|
280
|
+
// agents pull claude
|
|
281
|
+
agentFilter = resolveAgentName(arg1);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
// agents pull gh:user/repo [agent]
|
|
285
|
+
targetSource = arg1;
|
|
286
|
+
if (arg2 && isAgentName(arg2)) {
|
|
287
|
+
agentFilter = resolveAgentName(arg2);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
208
291
|
const scopeName = options.scope;
|
|
209
292
|
const meta = readState();
|
|
210
293
|
const existingScope = meta.scopes[scopeName];
|
|
211
294
|
// Try: 1) provided source, 2) existing scope source, 3) fall back to system scope
|
|
212
|
-
|
|
295
|
+
targetSource = targetSource || existingScope?.source;
|
|
213
296
|
let effectiveScope = scopeName;
|
|
214
297
|
if (!targetSource && scopeName === 'user') {
|
|
215
|
-
// Fall back to system scope if user scope has no source
|
|
216
298
|
const systemScope = meta.scopes['system'];
|
|
217
299
|
if (systemScope?.source) {
|
|
218
300
|
targetSource = systemScope.source;
|
|
@@ -221,13 +303,19 @@ program
|
|
|
221
303
|
}
|
|
222
304
|
}
|
|
223
305
|
if (!targetSource) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
306
|
+
if (scopeName === 'user' && Object.keys(meta.scopes).length === 0) {
|
|
307
|
+
console.log(chalk.gray(`First run detected. Initializing from ${DEFAULT_SYSTEM_REPO}...\n`));
|
|
308
|
+
targetSource = DEFAULT_SYSTEM_REPO;
|
|
309
|
+
effectiveScope = 'system';
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
console.log(chalk.red(`No source specified for scope '${scopeName}'.`));
|
|
313
|
+
const scopeHint = scopeName === 'user' ? '' : ` --scope ${scopeName}`;
|
|
314
|
+
console.log(chalk.gray(` Usage: agents pull <source>${scopeHint}`));
|
|
315
|
+
console.log(chalk.gray(' Example: agents pull gh:username/.agents'));
|
|
316
|
+
process.exit(1);
|
|
317
|
+
}
|
|
229
318
|
}
|
|
230
|
-
// Prevent modification of readonly scopes (but allow syncing from them)
|
|
231
319
|
const targetScopeConfig = meta.scopes[effectiveScope];
|
|
232
320
|
const isReadonly = targetScopeConfig?.readonly || effectiveScope === 'system';
|
|
233
321
|
const isUserScope = effectiveScope === 'user';
|
|
@@ -241,64 +329,18 @@ program
|
|
|
241
329
|
console.log(chalk.yellow(`No ${MANIFEST_FILENAME} found in repository`));
|
|
242
330
|
}
|
|
243
331
|
// Discover all assets
|
|
244
|
-
const
|
|
245
|
-
const
|
|
332
|
+
const allCommands = discoverCommands(localPath);
|
|
333
|
+
const allSkills = discoverSkillsFromRepo(localPath);
|
|
246
334
|
const discoveredHooks = discoverHooksFromRepo(localPath);
|
|
247
|
-
|
|
248
|
-
Object.values(discoveredHooks.agentSpecific).reduce((sum, arr) => sum + arr.length, 0);
|
|
249
|
-
console.log(chalk.bold(`\nDiscovered assets:\n`));
|
|
250
|
-
if (commands.length > 0) {
|
|
251
|
-
console.log(` Commands: ${commands.length}`);
|
|
252
|
-
for (const command of commands.slice(0, 5)) {
|
|
253
|
-
const src = command.isShared ? 'shared' : command.agentSpecific;
|
|
254
|
-
console.log(` ${chalk.cyan(command.name.padEnd(18))} ${chalk.gray(src)}`);
|
|
255
|
-
}
|
|
256
|
-
if (commands.length > 5) {
|
|
257
|
-
console.log(chalk.gray(` ... and ${commands.length - 5} more`));
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
if (skills.length > 0) {
|
|
261
|
-
console.log(` Skills: ${skills.length}`);
|
|
262
|
-
for (const skill of skills.slice(0, 5)) {
|
|
263
|
-
console.log(` ${chalk.cyan(skill.name.padEnd(18))} ${chalk.gray(skill.metadata.description || '')}`);
|
|
264
|
-
}
|
|
265
|
-
if (skills.length > 5) {
|
|
266
|
-
console.log(chalk.gray(` ... and ${skills.length - 5} more`));
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
if (totalHooks > 0) {
|
|
270
|
-
console.log(` Hooks: ${totalHooks}`);
|
|
271
|
-
for (const name of discoveredHooks.shared.slice(0, 3)) {
|
|
272
|
-
console.log(` ${chalk.cyan(name.padEnd(18))} ${chalk.gray('shared')}`);
|
|
273
|
-
}
|
|
274
|
-
for (const [agentId, hooks] of Object.entries(discoveredHooks.agentSpecific)) {
|
|
275
|
-
for (const name of hooks.slice(0, 2)) {
|
|
276
|
-
console.log(` ${chalk.cyan(name.padEnd(18))} ${chalk.gray(agentId)}`);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
if (totalHooks > 5) {
|
|
280
|
-
console.log(chalk.gray(` ... and more`));
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
const mcpCount = manifest?.mcp ? Object.keys(manifest.mcp).length : 0;
|
|
284
|
-
if (mcpCount > 0) {
|
|
285
|
-
console.log(` MCP Servers: ${mcpCount}`);
|
|
286
|
-
for (const name of Object.keys(manifest.mcp).slice(0, 5)) {
|
|
287
|
-
console.log(` ${chalk.cyan(name)}`);
|
|
288
|
-
}
|
|
289
|
-
if (mcpCount > 5) {
|
|
290
|
-
console.log(chalk.gray(` ... and ${mcpCount - 5} more`));
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
if (options.dryRun) {
|
|
294
|
-
console.log(chalk.yellow('\nDry run - no changes made'));
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
335
|
+
// Determine which agents to sync
|
|
297
336
|
let selectedAgents;
|
|
298
|
-
|
|
299
|
-
|
|
337
|
+
if (agentFilter) {
|
|
338
|
+
// Single agent filter
|
|
339
|
+
selectedAgents = [agentFilter];
|
|
340
|
+
console.log(chalk.gray(`\nFiltering for ${AGENTS[agentFilter].name} only\n`));
|
|
341
|
+
}
|
|
342
|
+
else if (options.yes || options.force) {
|
|
300
343
|
selectedAgents = (manifest?.defaults?.agents || ['claude', 'codex', 'gemini']);
|
|
301
|
-
method = manifest?.defaults?.method || 'symlink';
|
|
302
344
|
}
|
|
303
345
|
else {
|
|
304
346
|
const installedAgents = ALL_AGENT_IDS.filter((id) => isCliInstalled(id) || id === 'cursor');
|
|
@@ -310,225 +352,323 @@ program
|
|
|
310
352
|
checked: (manifest?.defaults?.agents || ['claude', 'codex', 'gemini']).includes(id),
|
|
311
353
|
})),
|
|
312
354
|
});
|
|
313
|
-
method = await select({
|
|
314
|
-
message: 'Installation method:',
|
|
315
|
-
choices: [
|
|
316
|
-
{ name: 'Symlink (updates automatically)', value: 'symlink' },
|
|
317
|
-
{ name: 'Copy (independent files)', value: 'copy' },
|
|
318
|
-
],
|
|
319
|
-
default: manifest?.defaults?.method || 'symlink',
|
|
320
|
-
});
|
|
321
355
|
}
|
|
322
|
-
//
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
356
|
+
// Filter agents to only installed ones (plus cursor which doesn't need CLI)
|
|
357
|
+
selectedAgents = selectedAgents.filter((id) => isCliInstalled(id) || id === 'cursor');
|
|
358
|
+
if (selectedAgents.length === 0) {
|
|
359
|
+
console.log(chalk.yellow('\nNo agents selected or installed. Nothing to sync.'));
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
// Build resource items with conflict detection
|
|
363
|
+
const newItems = [];
|
|
364
|
+
const existingItems = [];
|
|
365
|
+
// Process commands
|
|
366
|
+
for (const command of allCommands) {
|
|
367
|
+
const applicableAgents = selectedAgents.filter((agentId) => {
|
|
329
368
|
const sourcePath = resolveCommandSource(localPath, command.name, agentId);
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
369
|
+
return sourcePath !== null;
|
|
370
|
+
});
|
|
371
|
+
if (applicableAgents.length === 0)
|
|
372
|
+
continue;
|
|
373
|
+
const conflictingAgents = applicableAgents.filter((agentId) => commandExists(agentId, command.name));
|
|
374
|
+
const newAgents = applicableAgents.filter((agentId) => !commandExists(agentId, command.name));
|
|
375
|
+
if (conflictingAgents.length > 0) {
|
|
376
|
+
existingItems.push({ type: 'command', name: command.name, agents: conflictingAgents, isNew: false });
|
|
333
377
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
for (const skill of skills) {
|
|
337
|
-
const skillAgents = SKILLS_CAPABLE_AGENTS.filter((id) => selectedAgents.includes(id) && (isCliInstalled(id) || id === 'cursor'));
|
|
338
|
-
for (const agentId of skillAgents) {
|
|
339
|
-
if (skillExists(agentId, skill.name)) {
|
|
340
|
-
conflicts.push({ type: 'skill', name: skill.name, agent: agentId });
|
|
341
|
-
}
|
|
378
|
+
if (newAgents.length > 0) {
|
|
379
|
+
newItems.push({ type: 'command', name: command.name, agents: newAgents, isNew: true });
|
|
342
380
|
}
|
|
343
381
|
}
|
|
344
|
-
//
|
|
345
|
-
const
|
|
346
|
-
for (const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
382
|
+
// Process skills
|
|
383
|
+
const skillAgents = SKILLS_CAPABLE_AGENTS.filter((id) => selectedAgents.includes(id));
|
|
384
|
+
for (const skill of allSkills) {
|
|
385
|
+
const conflictingAgents = skillAgents.filter((agentId) => skillExists(agentId, skill.name));
|
|
386
|
+
const newAgents = skillAgents.filter((agentId) => !skillExists(agentId, skill.name));
|
|
387
|
+
if (conflictingAgents.length > 0) {
|
|
388
|
+
existingItems.push({ type: 'skill', name: skill.name, agents: conflictingAgents, isNew: false });
|
|
351
389
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
const agentId = agentIdStr;
|
|
355
|
-
if (hookAgents.includes(agentId)) {
|
|
356
|
-
for (const hookName of hookNames) {
|
|
357
|
-
if (hookExists(agentId, hookName)) {
|
|
358
|
-
conflicts.push({ type: 'hook', name: hookName, agent: agentId });
|
|
359
|
-
}
|
|
360
|
-
}
|
|
390
|
+
if (newAgents.length > 0) {
|
|
391
|
+
newItems.push({ type: 'skill', name: skill.name, agents: newAgents, isNew: true });
|
|
361
392
|
}
|
|
362
393
|
}
|
|
363
|
-
//
|
|
394
|
+
// Process hooks
|
|
395
|
+
const hookAgents = selectedAgents.filter((id) => HOOKS_CAPABLE_AGENTS.includes(id) && isCliInstalled(id));
|
|
396
|
+
const allHookNames = [
|
|
397
|
+
...discoveredHooks.shared,
|
|
398
|
+
...Object.entries(discoveredHooks.agentSpecific)
|
|
399
|
+
.filter(([agentId]) => hookAgents.includes(agentId))
|
|
400
|
+
.flatMap(([_, hooks]) => hooks),
|
|
401
|
+
];
|
|
402
|
+
const uniqueHookNames = [...new Set(allHookNames)];
|
|
403
|
+
for (const hookName of uniqueHookNames) {
|
|
404
|
+
const conflictingAgents = hookAgents.filter((agentId) => hookExists(agentId, hookName));
|
|
405
|
+
const newAgents = hookAgents.filter((agentId) => !hookExists(agentId, hookName));
|
|
406
|
+
if (conflictingAgents.length > 0) {
|
|
407
|
+
existingItems.push({ type: 'hook', name: hookName, agents: conflictingAgents, isNew: false });
|
|
408
|
+
}
|
|
409
|
+
if (newAgents.length > 0) {
|
|
410
|
+
newItems.push({ type: 'hook', name: hookName, agents: newAgents, isNew: true });
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
// Process MCPs
|
|
364
414
|
if (!options.skipMcp && manifest?.mcp) {
|
|
365
415
|
for (const [name, config] of Object.entries(manifest.mcp)) {
|
|
366
416
|
if (config.transport === 'http' || !config.command)
|
|
367
417
|
continue;
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
418
|
+
const mcpAgents = config.agents.filter((agentId) => selectedAgents.includes(agentId) && isCliInstalled(agentId));
|
|
419
|
+
if (mcpAgents.length === 0)
|
|
420
|
+
continue;
|
|
421
|
+
const conflictingAgents = mcpAgents.filter((agentId) => isMcpRegistered(agentId, name));
|
|
422
|
+
const newAgents = mcpAgents.filter((agentId) => !isMcpRegistered(agentId, name));
|
|
423
|
+
if (conflictingAgents.length > 0) {
|
|
424
|
+
existingItems.push({ type: 'mcp', name, agents: conflictingAgents, isNew: false });
|
|
425
|
+
}
|
|
426
|
+
if (newAgents.length > 0) {
|
|
427
|
+
newItems.push({ type: 'mcp', name, agents: newAgents, isNew: true });
|
|
374
428
|
}
|
|
375
429
|
}
|
|
376
430
|
}
|
|
377
|
-
//
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
431
|
+
// Display overview
|
|
432
|
+
console.log(chalk.bold('\nOverview\n'));
|
|
433
|
+
const formatAgentList = (agents) => agents.map((id) => AGENTS[id].name).join(', ');
|
|
434
|
+
if (newItems.length > 0) {
|
|
435
|
+
console.log(chalk.green(' NEW (will install):\n'));
|
|
436
|
+
const byType = { command: [], skill: [], hook: [], mcp: [] };
|
|
437
|
+
for (const item of newItems)
|
|
438
|
+
byType[item.type].push(item);
|
|
439
|
+
if (byType.command.length > 0) {
|
|
440
|
+
console.log(` Commands:`);
|
|
441
|
+
for (const item of byType.command) {
|
|
442
|
+
console.log(` ${chalk.cyan(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
|
|
386
443
|
}
|
|
387
444
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
mcp: [],
|
|
394
|
-
};
|
|
395
|
-
for (const c of uniqueConflicts.values()) {
|
|
396
|
-
byType[c.type].push(c.name);
|
|
445
|
+
if (byType.skill.length > 0) {
|
|
446
|
+
console.log(` Skills:`);
|
|
447
|
+
for (const item of byType.skill) {
|
|
448
|
+
console.log(` ${chalk.cyan(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
|
|
449
|
+
}
|
|
397
450
|
}
|
|
451
|
+
if (byType.hook.length > 0) {
|
|
452
|
+
console.log(` Hooks:`);
|
|
453
|
+
for (const item of byType.hook) {
|
|
454
|
+
console.log(` ${chalk.cyan(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (byType.mcp.length > 0) {
|
|
458
|
+
console.log(` MCP Servers:`);
|
|
459
|
+
for (const item of byType.mcp) {
|
|
460
|
+
console.log(` ${chalk.cyan(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
console.log();
|
|
464
|
+
}
|
|
465
|
+
if (existingItems.length > 0) {
|
|
466
|
+
console.log(chalk.yellow(' EXISTING (conflicts):\n'));
|
|
467
|
+
const byType = { command: [], skill: [], hook: [], mcp: [] };
|
|
468
|
+
for (const item of existingItems)
|
|
469
|
+
byType[item.type].push(item);
|
|
398
470
|
if (byType.command.length > 0) {
|
|
399
|
-
console.log(`
|
|
471
|
+
console.log(` Commands:`);
|
|
472
|
+
for (const item of byType.command) {
|
|
473
|
+
console.log(` ${chalk.yellow(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
|
|
474
|
+
}
|
|
400
475
|
}
|
|
401
476
|
if (byType.skill.length > 0) {
|
|
402
|
-
console.log(`
|
|
477
|
+
console.log(` Skills:`);
|
|
478
|
+
for (const item of byType.skill) {
|
|
479
|
+
console.log(` ${chalk.yellow(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
|
|
480
|
+
}
|
|
403
481
|
}
|
|
404
482
|
if (byType.hook.length > 0) {
|
|
405
|
-
console.log(`
|
|
483
|
+
console.log(` Hooks:`);
|
|
484
|
+
for (const item of byType.hook) {
|
|
485
|
+
console.log(` ${chalk.yellow(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
|
|
486
|
+
}
|
|
406
487
|
}
|
|
407
488
|
if (byType.mcp.length > 0) {
|
|
408
|
-
console.log(`
|
|
489
|
+
console.log(` MCP Servers:`);
|
|
490
|
+
for (const item of byType.mcp) {
|
|
491
|
+
console.log(` ${chalk.yellow(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
|
|
492
|
+
}
|
|
409
493
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
494
|
+
console.log();
|
|
495
|
+
}
|
|
496
|
+
if (newItems.length === 0 && existingItems.length === 0) {
|
|
497
|
+
console.log(chalk.gray(' Nothing to sync.\n'));
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (options.dryRun) {
|
|
501
|
+
console.log(chalk.yellow('Dry run - no changes made'));
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
// Confirmation prompt
|
|
505
|
+
if (!options.yes && !options.force) {
|
|
506
|
+
const proceed = await confirm({
|
|
507
|
+
message: 'Proceed with installation?',
|
|
508
|
+
default: true,
|
|
509
|
+
});
|
|
510
|
+
if (!proceed) {
|
|
511
|
+
console.log(chalk.yellow('\nSync cancelled'));
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
// Determine installation method
|
|
516
|
+
let method;
|
|
517
|
+
if (options.yes || options.force) {
|
|
518
|
+
method = manifest?.defaults?.method || 'symlink';
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
method = await select({
|
|
522
|
+
message: 'Installation method:',
|
|
523
|
+
choices: [
|
|
524
|
+
{ name: 'Symlink (updates automatically)', value: 'symlink' },
|
|
525
|
+
{ name: 'Copy (independent files)', value: 'copy' },
|
|
526
|
+
],
|
|
527
|
+
default: manifest?.defaults?.method || 'symlink',
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
// Per-resource conflict decisions
|
|
531
|
+
const decisions = new Map();
|
|
532
|
+
if (existingItems.length > 0 && !options.force && !options.yes) {
|
|
533
|
+
console.log(chalk.bold('\nResolve conflicts:\n'));
|
|
534
|
+
for (const item of existingItems) {
|
|
535
|
+
const typeLabel = item.type.charAt(0).toUpperCase() + item.type.slice(1);
|
|
536
|
+
const agentList = formatAgentList(item.agents);
|
|
537
|
+
const decision = await select({
|
|
538
|
+
message: `${typeLabel} '${item.name}' exists (${agentList})`,
|
|
413
539
|
choices: [
|
|
414
|
-
{ name: 'Overwrite
|
|
415
|
-
{ name: 'Skip
|
|
416
|
-
{ name: 'Cancel', value: 'cancel' },
|
|
540
|
+
{ name: 'Overwrite', value: 'overwrite' },
|
|
541
|
+
{ name: 'Skip', value: 'skip' },
|
|
542
|
+
{ name: 'Cancel all', value: 'cancel' },
|
|
417
543
|
],
|
|
418
544
|
});
|
|
419
|
-
if (
|
|
545
|
+
if (decision === 'cancel') {
|
|
420
546
|
console.log(chalk.yellow('\nSync cancelled'));
|
|
421
547
|
return;
|
|
422
548
|
}
|
|
549
|
+
decisions.set(`${item.type}:${item.name}`, decision);
|
|
423
550
|
}
|
|
424
551
|
}
|
|
425
|
-
|
|
426
|
-
|
|
552
|
+
else if (options.force) {
|
|
553
|
+
// Force mode: overwrite all
|
|
554
|
+
for (const item of existingItems) {
|
|
555
|
+
decisions.set(`${item.type}:${item.name}`, 'overwrite');
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
else if (options.yes) {
|
|
559
|
+
// Yes mode: skip all conflicts
|
|
560
|
+
for (const item of existingItems) {
|
|
561
|
+
decisions.set(`${item.type}:${item.name}`, 'skip');
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
// Install new items (no conflicts)
|
|
565
|
+
console.log();
|
|
566
|
+
let installed = { commands: 0, skills: 0, hooks: 0, mcps: 0 };
|
|
567
|
+
let skipped = { commands: 0, skills: 0, hooks: 0, mcps: 0 };
|
|
427
568
|
// Install commands
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
skipped++;
|
|
441
|
-
continue;
|
|
569
|
+
const cmdSpinner = ora('Installing commands...').start();
|
|
570
|
+
for (const item of [...newItems, ...existingItems].filter((i) => i.type === 'command')) {
|
|
571
|
+
const decision = item.isNew ? 'overwrite' : decisions.get(`command:${item.name}`);
|
|
572
|
+
if (decision === 'skip') {
|
|
573
|
+
skipped.commands++;
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
for (const agentId of item.agents) {
|
|
577
|
+
const sourcePath = resolveCommandSource(localPath, item.name, agentId);
|
|
578
|
+
if (sourcePath) {
|
|
579
|
+
installCommand(sourcePath, agentId, item.name, method);
|
|
580
|
+
installed.commands++;
|
|
442
581
|
}
|
|
443
|
-
installCommand(sourcePath, agentId, command.name, method);
|
|
444
|
-
installed++;
|
|
445
582
|
}
|
|
446
583
|
}
|
|
447
|
-
if (skipped > 0) {
|
|
448
|
-
|
|
584
|
+
if (skipped.commands > 0) {
|
|
585
|
+
cmdSpinner.succeed(`Installed ${installed.commands} commands (skipped ${skipped.commands})`);
|
|
586
|
+
}
|
|
587
|
+
else if (installed.commands > 0) {
|
|
588
|
+
cmdSpinner.succeed(`Installed ${installed.commands} commands`);
|
|
449
589
|
}
|
|
450
590
|
else {
|
|
451
|
-
|
|
591
|
+
cmdSpinner.info('No commands to install');
|
|
452
592
|
}
|
|
453
593
|
// Install skills
|
|
454
|
-
|
|
594
|
+
const skillItems = [...newItems, ...existingItems].filter((i) => i.type === 'skill');
|
|
595
|
+
if (skillItems.length > 0) {
|
|
455
596
|
const skillSpinner = ora('Installing skills...').start();
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
// Check if should skip
|
|
461
|
-
const shouldSkip = skipExisting && skillAgents.some((agentId) => conflictNames.has(`skill:${skill.name}:${agentId}`));
|
|
462
|
-
if (shouldSkip) {
|
|
463
|
-
skillsSkipped++;
|
|
597
|
+
for (const item of skillItems) {
|
|
598
|
+
const decision = item.isNew ? 'overwrite' : decisions.get(`skill:${item.name}`);
|
|
599
|
+
if (decision === 'skip') {
|
|
600
|
+
skipped.skills++;
|
|
464
601
|
continue;
|
|
465
602
|
}
|
|
466
|
-
|
|
467
|
-
|
|
603
|
+
const skill = allSkills.find((s) => s.name === item.name);
|
|
604
|
+
if (skill) {
|
|
605
|
+
const result = installSkill(skill.path, skill.name, item.agents);
|
|
468
606
|
if (result.success)
|
|
469
|
-
|
|
607
|
+
installed.skills++;
|
|
470
608
|
}
|
|
471
609
|
}
|
|
472
|
-
if (
|
|
473
|
-
skillSpinner.succeed(`Installed ${
|
|
610
|
+
if (skipped.skills > 0) {
|
|
611
|
+
skillSpinner.succeed(`Installed ${installed.skills} skills (skipped ${skipped.skills})`);
|
|
612
|
+
}
|
|
613
|
+
else if (installed.skills > 0) {
|
|
614
|
+
skillSpinner.succeed(`Installed ${installed.skills} skills`);
|
|
474
615
|
}
|
|
475
616
|
else {
|
|
476
|
-
skillSpinner.
|
|
617
|
+
skillSpinner.info('No skills to install');
|
|
477
618
|
}
|
|
478
619
|
}
|
|
479
620
|
// Install hooks
|
|
480
|
-
|
|
621
|
+
const hookItems = [...newItems, ...existingItems].filter((i) => i.type === 'hook');
|
|
622
|
+
if (hookItems.length > 0 && hookAgents.length > 0) {
|
|
481
623
|
const hookSpinner = ora('Installing hooks...').start();
|
|
482
|
-
// Note: installHooks handles its own conflict detection internally
|
|
483
|
-
// For now, pass the full list - in the future we could filter
|
|
484
624
|
const result = await installHooks(localPath, hookAgents, { scope: 'user' });
|
|
485
625
|
hookSpinner.succeed(`Installed ${result.installed.length} hooks`);
|
|
486
626
|
}
|
|
487
627
|
// Register MCP servers
|
|
488
|
-
|
|
628
|
+
const mcpItems = [...newItems, ...existingItems].filter((i) => i.type === 'mcp');
|
|
629
|
+
if (mcpItems.length > 0 && manifest?.mcp) {
|
|
489
630
|
const mcpSpinner = ora('Registering MCP servers...').start();
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
if (config.transport === 'http' || !config.command)
|
|
631
|
+
for (const item of mcpItems) {
|
|
632
|
+
const decision = item.isNew ? 'overwrite' : decisions.get(`mcp:${item.name}`);
|
|
633
|
+
if (decision === 'skip') {
|
|
634
|
+
skipped.mcps++;
|
|
495
635
|
continue;
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
continue;
|
|
504
|
-
}
|
|
505
|
-
// If overwriting, unregister first then re-register
|
|
506
|
-
unregisterMcp(agentId, name);
|
|
636
|
+
}
|
|
637
|
+
const config = manifest.mcp[item.name];
|
|
638
|
+
if (!config || !config.command)
|
|
639
|
+
continue;
|
|
640
|
+
for (const agentId of item.agents) {
|
|
641
|
+
if (!item.isNew) {
|
|
642
|
+
unregisterMcp(agentId, item.name);
|
|
507
643
|
}
|
|
508
|
-
const result = registerMcp(agentId, name, config.command, config.scope);
|
|
644
|
+
const result = registerMcp(agentId, item.name, config.command, config.scope);
|
|
509
645
|
if (result.success)
|
|
510
|
-
|
|
646
|
+
installed.mcps++;
|
|
511
647
|
}
|
|
512
648
|
}
|
|
513
|
-
if (
|
|
514
|
-
mcpSpinner.succeed(`Registered ${
|
|
649
|
+
if (skipped.mcps > 0) {
|
|
650
|
+
mcpSpinner.succeed(`Registered ${installed.mcps} MCP servers (skipped ${skipped.mcps})`);
|
|
651
|
+
}
|
|
652
|
+
else if (installed.mcps > 0) {
|
|
653
|
+
mcpSpinner.succeed(`Registered ${installed.mcps} MCP servers`);
|
|
515
654
|
}
|
|
516
655
|
else {
|
|
517
|
-
mcpSpinner.
|
|
656
|
+
mcpSpinner.info('No MCP servers to register');
|
|
518
657
|
}
|
|
519
658
|
}
|
|
520
|
-
// Sync CLI versions (user scope only
|
|
659
|
+
// Sync CLI versions (user scope only)
|
|
521
660
|
if (isUserScope && !options.skipClis && manifest?.clis) {
|
|
522
661
|
const cliSpinner = ora('Checking CLI versions...').start();
|
|
523
662
|
const cliUpdates = [];
|
|
524
663
|
for (const [agentIdStr, cliConfig] of Object.entries(manifest.clis)) {
|
|
525
664
|
const agentId = agentIdStr;
|
|
665
|
+
if (agentFilter && agentId !== agentFilter)
|
|
666
|
+
continue;
|
|
526
667
|
const agent = AGENTS[agentId];
|
|
527
668
|
if (!agent || !cliConfig.package)
|
|
528
669
|
continue;
|
|
529
670
|
const currentVersion = getCliVersion(agentId);
|
|
530
671
|
const targetVersion = cliConfig.version;
|
|
531
|
-
// Skip if same version or if target is "latest" and CLI is installed
|
|
532
672
|
if (currentVersion === targetVersion)
|
|
533
673
|
continue;
|
|
534
674
|
if (targetVersion === 'latest' && currentVersion)
|
|
@@ -546,7 +686,7 @@ program
|
|
|
546
686
|
cliSpinner.succeed('CLI versions match');
|
|
547
687
|
}
|
|
548
688
|
}
|
|
549
|
-
// Update scope config
|
|
689
|
+
// Update scope config
|
|
550
690
|
if (!isReadonly) {
|
|
551
691
|
const priority = getScopePriority(effectiveScope);
|
|
552
692
|
setScope(effectiveScope, {
|
|
@@ -561,6 +701,10 @@ program
|
|
|
561
701
|
console.log(chalk.green(`\nSync complete from ${effectiveScope} scope`));
|
|
562
702
|
}
|
|
563
703
|
catch (err) {
|
|
704
|
+
if (isPromptCancelled(err)) {
|
|
705
|
+
console.log(chalk.yellow('\nCancelled'));
|
|
706
|
+
process.exit(0);
|
|
707
|
+
}
|
|
564
708
|
spinner.fail('Failed to sync');
|
|
565
709
|
console.error(chalk.red(err.message));
|
|
566
710
|
process.exit(1);
|
|
@@ -1076,9 +1220,48 @@ skillsCmd
|
|
|
1076
1220
|
}
|
|
1077
1221
|
});
|
|
1078
1222
|
skillsCmd
|
|
1079
|
-
.command('
|
|
1080
|
-
.
|
|
1081
|
-
.
|
|
1223
|
+
.command('view [name]')
|
|
1224
|
+
.alias('info')
|
|
1225
|
+
.description('View detailed info about an installed skill')
|
|
1226
|
+
.action(async (name) => {
|
|
1227
|
+
// If no name provided, show interactive select
|
|
1228
|
+
if (!name) {
|
|
1229
|
+
const cwd = process.cwd();
|
|
1230
|
+
const allSkills = [];
|
|
1231
|
+
const seenNames = new Set();
|
|
1232
|
+
for (const agentId of SKILLS_CAPABLE_AGENTS) {
|
|
1233
|
+
const skills = listInstalledSkillsWithScope(agentId, cwd);
|
|
1234
|
+
for (const skill of skills) {
|
|
1235
|
+
if (!seenNames.has(skill.name)) {
|
|
1236
|
+
seenNames.add(skill.name);
|
|
1237
|
+
allSkills.push({
|
|
1238
|
+
name: skill.name,
|
|
1239
|
+
description: skill.metadata.description || '',
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
if (allSkills.length === 0) {
|
|
1245
|
+
console.log(chalk.yellow('No skills installed'));
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
try {
|
|
1249
|
+
name = await select({
|
|
1250
|
+
message: 'Select a skill to view',
|
|
1251
|
+
choices: allSkills.map((s) => ({
|
|
1252
|
+
value: s.name,
|
|
1253
|
+
name: s.description ? `${s.name} - ${s.description}` : s.name,
|
|
1254
|
+
})),
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
catch (err) {
|
|
1258
|
+
if (isPromptCancelled(err)) {
|
|
1259
|
+
console.log(chalk.gray('Cancelled'));
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
throw err;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1082
1265
|
const skill = getSkillInfo(name);
|
|
1083
1266
|
if (!skill) {
|
|
1084
1267
|
console.log(chalk.yellow(`Skill '${name}' not found`));
|
|
@@ -1968,7 +2151,7 @@ program
|
|
|
1968
2151
|
const spinner = ora('Checking for updates...').start();
|
|
1969
2152
|
try {
|
|
1970
2153
|
// Get current version from package.json
|
|
1971
|
-
const currentVersion = program.version();
|
|
2154
|
+
const currentVersion = program.version() || '0.0.0';
|
|
1972
2155
|
// Fetch latest version from npm
|
|
1973
2156
|
const response = await fetch('https://registry.npmjs.org/@swarmify/agents-cli/latest');
|
|
1974
2157
|
if (!response.ok) {
|
|
@@ -1980,7 +2163,7 @@ program
|
|
|
1980
2163
|
spinner.succeed(`Already on latest version (${currentVersion})`);
|
|
1981
2164
|
return;
|
|
1982
2165
|
}
|
|
1983
|
-
spinner.text = `Upgrading
|
|
2166
|
+
spinner.text = `Upgrading to ${latestVersion}...`;
|
|
1984
2167
|
// Detect package manager
|
|
1985
2168
|
const { execSync } = await import('child_process');
|
|
1986
2169
|
let cmd;
|
|
@@ -2009,8 +2192,11 @@ program
|
|
|
2009
2192
|
cmd = 'npm install -g @swarmify/agents-cli@latest';
|
|
2010
2193
|
}
|
|
2011
2194
|
}
|
|
2012
|
-
|
|
2195
|
+
// Run silently (suppress npm/bun output)
|
|
2196
|
+
execSync(cmd, { stdio: 'pipe' });
|
|
2013
2197
|
spinner.succeed(`Upgraded to ${latestVersion}`);
|
|
2198
|
+
// Show what's new from changelog
|
|
2199
|
+
await showWhatsNew(currentVersion, latestVersion);
|
|
2014
2200
|
}
|
|
2015
2201
|
catch (err) {
|
|
2016
2202
|
spinner.fail('Upgrade failed');
|
|
@@ -2019,5 +2205,67 @@ program
|
|
|
2019
2205
|
process.exit(1);
|
|
2020
2206
|
}
|
|
2021
2207
|
});
|
|
2208
|
+
async function showWhatsNew(fromVersion, toVersion) {
|
|
2209
|
+
try {
|
|
2210
|
+
// Fetch changelog from npm package
|
|
2211
|
+
const response = await fetch(`https://unpkg.com/@swarmify/agents-cli@${toVersion}/CHANGELOG.md`);
|
|
2212
|
+
if (!response.ok)
|
|
2213
|
+
return;
|
|
2214
|
+
const changelog = await response.text();
|
|
2215
|
+
const lines = changelog.split('\n');
|
|
2216
|
+
// Parse changelog to find relevant sections
|
|
2217
|
+
const relevantChanges = [];
|
|
2218
|
+
let inRelevantSection = false;
|
|
2219
|
+
let currentVersion = '';
|
|
2220
|
+
for (const line of lines) {
|
|
2221
|
+
// Check for version header (## 1.5.0)
|
|
2222
|
+
const versionMatch = line.match(/^## (\d+\.\d+\.\d+)/);
|
|
2223
|
+
if (versionMatch) {
|
|
2224
|
+
currentVersion = versionMatch[1];
|
|
2225
|
+
// Include versions newer than fromVersion
|
|
2226
|
+
const isNewer = currentVersion !== fromVersion &&
|
|
2227
|
+
compareVersions(currentVersion, fromVersion) > 0;
|
|
2228
|
+
inRelevantSection = isNewer;
|
|
2229
|
+
if (inRelevantSection) {
|
|
2230
|
+
relevantChanges.push('');
|
|
2231
|
+
relevantChanges.push(chalk.bold(`v${currentVersion}`));
|
|
2232
|
+
}
|
|
2233
|
+
continue;
|
|
2234
|
+
}
|
|
2235
|
+
if (inRelevantSection && line.trim()) {
|
|
2236
|
+
// Format the line
|
|
2237
|
+
if (line.startsWith('**') && line.endsWith('**')) {
|
|
2238
|
+
// Section header like **Pull command redesign**
|
|
2239
|
+
relevantChanges.push(chalk.cyan(line.replace(/\*\*/g, '')));
|
|
2240
|
+
}
|
|
2241
|
+
else if (line.startsWith('- ')) {
|
|
2242
|
+
// Bullet point
|
|
2243
|
+
relevantChanges.push(chalk.gray(` ${line}`));
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
if (relevantChanges.length > 0) {
|
|
2248
|
+
console.log(chalk.bold("\nWhat's new:\n"));
|
|
2249
|
+
for (const line of relevantChanges) {
|
|
2250
|
+
console.log(line);
|
|
2251
|
+
}
|
|
2252
|
+
console.log();
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
catch {
|
|
2256
|
+
// Silently ignore changelog fetch errors
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
function compareVersions(a, b) {
|
|
2260
|
+
const partsA = a.split('.').map(Number);
|
|
2261
|
+
const partsB = b.split('.').map(Number);
|
|
2262
|
+
for (let i = 0; i < 3; i++) {
|
|
2263
|
+
if (partsA[i] > partsB[i])
|
|
2264
|
+
return 1;
|
|
2265
|
+
if (partsA[i] < partsB[i])
|
|
2266
|
+
return -1;
|
|
2267
|
+
}
|
|
2268
|
+
return 0;
|
|
2269
|
+
}
|
|
2022
2270
|
program.parse();
|
|
2023
2271
|
//# sourceMappingURL=index.js.map
|