@swarmify/agents-cli 1.5.13 → 1.5.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -213
- package/dist/index.js +1194 -173
- package/dist/index.js.map +1 -1
- package/dist/lib/agents.d.ts.map +1 -1
- package/dist/lib/agents.js +19 -2
- package/dist/lib/agents.js.map +1 -1
- package/dist/lib/artifact-actions.d.ts +22 -0
- package/dist/lib/artifact-actions.d.ts.map +1 -0
- package/dist/lib/artifact-actions.js +55 -0
- package/dist/lib/artifact-actions.js.map +1 -0
- package/dist/lib/commands.d.ts.map +1 -1
- package/dist/lib/commands.js +3 -5
- package/dist/lib/commands.js.map +1 -1
- package/dist/lib/daemon.d.ts +22 -0
- package/dist/lib/daemon.d.ts.map +1 -0
- package/dist/lib/daemon.js +303 -0
- package/dist/lib/daemon.js.map +1 -0
- package/dist/lib/drive-server.d.ts +9 -0
- package/dist/lib/drive-server.d.ts.map +1 -0
- package/dist/lib/drive-server.js +217 -0
- package/dist/lib/drive-server.js.map +1 -0
- package/dist/lib/drives.d.ts +34 -0
- package/dist/lib/drives.d.ts.map +1 -0
- package/dist/lib/drives.js +267 -0
- package/dist/lib/drives.js.map +1 -0
- package/dist/lib/instructions.d.ts +31 -0
- package/dist/lib/instructions.d.ts.map +1 -0
- package/dist/lib/instructions.js +161 -0
- package/dist/lib/instructions.js.map +1 -0
- package/dist/lib/jobs.d.ts +53 -0
- package/dist/lib/jobs.d.ts.map +1 -0
- package/dist/lib/jobs.js +242 -0
- package/dist/lib/jobs.js.map +1 -0
- package/dist/lib/runner.d.ts +12 -0
- package/dist/lib/runner.d.ts.map +1 -0
- package/dist/lib/runner.js +266 -0
- package/dist/lib/runner.js.map +1 -0
- package/dist/lib/sandbox.d.ts +10 -0
- package/dist/lib/sandbox.d.ts.map +1 -0
- package/dist/lib/sandbox.js +166 -0
- package/dist/lib/sandbox.js.map +1 -0
- package/dist/lib/scheduler.d.ts +18 -0
- package/dist/lib/scheduler.d.ts.map +1 -0
- package/dist/lib/scheduler.js +64 -0
- package/dist/lib/scheduler.js.map +1 -0
- package/dist/lib/shims.d.ts +32 -0
- package/dist/lib/shims.d.ts.map +1 -0
- package/dist/lib/shims.js +175 -0
- package/dist/lib/shims.js.map +1 -0
- package/dist/lib/state.d.ts +5 -0
- package/dist/lib/state.d.ts.map +1 -1
- package/dist/lib/state.js +35 -0
- package/dist/lib/state.js.map +1 -1
- package/dist/lib/template.d.ts +24 -0
- package/dist/lib/template.d.ts.map +1 -0
- package/dist/lib/template.js +57 -0
- package/dist/lib/template.js.map +1 -0
- package/dist/lib/types.d.ts +11 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/versions.d.ts +79 -0
- package/dist/lib/versions.d.ts.map +1 -0
- package/dist/lib/versions.js +289 -0
- package/dist/lib/versions.js.map +1 -0
- package/package.json +8 -5
- package/scripts/postinstall.js +72 -0
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ function isPromptCancelled(err) {
|
|
|
16
16
|
err.message.includes('force closed') ||
|
|
17
17
|
err.message.includes('User force closed'));
|
|
18
18
|
}
|
|
19
|
-
import { AGENTS, ALL_AGENT_IDS, MCP_CAPABLE_AGENTS, SKILLS_CAPABLE_AGENTS, HOOKS_CAPABLE_AGENTS, getAllCliStates,
|
|
19
|
+
import { AGENTS, ALL_AGENT_IDS, MCP_CAPABLE_AGENTS, SKILLS_CAPABLE_AGENTS, HOOKS_CAPABLE_AGENTS, getAllCliStates, getCliVersion, isMcpRegistered, registerMcp, unregisterMcp, listInstalledMcpsWithScope, promoteMcpToUser, } from './lib/agents.js';
|
|
20
20
|
import { readManifest, writeManifest, createDefaultManifest, MANIFEST_FILENAME, } from './lib/manifest.js';
|
|
21
21
|
import { readState, ensureAgentsDir, getRepoLocalPath, getScope, setScope, removeScope, getScopesByPriority, getScopePriority, } from './lib/state.js';
|
|
22
22
|
import { SCOPE_PRIORITIES, DEFAULT_SYSTEM_REPO } from './lib/types.js';
|
|
@@ -24,8 +24,11 @@ import { cloneRepo, parseSource } from './lib/git.js';
|
|
|
24
24
|
import { discoverCommands, resolveCommandSource, installCommand, uninstallCommand, listInstalledCommandsWithScope, promoteCommandToUser, commandExists, commandContentMatches, } from './lib/commands.js';
|
|
25
25
|
import { discoverHooksFromRepo, installHooks, listInstalledHooksWithScope, promoteHookToUser, removeHook, hookExists, hookContentMatches, getSourceHookEntry, } from './lib/hooks.js';
|
|
26
26
|
import { discoverSkillsFromRepo, installSkill, uninstallSkill, listInstalledSkillsWithScope, promoteSkillToUser, getSkillInfo, getSkillRules, skillExists, skillContentMatches, } from './lib/skills.js';
|
|
27
|
+
import { discoverInstructionsFromRepo, resolveInstructionsSource, installInstructions, uninstallInstructions, listInstalledInstructionsWithScope, promoteInstructionsToUser, instructionsExists, instructionsContentMatches, getInstructionsContent, } from './lib/instructions.js';
|
|
27
28
|
import { DEFAULT_REGISTRIES } from './lib/types.js';
|
|
28
29
|
import { search as searchRegistries, getRegistries, setRegistry, removeRegistry, resolvePackage, } from './lib/registry.js';
|
|
30
|
+
import { parseAgentSpec, installVersion, removeVersion, removeAllVersions, listInstalledVersions, getGlobalDefault, setGlobalDefault, isVersionInstalled, } from './lib/versions.js';
|
|
31
|
+
import { createShim, removeShim, shimExists, isShimsInPath, getPathSetupInstructions, getShimsDir, } from './lib/shims.js';
|
|
29
32
|
const program = new Command();
|
|
30
33
|
/**
|
|
31
34
|
* Ensure at least one scope is configured.
|
|
@@ -179,6 +182,10 @@ program
|
|
|
179
182
|
agent: AGENTS[agentId],
|
|
180
183
|
mcps: listInstalledMcpsWithScope(agentId, cwd),
|
|
181
184
|
}));
|
|
185
|
+
const instructionsData = agentsToShow.map((agentId) => ({
|
|
186
|
+
agent: AGENTS[agentId],
|
|
187
|
+
instructions: listInstalledInstructionsWithScope(agentId, cwd),
|
|
188
|
+
}));
|
|
182
189
|
const scopes = filterAgentId ? [] : getScopesByPriority();
|
|
183
190
|
spinner.stop();
|
|
184
191
|
// Helper to format MCP with version
|
|
@@ -249,6 +256,23 @@ program
|
|
|
249
256
|
}
|
|
250
257
|
}
|
|
251
258
|
}
|
|
259
|
+
console.log(chalk.bold('\nInstalled Instructions\n'));
|
|
260
|
+
for (const { agent, instructions } of instructionsData) {
|
|
261
|
+
const userInstr = instructions.find((i) => i.scope === 'user' && i.exists);
|
|
262
|
+
const projectInstr = instructions.find((i) => i.scope === 'project' && i.exists);
|
|
263
|
+
if (!userInstr && !projectInstr) {
|
|
264
|
+
console.log(` ${chalk.bold(agent.name)}: ${chalk.gray('none')}`);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
console.log(` ${chalk.bold(agent.name)}:`);
|
|
268
|
+
if (userInstr) {
|
|
269
|
+
console.log(` ${chalk.gray('User:')} ${chalk.cyan(agent.instructionsFile)}`);
|
|
270
|
+
}
|
|
271
|
+
if (projectInstr) {
|
|
272
|
+
console.log(` ${chalk.gray('Project:')} ${chalk.yellow(agent.instructionsFile)}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
252
276
|
// Only show scopes when not filtering by agent
|
|
253
277
|
if (!filterAgentId) {
|
|
254
278
|
if (scopes.length > 0) {
|
|
@@ -364,6 +388,9 @@ program
|
|
|
364
388
|
const allCommands = discoverCommands(localPath);
|
|
365
389
|
const allSkills = discoverSkillsFromRepo(localPath);
|
|
366
390
|
const discoveredHooks = discoverHooksFromRepo(localPath);
|
|
391
|
+
const allInstructions = discoverInstructionsFromRepo(localPath);
|
|
392
|
+
const allDiscoveredJobs = discoverJobsFromRepo(localPath);
|
|
393
|
+
const allDiscoveredDrives = discoverDrivesFromRepo(localPath);
|
|
367
394
|
// Determine which agents to sync
|
|
368
395
|
const cliStates = await getAllCliStates();
|
|
369
396
|
let selectedAgents;
|
|
@@ -506,12 +533,51 @@ program
|
|
|
506
533
|
}
|
|
507
534
|
}
|
|
508
535
|
}
|
|
536
|
+
// Process instructions
|
|
537
|
+
for (const instr of allInstructions) {
|
|
538
|
+
if (!selectedAgents.includes(instr.agentId))
|
|
539
|
+
continue;
|
|
540
|
+
const hasExisting = instructionsExists(instr.agentId, 'user');
|
|
541
|
+
if (!hasExisting) {
|
|
542
|
+
newItems.push({ type: 'instructions', name: AGENTS[instr.agentId].instructionsFile, agents: [instr.agentId], isNew: true });
|
|
543
|
+
}
|
|
544
|
+
else if (instructionsContentMatches(instr.agentId, instr.sourcePath, 'user')) {
|
|
545
|
+
upToDateItems.push({ type: 'instructions', name: AGENTS[instr.agentId].instructionsFile, agents: [instr.agentId], isNew: false });
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
existingItems.push({ type: 'instructions', name: AGENTS[instr.agentId].instructionsFile, agents: [instr.agentId], isNew: false });
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
// Process jobs
|
|
552
|
+
for (const discoveredJob of allDiscoveredJobs) {
|
|
553
|
+
if (!jobExists(discoveredJob.name)) {
|
|
554
|
+
newItems.push({ type: 'job', name: discoveredJob.name, agents: [], isNew: true });
|
|
555
|
+
}
|
|
556
|
+
else if (jobContentMatches(discoveredJob.name, discoveredJob.path)) {
|
|
557
|
+
upToDateItems.push({ type: 'job', name: discoveredJob.name, agents: [], isNew: false });
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
existingItems.push({ type: 'job', name: discoveredJob.name, agents: [], isNew: false });
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
// Process drives
|
|
564
|
+
for (const discoveredDrive of allDiscoveredDrives) {
|
|
565
|
+
if (!driveExists(discoveredDrive.name)) {
|
|
566
|
+
newItems.push({ type: 'drive', name: discoveredDrive.name, agents: [], isNew: true });
|
|
567
|
+
}
|
|
568
|
+
else if (driveContentMatches(discoveredDrive.name, discoveredDrive.path)) {
|
|
569
|
+
upToDateItems.push({ type: 'drive', name: discoveredDrive.name, agents: [], isNew: false });
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
existingItems.push({ type: 'drive', name: discoveredDrive.name, agents: [], isNew: false });
|
|
573
|
+
}
|
|
574
|
+
}
|
|
509
575
|
// Display overview
|
|
510
576
|
console.log(chalk.bold('\nOverview\n'));
|
|
511
577
|
const formatAgentList = (agents) => agents.map((id) => AGENTS[id].name).join(', ');
|
|
512
578
|
if (newItems.length > 0) {
|
|
513
579
|
console.log(chalk.green(' NEW (will install):\n'));
|
|
514
|
-
const byType = { command: [], skill: [], hook: [], mcp: [] };
|
|
580
|
+
const byType = { command: [], skill: [], hook: [], mcp: [], instructions: [], job: [], drive: [] };
|
|
515
581
|
for (const item of newItems)
|
|
516
582
|
byType[item.type].push(item);
|
|
517
583
|
if (byType.command.length > 0) {
|
|
@@ -538,11 +604,29 @@ program
|
|
|
538
604
|
console.log(` ${chalk.cyan(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
|
|
539
605
|
}
|
|
540
606
|
}
|
|
607
|
+
if (byType.instructions.length > 0) {
|
|
608
|
+
console.log(` Instructions:`);
|
|
609
|
+
for (const item of byType.instructions) {
|
|
610
|
+
console.log(` ${chalk.cyan(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
if (byType.job.length > 0) {
|
|
614
|
+
console.log(` Jobs:`);
|
|
615
|
+
for (const item of byType.job) {
|
|
616
|
+
console.log(` ${chalk.cyan(item.name)}`);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
if (byType.drive.length > 0) {
|
|
620
|
+
console.log(` Drives:`);
|
|
621
|
+
for (const item of byType.drive) {
|
|
622
|
+
console.log(` ${chalk.cyan(item.name)}`);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
541
625
|
console.log();
|
|
542
626
|
}
|
|
543
627
|
if (upToDateItems.length > 0) {
|
|
544
628
|
console.log(chalk.gray(' UP TO DATE (no changes):\n'));
|
|
545
|
-
const byType = { command: [], skill: [], hook: [], mcp: [] };
|
|
629
|
+
const byType = { command: [], skill: [], hook: [], mcp: [], instructions: [], job: [], drive: [] };
|
|
546
630
|
for (const item of upToDateItems)
|
|
547
631
|
byType[item.type].push(item);
|
|
548
632
|
if (byType.command.length > 0) {
|
|
@@ -563,11 +647,29 @@ program
|
|
|
563
647
|
console.log(` ${chalk.dim(item.name.padEnd(20))} ${chalk.dim(formatAgentList(item.agents))}`);
|
|
564
648
|
}
|
|
565
649
|
}
|
|
650
|
+
if (byType.instructions.length > 0) {
|
|
651
|
+
console.log(` Instructions:`);
|
|
652
|
+
for (const item of byType.instructions) {
|
|
653
|
+
console.log(` ${chalk.dim(item.name.padEnd(20))} ${chalk.dim(formatAgentList(item.agents))}`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
if (byType.job.length > 0) {
|
|
657
|
+
console.log(` Jobs:`);
|
|
658
|
+
for (const item of byType.job) {
|
|
659
|
+
console.log(` ${chalk.dim(item.name)}`);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
if (byType.drive.length > 0) {
|
|
663
|
+
console.log(` Drives:`);
|
|
664
|
+
for (const item of byType.drive) {
|
|
665
|
+
console.log(` ${chalk.dim(item.name)}`);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
566
668
|
console.log();
|
|
567
669
|
}
|
|
568
670
|
if (existingItems.length > 0) {
|
|
569
671
|
console.log(chalk.yellow(' EXISTING (conflicts):\n'));
|
|
570
|
-
const byType = { command: [], skill: [], hook: [], mcp: [] };
|
|
672
|
+
const byType = { command: [], skill: [], hook: [], mcp: [], instructions: [], job: [], drive: [] };
|
|
571
673
|
for (const item of existingItems)
|
|
572
674
|
byType[item.type].push(item);
|
|
573
675
|
if (byType.command.length > 0) {
|
|
@@ -594,6 +696,24 @@ program
|
|
|
594
696
|
console.log(` ${chalk.yellow(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
|
|
595
697
|
}
|
|
596
698
|
}
|
|
699
|
+
if (byType.instructions.length > 0) {
|
|
700
|
+
console.log(` Instructions:`);
|
|
701
|
+
for (const item of byType.instructions) {
|
|
702
|
+
console.log(` ${chalk.yellow(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
if (byType.job.length > 0) {
|
|
706
|
+
console.log(` Jobs:`);
|
|
707
|
+
for (const item of byType.job) {
|
|
708
|
+
console.log(` ${chalk.yellow(item.name)}`);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (byType.drive.length > 0) {
|
|
712
|
+
console.log(` Drives:`);
|
|
713
|
+
for (const item of byType.drive) {
|
|
714
|
+
console.log(` ${chalk.yellow(item.name)}`);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
597
717
|
console.log();
|
|
598
718
|
}
|
|
599
719
|
if (newItems.length === 0 && existingItems.length === 0) {
|
|
@@ -666,8 +786,8 @@ program
|
|
|
666
786
|
}
|
|
667
787
|
// Install new items (no conflicts)
|
|
668
788
|
console.log();
|
|
669
|
-
let installed = { commands: 0, skills: 0, hooks: 0, mcps: 0 };
|
|
670
|
-
let skipped = { commands: 0, skills: 0, hooks: 0, mcps: 0 };
|
|
789
|
+
let installed = { commands: 0, skills: 0, hooks: 0, mcps: 0, instructions: 0, jobs: 0, drives: 0 };
|
|
790
|
+
let skipped = { commands: 0, skills: 0, hooks: 0, mcps: 0, instructions: 0, jobs: 0, drives: 0 };
|
|
671
791
|
// Install commands
|
|
672
792
|
const cmdSpinner = ora('Installing commands...').start();
|
|
673
793
|
for (const item of [...newItems, ...existingItems].filter((i) => i.type === 'command')) {
|
|
@@ -679,8 +799,13 @@ program
|
|
|
679
799
|
for (const agentId of item.agents) {
|
|
680
800
|
const sourcePath = resolveCommandSource(localPath, item.name, agentId);
|
|
681
801
|
if (sourcePath) {
|
|
682
|
-
installCommand(sourcePath, agentId, item.name, method);
|
|
683
|
-
|
|
802
|
+
const result = installCommand(sourcePath, agentId, item.name, method);
|
|
803
|
+
if (result.error) {
|
|
804
|
+
console.log(chalk.yellow(`\n Warning: ${item.name} (${AGENTS[agentId].name}): ${result.error}`));
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
installed.commands++;
|
|
808
|
+
}
|
|
684
809
|
}
|
|
685
810
|
}
|
|
686
811
|
}
|
|
@@ -759,6 +884,104 @@ program
|
|
|
759
884
|
mcpSpinner.info('No MCP servers to register');
|
|
760
885
|
}
|
|
761
886
|
}
|
|
887
|
+
// Install instructions
|
|
888
|
+
const instructionItems = [...newItems, ...existingItems].filter((i) => i.type === 'instructions');
|
|
889
|
+
if (instructionItems.length > 0) {
|
|
890
|
+
const instrSpinner = ora('Installing instructions...').start();
|
|
891
|
+
for (const item of instructionItems) {
|
|
892
|
+
const decision = item.isNew ? 'overwrite' : decisions.get(`instructions:${item.name}`);
|
|
893
|
+
if (decision === 'skip') {
|
|
894
|
+
skipped.instructions++;
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
for (const agentId of item.agents) {
|
|
898
|
+
const sourcePath = resolveInstructionsSource(localPath, agentId);
|
|
899
|
+
if (sourcePath) {
|
|
900
|
+
const result = installInstructions(sourcePath, agentId, method);
|
|
901
|
+
if (result.error) {
|
|
902
|
+
console.log(chalk.yellow(`\n Warning: ${item.name} (${AGENTS[agentId].name}): ${result.error}`));
|
|
903
|
+
}
|
|
904
|
+
else {
|
|
905
|
+
installed.instructions++;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
if (skipped.instructions > 0) {
|
|
911
|
+
instrSpinner.succeed(`Installed ${installed.instructions} instructions (skipped ${skipped.instructions})`);
|
|
912
|
+
}
|
|
913
|
+
else if (installed.instructions > 0) {
|
|
914
|
+
instrSpinner.succeed(`Installed ${installed.instructions} instructions`);
|
|
915
|
+
}
|
|
916
|
+
else {
|
|
917
|
+
instrSpinner.info('No instructions to install');
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
// Install jobs
|
|
921
|
+
const jobItems = [...newItems, ...existingItems].filter((i) => i.type === 'job');
|
|
922
|
+
if (jobItems.length > 0) {
|
|
923
|
+
const jobSpinner = ora('Installing jobs...').start();
|
|
924
|
+
for (const item of jobItems) {
|
|
925
|
+
const decision = item.isNew ? 'overwrite' : decisions.get(`job:${item.name}`);
|
|
926
|
+
if (decision === 'skip') {
|
|
927
|
+
skipped.jobs++;
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
const discovered = allDiscoveredJobs.find((j) => j.name === item.name);
|
|
931
|
+
if (discovered) {
|
|
932
|
+
const result = installJobFromSource(discovered.path, discovered.name);
|
|
933
|
+
if (result.success) {
|
|
934
|
+
installed.jobs++;
|
|
935
|
+
}
|
|
936
|
+
else {
|
|
937
|
+
console.log(chalk.yellow(`\n Warning: job ${item.name}: ${result.error}`));
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
if (skipped.jobs > 0) {
|
|
942
|
+
jobSpinner.succeed(`Installed ${installed.jobs} jobs (skipped ${skipped.jobs})`);
|
|
943
|
+
}
|
|
944
|
+
else if (installed.jobs > 0) {
|
|
945
|
+
jobSpinner.succeed(`Installed ${installed.jobs} jobs`);
|
|
946
|
+
}
|
|
947
|
+
else {
|
|
948
|
+
jobSpinner.info('No jobs to install');
|
|
949
|
+
}
|
|
950
|
+
if (installed.jobs > 0 && isDaemonRunning()) {
|
|
951
|
+
signalDaemonReload();
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
// Install drives
|
|
955
|
+
const driveItems = [...newItems, ...existingItems].filter((i) => i.type === 'drive');
|
|
956
|
+
if (driveItems.length > 0) {
|
|
957
|
+
const driveSpinner = ora('Installing drives...').start();
|
|
958
|
+
for (const item of driveItems) {
|
|
959
|
+
const decision = item.isNew ? 'overwrite' : decisions.get(`drive:${item.name}`);
|
|
960
|
+
if (decision === 'skip') {
|
|
961
|
+
skipped.drives++;
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
const discovered = allDiscoveredDrives.find((d) => d.name === item.name);
|
|
965
|
+
if (discovered) {
|
|
966
|
+
const result = installDriveFromSource(discovered.path, discovered.name);
|
|
967
|
+
if (result.success) {
|
|
968
|
+
installed.drives++;
|
|
969
|
+
}
|
|
970
|
+
else {
|
|
971
|
+
console.log(chalk.yellow(`\n Warning: drive ${item.name}: ${result.error}`));
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
if (skipped.drives > 0) {
|
|
976
|
+
driveSpinner.succeed(`Installed ${installed.drives} drives (skipped ${skipped.drives})`);
|
|
977
|
+
}
|
|
978
|
+
else if (installed.drives > 0) {
|
|
979
|
+
driveSpinner.succeed(`Installed ${installed.drives} drives`);
|
|
980
|
+
}
|
|
981
|
+
else {
|
|
982
|
+
driveSpinner.info('No drives to install');
|
|
983
|
+
}
|
|
984
|
+
}
|
|
762
985
|
// Sync CLI versions (user scope only)
|
|
763
986
|
if (isUserScope && !options.skipClis && manifest?.clis) {
|
|
764
987
|
const cliSpinner = ora('Checking CLI versions...').start();
|
|
@@ -852,6 +1075,44 @@ program
|
|
|
852
1075
|
exported++;
|
|
853
1076
|
}
|
|
854
1077
|
}
|
|
1078
|
+
// Export MCP servers from installed agents
|
|
1079
|
+
console.log();
|
|
1080
|
+
let mcpExported = 0;
|
|
1081
|
+
const mcpByName = new Map();
|
|
1082
|
+
for (const agentId of MCP_CAPABLE_AGENTS) {
|
|
1083
|
+
if (!cliStates[agentId]?.installed)
|
|
1084
|
+
continue;
|
|
1085
|
+
const mcps = listInstalledMcpsWithScope(agentId);
|
|
1086
|
+
for (const mcp of mcps) {
|
|
1087
|
+
if (mcp.scope !== 'user')
|
|
1088
|
+
continue; // Only export user-scoped MCPs
|
|
1089
|
+
const existing = mcpByName.get(mcp.name);
|
|
1090
|
+
if (existing) {
|
|
1091
|
+
if (!existing.agents.includes(agentId)) {
|
|
1092
|
+
existing.agents.push(agentId);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
else {
|
|
1096
|
+
mcpByName.set(mcp.name, {
|
|
1097
|
+
command: mcp.command || '',
|
|
1098
|
+
agents: [agentId],
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
if (mcpByName.size > 0) {
|
|
1104
|
+
manifest.mcp = manifest.mcp || {};
|
|
1105
|
+
for (const [name, config] of mcpByName) {
|
|
1106
|
+
manifest.mcp[name] = {
|
|
1107
|
+
command: config.command,
|
|
1108
|
+
transport: 'stdio',
|
|
1109
|
+
agents: config.agents,
|
|
1110
|
+
scope: 'user',
|
|
1111
|
+
};
|
|
1112
|
+
console.log(` ${chalk.green('+')} MCP: ${name} (${config.agents.map(id => AGENTS[id].name).join(', ')})`);
|
|
1113
|
+
mcpExported++;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
855
1116
|
if (!options.exportOnly) {
|
|
856
1117
|
writeManifest(localPath, manifest);
|
|
857
1118
|
console.log(chalk.bold(`\nUpdated ${MANIFEST_FILENAME}`));
|
|
@@ -952,8 +1213,13 @@ commandsCmd
|
|
|
952
1213
|
continue;
|
|
953
1214
|
const sourcePath = resolveCommandSource(localPath, command.name, agentId);
|
|
954
1215
|
if (sourcePath) {
|
|
955
|
-
installCommand(sourcePath, agentId, command.name, 'symlink');
|
|
956
|
-
|
|
1216
|
+
const result = installCommand(sourcePath, agentId, command.name, 'symlink');
|
|
1217
|
+
if (result.error) {
|
|
1218
|
+
console.log(` ${chalk.yellow('!')} ${AGENTS[agentId].name}: ${result.error}`);
|
|
1219
|
+
}
|
|
1220
|
+
else {
|
|
1221
|
+
console.log(` ${chalk.green('+')} ${AGENTS[agentId].name}`);
|
|
1222
|
+
}
|
|
957
1223
|
}
|
|
958
1224
|
}
|
|
959
1225
|
}
|
|
@@ -1427,6 +1693,152 @@ skillsCmd
|
|
|
1427
1693
|
}
|
|
1428
1694
|
});
|
|
1429
1695
|
// =============================================================================
|
|
1696
|
+
// INSTRUCTIONS COMMANDS
|
|
1697
|
+
// =============================================================================
|
|
1698
|
+
const instructionsCmd = program
|
|
1699
|
+
.command('instructions')
|
|
1700
|
+
.alias('instr')
|
|
1701
|
+
.description('Manage agent instructions (CLAUDE.md, GEMINI.md, etc.)');
|
|
1702
|
+
instructionsCmd
|
|
1703
|
+
.command('list')
|
|
1704
|
+
.description('List installed instructions files')
|
|
1705
|
+
.option('-a, --agent <agent>', 'Filter by agent')
|
|
1706
|
+
.action(async (options) => {
|
|
1707
|
+
const cwd = process.cwd();
|
|
1708
|
+
const agents = options.agent
|
|
1709
|
+
? [resolveAgentName(options.agent)].filter(Boolean)
|
|
1710
|
+
: ALL_AGENT_IDS;
|
|
1711
|
+
console.log(chalk.bold('Installed Instructions\n'));
|
|
1712
|
+
for (const agentId of agents) {
|
|
1713
|
+
const agent = AGENTS[agentId];
|
|
1714
|
+
const installed = listInstalledInstructionsWithScope(agentId, cwd);
|
|
1715
|
+
const userInstr = installed.find((i) => i.scope === 'user');
|
|
1716
|
+
const projectInstr = installed.find((i) => i.scope === 'project');
|
|
1717
|
+
const userStatus = userInstr?.exists ? chalk.green(agent.instructionsFile) : chalk.gray('none');
|
|
1718
|
+
const projectStatus = projectInstr?.exists ? chalk.yellow(agent.instructionsFile) : chalk.gray('none');
|
|
1719
|
+
console.log(` ${chalk.bold(agent.name)}:`);
|
|
1720
|
+
console.log(` ${chalk.gray('User:')} ${userStatus}`);
|
|
1721
|
+
console.log(` ${chalk.gray('Project:')} ${projectStatus}`);
|
|
1722
|
+
console.log();
|
|
1723
|
+
}
|
|
1724
|
+
});
|
|
1725
|
+
instructionsCmd
|
|
1726
|
+
.command('view [agent]')
|
|
1727
|
+
.alias('show')
|
|
1728
|
+
.description('View instructions content for an agent')
|
|
1729
|
+
.option('-s, --scope <scope>', 'Scope: user or project', 'user')
|
|
1730
|
+
.action(async (agentArg, options) => {
|
|
1731
|
+
const cwd = process.cwd();
|
|
1732
|
+
let agentId;
|
|
1733
|
+
if (agentArg) {
|
|
1734
|
+
agentId = resolveAgentName(agentArg) || undefined;
|
|
1735
|
+
if (!agentId) {
|
|
1736
|
+
console.log(chalk.red(`Unknown agent: ${agentArg}`));
|
|
1737
|
+
process.exit(1);
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
else {
|
|
1741
|
+
const choices = ALL_AGENT_IDS.filter((id) => instructionsExists(id, 'user', cwd) || instructionsExists(id, 'project', cwd));
|
|
1742
|
+
if (choices.length === 0) {
|
|
1743
|
+
console.log(chalk.yellow('No instructions files found.'));
|
|
1744
|
+
return;
|
|
1745
|
+
}
|
|
1746
|
+
agentId = await select({
|
|
1747
|
+
message: 'Select agent:',
|
|
1748
|
+
choices: choices.map((id) => ({ name: AGENTS[id].name, value: id })),
|
|
1749
|
+
});
|
|
1750
|
+
}
|
|
1751
|
+
const scope = (options?.scope || 'user');
|
|
1752
|
+
const content = getInstructionsContent(agentId, scope, cwd);
|
|
1753
|
+
if (!content) {
|
|
1754
|
+
console.log(chalk.yellow(`No ${scope} instructions found for ${AGENTS[agentId].name}`));
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
console.log(chalk.bold(`\n${AGENTS[agentId].name} Instructions (${scope}):\n`));
|
|
1758
|
+
console.log(content);
|
|
1759
|
+
});
|
|
1760
|
+
instructionsCmd
|
|
1761
|
+
.command('diff [agent]')
|
|
1762
|
+
.description('Show differences between local and repo instructions')
|
|
1763
|
+
.action(async (agentArg) => {
|
|
1764
|
+
const cwd = process.cwd();
|
|
1765
|
+
const meta = readState();
|
|
1766
|
+
const scopes = getScopesByPriority();
|
|
1767
|
+
if (scopes.length === 0) {
|
|
1768
|
+
console.log(chalk.yellow('No repo configured. Run: agents repo add <source>'));
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
const agents = agentArg
|
|
1772
|
+
? [resolveAgentName(agentArg)].filter(Boolean)
|
|
1773
|
+
: ALL_AGENT_IDS;
|
|
1774
|
+
const diff = await import('diff');
|
|
1775
|
+
for (const { name: scopeName, config } of scopes) {
|
|
1776
|
+
const localPath = getRepoLocalPath(config.source);
|
|
1777
|
+
const repoInstructions = discoverInstructionsFromRepo(localPath);
|
|
1778
|
+
for (const agentId of agents) {
|
|
1779
|
+
const repoInstr = repoInstructions.find((i) => i.agentId === agentId);
|
|
1780
|
+
if (!repoInstr)
|
|
1781
|
+
continue;
|
|
1782
|
+
const installedContent = getInstructionsContent(agentId, 'user', cwd);
|
|
1783
|
+
if (!installedContent) {
|
|
1784
|
+
console.log(`${chalk.bold(AGENTS[agentId].name)}: ${chalk.green('NEW')} (not installed)`);
|
|
1785
|
+
continue;
|
|
1786
|
+
}
|
|
1787
|
+
const repoContent = fs.readFileSync(repoInstr.sourcePath, 'utf-8');
|
|
1788
|
+
if (installedContent.trim() === repoContent.trim()) {
|
|
1789
|
+
console.log(`${chalk.bold(AGENTS[agentId].name)}: ${chalk.gray('up to date')}`);
|
|
1790
|
+
continue;
|
|
1791
|
+
}
|
|
1792
|
+
console.log(`${chalk.bold(AGENTS[agentId].name)}:`);
|
|
1793
|
+
const changes = diff.diffLines(installedContent, repoContent);
|
|
1794
|
+
for (const change of changes) {
|
|
1795
|
+
if (change.added) {
|
|
1796
|
+
process.stdout.write(chalk.green(change.value));
|
|
1797
|
+
}
|
|
1798
|
+
else if (change.removed) {
|
|
1799
|
+
process.stdout.write(chalk.red(change.value));
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
console.log();
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
});
|
|
1806
|
+
instructionsCmd
|
|
1807
|
+
.command('push <agent>')
|
|
1808
|
+
.description('Save project-scoped instructions to user scope')
|
|
1809
|
+
.action((agentArg) => {
|
|
1810
|
+
const cwd = process.cwd();
|
|
1811
|
+
const agentId = resolveAgentName(agentArg);
|
|
1812
|
+
if (!agentId) {
|
|
1813
|
+
console.log(chalk.red(`Unknown agent: ${agentArg}`));
|
|
1814
|
+
process.exit(1);
|
|
1815
|
+
}
|
|
1816
|
+
const result = promoteInstructionsToUser(agentId, cwd);
|
|
1817
|
+
if (result.success) {
|
|
1818
|
+
console.log(chalk.green(`Pushed ${AGENTS[agentId].instructionsFile} to user scope`));
|
|
1819
|
+
}
|
|
1820
|
+
else {
|
|
1821
|
+
console.log(chalk.red(result.error || 'Failed to push instructions'));
|
|
1822
|
+
}
|
|
1823
|
+
});
|
|
1824
|
+
instructionsCmd
|
|
1825
|
+
.command('remove <agent>')
|
|
1826
|
+
.description('Remove user-scoped instructions for an agent')
|
|
1827
|
+
.action((agentArg) => {
|
|
1828
|
+
const agentId = resolveAgentName(agentArg);
|
|
1829
|
+
if (!agentId) {
|
|
1830
|
+
console.log(chalk.red(`Unknown agent: ${agentArg}`));
|
|
1831
|
+
process.exit(1);
|
|
1832
|
+
}
|
|
1833
|
+
const result = uninstallInstructions(agentId);
|
|
1834
|
+
if (result) {
|
|
1835
|
+
console.log(chalk.green(`Removed ${AGENTS[agentId].instructionsFile}`));
|
|
1836
|
+
}
|
|
1837
|
+
else {
|
|
1838
|
+
console.log(chalk.yellow(`No instructions file found for ${AGENTS[agentId].name}`));
|
|
1839
|
+
}
|
|
1840
|
+
});
|
|
1841
|
+
// =============================================================================
|
|
1430
1842
|
// MCP COMMANDS
|
|
1431
1843
|
// =============================================================================
|
|
1432
1844
|
const mcpCmd = program
|
|
@@ -1641,207 +2053,313 @@ mcpCmd
|
|
|
1641
2053
|
}
|
|
1642
2054
|
});
|
|
1643
2055
|
// =============================================================================
|
|
1644
|
-
//
|
|
2056
|
+
// VERSION MANAGEMENT COMMANDS (add, remove, use, list, upgrade)
|
|
1645
2057
|
// =============================================================================
|
|
1646
|
-
|
|
1647
|
-
.command('
|
|
1648
|
-
.description('
|
|
1649
|
-
|
|
1650
|
-
.
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
const agent = AGENTS[agentId];
|
|
1659
|
-
const state = states[agentId];
|
|
1660
|
-
if (state?.installed) {
|
|
1661
|
-
console.log(` ${agent.name.padEnd(14)} ${chalk.green(state.version || 'installed')}`);
|
|
1662
|
-
if (state.path) {
|
|
1663
|
-
console.log(` ${''.padEnd(14)} ${chalk.gray(state.path)}`);
|
|
1664
|
-
}
|
|
2058
|
+
program
|
|
2059
|
+
.command('add <specs...>')
|
|
2060
|
+
.description('Install agent CLI(s). Examples: agents add claude@1.5.0, agents add claude codex')
|
|
2061
|
+
.option('-p, --project', 'Pin version in project manifest (.agents/agents.yaml)')
|
|
2062
|
+
.action(async (specs, options) => {
|
|
2063
|
+
const isProject = options.project;
|
|
2064
|
+
for (const spec of specs) {
|
|
2065
|
+
const parsed = parseAgentSpec(spec);
|
|
2066
|
+
if (!parsed) {
|
|
2067
|
+
console.log(chalk.red(`Invalid agent: ${spec}`));
|
|
2068
|
+
console.log(chalk.gray(`Format: <agent>[@version]. Available: ${ALL_AGENT_IDS.join(', ')}`));
|
|
2069
|
+
continue;
|
|
1665
2070
|
}
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
cliCmd
|
|
1672
|
-
.command('add <agents...>')
|
|
1673
|
-
.description('Install agent CLI(s)')
|
|
1674
|
-
.option('-v, --version <version>', 'Version to install', 'latest')
|
|
1675
|
-
.option('--manifest-only', 'Only add to manifest, do not install')
|
|
1676
|
-
.action(async (agents, options) => {
|
|
1677
|
-
const validAgents = [];
|
|
1678
|
-
for (const agent of agents) {
|
|
1679
|
-
const agentId = agent.toLowerCase();
|
|
1680
|
-
if (!AGENTS[agentId]) {
|
|
1681
|
-
console.log(chalk.red(`Unknown agent: ${agent}`));
|
|
1682
|
-
console.log(chalk.gray(`Available: ${ALL_AGENT_IDS.join(', ')}`));
|
|
1683
|
-
return;
|
|
2071
|
+
const { agent, version } = parsed;
|
|
2072
|
+
const agentConfig = AGENTS[agent];
|
|
2073
|
+
if (!agentConfig.npmPackage) {
|
|
2074
|
+
console.log(chalk.yellow(`${agentConfig.name} has no npm package. Install manually.`));
|
|
2075
|
+
continue;
|
|
1684
2076
|
}
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
spinner.succeed(`Installed ${agentConfig.name}@${version}`);
|
|
1701
|
-
}
|
|
1702
|
-
catch (err) {
|
|
1703
|
-
spinner.fail(`Failed to install ${agentConfig.name}`);
|
|
1704
|
-
console.error(chalk.gray(err.message));
|
|
1705
|
-
continue;
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
|
-
else if (installScript) {
|
|
1709
|
-
const spinner = ora(`Installing ${agentConfig.name}...`).start();
|
|
1710
|
-
try {
|
|
1711
|
-
await execAsync(installScript, { shell: '/bin/bash' });
|
|
1712
|
-
spinner.succeed(`Installed ${agentConfig.name}`);
|
|
2077
|
+
// Check if already installed
|
|
2078
|
+
if (isVersionInstalled(agent, version)) {
|
|
2079
|
+
console.log(chalk.gray(`${agentConfig.name}@${version} already installed`));
|
|
2080
|
+
}
|
|
2081
|
+
else {
|
|
2082
|
+
const spinner = ora(`Installing ${agentConfig.name}@${version}...`).start();
|
|
2083
|
+
const result = await installVersion(agent, version, (msg) => {
|
|
2084
|
+
spinner.text = msg;
|
|
2085
|
+
});
|
|
2086
|
+
if (result.success) {
|
|
2087
|
+
spinner.succeed(`Installed ${agentConfig.name}@${result.installedVersion}`);
|
|
2088
|
+
// Create shim if first install
|
|
2089
|
+
if (!shimExists(agent)) {
|
|
2090
|
+
createShim(agent);
|
|
2091
|
+
console.log(chalk.gray(` Created shim: ${getShimsDir()}/${agentConfig.cliCommand}`));
|
|
1713
2092
|
}
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
console.
|
|
1717
|
-
|
|
2093
|
+
// Check if shims in PATH
|
|
2094
|
+
if (!isShimsInPath()) {
|
|
2095
|
+
console.log();
|
|
2096
|
+
console.log(chalk.yellow('Shims directory not in PATH. Add it to use version switching:'));
|
|
2097
|
+
console.log(chalk.gray(getPathSetupInstructions()));
|
|
2098
|
+
console.log();
|
|
1718
2099
|
}
|
|
1719
2100
|
}
|
|
1720
2101
|
else {
|
|
1721
|
-
|
|
2102
|
+
spinner.fail(`Failed to install ${agentConfig.name}@${version}`);
|
|
2103
|
+
console.error(chalk.gray(result.error || 'Unknown error'));
|
|
2104
|
+
continue;
|
|
1722
2105
|
}
|
|
1723
2106
|
}
|
|
2107
|
+
// Update project manifest if -p flag
|
|
2108
|
+
if (isProject) {
|
|
2109
|
+
const projectManifestDir = path.join(process.cwd(), '.agents');
|
|
2110
|
+
const projectManifestPath = path.join(projectManifestDir, 'agents.yaml');
|
|
2111
|
+
if (!fs.existsSync(projectManifestDir)) {
|
|
2112
|
+
fs.mkdirSync(projectManifestDir, { recursive: true });
|
|
2113
|
+
}
|
|
2114
|
+
const manifest = fs.existsSync(projectManifestPath)
|
|
2115
|
+
? readManifest(process.cwd()) || createDefaultManifest()
|
|
2116
|
+
: createDefaultManifest();
|
|
2117
|
+
manifest.clis = manifest.clis || {};
|
|
2118
|
+
manifest.clis[agent] = {
|
|
2119
|
+
package: agentConfig.npmPackage,
|
|
2120
|
+
version: version === 'latest' ? (await getInstalledVersionForAgent(agent, version)) : version,
|
|
2121
|
+
};
|
|
2122
|
+
writeManifest(process.cwd(), manifest);
|
|
2123
|
+
console.log(chalk.green(` Pinned ${agentConfig.name}@${version} in .agents/agents.yaml`));
|
|
2124
|
+
}
|
|
1724
2125
|
}
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
version: version,
|
|
1734
|
-
};
|
|
1735
|
-
}
|
|
1736
|
-
writeManifest(localPath, manifest);
|
|
1737
|
-
if (validAgents.length === 1) {
|
|
1738
|
-
console.log(chalk.green(`Added ${AGENTS[validAgents[0]].name} to manifest`));
|
|
2126
|
+
});
|
|
2127
|
+
/**
|
|
2128
|
+
* Helper to get actual installed version for an agent.
|
|
2129
|
+
*/
|
|
2130
|
+
async function getInstalledVersionForAgent(agent, requestedVersion) {
|
|
2131
|
+
const versions = listInstalledVersions(agent);
|
|
2132
|
+
if (versions.length > 0) {
|
|
2133
|
+
return versions[versions.length - 1];
|
|
1739
2134
|
}
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
.
|
|
1746
|
-
.
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
console.log(chalk.gray(`Available: ${ALL_AGENT_IDS.join(', ')}`));
|
|
1755
|
-
return;
|
|
2135
|
+
return requestedVersion;
|
|
2136
|
+
}
|
|
2137
|
+
program
|
|
2138
|
+
.command('remove <specs...>')
|
|
2139
|
+
.description('Remove agent CLI version(s). Examples: agents remove claude@1.5.0, agents remove claude')
|
|
2140
|
+
.option('-p, --project', 'Also remove from project manifest')
|
|
2141
|
+
.action(async (specs, options) => {
|
|
2142
|
+
const isProject = options.project;
|
|
2143
|
+
for (const spec of specs) {
|
|
2144
|
+
const parsed = parseAgentSpec(spec);
|
|
2145
|
+
if (!parsed) {
|
|
2146
|
+
console.log(chalk.red(`Invalid agent: ${spec}`));
|
|
2147
|
+
console.log(chalk.gray(`Format: <agent>[@version]. Available: ${ALL_AGENT_IDS.join(', ')}`));
|
|
2148
|
+
continue;
|
|
1756
2149
|
}
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
const pkg = agentConfig.npmPackage;
|
|
1765
|
-
if (!options.manifestOnly) {
|
|
1766
|
-
if (!pkg) {
|
|
1767
|
-
console.log(chalk.yellow(`${agentConfig.name} has no npm package.`));
|
|
2150
|
+
const { agent, version } = parsed;
|
|
2151
|
+
const agentConfig = AGENTS[agent];
|
|
2152
|
+
if (version === 'latest' || !spec.includes('@')) {
|
|
2153
|
+
// Remove all versions
|
|
2154
|
+
const versions = listInstalledVersions(agent);
|
|
2155
|
+
if (versions.length === 0) {
|
|
2156
|
+
console.log(chalk.gray(`No versions of ${agentConfig.name} installed`));
|
|
1768
2157
|
}
|
|
1769
|
-
else
|
|
1770
|
-
|
|
2158
|
+
else {
|
|
2159
|
+
const count = removeAllVersions(agent);
|
|
2160
|
+
removeShim(agent);
|
|
2161
|
+
console.log(chalk.green(`Removed ${count} version(s) of ${agentConfig.name}`));
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
else {
|
|
2165
|
+
// Remove specific version
|
|
2166
|
+
if (!isVersionInstalled(agent, version)) {
|
|
2167
|
+
console.log(chalk.gray(`${agentConfig.name}@${version} not installed`));
|
|
1771
2168
|
}
|
|
1772
2169
|
else {
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
2170
|
+
removeVersion(agent, version);
|
|
2171
|
+
console.log(chalk.green(`Removed ${agentConfig.name}@${version}`));
|
|
2172
|
+
// Remove shim if no versions left
|
|
2173
|
+
const remaining = listInstalledVersions(agent);
|
|
2174
|
+
if (remaining.length === 0) {
|
|
2175
|
+
removeShim(agent);
|
|
1777
2176
|
}
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
// Update project manifest if -p flag
|
|
2180
|
+
if (isProject) {
|
|
2181
|
+
const projectManifestPath = path.join(process.cwd(), '.agents', 'agents.yaml');
|
|
2182
|
+
if (fs.existsSync(projectManifestPath)) {
|
|
2183
|
+
const manifest = readManifest(process.cwd());
|
|
2184
|
+
if (manifest?.clis?.[agent]) {
|
|
2185
|
+
delete manifest.clis[agent];
|
|
2186
|
+
writeManifest(process.cwd(), manifest);
|
|
2187
|
+
console.log(chalk.gray(` Removed from .agents/agents.yaml`));
|
|
1781
2188
|
}
|
|
1782
2189
|
}
|
|
1783
2190
|
}
|
|
1784
2191
|
}
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
2192
|
+
});
|
|
2193
|
+
program
|
|
2194
|
+
.command('use <spec>')
|
|
2195
|
+
.description('Set default agent version. Examples: agents use claude@1.5.0')
|
|
2196
|
+
.option('-p, --project', 'Set in project manifest instead of global default')
|
|
2197
|
+
.action(async (spec, options) => {
|
|
2198
|
+
const parsed = parseAgentSpec(spec);
|
|
2199
|
+
if (!parsed) {
|
|
2200
|
+
console.log(chalk.red(`Invalid agent: ${spec}`));
|
|
2201
|
+
console.log(chalk.gray(`Format: <agent>@<version>. Available: ${ALL_AGENT_IDS.join(', ')}`));
|
|
2202
|
+
return;
|
|
2203
|
+
}
|
|
2204
|
+
const { agent, version } = parsed;
|
|
2205
|
+
const agentConfig = AGENTS[agent];
|
|
2206
|
+
if (!spec.includes('@') || version === 'latest') {
|
|
2207
|
+
console.log(chalk.red('Please specify a version: agents use <agent>@<version>'));
|
|
2208
|
+
const versions = listInstalledVersions(agent);
|
|
2209
|
+
if (versions.length > 0) {
|
|
2210
|
+
console.log(chalk.gray(`Installed versions: ${versions.join(', ')}`));
|
|
1793
2211
|
}
|
|
2212
|
+
return;
|
|
1794
2213
|
}
|
|
1795
|
-
if (
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
2214
|
+
if (!isVersionInstalled(agent, version)) {
|
|
2215
|
+
console.log(chalk.red(`${agentConfig.name}@${version} not installed`));
|
|
2216
|
+
console.log(chalk.gray(`Run: agents add ${agent}@${version}`));
|
|
2217
|
+
return;
|
|
2218
|
+
}
|
|
2219
|
+
if (options.project) {
|
|
2220
|
+
// Set in project manifest
|
|
2221
|
+
const projectManifestDir = path.join(process.cwd(), '.agents');
|
|
2222
|
+
const projectManifestPath = path.join(projectManifestDir, 'agents.yaml');
|
|
2223
|
+
if (!fs.existsSync(projectManifestDir)) {
|
|
2224
|
+
fs.mkdirSync(projectManifestDir, { recursive: true });
|
|
2225
|
+
}
|
|
2226
|
+
const manifest = fs.existsSync(projectManifestPath)
|
|
2227
|
+
? readManifest(process.cwd()) || createDefaultManifest()
|
|
2228
|
+
: createDefaultManifest();
|
|
2229
|
+
manifest.clis = manifest.clis || {};
|
|
2230
|
+
manifest.clis[agent] = {
|
|
2231
|
+
package: agentConfig.npmPackage,
|
|
2232
|
+
version,
|
|
2233
|
+
};
|
|
2234
|
+
writeManifest(process.cwd(), manifest);
|
|
2235
|
+
console.log(chalk.green(`Set ${agentConfig.name}@${version} for this project`));
|
|
2236
|
+
}
|
|
2237
|
+
else {
|
|
2238
|
+
// Set global default
|
|
2239
|
+
setGlobalDefault(agent, version);
|
|
2240
|
+
console.log(chalk.green(`Set ${agentConfig.name}@${version} as global default`));
|
|
2241
|
+
}
|
|
2242
|
+
});
|
|
2243
|
+
program
|
|
2244
|
+
.command('list')
|
|
2245
|
+
.description('List installed agent CLI versions')
|
|
2246
|
+
.action(async () => {
|
|
2247
|
+
console.log(chalk.bold('Installed Agent CLIs\n'));
|
|
2248
|
+
let hasAny = false;
|
|
2249
|
+
for (const agentId of ALL_AGENT_IDS) {
|
|
2250
|
+
const agent = AGENTS[agentId];
|
|
2251
|
+
const versions = listInstalledVersions(agentId);
|
|
2252
|
+
const globalDefault = getGlobalDefault(agentId);
|
|
2253
|
+
if (versions.length > 0) {
|
|
2254
|
+
hasAny = true;
|
|
2255
|
+
console.log(` ${chalk.bold(agent.name)}`);
|
|
2256
|
+
for (const version of versions) {
|
|
2257
|
+
const isDefault = version === globalDefault;
|
|
2258
|
+
const marker = isDefault ? chalk.green(' (default)') : '';
|
|
2259
|
+
console.log(` ${version}${marker}`);
|
|
2260
|
+
}
|
|
2261
|
+
// Check for project override
|
|
2262
|
+
const projectVersion = getProjectVersionFromCwd(agentId);
|
|
2263
|
+
if (projectVersion && projectVersion !== globalDefault) {
|
|
2264
|
+
console.log(chalk.cyan(` -> ${projectVersion} (project)`));
|
|
2265
|
+
}
|
|
2266
|
+
console.log();
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
if (!hasAny) {
|
|
2270
|
+
console.log(chalk.gray(' No agent CLIs installed.'));
|
|
2271
|
+
console.log(chalk.gray(' Run: agents add claude@latest'));
|
|
2272
|
+
console.log();
|
|
2273
|
+
}
|
|
2274
|
+
// Show shims path status
|
|
2275
|
+
if (hasAny) {
|
|
2276
|
+
const shimsDir = getShimsDir();
|
|
2277
|
+
if (isShimsInPath()) {
|
|
2278
|
+
console.log(chalk.gray(`Shims: ${shimsDir} (in PATH)`));
|
|
1799
2279
|
}
|
|
1800
2280
|
else {
|
|
1801
|
-
console.log(chalk.
|
|
2281
|
+
console.log(chalk.yellow(`Shims: ${shimsDir} (not in PATH)`));
|
|
2282
|
+
console.log(chalk.gray('Add to PATH for automatic version switching'));
|
|
1802
2283
|
}
|
|
1803
2284
|
}
|
|
1804
2285
|
});
|
|
1805
|
-
|
|
2286
|
+
/**
|
|
2287
|
+
* Helper to get project version from current working directory.
|
|
2288
|
+
*/
|
|
2289
|
+
function getProjectVersionFromCwd(agent) {
|
|
2290
|
+
const manifestPath = path.join(process.cwd(), '.agents', 'agents.yaml');
|
|
2291
|
+
if (!fs.existsSync(manifestPath)) {
|
|
2292
|
+
return null;
|
|
2293
|
+
}
|
|
2294
|
+
try {
|
|
2295
|
+
const manifest = readManifest(process.cwd());
|
|
2296
|
+
return manifest?.clis?.[agent]?.version || null;
|
|
2297
|
+
}
|
|
2298
|
+
catch {
|
|
2299
|
+
return null;
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
program
|
|
1806
2303
|
.command('upgrade [agent]')
|
|
1807
|
-
.description('Upgrade agent CLI(s) to version
|
|
1808
|
-
.option('-
|
|
1809
|
-
.option('--latest', 'Upgrade to latest version (ignore manifest)')
|
|
2304
|
+
.description('Upgrade agent CLI(s) to latest version')
|
|
2305
|
+
.option('-p, --project', 'Upgrade to version in project manifest')
|
|
1810
2306
|
.action(async (agent, options) => {
|
|
1811
|
-
const scopeName = options.scope;
|
|
1812
|
-
const scope = getScope(scopeName);
|
|
1813
|
-
const localPath = scope ? getRepoLocalPath(scope.source) : null;
|
|
1814
|
-
const manifest = localPath ? readManifest(localPath) : null;
|
|
1815
2307
|
const agentsToUpgrade = agent
|
|
1816
2308
|
? [agent.toLowerCase()]
|
|
1817
|
-
: ALL_AGENT_IDS.filter((id) =>
|
|
2309
|
+
: ALL_AGENT_IDS.filter((id) => listInstalledVersions(id).length > 0);
|
|
1818
2310
|
if (agentsToUpgrade.length === 0) {
|
|
1819
|
-
console.log(chalk.yellow('No CLIs
|
|
2311
|
+
console.log(chalk.yellow('No agent CLIs installed. Run: agents add <agent>@<version>'));
|
|
1820
2312
|
return;
|
|
1821
2313
|
}
|
|
1822
|
-
const { exec } = await import('child_process');
|
|
1823
|
-
const { promisify } = await import('util');
|
|
1824
|
-
const execAsync = promisify(exec);
|
|
1825
2314
|
for (const agentId of agentsToUpgrade) {
|
|
1826
2315
|
const agentConfig = AGENTS[agentId];
|
|
1827
2316
|
if (!agentConfig) {
|
|
1828
2317
|
console.log(chalk.red(`Unknown agent: ${agentId}`));
|
|
1829
2318
|
continue;
|
|
1830
2319
|
}
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
2320
|
+
// Determine target version
|
|
2321
|
+
let targetVersion = 'latest';
|
|
2322
|
+
if (options.project) {
|
|
2323
|
+
const projectVersion = getProjectVersionFromCwd(agentId);
|
|
2324
|
+
if (projectVersion) {
|
|
2325
|
+
targetVersion = projectVersion;
|
|
2326
|
+
}
|
|
1838
2327
|
}
|
|
1839
|
-
|
|
2328
|
+
const spinner = ora(`Upgrading ${agentConfig.name} to ${targetVersion}...`).start();
|
|
2329
|
+
const result = await installVersion(agentId, targetVersion, (msg) => {
|
|
2330
|
+
spinner.text = msg;
|
|
2331
|
+
});
|
|
2332
|
+
if (result.success) {
|
|
2333
|
+
spinner.succeed(`Upgraded ${agentConfig.name} to ${result.installedVersion}`);
|
|
2334
|
+
// Update global default to new version
|
|
2335
|
+
setGlobalDefault(agentId, result.installedVersion);
|
|
2336
|
+
}
|
|
2337
|
+
else {
|
|
1840
2338
|
spinner.fail(`Failed to upgrade ${agentConfig.name}`);
|
|
1841
|
-
console.error(chalk.gray(
|
|
2339
|
+
console.error(chalk.gray(result.error || 'Unknown error'));
|
|
1842
2340
|
}
|
|
1843
2341
|
}
|
|
1844
2342
|
});
|
|
2343
|
+
// Legacy alias for backwards compatibility
|
|
2344
|
+
program
|
|
2345
|
+
.command('cli')
|
|
2346
|
+
.description('(deprecated) Use: agents add, agents remove, agents list')
|
|
2347
|
+
.action(() => {
|
|
2348
|
+
console.log(chalk.yellow('The "cli" subcommand is deprecated.'));
|
|
2349
|
+
console.log();
|
|
2350
|
+
console.log('New commands:');
|
|
2351
|
+
console.log(' agents add <agent>@<version> Install agent CLI');
|
|
2352
|
+
console.log(' agents remove <agent>[@version] Remove agent CLI');
|
|
2353
|
+
console.log(' agents use <agent>@<version> Set default version');
|
|
2354
|
+
console.log(' agents list List installed versions');
|
|
2355
|
+
console.log(' agents upgrade [agent] Upgrade to latest');
|
|
2356
|
+
console.log();
|
|
2357
|
+
console.log('Examples:');
|
|
2358
|
+
console.log(' agents add claude@1.5.0');
|
|
2359
|
+
console.log(' agents add claude@1.5.0 -p Pin to project');
|
|
2360
|
+
console.log(' agents use claude@1.4.0');
|
|
2361
|
+
console.log(' agents remove claude@1.4.0');
|
|
2362
|
+
});
|
|
1845
2363
|
// =============================================================================
|
|
1846
2364
|
// REPO COMMANDS
|
|
1847
2365
|
// =============================================================================
|
|
@@ -2189,11 +2707,11 @@ program
|
|
|
2189
2707
|
}
|
|
2190
2708
|
});
|
|
2191
2709
|
// =============================================================================
|
|
2192
|
-
//
|
|
2710
|
+
// INSTALL COMMAND (unified package installation)
|
|
2193
2711
|
// =============================================================================
|
|
2194
2712
|
program
|
|
2195
|
-
.command('
|
|
2196
|
-
.description('
|
|
2713
|
+
.command('install <identifier>')
|
|
2714
|
+
.description('Install a package (mcp:name, skill:user/repo, or gh:user/repo)')
|
|
2197
2715
|
.option('-a, --agents <list>', 'Comma-separated agents to install to')
|
|
2198
2716
|
.action(async (identifier, options) => {
|
|
2199
2717
|
const spinner = ora('Resolving package...').start();
|
|
@@ -2300,18 +2818,29 @@ program
|
|
|
2300
2818
|
if (hasCommands) {
|
|
2301
2819
|
console.log(chalk.bold('\nInstalling commands...'));
|
|
2302
2820
|
let installed = 0;
|
|
2821
|
+
let failed = 0;
|
|
2303
2822
|
for (const command of commands) {
|
|
2304
2823
|
for (const agentId of agents) {
|
|
2305
2824
|
if (!gitCliStates[agentId]?.installed && agentId !== 'cursor')
|
|
2306
2825
|
continue;
|
|
2307
2826
|
const sourcePath = resolveCommandSource(localPath, command.name, agentId);
|
|
2308
2827
|
if (sourcePath) {
|
|
2309
|
-
installCommand(sourcePath, agentId, command.name, 'symlink');
|
|
2310
|
-
|
|
2828
|
+
const result = installCommand(sourcePath, agentId, command.name, 'symlink');
|
|
2829
|
+
if (result.error) {
|
|
2830
|
+
failed++;
|
|
2831
|
+
}
|
|
2832
|
+
else {
|
|
2833
|
+
installed++;
|
|
2834
|
+
}
|
|
2311
2835
|
}
|
|
2312
2836
|
}
|
|
2313
2837
|
}
|
|
2314
|
-
|
|
2838
|
+
if (failed > 0) {
|
|
2839
|
+
console.log(` Installed ${installed} command instances (${failed} failed)`);
|
|
2840
|
+
}
|
|
2841
|
+
else {
|
|
2842
|
+
console.log(` Installed ${installed} command instances`);
|
|
2843
|
+
}
|
|
2315
2844
|
}
|
|
2316
2845
|
// Install skills
|
|
2317
2846
|
if (hasSkills) {
|
|
@@ -2344,7 +2873,7 @@ program
|
|
|
2344
2873
|
});
|
|
2345
2874
|
// Self-upgrade command
|
|
2346
2875
|
program
|
|
2347
|
-
.command('upgrade')
|
|
2876
|
+
.command('self-upgrade')
|
|
2348
2877
|
.description('Upgrade agents-cli to the latest version')
|
|
2349
2878
|
.action(async () => {
|
|
2350
2879
|
const spinner = ora('Checking for updates...').start();
|
|
@@ -2406,6 +2935,498 @@ program
|
|
|
2406
2935
|
process.exit(1);
|
|
2407
2936
|
}
|
|
2408
2937
|
});
|
|
2938
|
+
// =============================================================================
|
|
2939
|
+
// DAEMON COMMANDS
|
|
2940
|
+
// =============================================================================
|
|
2941
|
+
import { startDaemon, stopDaemon, isDaemonRunning, readDaemonPid, readDaemonLog, runDaemon, signalDaemonReload, } from './lib/daemon.js';
|
|
2942
|
+
import { listJobs as listAllJobs, readJob, validateJob, writeJob, setJobEnabled, getLatestRun, getRunDir, discoverJobsFromRepo, jobExists, jobContentMatches, installJobFromSource, } from './lib/jobs.js';
|
|
2943
|
+
import { executeJob } from './lib/runner.js';
|
|
2944
|
+
import { JobScheduler } from './lib/scheduler.js';
|
|
2945
|
+
const daemonCmd = program.command('daemon').description('Manage the jobs daemon');
|
|
2946
|
+
daemonCmd
|
|
2947
|
+
.command('start')
|
|
2948
|
+
.description('Start the daemon')
|
|
2949
|
+
.action(() => {
|
|
2950
|
+
const result = startDaemon();
|
|
2951
|
+
if (result.method === 'already-running') {
|
|
2952
|
+
console.log(chalk.yellow(`Daemon already running (PID: ${result.pid})`));
|
|
2953
|
+
}
|
|
2954
|
+
else {
|
|
2955
|
+
console.log(chalk.green(`Daemon started (PID: ${result.pid}, method: ${result.method})`));
|
|
2956
|
+
}
|
|
2957
|
+
});
|
|
2958
|
+
daemonCmd
|
|
2959
|
+
.command('stop')
|
|
2960
|
+
.description('Stop the daemon')
|
|
2961
|
+
.action(() => {
|
|
2962
|
+
if (!isDaemonRunning()) {
|
|
2963
|
+
console.log(chalk.yellow('Daemon is not running'));
|
|
2964
|
+
return;
|
|
2965
|
+
}
|
|
2966
|
+
stopDaemon();
|
|
2967
|
+
console.log(chalk.green('Daemon stopped'));
|
|
2968
|
+
});
|
|
2969
|
+
daemonCmd
|
|
2970
|
+
.command('status')
|
|
2971
|
+
.description('Show daemon status')
|
|
2972
|
+
.action(() => {
|
|
2973
|
+
const running = isDaemonRunning();
|
|
2974
|
+
const pid = readDaemonPid();
|
|
2975
|
+
console.log(chalk.bold('Daemon Status\n'));
|
|
2976
|
+
console.log(` Status: ${running ? chalk.green('running') : chalk.gray('stopped')}`);
|
|
2977
|
+
if (pid)
|
|
2978
|
+
console.log(` PID: ${pid}`);
|
|
2979
|
+
const jobs = listAllJobs();
|
|
2980
|
+
const enabled = jobs.filter((j) => j.enabled);
|
|
2981
|
+
console.log(` Jobs: ${enabled.length} enabled / ${jobs.length} total`);
|
|
2982
|
+
if (running && enabled.length > 0) {
|
|
2983
|
+
const scheduler = new JobScheduler(async () => { });
|
|
2984
|
+
scheduler.loadAll();
|
|
2985
|
+
const scheduled = scheduler.listScheduled();
|
|
2986
|
+
console.log(chalk.bold('\n Scheduled Jobs\n'));
|
|
2987
|
+
for (const job of scheduled) {
|
|
2988
|
+
const next = job.nextRun ? job.nextRun.toLocaleString() : 'unknown';
|
|
2989
|
+
console.log(` ${chalk.cyan(job.name.padEnd(24))} next: ${chalk.gray(next)}`);
|
|
2990
|
+
}
|
|
2991
|
+
scheduler.stopAll();
|
|
2992
|
+
}
|
|
2993
|
+
});
|
|
2994
|
+
daemonCmd
|
|
2995
|
+
.command('logs')
|
|
2996
|
+
.description('Show daemon logs')
|
|
2997
|
+
.option('-n, --lines <number>', 'Number of lines to show', '50')
|
|
2998
|
+
.option('-f, --follow', 'Follow log output')
|
|
2999
|
+
.action(async (options) => {
|
|
3000
|
+
if (options.follow) {
|
|
3001
|
+
const { exec: execCb } = await import('child_process');
|
|
3002
|
+
const { getAgentsDir } = await import('./lib/state.js');
|
|
3003
|
+
const logPath = path.join(getAgentsDir(), 'daemon.log');
|
|
3004
|
+
const child = execCb(`tail -f "${logPath}"`);
|
|
3005
|
+
child.stdout?.pipe(process.stdout);
|
|
3006
|
+
child.stderr?.pipe(process.stderr);
|
|
3007
|
+
child.on('exit', () => process.exit(0));
|
|
3008
|
+
process.on('SIGINT', () => { child.kill(); process.exit(0); });
|
|
3009
|
+
return;
|
|
3010
|
+
}
|
|
3011
|
+
const lines = parseInt(options.lines, 10);
|
|
3012
|
+
const output = readDaemonLog(lines);
|
|
3013
|
+
if (output) {
|
|
3014
|
+
console.log(output);
|
|
3015
|
+
}
|
|
3016
|
+
else {
|
|
3017
|
+
console.log(chalk.gray('No daemon logs'));
|
|
3018
|
+
}
|
|
3019
|
+
});
|
|
3020
|
+
daemonCmd
|
|
3021
|
+
.command('_run')
|
|
3022
|
+
.description('Run daemon in foreground (internal)')
|
|
3023
|
+
.action(async () => {
|
|
3024
|
+
await runDaemon();
|
|
3025
|
+
});
|
|
3026
|
+
// =============================================================================
|
|
3027
|
+
// JOBS COMMANDS
|
|
3028
|
+
// =============================================================================
|
|
3029
|
+
const jobsCmd = program.command('jobs').description('Manage scheduled jobs');
|
|
3030
|
+
jobsCmd
|
|
3031
|
+
.command('list')
|
|
3032
|
+
.description('List all jobs')
|
|
3033
|
+
.action(() => {
|
|
3034
|
+
const jobs = listAllJobs();
|
|
3035
|
+
if (jobs.length === 0) {
|
|
3036
|
+
console.log(chalk.gray('No jobs configured'));
|
|
3037
|
+
console.log(chalk.gray(' Add a job: agents jobs add <path-to-job.yml>'));
|
|
3038
|
+
return;
|
|
3039
|
+
}
|
|
3040
|
+
const scheduler = new JobScheduler(async () => { });
|
|
3041
|
+
scheduler.loadAll();
|
|
3042
|
+
console.log(chalk.bold('Scheduled Jobs\n'));
|
|
3043
|
+
const header = ` ${'Name'.padEnd(24)} ${'Agent'.padEnd(10)} ${'Schedule'.padEnd(20)} ${'Enabled'.padEnd(10)} ${'Next Run'.padEnd(24)} ${'Last Status'}`;
|
|
3044
|
+
console.log(chalk.gray(header));
|
|
3045
|
+
console.log(chalk.gray(' ' + '-'.repeat(110)));
|
|
3046
|
+
for (const job of jobs) {
|
|
3047
|
+
const nextRun = scheduler.getNextRun(job.name);
|
|
3048
|
+
const nextStr = nextRun ? nextRun.toLocaleString() : '-';
|
|
3049
|
+
const latestRun = getLatestRun(job.name);
|
|
3050
|
+
const lastStatus = latestRun?.status || '-';
|
|
3051
|
+
const enabledStr = job.enabled ? chalk.green('yes') : chalk.gray('no');
|
|
3052
|
+
const statusColor = lastStatus === 'completed' ? chalk.green : lastStatus === 'failed' ? chalk.red : lastStatus === 'timeout' ? chalk.yellow : chalk.gray;
|
|
3053
|
+
console.log(` ${chalk.cyan(job.name.padEnd(24))} ${job.agent.padEnd(10)} ${job.schedule.padEnd(20)} ${enabledStr.padEnd(10 + 10)} ${chalk.gray(nextStr.padEnd(24))} ${statusColor(lastStatus)}`);
|
|
3054
|
+
}
|
|
3055
|
+
scheduler.stopAll();
|
|
3056
|
+
console.log();
|
|
3057
|
+
});
|
|
3058
|
+
jobsCmd
|
|
3059
|
+
.command('add <path>')
|
|
3060
|
+
.description('Add a job from a YAML file')
|
|
3061
|
+
.action(async (filePath) => {
|
|
3062
|
+
const resolved = path.resolve(filePath);
|
|
3063
|
+
if (!fs.existsSync(resolved)) {
|
|
3064
|
+
console.log(chalk.red(`File not found: ${resolved}`));
|
|
3065
|
+
process.exit(1);
|
|
3066
|
+
}
|
|
3067
|
+
const content = fs.readFileSync(resolved, 'utf-8');
|
|
3068
|
+
let parsed;
|
|
3069
|
+
try {
|
|
3070
|
+
const yamlMod = await import('yaml');
|
|
3071
|
+
parsed = yamlMod.parse(content);
|
|
3072
|
+
}
|
|
3073
|
+
catch (err) {
|
|
3074
|
+
console.log(chalk.red(`Invalid YAML: ${err.message}`));
|
|
3075
|
+
process.exit(1);
|
|
3076
|
+
}
|
|
3077
|
+
const name = parsed.name || path.basename(resolved).replace(/\.ya?ml$/, '');
|
|
3078
|
+
parsed.name = name;
|
|
3079
|
+
const errors = validateJob(parsed);
|
|
3080
|
+
if (errors.length > 0) {
|
|
3081
|
+
console.log(chalk.red('Validation errors:'));
|
|
3082
|
+
for (const err of errors) {
|
|
3083
|
+
console.log(chalk.red(` - ${err}`));
|
|
3084
|
+
}
|
|
3085
|
+
process.exit(1);
|
|
3086
|
+
}
|
|
3087
|
+
const config = {
|
|
3088
|
+
mode: 'plan',
|
|
3089
|
+
effort: 'default',
|
|
3090
|
+
timeout: '30m',
|
|
3091
|
+
enabled: true,
|
|
3092
|
+
...parsed,
|
|
3093
|
+
};
|
|
3094
|
+
writeJob(config);
|
|
3095
|
+
console.log(chalk.green(`Job '${name}' added`));
|
|
3096
|
+
if (isDaemonRunning()) {
|
|
3097
|
+
signalDaemonReload();
|
|
3098
|
+
console.log(chalk.gray('Daemon reloaded'));
|
|
3099
|
+
}
|
|
3100
|
+
});
|
|
3101
|
+
jobsCmd
|
|
3102
|
+
.command('run <name>')
|
|
3103
|
+
.description('Run a job immediately in the foreground')
|
|
3104
|
+
.action(async (name) => {
|
|
3105
|
+
const job = readJob(name);
|
|
3106
|
+
if (!job) {
|
|
3107
|
+
console.log(chalk.red(`Job '${name}' not found`));
|
|
3108
|
+
process.exit(1);
|
|
3109
|
+
}
|
|
3110
|
+
console.log(chalk.bold(`Running job '${name}' (agent: ${job.agent}, mode: ${job.mode})\n`));
|
|
3111
|
+
const spinner = ora('Executing...').start();
|
|
3112
|
+
try {
|
|
3113
|
+
const result = await executeJob(job);
|
|
3114
|
+
if (result.meta.status === 'completed') {
|
|
3115
|
+
spinner.succeed(`Job completed (exit code: ${result.meta.exitCode})`);
|
|
3116
|
+
}
|
|
3117
|
+
else if (result.meta.status === 'timeout') {
|
|
3118
|
+
spinner.warn(`Job timed out after ${job.timeout}`);
|
|
3119
|
+
}
|
|
3120
|
+
else {
|
|
3121
|
+
spinner.fail(`Job failed (exit code: ${result.meta.exitCode})`);
|
|
3122
|
+
}
|
|
3123
|
+
console.log(chalk.gray(` Run: ${result.meta.runId}`));
|
|
3124
|
+
console.log(chalk.gray(` Log: ${getRunDir(name, result.meta.runId)}/stdout.log`));
|
|
3125
|
+
if (result.reportPath) {
|
|
3126
|
+
console.log(chalk.bold('\nReport:\n'));
|
|
3127
|
+
console.log(fs.readFileSync(result.reportPath, 'utf-8'));
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
catch (err) {
|
|
3131
|
+
spinner.fail('Execution failed');
|
|
3132
|
+
console.error(chalk.red(err.message));
|
|
3133
|
+
process.exit(1);
|
|
3134
|
+
}
|
|
3135
|
+
});
|
|
3136
|
+
jobsCmd
|
|
3137
|
+
.command('logs <name>')
|
|
3138
|
+
.description('Show stdout from the latest (or specific) run')
|
|
3139
|
+
.option('-r, --run <runId>', 'Specific run ID')
|
|
3140
|
+
.action((name, options) => {
|
|
3141
|
+
let runId = options.run;
|
|
3142
|
+
if (!runId) {
|
|
3143
|
+
const latest = getLatestRun(name);
|
|
3144
|
+
if (!latest) {
|
|
3145
|
+
console.log(chalk.yellow(`No runs found for job '${name}'`));
|
|
3146
|
+
return;
|
|
3147
|
+
}
|
|
3148
|
+
runId = latest.runId;
|
|
3149
|
+
}
|
|
3150
|
+
const logPath = path.join(getRunDir(name, runId), 'stdout.log');
|
|
3151
|
+
if (!fs.existsSync(logPath)) {
|
|
3152
|
+
console.log(chalk.yellow(`Log not found: ${logPath}`));
|
|
3153
|
+
return;
|
|
3154
|
+
}
|
|
3155
|
+
console.log(chalk.gray(`Run: ${runId}\n`));
|
|
3156
|
+
console.log(fs.readFileSync(logPath, 'utf-8'));
|
|
3157
|
+
});
|
|
3158
|
+
jobsCmd
|
|
3159
|
+
.command('report <name>')
|
|
3160
|
+
.description('Show report from the latest (or specific) run')
|
|
3161
|
+
.option('-r, --run <runId>', 'Specific run ID')
|
|
3162
|
+
.action((name, options) => {
|
|
3163
|
+
let runId = options.run;
|
|
3164
|
+
if (!runId) {
|
|
3165
|
+
const latest = getLatestRun(name);
|
|
3166
|
+
if (!latest) {
|
|
3167
|
+
console.log(chalk.yellow(`No runs found for job '${name}'`));
|
|
3168
|
+
return;
|
|
3169
|
+
}
|
|
3170
|
+
runId = latest.runId;
|
|
3171
|
+
}
|
|
3172
|
+
const reportPath = path.join(getRunDir(name, runId), 'report.md');
|
|
3173
|
+
if (!fs.existsSync(reportPath)) {
|
|
3174
|
+
console.log(chalk.yellow(`No report found for run ${runId}`));
|
|
3175
|
+
console.log(chalk.gray(` Reports are extracted from agent output on completion`));
|
|
3176
|
+
return;
|
|
3177
|
+
}
|
|
3178
|
+
console.log(chalk.gray(`Run: ${runId}\n`));
|
|
3179
|
+
console.log(fs.readFileSync(reportPath, 'utf-8'));
|
|
3180
|
+
});
|
|
3181
|
+
jobsCmd
|
|
3182
|
+
.command('enable <name>')
|
|
3183
|
+
.description('Enable a job')
|
|
3184
|
+
.action((name) => {
|
|
3185
|
+
try {
|
|
3186
|
+
setJobEnabled(name, true);
|
|
3187
|
+
console.log(chalk.green(`Job '${name}' enabled`));
|
|
3188
|
+
if (isDaemonRunning()) {
|
|
3189
|
+
signalDaemonReload();
|
|
3190
|
+
console.log(chalk.gray('Daemon reloaded'));
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
catch (err) {
|
|
3194
|
+
console.log(chalk.red(err.message));
|
|
3195
|
+
process.exit(1);
|
|
3196
|
+
}
|
|
3197
|
+
});
|
|
3198
|
+
jobsCmd
|
|
3199
|
+
.command('disable <name>')
|
|
3200
|
+
.description('Disable a job')
|
|
3201
|
+
.action((name) => {
|
|
3202
|
+
try {
|
|
3203
|
+
setJobEnabled(name, false);
|
|
3204
|
+
console.log(chalk.green(`Job '${name}' disabled`));
|
|
3205
|
+
if (isDaemonRunning()) {
|
|
3206
|
+
signalDaemonReload();
|
|
3207
|
+
console.log(chalk.gray('Daemon reloaded'));
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
catch (err) {
|
|
3211
|
+
console.log(chalk.red(err.message));
|
|
3212
|
+
process.exit(1);
|
|
3213
|
+
}
|
|
3214
|
+
});
|
|
3215
|
+
// =============================================================================
|
|
3216
|
+
// DRIVE COMMANDS
|
|
3217
|
+
// =============================================================================
|
|
3218
|
+
import { createDrive, readDrive, listDrives as listAllDrives, deleteDrive, updateDriveFrontmatter, driveExists, discoverDrivesFromRepo, installDriveFromSource, driveContentMatches, } from './lib/drives.js';
|
|
3219
|
+
import { runDriveServer } from './lib/drive-server.js';
|
|
3220
|
+
const driveCmd = program.command('drive').description('Manage context drives');
|
|
3221
|
+
driveCmd
|
|
3222
|
+
.command('create <name>')
|
|
3223
|
+
.description('Create a new empty drive')
|
|
3224
|
+
.option('-d, --description <desc>', 'Drive description')
|
|
3225
|
+
.option('-p, --project <path>', 'Link to a project directory')
|
|
3226
|
+
.action((name, options) => {
|
|
3227
|
+
try {
|
|
3228
|
+
const filePath = createDrive(name, options.description);
|
|
3229
|
+
if (options.project) {
|
|
3230
|
+
updateDriveFrontmatter(name, { project: path.resolve(options.project) });
|
|
3231
|
+
}
|
|
3232
|
+
console.log(chalk.green(`Drive '${name}' created`));
|
|
3233
|
+
console.log(chalk.gray(` ${filePath}`));
|
|
3234
|
+
}
|
|
3235
|
+
catch (err) {
|
|
3236
|
+
console.log(chalk.red(err.message));
|
|
3237
|
+
process.exit(1);
|
|
3238
|
+
}
|
|
3239
|
+
});
|
|
3240
|
+
driveCmd
|
|
3241
|
+
.command('list')
|
|
3242
|
+
.description('List all drives')
|
|
3243
|
+
.action(() => {
|
|
3244
|
+
const drives = listAllDrives();
|
|
3245
|
+
if (drives.length === 0) {
|
|
3246
|
+
console.log(chalk.gray('No drives configured'));
|
|
3247
|
+
console.log(chalk.gray(' Create a drive: agents drive create <name>'));
|
|
3248
|
+
return;
|
|
3249
|
+
}
|
|
3250
|
+
console.log(chalk.bold('Context Drives\n'));
|
|
3251
|
+
const header = ` ${'Name'.padEnd(24)} ${'Description'.padEnd(40)} ${'Project'}`;
|
|
3252
|
+
console.log(chalk.gray(header));
|
|
3253
|
+
console.log(chalk.gray(' ' + '-'.repeat(90)));
|
|
3254
|
+
for (const drive of drives) {
|
|
3255
|
+
const desc = (drive.description || '-').slice(0, 38);
|
|
3256
|
+
const proj = drive.project || '-';
|
|
3257
|
+
console.log(` ${chalk.cyan(drive.name.padEnd(24))} ${desc.padEnd(40)} ${chalk.gray(proj)}`);
|
|
3258
|
+
}
|
|
3259
|
+
console.log();
|
|
3260
|
+
});
|
|
3261
|
+
driveCmd
|
|
3262
|
+
.command('info <name>')
|
|
3263
|
+
.description('Show drive metadata and content preview')
|
|
3264
|
+
.action((name) => {
|
|
3265
|
+
const drive = readDrive(name);
|
|
3266
|
+
if (!drive) {
|
|
3267
|
+
console.log(chalk.red(`Drive '${name}' not found`));
|
|
3268
|
+
process.exit(1);
|
|
3269
|
+
}
|
|
3270
|
+
console.log(chalk.bold(`Drive: ${drive.frontmatter.name}\n`));
|
|
3271
|
+
if (drive.frontmatter.description) {
|
|
3272
|
+
console.log(chalk.gray(` Description: ${drive.frontmatter.description}`));
|
|
3273
|
+
}
|
|
3274
|
+
if (drive.frontmatter.project) {
|
|
3275
|
+
console.log(chalk.gray(` Project: ${drive.frontmatter.project}`));
|
|
3276
|
+
}
|
|
3277
|
+
if (drive.frontmatter.repo) {
|
|
3278
|
+
console.log(chalk.gray(` Repo: ${drive.frontmatter.repo}`));
|
|
3279
|
+
}
|
|
3280
|
+
if (drive.frontmatter.updated) {
|
|
3281
|
+
console.log(chalk.gray(` Updated: ${drive.frontmatter.updated}`));
|
|
3282
|
+
}
|
|
3283
|
+
console.log(chalk.gray(` Path: ${drive.path}`));
|
|
3284
|
+
const content = drive.content.trim();
|
|
3285
|
+
if (content) {
|
|
3286
|
+
console.log(chalk.bold('\nContent:\n'));
|
|
3287
|
+
const lines = content.split('\n');
|
|
3288
|
+
const preview = lines.slice(0, 30);
|
|
3289
|
+
for (const line of preview) {
|
|
3290
|
+
console.log(` ${line}`);
|
|
3291
|
+
}
|
|
3292
|
+
if (lines.length > 30) {
|
|
3293
|
+
console.log(chalk.gray(`\n ... ${lines.length - 30} more lines`));
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
console.log();
|
|
3297
|
+
});
|
|
3298
|
+
driveCmd
|
|
3299
|
+
.command('edit <name>')
|
|
3300
|
+
.description('Open drive in $EDITOR')
|
|
3301
|
+
.action((name) => {
|
|
3302
|
+
const drive = readDrive(name);
|
|
3303
|
+
if (!drive) {
|
|
3304
|
+
console.log(chalk.red(`Drive '${name}' not found`));
|
|
3305
|
+
process.exit(1);
|
|
3306
|
+
}
|
|
3307
|
+
const editor = process.env.EDITOR || process.env.VISUAL || 'vi';
|
|
3308
|
+
const { execSync } = require('child_process');
|
|
3309
|
+
try {
|
|
3310
|
+
execSync(`${editor} "${drive.path}"`, { stdio: 'inherit' });
|
|
3311
|
+
}
|
|
3312
|
+
catch {
|
|
3313
|
+
console.log(chalk.red(`Failed to open editor: ${editor}`));
|
|
3314
|
+
process.exit(1);
|
|
3315
|
+
}
|
|
3316
|
+
});
|
|
3317
|
+
driveCmd
|
|
3318
|
+
.command('delete <name>')
|
|
3319
|
+
.description('Delete a drive')
|
|
3320
|
+
.action(async (name) => {
|
|
3321
|
+
if (!driveExists(name)) {
|
|
3322
|
+
console.log(chalk.red(`Drive '${name}' not found`));
|
|
3323
|
+
process.exit(1);
|
|
3324
|
+
}
|
|
3325
|
+
try {
|
|
3326
|
+
const answer = await confirm({ message: `Delete drive '${name}'?` });
|
|
3327
|
+
if (!answer)
|
|
3328
|
+
return;
|
|
3329
|
+
}
|
|
3330
|
+
catch (err) {
|
|
3331
|
+
if (isPromptCancelled(err))
|
|
3332
|
+
return;
|
|
3333
|
+
throw err;
|
|
3334
|
+
}
|
|
3335
|
+
deleteDrive(name);
|
|
3336
|
+
console.log(chalk.green(`Drive '${name}' deleted`));
|
|
3337
|
+
});
|
|
3338
|
+
driveCmd
|
|
3339
|
+
.command('link <name> <path>')
|
|
3340
|
+
.description('Link a drive to a project directory')
|
|
3341
|
+
.action((name, projectPath) => {
|
|
3342
|
+
if (!driveExists(name)) {
|
|
3343
|
+
console.log(chalk.red(`Drive '${name}' not found`));
|
|
3344
|
+
process.exit(1);
|
|
3345
|
+
}
|
|
3346
|
+
const resolved = path.resolve(projectPath);
|
|
3347
|
+
if (!fs.existsSync(resolved)) {
|
|
3348
|
+
console.log(chalk.red(`Directory not found: ${resolved}`));
|
|
3349
|
+
process.exit(1);
|
|
3350
|
+
}
|
|
3351
|
+
updateDriveFrontmatter(name, { project: resolved });
|
|
3352
|
+
console.log(chalk.green(`Drive '${name}' linked to ${resolved}`));
|
|
3353
|
+
});
|
|
3354
|
+
driveCmd
|
|
3355
|
+
.command('sync')
|
|
3356
|
+
.description('Sync drives with .agents repo')
|
|
3357
|
+
.action(async () => {
|
|
3358
|
+
const source = await ensureSource();
|
|
3359
|
+
const repoPath = getRepoLocalPath(source);
|
|
3360
|
+
const discovered = discoverDrivesFromRepo(repoPath);
|
|
3361
|
+
if (discovered.length === 0) {
|
|
3362
|
+
console.log(chalk.gray('No drives found in repo'));
|
|
3363
|
+
return;
|
|
3364
|
+
}
|
|
3365
|
+
let installed = 0;
|
|
3366
|
+
let skipped = 0;
|
|
3367
|
+
for (const d of discovered) {
|
|
3368
|
+
if (driveExists(d.name) && driveContentMatches(d.name, d.path)) {
|
|
3369
|
+
skipped++;
|
|
3370
|
+
continue;
|
|
3371
|
+
}
|
|
3372
|
+
const result = installDriveFromSource(d.path, d.name);
|
|
3373
|
+
if (result.success) {
|
|
3374
|
+
installed++;
|
|
3375
|
+
console.log(chalk.green(` + ${d.name}`));
|
|
3376
|
+
}
|
|
3377
|
+
else {
|
|
3378
|
+
console.log(chalk.red(` x ${d.name}: ${result.error}`));
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
if (installed === 0 && skipped > 0) {
|
|
3382
|
+
console.log(chalk.gray(`All ${skipped} drives up to date`));
|
|
3383
|
+
}
|
|
3384
|
+
else if (installed > 0) {
|
|
3385
|
+
console.log(chalk.green(`\nSynced ${installed} drive(s)`));
|
|
3386
|
+
}
|
|
3387
|
+
});
|
|
3388
|
+
driveCmd
|
|
3389
|
+
.command('generate <name>')
|
|
3390
|
+
.description('Run a drive generation job now')
|
|
3391
|
+
.action(async (name) => {
|
|
3392
|
+
if (!driveExists(name)) {
|
|
3393
|
+
console.log(chalk.red(`Drive '${name}' not found`));
|
|
3394
|
+
process.exit(1);
|
|
3395
|
+
}
|
|
3396
|
+
const jobName = `update-drive-${name}`;
|
|
3397
|
+
const job = readJob(jobName);
|
|
3398
|
+
if (!job) {
|
|
3399
|
+
console.log(chalk.red(`No generation job found for drive '${name}'`));
|
|
3400
|
+
console.log(chalk.gray(` Expected job name: ${jobName}`));
|
|
3401
|
+
console.log(chalk.gray(` Create one at: ~/.agents/jobs/${jobName}.yml`));
|
|
3402
|
+
process.exit(1);
|
|
3403
|
+
}
|
|
3404
|
+
console.log(chalk.bold(`Generating drive '${name}' (job: ${jobName})\n`));
|
|
3405
|
+
const spinner = ora('Executing...').start();
|
|
3406
|
+
try {
|
|
3407
|
+
const result = await executeJob(job);
|
|
3408
|
+
if (result.meta.status === 'completed') {
|
|
3409
|
+
spinner.succeed(`Drive '${name}' updated`);
|
|
3410
|
+
}
|
|
3411
|
+
else if (result.meta.status === 'timeout') {
|
|
3412
|
+
spinner.warn(`Generation timed out after ${job.timeout}`);
|
|
3413
|
+
}
|
|
3414
|
+
else {
|
|
3415
|
+
spinner.fail(`Generation failed (exit code: ${result.meta.exitCode})`);
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
catch (err) {
|
|
3419
|
+
spinner.fail('Generation failed');
|
|
3420
|
+
console.error(chalk.red(err.message));
|
|
3421
|
+
process.exit(1);
|
|
3422
|
+
}
|
|
3423
|
+
});
|
|
3424
|
+
driveCmd
|
|
3425
|
+
.command('serve')
|
|
3426
|
+
.description('Start the drive MCP server (stdio)')
|
|
3427
|
+
.action(async () => {
|
|
3428
|
+
await runDriveServer();
|
|
3429
|
+
});
|
|
2409
3430
|
async function showWhatsNew(fromVersion, toVersion) {
|
|
2410
3431
|
try {
|
|
2411
3432
|
// Fetch changelog from npm package
|