@swarmify/agents-cli 1.4.0 → 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 +33 -11
- package/dist/index.js +385 -212
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -3,6 +3,11 @@ 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
|
+
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
|
+
}
|
|
6
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';
|
|
@@ -195,24 +200,65 @@ program
|
|
|
195
200
|
}
|
|
196
201
|
console.log();
|
|
197
202
|
});
|
|
203
|
+
// =============================================================================
|
|
204
|
+
// PULL COMMAND
|
|
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
|
+
}
|
|
198
229
|
program
|
|
199
|
-
.command('pull [source]')
|
|
230
|
+
.command('pull [source] [agent]')
|
|
200
231
|
.description('Pull and sync from remote .agents repo')
|
|
201
|
-
.option('-y, --yes', '
|
|
202
|
-
.option('-f, --force', '
|
|
232
|
+
.option('-y, --yes', 'Auto-confirm and skip all conflicts')
|
|
233
|
+
.option('-f, --force', 'Auto-confirm and overwrite all conflicts')
|
|
203
234
|
.option('-s, --scope <scope>', 'Target scope (default: user)', 'user')
|
|
204
235
|
.option('--dry-run', 'Show what would change')
|
|
205
236
|
.option('--skip-clis', 'Skip CLI version sync')
|
|
206
237
|
.option('--skip-mcp', 'Skip MCP registration')
|
|
207
|
-
.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
|
+
}
|
|
208
255
|
const scopeName = options.scope;
|
|
209
256
|
const meta = readState();
|
|
210
257
|
const existingScope = meta.scopes[scopeName];
|
|
211
258
|
// Try: 1) provided source, 2) existing scope source, 3) fall back to system scope
|
|
212
|
-
|
|
259
|
+
targetSource = targetSource || existingScope?.source;
|
|
213
260
|
let effectiveScope = scopeName;
|
|
214
261
|
if (!targetSource && scopeName === 'user') {
|
|
215
|
-
// Fall back to system scope if user scope has no source
|
|
216
262
|
const systemScope = meta.scopes['system'];
|
|
217
263
|
if (systemScope?.source) {
|
|
218
264
|
targetSource = systemScope.source;
|
|
@@ -221,13 +267,19 @@ program
|
|
|
221
267
|
}
|
|
222
268
|
}
|
|
223
269
|
if (!targetSource) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
+
}
|
|
229
282
|
}
|
|
230
|
-
// Prevent modification of readonly scopes (but allow syncing from them)
|
|
231
283
|
const targetScopeConfig = meta.scopes[effectiveScope];
|
|
232
284
|
const isReadonly = targetScopeConfig?.readonly || effectiveScope === 'system';
|
|
233
285
|
const isUserScope = effectiveScope === 'user';
|
|
@@ -241,64 +293,18 @@ program
|
|
|
241
293
|
console.log(chalk.yellow(`No ${MANIFEST_FILENAME} found in repository`));
|
|
242
294
|
}
|
|
243
295
|
// Discover all assets
|
|
244
|
-
const
|
|
245
|
-
const
|
|
296
|
+
const allCommands = discoverCommands(localPath);
|
|
297
|
+
const allSkills = discoverSkillsFromRepo(localPath);
|
|
246
298
|
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
|
-
}
|
|
299
|
+
// Determine which agents to sync
|
|
297
300
|
let selectedAgents;
|
|
298
|
-
|
|
299
|
-
|
|
301
|
+
if (agentFilter) {
|
|
302
|
+
// Single agent filter
|
|
303
|
+
selectedAgents = [agentFilter];
|
|
304
|
+
console.log(chalk.gray(`\nFiltering for ${AGENTS[agentFilter].name} only\n`));
|
|
305
|
+
}
|
|
306
|
+
else if (options.yes || options.force) {
|
|
300
307
|
selectedAgents = (manifest?.defaults?.agents || ['claude', 'codex', 'gemini']);
|
|
301
|
-
method = manifest?.defaults?.method || 'symlink';
|
|
302
308
|
}
|
|
303
309
|
else {
|
|
304
310
|
const installedAgents = ALL_AGENT_IDS.filter((id) => isCliInstalled(id) || id === 'cursor');
|
|
@@ -310,225 +316,323 @@ program
|
|
|
310
316
|
checked: (manifest?.defaults?.agents || ['claude', 'codex', 'gemini']).includes(id),
|
|
311
317
|
})),
|
|
312
318
|
});
|
|
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
319
|
}
|
|
322
|
-
//
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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) => {
|
|
329
332
|
const sourcePath = resolveCommandSource(localPath, command.name, agentId);
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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 });
|
|
333
341
|
}
|
|
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
|
-
}
|
|
342
|
+
if (newAgents.length > 0) {
|
|
343
|
+
newItems.push({ type: 'command', name: command.name, agents: newAgents, isNew: true });
|
|
342
344
|
}
|
|
343
345
|
}
|
|
344
|
-
//
|
|
345
|
-
const
|
|
346
|
-
for (const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
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 });
|
|
351
353
|
}
|
|
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
|
-
}
|
|
354
|
+
if (newAgents.length > 0) {
|
|
355
|
+
newItems.push({ type: 'skill', name: skill.name, agents: newAgents, isNew: true });
|
|
361
356
|
}
|
|
362
357
|
}
|
|
363
|
-
//
|
|
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
|
|
364
378
|
if (!options.skipMcp && manifest?.mcp) {
|
|
365
379
|
for (const [name, config] of Object.entries(manifest.mcp)) {
|
|
366
380
|
if (config.transport === 'http' || !config.command)
|
|
367
381
|
continue;
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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 });
|
|
374
392
|
}
|
|
375
393
|
}
|
|
376
394
|
}
|
|
377
|
-
//
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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))}`);
|
|
386
407
|
}
|
|
387
408
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
mcp: [],
|
|
394
|
-
};
|
|
395
|
-
for (const c of uniqueConflicts.values()) {
|
|
396
|
-
byType[c.type].push(c.name);
|
|
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))}`);
|
|
413
|
+
}
|
|
397
414
|
}
|
|
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
|
+
}
|
|
426
|
+
}
|
|
427
|
+
console.log();
|
|
428
|
+
}
|
|
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);
|
|
398
434
|
if (byType.command.length > 0) {
|
|
399
|
-
console.log(`
|
|
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
|
+
}
|
|
400
439
|
}
|
|
401
440
|
if (byType.skill.length > 0) {
|
|
402
|
-
console.log(`
|
|
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
|
+
}
|
|
403
445
|
}
|
|
404
446
|
if (byType.hook.length > 0) {
|
|
405
|
-
console.log(`
|
|
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
|
+
}
|
|
406
451
|
}
|
|
407
452
|
if (byType.mcp.length > 0) {
|
|
408
|
-
console.log(`
|
|
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
|
+
}
|
|
409
457
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
458
|
+
console.log();
|
|
459
|
+
}
|
|
460
|
+
if (newItems.length === 0 && existingItems.length === 0) {
|
|
461
|
+
console.log(chalk.gray(' Nothing to sync.\n'));
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
if (options.dryRun) {
|
|
465
|
+
console.log(chalk.yellow('Dry run - no changes made'));
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
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
|
|
480
|
+
let method;
|
|
481
|
+
if (options.yes || options.force) {
|
|
482
|
+
method = manifest?.defaults?.method || 'symlink';
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
method = await select({
|
|
486
|
+
message: 'Installation method:',
|
|
487
|
+
choices: [
|
|
488
|
+
{ name: 'Symlink (updates automatically)', value: 'symlink' },
|
|
489
|
+
{ name: 'Copy (independent files)', value: 'copy' },
|
|
490
|
+
],
|
|
491
|
+
default: manifest?.defaults?.method || 'symlink',
|
|
492
|
+
});
|
|
493
|
+
}
|
|
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})`,
|
|
413
503
|
choices: [
|
|
414
|
-
{ name: 'Overwrite
|
|
415
|
-
{ name: 'Skip
|
|
416
|
-
{ name: 'Cancel', value: 'cancel' },
|
|
504
|
+
{ name: 'Overwrite', value: 'overwrite' },
|
|
505
|
+
{ name: 'Skip', value: 'skip' },
|
|
506
|
+
{ name: 'Cancel all', value: 'cancel' },
|
|
417
507
|
],
|
|
418
508
|
});
|
|
419
|
-
if (
|
|
509
|
+
if (decision === 'cancel') {
|
|
420
510
|
console.log(chalk.yellow('\nSync cancelled'));
|
|
421
511
|
return;
|
|
422
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');
|
|
423
520
|
}
|
|
424
521
|
}
|
|
425
|
-
|
|
426
|
-
|
|
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 };
|
|
427
532
|
// Install commands
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
skipped++;
|
|
441
|
-
continue;
|
|
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);
|
|
542
|
+
if (sourcePath) {
|
|
543
|
+
installCommand(sourcePath, agentId, item.name, method);
|
|
544
|
+
installed.commands++;
|
|
442
545
|
}
|
|
443
|
-
installCommand(sourcePath, agentId, command.name, method);
|
|
444
|
-
installed++;
|
|
445
546
|
}
|
|
446
547
|
}
|
|
447
|
-
if (skipped > 0) {
|
|
448
|
-
|
|
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`);
|
|
449
553
|
}
|
|
450
554
|
else {
|
|
451
|
-
|
|
555
|
+
cmdSpinner.info('No commands to install');
|
|
452
556
|
}
|
|
453
557
|
// Install skills
|
|
454
|
-
|
|
558
|
+
const skillItems = [...newItems, ...existingItems].filter((i) => i.type === 'skill');
|
|
559
|
+
if (skillItems.length > 0) {
|
|
455
560
|
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++;
|
|
561
|
+
for (const item of skillItems) {
|
|
562
|
+
const decision = item.isNew ? 'overwrite' : decisions.get(`skill:${item.name}`);
|
|
563
|
+
if (decision === 'skip') {
|
|
564
|
+
skipped.skills++;
|
|
464
565
|
continue;
|
|
465
566
|
}
|
|
466
|
-
|
|
467
|
-
|
|
567
|
+
const skill = allSkills.find((s) => s.name === item.name);
|
|
568
|
+
if (skill) {
|
|
569
|
+
const result = installSkill(skill.path, skill.name, item.agents);
|
|
468
570
|
if (result.success)
|
|
469
|
-
|
|
571
|
+
installed.skills++;
|
|
470
572
|
}
|
|
471
573
|
}
|
|
472
|
-
if (
|
|
473
|
-
skillSpinner.succeed(`Installed ${
|
|
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`);
|
|
474
579
|
}
|
|
475
580
|
else {
|
|
476
|
-
skillSpinner.
|
|
581
|
+
skillSpinner.info('No skills to install');
|
|
477
582
|
}
|
|
478
583
|
}
|
|
479
584
|
// Install hooks
|
|
480
|
-
|
|
585
|
+
const hookItems = [...newItems, ...existingItems].filter((i) => i.type === 'hook');
|
|
586
|
+
if (hookItems.length > 0 && hookAgents.length > 0) {
|
|
481
587
|
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
588
|
const result = await installHooks(localPath, hookAgents, { scope: 'user' });
|
|
485
589
|
hookSpinner.succeed(`Installed ${result.installed.length} hooks`);
|
|
486
590
|
}
|
|
487
591
|
// Register MCP servers
|
|
488
|
-
|
|
592
|
+
const mcpItems = [...newItems, ...existingItems].filter((i) => i.type === 'mcp');
|
|
593
|
+
if (mcpItems.length > 0 && manifest?.mcp) {
|
|
489
594
|
const mcpSpinner = ora('Registering MCP servers...').start();
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
if (config.transport === 'http' || !config.command)
|
|
595
|
+
for (const item of mcpItems) {
|
|
596
|
+
const decision = item.isNew ? 'overwrite' : decisions.get(`mcp:${item.name}`);
|
|
597
|
+
if (decision === 'skip') {
|
|
598
|
+
skipped.mcps++;
|
|
495
599
|
continue;
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
continue;
|
|
504
|
-
}
|
|
505
|
-
// If overwriting, unregister first then re-register
|
|
506
|
-
unregisterMcp(agentId, name);
|
|
600
|
+
}
|
|
601
|
+
const config = manifest.mcp[item.name];
|
|
602
|
+
if (!config || !config.command)
|
|
603
|
+
continue;
|
|
604
|
+
for (const agentId of item.agents) {
|
|
605
|
+
if (!item.isNew) {
|
|
606
|
+
unregisterMcp(agentId, item.name);
|
|
507
607
|
}
|
|
508
|
-
const result = registerMcp(agentId, name, config.command, config.scope);
|
|
608
|
+
const result = registerMcp(agentId, item.name, config.command, config.scope);
|
|
509
609
|
if (result.success)
|
|
510
|
-
|
|
610
|
+
installed.mcps++;
|
|
511
611
|
}
|
|
512
612
|
}
|
|
513
|
-
if (
|
|
514
|
-
mcpSpinner.succeed(`Registered ${
|
|
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`);
|
|
515
618
|
}
|
|
516
619
|
else {
|
|
517
|
-
mcpSpinner.
|
|
620
|
+
mcpSpinner.info('No MCP servers to register');
|
|
518
621
|
}
|
|
519
622
|
}
|
|
520
|
-
// Sync CLI versions (user scope only
|
|
623
|
+
// Sync CLI versions (user scope only)
|
|
521
624
|
if (isUserScope && !options.skipClis && manifest?.clis) {
|
|
522
625
|
const cliSpinner = ora('Checking CLI versions...').start();
|
|
523
626
|
const cliUpdates = [];
|
|
524
627
|
for (const [agentIdStr, cliConfig] of Object.entries(manifest.clis)) {
|
|
525
628
|
const agentId = agentIdStr;
|
|
629
|
+
if (agentFilter && agentId !== agentFilter)
|
|
630
|
+
continue;
|
|
526
631
|
const agent = AGENTS[agentId];
|
|
527
632
|
if (!agent || !cliConfig.package)
|
|
528
633
|
continue;
|
|
529
634
|
const currentVersion = getCliVersion(agentId);
|
|
530
635
|
const targetVersion = cliConfig.version;
|
|
531
|
-
// Skip if same version or if target is "latest" and CLI is installed
|
|
532
636
|
if (currentVersion === targetVersion)
|
|
533
637
|
continue;
|
|
534
638
|
if (targetVersion === 'latest' && currentVersion)
|
|
@@ -546,7 +650,7 @@ program
|
|
|
546
650
|
cliSpinner.succeed('CLI versions match');
|
|
547
651
|
}
|
|
548
652
|
}
|
|
549
|
-
// Update scope config
|
|
653
|
+
// Update scope config
|
|
550
654
|
if (!isReadonly) {
|
|
551
655
|
const priority = getScopePriority(effectiveScope);
|
|
552
656
|
setScope(effectiveScope, {
|
|
@@ -561,6 +665,10 @@ program
|
|
|
561
665
|
console.log(chalk.green(`\nSync complete from ${effectiveScope} scope`));
|
|
562
666
|
}
|
|
563
667
|
catch (err) {
|
|
668
|
+
if (isPromptCancelled(err)) {
|
|
669
|
+
console.log(chalk.yellow('\nCancelled'));
|
|
670
|
+
process.exit(0);
|
|
671
|
+
}
|
|
564
672
|
spinner.fail('Failed to sync');
|
|
565
673
|
console.error(chalk.red(err.message));
|
|
566
674
|
process.exit(1);
|
|
@@ -1968,7 +2076,7 @@ program
|
|
|
1968
2076
|
const spinner = ora('Checking for updates...').start();
|
|
1969
2077
|
try {
|
|
1970
2078
|
// Get current version from package.json
|
|
1971
|
-
const currentVersion = program.version();
|
|
2079
|
+
const currentVersion = program.version() || '0.0.0';
|
|
1972
2080
|
// Fetch latest version from npm
|
|
1973
2081
|
const response = await fetch('https://registry.npmjs.org/@swarmify/agents-cli/latest');
|
|
1974
2082
|
if (!response.ok) {
|
|
@@ -1980,7 +2088,7 @@ program
|
|
|
1980
2088
|
spinner.succeed(`Already on latest version (${currentVersion})`);
|
|
1981
2089
|
return;
|
|
1982
2090
|
}
|
|
1983
|
-
spinner.text = `Upgrading
|
|
2091
|
+
spinner.text = `Upgrading to ${latestVersion}...`;
|
|
1984
2092
|
// Detect package manager
|
|
1985
2093
|
const { execSync } = await import('child_process');
|
|
1986
2094
|
let cmd;
|
|
@@ -2009,8 +2117,11 @@ program
|
|
|
2009
2117
|
cmd = 'npm install -g @swarmify/agents-cli@latest';
|
|
2010
2118
|
}
|
|
2011
2119
|
}
|
|
2012
|
-
|
|
2120
|
+
// Run silently (suppress npm/bun output)
|
|
2121
|
+
execSync(cmd, { stdio: 'pipe' });
|
|
2013
2122
|
spinner.succeed(`Upgraded to ${latestVersion}`);
|
|
2123
|
+
// Show what's new from changelog
|
|
2124
|
+
await showWhatsNew(currentVersion, latestVersion);
|
|
2014
2125
|
}
|
|
2015
2126
|
catch (err) {
|
|
2016
2127
|
spinner.fail('Upgrade failed');
|
|
@@ -2019,5 +2130,67 @@ program
|
|
|
2019
2130
|
process.exit(1);
|
|
2020
2131
|
}
|
|
2021
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
|
+
}
|
|
2022
2195
|
program.parse();
|
|
2023
2196
|
//# sourceMappingURL=index.js.map
|