@swarmify/agents-cli 1.3.13 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/README.md +158 -117
- package/dist/index.js +452 -111
- package/dist/index.js.map +1 -1
- package/dist/lib/commands.d.ts +4 -0
- package/dist/lib/commands.d.ts.map +1 -1
- package/dist/lib/commands.js +9 -0
- package/dist/lib/commands.js.map +1 -1
- package/dist/lib/hooks.d.ts +4 -0
- package/dist/lib/hooks.d.ts.map +1 -1
- package/dist/lib/hooks.js +19 -0
- package/dist/lib/hooks.js.map +1 -1
- package/dist/lib/skills.d.ts +4 -0
- package/dist/lib/skills.d.ts.map +1 -1
- package/dist/lib/skills.js +7 -0
- package/dist/lib/skills.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -3,14 +3,19 @@ import { Command } from 'commander';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import ora from 'ora';
|
|
5
5
|
import { checkbox, confirm, select } from '@inquirer/prompts';
|
|
6
|
-
|
|
6
|
+
function isPromptCancelled(err) {
|
|
7
|
+
return err instanceof Error && (err.name === 'ExitPromptError' ||
|
|
8
|
+
err.message.includes('force closed') ||
|
|
9
|
+
err.message.includes('User force closed'));
|
|
10
|
+
}
|
|
11
|
+
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
12
|
import { readManifest, writeManifest, createDefaultManifest, MANIFEST_FILENAME, } from './lib/manifest.js';
|
|
8
13
|
import { readState, ensureAgentsDir, getRepoLocalPath, getScope, setScope, removeScope, getScopesByPriority, getScopePriority, } from './lib/state.js';
|
|
9
14
|
import { SCOPE_PRIORITIES, DEFAULT_SYSTEM_REPO } from './lib/types.js';
|
|
10
15
|
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';
|
|
16
|
+
import { discoverCommands, resolveCommandSource, installCommand, uninstallCommand, listInstalledCommandsWithScope, promoteCommandToUser, commandExists, } from './lib/commands.js';
|
|
17
|
+
import { discoverHooksFromRepo, installHooks, listInstalledHooksWithScope, promoteHookToUser, removeHook, hookExists, } from './lib/hooks.js';
|
|
18
|
+
import { discoverSkillsFromRepo, installSkill, uninstallSkill, listInstalledSkillsWithScope, promoteSkillToUser, getSkillInfo, getSkillRules, skillExists, } from './lib/skills.js';
|
|
14
19
|
import { DEFAULT_REGISTRIES } from './lib/types.js';
|
|
15
20
|
import { search as searchRegistries, getRegistries, setRegistry, removeRegistry, resolvePackage, } from './lib/registry.js';
|
|
16
21
|
const program = new Command();
|
|
@@ -198,24 +203,62 @@ program
|
|
|
198
203
|
// =============================================================================
|
|
199
204
|
// PULL COMMAND
|
|
200
205
|
// =============================================================================
|
|
206
|
+
// Agent name aliases for flexible input
|
|
207
|
+
const AGENT_NAME_ALIASES = {
|
|
208
|
+
claude: 'claude',
|
|
209
|
+
'claude-code': 'claude',
|
|
210
|
+
cc: 'claude',
|
|
211
|
+
codex: 'codex',
|
|
212
|
+
'openai-codex': 'codex',
|
|
213
|
+
cx: 'codex',
|
|
214
|
+
gemini: 'gemini',
|
|
215
|
+
'gemini-cli': 'gemini',
|
|
216
|
+
gx: 'gemini',
|
|
217
|
+
cursor: 'cursor',
|
|
218
|
+
'cursor-agent': 'cursor',
|
|
219
|
+
cr: 'cursor',
|
|
220
|
+
opencode: 'opencode',
|
|
221
|
+
oc: 'opencode',
|
|
222
|
+
};
|
|
223
|
+
function resolveAgentName(input) {
|
|
224
|
+
return AGENT_NAME_ALIASES[input.toLowerCase()] || null;
|
|
225
|
+
}
|
|
226
|
+
function isAgentName(input) {
|
|
227
|
+
return resolveAgentName(input) !== null;
|
|
228
|
+
}
|
|
201
229
|
program
|
|
202
|
-
.command('pull [source]')
|
|
230
|
+
.command('pull [source] [agent]')
|
|
203
231
|
.description('Pull and sync from remote .agents repo')
|
|
204
|
-
.option('-y, --yes', '
|
|
205
|
-
.option('-f, --force', '
|
|
232
|
+
.option('-y, --yes', 'Auto-confirm and skip all conflicts')
|
|
233
|
+
.option('-f, --force', 'Auto-confirm and overwrite all conflicts')
|
|
206
234
|
.option('-s, --scope <scope>', 'Target scope (default: user)', 'user')
|
|
207
235
|
.option('--dry-run', 'Show what would change')
|
|
208
|
-
.option('--skip-clis', 'Skip CLI
|
|
236
|
+
.option('--skip-clis', 'Skip CLI version sync')
|
|
209
237
|
.option('--skip-mcp', 'Skip MCP registration')
|
|
210
|
-
.action(async (
|
|
238
|
+
.action(async (arg1, arg2, options) => {
|
|
239
|
+
// Parse source and agent filter from positional args
|
|
240
|
+
let targetSource;
|
|
241
|
+
let agentFilter;
|
|
242
|
+
if (arg1) {
|
|
243
|
+
if (isAgentName(arg1)) {
|
|
244
|
+
// agents pull claude
|
|
245
|
+
agentFilter = resolveAgentName(arg1);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
// agents pull gh:user/repo [agent]
|
|
249
|
+
targetSource = arg1;
|
|
250
|
+
if (arg2 && isAgentName(arg2)) {
|
|
251
|
+
agentFilter = resolveAgentName(arg2);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
211
255
|
const scopeName = options.scope;
|
|
212
256
|
const meta = readState();
|
|
213
257
|
const existingScope = meta.scopes[scopeName];
|
|
214
258
|
// Try: 1) provided source, 2) existing scope source, 3) fall back to system scope
|
|
215
|
-
|
|
259
|
+
targetSource = targetSource || existingScope?.source;
|
|
216
260
|
let effectiveScope = scopeName;
|
|
217
261
|
if (!targetSource && scopeName === 'user') {
|
|
218
|
-
// Fall back to system scope if user scope has no source
|
|
219
262
|
const systemScope = meta.scopes['system'];
|
|
220
263
|
if (systemScope?.source) {
|
|
221
264
|
targetSource = systemScope.source;
|
|
@@ -224,15 +267,22 @@ program
|
|
|
224
267
|
}
|
|
225
268
|
}
|
|
226
269
|
if (!targetSource) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
270
|
+
if (scopeName === 'user' && Object.keys(meta.scopes).length === 0) {
|
|
271
|
+
console.log(chalk.gray(`First run detected. Initializing from ${DEFAULT_SYSTEM_REPO}...\n`));
|
|
272
|
+
targetSource = DEFAULT_SYSTEM_REPO;
|
|
273
|
+
effectiveScope = 'system';
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
console.log(chalk.red(`No source specified for scope '${scopeName}'.`));
|
|
277
|
+
const scopeHint = scopeName === 'user' ? '' : ` --scope ${scopeName}`;
|
|
278
|
+
console.log(chalk.gray(` Usage: agents pull <source>${scopeHint}`));
|
|
279
|
+
console.log(chalk.gray(' Example: agents pull gh:username/.agents'));
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
232
282
|
}
|
|
233
|
-
// Prevent modification of readonly scopes (but allow syncing from them)
|
|
234
283
|
const targetScopeConfig = meta.scopes[effectiveScope];
|
|
235
284
|
const isReadonly = targetScopeConfig?.readonly || effectiveScope === 'system';
|
|
285
|
+
const isUserScope = effectiveScope === 'user';
|
|
236
286
|
const parsed = parseSource(targetSource);
|
|
237
287
|
const spinner = ora(`Syncing from ${effectiveScope} scope...`).start();
|
|
238
288
|
try {
|
|
@@ -243,75 +293,195 @@ program
|
|
|
243
293
|
console.log(chalk.yellow(`No ${MANIFEST_FILENAME} found in repository`));
|
|
244
294
|
}
|
|
245
295
|
// Discover all assets
|
|
246
|
-
const
|
|
247
|
-
const
|
|
296
|
+
const allCommands = discoverCommands(localPath);
|
|
297
|
+
const allSkills = discoverSkillsFromRepo(localPath);
|
|
248
298
|
const discoveredHooks = discoverHooksFromRepo(localPath);
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const src = command.isShared ? 'shared' : command.agentSpecific;
|
|
256
|
-
console.log(` ${chalk.cyan(command.name.padEnd(18))} ${chalk.gray(src)}`);
|
|
257
|
-
}
|
|
258
|
-
if (commands.length > 5) {
|
|
259
|
-
console.log(chalk.gray(` ... and ${commands.length - 5} more`));
|
|
260
|
-
}
|
|
299
|
+
// Determine which agents to sync
|
|
300
|
+
let selectedAgents;
|
|
301
|
+
if (agentFilter) {
|
|
302
|
+
// Single agent filter
|
|
303
|
+
selectedAgents = [agentFilter];
|
|
304
|
+
console.log(chalk.gray(`\nFiltering for ${AGENTS[agentFilter].name} only\n`));
|
|
261
305
|
}
|
|
262
|
-
if (
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
306
|
+
else if (options.yes || options.force) {
|
|
307
|
+
selectedAgents = (manifest?.defaults?.agents || ['claude', 'codex', 'gemini']);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
const installedAgents = ALL_AGENT_IDS.filter((id) => isCliInstalled(id) || id === 'cursor');
|
|
311
|
+
selectedAgents = await checkbox({
|
|
312
|
+
message: 'Select agents to sync:',
|
|
313
|
+
choices: installedAgents.map((id) => ({
|
|
314
|
+
name: AGENTS[id].name,
|
|
315
|
+
value: id,
|
|
316
|
+
checked: (manifest?.defaults?.agents || ['claude', 'codex', 'gemini']).includes(id),
|
|
317
|
+
})),
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
// Filter agents to only installed ones (plus cursor which doesn't need CLI)
|
|
321
|
+
selectedAgents = selectedAgents.filter((id) => isCliInstalled(id) || id === 'cursor');
|
|
322
|
+
if (selectedAgents.length === 0) {
|
|
323
|
+
console.log(chalk.yellow('\nNo agents selected or installed. Nothing to sync.'));
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
// Build resource items with conflict detection
|
|
327
|
+
const newItems = [];
|
|
328
|
+
const existingItems = [];
|
|
329
|
+
// Process commands
|
|
330
|
+
for (const command of allCommands) {
|
|
331
|
+
const applicableAgents = selectedAgents.filter((agentId) => {
|
|
332
|
+
const sourcePath = resolveCommandSource(localPath, command.name, agentId);
|
|
333
|
+
return sourcePath !== null;
|
|
334
|
+
});
|
|
335
|
+
if (applicableAgents.length === 0)
|
|
336
|
+
continue;
|
|
337
|
+
const conflictingAgents = applicableAgents.filter((agentId) => commandExists(agentId, command.name));
|
|
338
|
+
const newAgents = applicableAgents.filter((agentId) => !commandExists(agentId, command.name));
|
|
339
|
+
if (conflictingAgents.length > 0) {
|
|
340
|
+
existingItems.push({ type: 'command', name: command.name, agents: conflictingAgents, isNew: false });
|
|
341
|
+
}
|
|
342
|
+
if (newAgents.length > 0) {
|
|
343
|
+
newItems.push({ type: 'command', name: command.name, agents: newAgents, isNew: true });
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
// Process skills
|
|
347
|
+
const skillAgents = SKILLS_CAPABLE_AGENTS.filter((id) => selectedAgents.includes(id));
|
|
348
|
+
for (const skill of allSkills) {
|
|
349
|
+
const conflictingAgents = skillAgents.filter((agentId) => skillExists(agentId, skill.name));
|
|
350
|
+
const newAgents = skillAgents.filter((agentId) => !skillExists(agentId, skill.name));
|
|
351
|
+
if (conflictingAgents.length > 0) {
|
|
352
|
+
existingItems.push({ type: 'skill', name: skill.name, agents: conflictingAgents, isNew: false });
|
|
353
|
+
}
|
|
354
|
+
if (newAgents.length > 0) {
|
|
355
|
+
newItems.push({ type: 'skill', name: skill.name, agents: newAgents, isNew: true });
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// Process hooks
|
|
359
|
+
const hookAgents = selectedAgents.filter((id) => HOOKS_CAPABLE_AGENTS.includes(id) && isCliInstalled(id));
|
|
360
|
+
const allHookNames = [
|
|
361
|
+
...discoveredHooks.shared,
|
|
362
|
+
...Object.entries(discoveredHooks.agentSpecific)
|
|
363
|
+
.filter(([agentId]) => hookAgents.includes(agentId))
|
|
364
|
+
.flatMap(([_, hooks]) => hooks),
|
|
365
|
+
];
|
|
366
|
+
const uniqueHookNames = [...new Set(allHookNames)];
|
|
367
|
+
for (const hookName of uniqueHookNames) {
|
|
368
|
+
const conflictingAgents = hookAgents.filter((agentId) => hookExists(agentId, hookName));
|
|
369
|
+
const newAgents = hookAgents.filter((agentId) => !hookExists(agentId, hookName));
|
|
370
|
+
if (conflictingAgents.length > 0) {
|
|
371
|
+
existingItems.push({ type: 'hook', name: hookName, agents: conflictingAgents, isNew: false });
|
|
372
|
+
}
|
|
373
|
+
if (newAgents.length > 0) {
|
|
374
|
+
newItems.push({ type: 'hook', name: hookName, agents: newAgents, isNew: true });
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
// Process MCPs
|
|
378
|
+
if (!options.skipMcp && manifest?.mcp) {
|
|
379
|
+
for (const [name, config] of Object.entries(manifest.mcp)) {
|
|
380
|
+
if (config.transport === 'http' || !config.command)
|
|
381
|
+
continue;
|
|
382
|
+
const mcpAgents = config.agents.filter((agentId) => selectedAgents.includes(agentId) && isCliInstalled(agentId));
|
|
383
|
+
if (mcpAgents.length === 0)
|
|
384
|
+
continue;
|
|
385
|
+
const conflictingAgents = mcpAgents.filter((agentId) => isMcpRegistered(agentId, name));
|
|
386
|
+
const newAgents = mcpAgents.filter((agentId) => !isMcpRegistered(agentId, name));
|
|
387
|
+
if (conflictingAgents.length > 0) {
|
|
388
|
+
existingItems.push({ type: 'mcp', name, agents: conflictingAgents, isNew: false });
|
|
389
|
+
}
|
|
390
|
+
if (newAgents.length > 0) {
|
|
391
|
+
newItems.push({ type: 'mcp', name, agents: newAgents, isNew: true });
|
|
392
|
+
}
|
|
269
393
|
}
|
|
270
394
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
395
|
+
// Display overview
|
|
396
|
+
console.log(chalk.bold('\nOverview\n'));
|
|
397
|
+
const formatAgentList = (agents) => agents.map((id) => AGENTS[id].name).join(', ');
|
|
398
|
+
if (newItems.length > 0) {
|
|
399
|
+
console.log(chalk.green(' NEW (will install):\n'));
|
|
400
|
+
const byType = { command: [], skill: [], hook: [], mcp: [] };
|
|
401
|
+
for (const item of newItems)
|
|
402
|
+
byType[item.type].push(item);
|
|
403
|
+
if (byType.command.length > 0) {
|
|
404
|
+
console.log(` Commands:`);
|
|
405
|
+
for (const item of byType.command) {
|
|
406
|
+
console.log(` ${chalk.cyan(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
|
|
407
|
+
}
|
|
275
408
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
409
|
+
if (byType.skill.length > 0) {
|
|
410
|
+
console.log(` Skills:`);
|
|
411
|
+
for (const item of byType.skill) {
|
|
412
|
+
console.log(` ${chalk.cyan(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
|
|
279
413
|
}
|
|
280
414
|
}
|
|
281
|
-
if (
|
|
282
|
-
console.log(
|
|
415
|
+
if (byType.hook.length > 0) {
|
|
416
|
+
console.log(` Hooks:`);
|
|
417
|
+
for (const item of byType.hook) {
|
|
418
|
+
console.log(` ${chalk.cyan(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (byType.mcp.length > 0) {
|
|
422
|
+
console.log(` MCP Servers:`);
|
|
423
|
+
for (const item of byType.mcp) {
|
|
424
|
+
console.log(` ${chalk.cyan(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
|
|
425
|
+
}
|
|
283
426
|
}
|
|
427
|
+
console.log();
|
|
284
428
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
for (const
|
|
289
|
-
|
|
429
|
+
if (existingItems.length > 0) {
|
|
430
|
+
console.log(chalk.yellow(' EXISTING (conflicts):\n'));
|
|
431
|
+
const byType = { command: [], skill: [], hook: [], mcp: [] };
|
|
432
|
+
for (const item of existingItems)
|
|
433
|
+
byType[item.type].push(item);
|
|
434
|
+
if (byType.command.length > 0) {
|
|
435
|
+
console.log(` Commands:`);
|
|
436
|
+
for (const item of byType.command) {
|
|
437
|
+
console.log(` ${chalk.yellow(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
|
|
438
|
+
}
|
|
290
439
|
}
|
|
291
|
-
if (
|
|
292
|
-
console.log(
|
|
440
|
+
if (byType.skill.length > 0) {
|
|
441
|
+
console.log(` Skills:`);
|
|
442
|
+
for (const item of byType.skill) {
|
|
443
|
+
console.log(` ${chalk.yellow(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
|
|
444
|
+
}
|
|
293
445
|
}
|
|
446
|
+
if (byType.hook.length > 0) {
|
|
447
|
+
console.log(` Hooks:`);
|
|
448
|
+
for (const item of byType.hook) {
|
|
449
|
+
console.log(` ${chalk.yellow(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (byType.mcp.length > 0) {
|
|
453
|
+
console.log(` MCP Servers:`);
|
|
454
|
+
for (const item of byType.mcp) {
|
|
455
|
+
console.log(` ${chalk.yellow(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
console.log();
|
|
459
|
+
}
|
|
460
|
+
if (newItems.length === 0 && existingItems.length === 0) {
|
|
461
|
+
console.log(chalk.gray(' Nothing to sync.\n'));
|
|
462
|
+
return;
|
|
294
463
|
}
|
|
295
464
|
if (options.dryRun) {
|
|
296
|
-
console.log(chalk.yellow('
|
|
465
|
+
console.log(chalk.yellow('Dry run - no changes made'));
|
|
297
466
|
return;
|
|
298
467
|
}
|
|
299
|
-
|
|
468
|
+
// Confirmation prompt
|
|
469
|
+
if (!options.yes && !options.force) {
|
|
470
|
+
const proceed = await confirm({
|
|
471
|
+
message: 'Proceed with installation?',
|
|
472
|
+
default: true,
|
|
473
|
+
});
|
|
474
|
+
if (!proceed) {
|
|
475
|
+
console.log(chalk.yellow('\nSync cancelled'));
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
// Determine installation method
|
|
300
480
|
let method;
|
|
301
|
-
if (options.yes) {
|
|
302
|
-
selectedAgents = (manifest?.defaults?.agents || ['claude', 'codex', 'gemini']);
|
|
481
|
+
if (options.yes || options.force) {
|
|
303
482
|
method = manifest?.defaults?.method || 'symlink';
|
|
304
483
|
}
|
|
305
484
|
else {
|
|
306
|
-
const installedAgents = ALL_AGENT_IDS.filter((id) => isCliInstalled(id) || id === 'cursor');
|
|
307
|
-
selectedAgents = await checkbox({
|
|
308
|
-
message: 'Select agents to sync:',
|
|
309
|
-
choices: installedAgents.map((id) => ({
|
|
310
|
-
name: AGENTS[id].name,
|
|
311
|
-
value: id,
|
|
312
|
-
checked: (manifest?.defaults?.agents || ['claude', 'codex', 'gemini']).includes(id),
|
|
313
|
-
})),
|
|
314
|
-
});
|
|
315
485
|
method = await select({
|
|
316
486
|
message: 'Installation method:',
|
|
317
487
|
choices: [
|
|
@@ -321,64 +491,166 @@ program
|
|
|
321
491
|
default: manifest?.defaults?.method || 'symlink',
|
|
322
492
|
});
|
|
323
493
|
}
|
|
324
|
-
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
for (const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const
|
|
494
|
+
// Per-resource conflict decisions
|
|
495
|
+
const decisions = new Map();
|
|
496
|
+
if (existingItems.length > 0 && !options.force && !options.yes) {
|
|
497
|
+
console.log(chalk.bold('\nResolve conflicts:\n'));
|
|
498
|
+
for (const item of existingItems) {
|
|
499
|
+
const typeLabel = item.type.charAt(0).toUpperCase() + item.type.slice(1);
|
|
500
|
+
const agentList = formatAgentList(item.agents);
|
|
501
|
+
const decision = await select({
|
|
502
|
+
message: `${typeLabel} '${item.name}' exists (${agentList})`,
|
|
503
|
+
choices: [
|
|
504
|
+
{ name: 'Overwrite', value: 'overwrite' },
|
|
505
|
+
{ name: 'Skip', value: 'skip' },
|
|
506
|
+
{ name: 'Cancel all', value: 'cancel' },
|
|
507
|
+
],
|
|
508
|
+
});
|
|
509
|
+
if (decision === 'cancel') {
|
|
510
|
+
console.log(chalk.yellow('\nSync cancelled'));
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
decisions.set(`${item.type}:${item.name}`, decision);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
else if (options.force) {
|
|
517
|
+
// Force mode: overwrite all
|
|
518
|
+
for (const item of existingItems) {
|
|
519
|
+
decisions.set(`${item.type}:${item.name}`, 'overwrite');
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
else if (options.yes) {
|
|
523
|
+
// Yes mode: skip all conflicts
|
|
524
|
+
for (const item of existingItems) {
|
|
525
|
+
decisions.set(`${item.type}:${item.name}`, 'skip');
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
// Install new items (no conflicts)
|
|
529
|
+
console.log();
|
|
530
|
+
let installed = { commands: 0, skills: 0, hooks: 0, mcps: 0 };
|
|
531
|
+
let skipped = { commands: 0, skills: 0, hooks: 0, mcps: 0 };
|
|
532
|
+
// Install commands
|
|
533
|
+
const cmdSpinner = ora('Installing commands...').start();
|
|
534
|
+
for (const item of [...newItems, ...existingItems].filter((i) => i.type === 'command')) {
|
|
535
|
+
const decision = item.isNew ? 'overwrite' : decisions.get(`command:${item.name}`);
|
|
536
|
+
if (decision === 'skip') {
|
|
537
|
+
skipped.commands++;
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
for (const agentId of item.agents) {
|
|
541
|
+
const sourcePath = resolveCommandSource(localPath, item.name, agentId);
|
|
332
542
|
if (sourcePath) {
|
|
333
|
-
installCommand(sourcePath, agentId,
|
|
334
|
-
installed++;
|
|
543
|
+
installCommand(sourcePath, agentId, item.name, method);
|
|
544
|
+
installed.commands++;
|
|
335
545
|
}
|
|
336
546
|
}
|
|
337
547
|
}
|
|
338
|
-
|
|
548
|
+
if (skipped.commands > 0) {
|
|
549
|
+
cmdSpinner.succeed(`Installed ${installed.commands} commands (skipped ${skipped.commands})`);
|
|
550
|
+
}
|
|
551
|
+
else if (installed.commands > 0) {
|
|
552
|
+
cmdSpinner.succeed(`Installed ${installed.commands} commands`);
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
cmdSpinner.info('No commands to install');
|
|
556
|
+
}
|
|
339
557
|
// Install skills
|
|
340
|
-
|
|
558
|
+
const skillItems = [...newItems, ...existingItems].filter((i) => i.type === 'skill');
|
|
559
|
+
if (skillItems.length > 0) {
|
|
341
560
|
const skillSpinner = ora('Installing skills...').start();
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
561
|
+
for (const item of skillItems) {
|
|
562
|
+
const decision = item.isNew ? 'overwrite' : decisions.get(`skill:${item.name}`);
|
|
563
|
+
if (decision === 'skip') {
|
|
564
|
+
skipped.skills++;
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
const skill = allSkills.find((s) => s.name === item.name);
|
|
568
|
+
if (skill) {
|
|
569
|
+
const result = installSkill(skill.path, skill.name, item.agents);
|
|
347
570
|
if (result.success)
|
|
348
|
-
|
|
571
|
+
installed.skills++;
|
|
349
572
|
}
|
|
350
573
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
hookSpinner.succeed(`Installed ${result.installed.length} hooks`);
|
|
574
|
+
if (skipped.skills > 0) {
|
|
575
|
+
skillSpinner.succeed(`Installed ${installed.skills} skills (skipped ${skipped.skills})`);
|
|
576
|
+
}
|
|
577
|
+
else if (installed.skills > 0) {
|
|
578
|
+
skillSpinner.succeed(`Installed ${installed.skills} skills`);
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
skillSpinner.info('No skills to install');
|
|
360
582
|
}
|
|
361
583
|
}
|
|
362
|
-
|
|
584
|
+
// Install hooks
|
|
585
|
+
const hookItems = [...newItems, ...existingItems].filter((i) => i.type === 'hook');
|
|
586
|
+
if (hookItems.length > 0 && hookAgents.length > 0) {
|
|
587
|
+
const hookSpinner = ora('Installing hooks...').start();
|
|
588
|
+
const result = await installHooks(localPath, hookAgents, { scope: 'user' });
|
|
589
|
+
hookSpinner.succeed(`Installed ${result.installed.length} hooks`);
|
|
590
|
+
}
|
|
591
|
+
// Register MCP servers
|
|
592
|
+
const mcpItems = [...newItems, ...existingItems].filter((i) => i.type === 'mcp');
|
|
593
|
+
if (mcpItems.length > 0 && manifest?.mcp) {
|
|
363
594
|
const mcpSpinner = ora('Registering MCP servers...').start();
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
595
|
+
for (const item of mcpItems) {
|
|
596
|
+
const decision = item.isNew ? 'overwrite' : decisions.get(`mcp:${item.name}`);
|
|
597
|
+
if (decision === 'skip') {
|
|
598
|
+
skipped.mcps++;
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
const config = manifest.mcp[item.name];
|
|
602
|
+
if (!config || !config.command)
|
|
368
603
|
continue;
|
|
369
|
-
for (const agentId of
|
|
370
|
-
if (!
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
const result = registerMcp(agentId, name, config.command, config.scope);
|
|
604
|
+
for (const agentId of item.agents) {
|
|
605
|
+
if (!item.isNew) {
|
|
606
|
+
unregisterMcp(agentId, item.name);
|
|
607
|
+
}
|
|
608
|
+
const result = registerMcp(agentId, item.name, config.command, config.scope);
|
|
375
609
|
if (result.success)
|
|
376
|
-
|
|
610
|
+
installed.mcps++;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
if (skipped.mcps > 0) {
|
|
614
|
+
mcpSpinner.succeed(`Registered ${installed.mcps} MCP servers (skipped ${skipped.mcps})`);
|
|
615
|
+
}
|
|
616
|
+
else if (installed.mcps > 0) {
|
|
617
|
+
mcpSpinner.succeed(`Registered ${installed.mcps} MCP servers`);
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
mcpSpinner.info('No MCP servers to register');
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
// Sync CLI versions (user scope only)
|
|
624
|
+
if (isUserScope && !options.skipClis && manifest?.clis) {
|
|
625
|
+
const cliSpinner = ora('Checking CLI versions...').start();
|
|
626
|
+
const cliUpdates = [];
|
|
627
|
+
for (const [agentIdStr, cliConfig] of Object.entries(manifest.clis)) {
|
|
628
|
+
const agentId = agentIdStr;
|
|
629
|
+
if (agentFilter && agentId !== agentFilter)
|
|
630
|
+
continue;
|
|
631
|
+
const agent = AGENTS[agentId];
|
|
632
|
+
if (!agent || !cliConfig.package)
|
|
633
|
+
continue;
|
|
634
|
+
const currentVersion = getCliVersion(agentId);
|
|
635
|
+
const targetVersion = cliConfig.version;
|
|
636
|
+
if (currentVersion === targetVersion)
|
|
637
|
+
continue;
|
|
638
|
+
if (targetVersion === 'latest' && currentVersion)
|
|
639
|
+
continue;
|
|
640
|
+
cliUpdates.push(`${agent.name}: ${currentVersion || 'not installed'} -> ${targetVersion}`);
|
|
641
|
+
}
|
|
642
|
+
if (cliUpdates.length > 0) {
|
|
643
|
+
cliSpinner.info('CLI version differences detected');
|
|
644
|
+
console.log(chalk.gray(' Run `agents cli upgrade` to update CLIs'));
|
|
645
|
+
for (const update of cliUpdates) {
|
|
646
|
+
console.log(chalk.gray(` ${update}`));
|
|
377
647
|
}
|
|
378
648
|
}
|
|
379
|
-
|
|
649
|
+
else {
|
|
650
|
+
cliSpinner.succeed('CLI versions match');
|
|
651
|
+
}
|
|
380
652
|
}
|
|
381
|
-
// Update scope config
|
|
653
|
+
// Update scope config
|
|
382
654
|
if (!isReadonly) {
|
|
383
655
|
const priority = getScopePriority(effectiveScope);
|
|
384
656
|
setScope(effectiveScope, {
|
|
@@ -393,6 +665,10 @@ program
|
|
|
393
665
|
console.log(chalk.green(`\nSync complete from ${effectiveScope} scope`));
|
|
394
666
|
}
|
|
395
667
|
catch (err) {
|
|
668
|
+
if (isPromptCancelled(err)) {
|
|
669
|
+
console.log(chalk.yellow('\nCancelled'));
|
|
670
|
+
process.exit(0);
|
|
671
|
+
}
|
|
396
672
|
spinner.fail('Failed to sync');
|
|
397
673
|
console.error(chalk.red(err.message));
|
|
398
674
|
process.exit(1);
|
|
@@ -1800,7 +2076,7 @@ program
|
|
|
1800
2076
|
const spinner = ora('Checking for updates...').start();
|
|
1801
2077
|
try {
|
|
1802
2078
|
// Get current version from package.json
|
|
1803
|
-
const currentVersion = program.version();
|
|
2079
|
+
const currentVersion = program.version() || '0.0.0';
|
|
1804
2080
|
// Fetch latest version from npm
|
|
1805
2081
|
const response = await fetch('https://registry.npmjs.org/@swarmify/agents-cli/latest');
|
|
1806
2082
|
if (!response.ok) {
|
|
@@ -1812,7 +2088,7 @@ program
|
|
|
1812
2088
|
spinner.succeed(`Already on latest version (${currentVersion})`);
|
|
1813
2089
|
return;
|
|
1814
2090
|
}
|
|
1815
|
-
spinner.text = `Upgrading
|
|
2091
|
+
spinner.text = `Upgrading to ${latestVersion}...`;
|
|
1816
2092
|
// Detect package manager
|
|
1817
2093
|
const { execSync } = await import('child_process');
|
|
1818
2094
|
let cmd;
|
|
@@ -1841,8 +2117,11 @@ program
|
|
|
1841
2117
|
cmd = 'npm install -g @swarmify/agents-cli@latest';
|
|
1842
2118
|
}
|
|
1843
2119
|
}
|
|
1844
|
-
|
|
2120
|
+
// Run silently (suppress npm/bun output)
|
|
2121
|
+
execSync(cmd, { stdio: 'pipe' });
|
|
1845
2122
|
spinner.succeed(`Upgraded to ${latestVersion}`);
|
|
2123
|
+
// Show what's new from changelog
|
|
2124
|
+
await showWhatsNew(currentVersion, latestVersion);
|
|
1846
2125
|
}
|
|
1847
2126
|
catch (err) {
|
|
1848
2127
|
spinner.fail('Upgrade failed');
|
|
@@ -1851,5 +2130,67 @@ program
|
|
|
1851
2130
|
process.exit(1);
|
|
1852
2131
|
}
|
|
1853
2132
|
});
|
|
2133
|
+
async function showWhatsNew(fromVersion, toVersion) {
|
|
2134
|
+
try {
|
|
2135
|
+
// Fetch changelog from npm package
|
|
2136
|
+
const response = await fetch(`https://unpkg.com/@swarmify/agents-cli@${toVersion}/CHANGELOG.md`);
|
|
2137
|
+
if (!response.ok)
|
|
2138
|
+
return;
|
|
2139
|
+
const changelog = await response.text();
|
|
2140
|
+
const lines = changelog.split('\n');
|
|
2141
|
+
// Parse changelog to find relevant sections
|
|
2142
|
+
const relevantChanges = [];
|
|
2143
|
+
let inRelevantSection = false;
|
|
2144
|
+
let currentVersion = '';
|
|
2145
|
+
for (const line of lines) {
|
|
2146
|
+
// Check for version header (## 1.5.0)
|
|
2147
|
+
const versionMatch = line.match(/^## (\d+\.\d+\.\d+)/);
|
|
2148
|
+
if (versionMatch) {
|
|
2149
|
+
currentVersion = versionMatch[1];
|
|
2150
|
+
// Include versions newer than fromVersion
|
|
2151
|
+
const isNewer = currentVersion !== fromVersion &&
|
|
2152
|
+
compareVersions(currentVersion, fromVersion) > 0;
|
|
2153
|
+
inRelevantSection = isNewer;
|
|
2154
|
+
if (inRelevantSection) {
|
|
2155
|
+
relevantChanges.push('');
|
|
2156
|
+
relevantChanges.push(chalk.bold(`v${currentVersion}`));
|
|
2157
|
+
}
|
|
2158
|
+
continue;
|
|
2159
|
+
}
|
|
2160
|
+
if (inRelevantSection && line.trim()) {
|
|
2161
|
+
// Format the line
|
|
2162
|
+
if (line.startsWith('**') && line.endsWith('**')) {
|
|
2163
|
+
// Section header like **Pull command redesign**
|
|
2164
|
+
relevantChanges.push(chalk.cyan(line.replace(/\*\*/g, '')));
|
|
2165
|
+
}
|
|
2166
|
+
else if (line.startsWith('- ')) {
|
|
2167
|
+
// Bullet point
|
|
2168
|
+
relevantChanges.push(chalk.gray(` ${line}`));
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
if (relevantChanges.length > 0) {
|
|
2173
|
+
console.log(chalk.bold("\nWhat's new:\n"));
|
|
2174
|
+
for (const line of relevantChanges) {
|
|
2175
|
+
console.log(line);
|
|
2176
|
+
}
|
|
2177
|
+
console.log();
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
catch {
|
|
2181
|
+
// Silently ignore changelog fetch errors
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
function compareVersions(a, b) {
|
|
2185
|
+
const partsA = a.split('.').map(Number);
|
|
2186
|
+
const partsB = b.split('.').map(Number);
|
|
2187
|
+
for (let i = 0; i < 3; i++) {
|
|
2188
|
+
if (partsA[i] > partsB[i])
|
|
2189
|
+
return 1;
|
|
2190
|
+
if (partsA[i] < partsB[i])
|
|
2191
|
+
return -1;
|
|
2192
|
+
}
|
|
2193
|
+
return 0;
|
|
2194
|
+
}
|
|
1854
2195
|
program.parse();
|
|
1855
2196
|
//# sourceMappingURL=index.js.map
|