@swarmify/agents-cli 1.5.14 → 1.5.16
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 +86 -291
- package/dist/index.js +879 -197
- package/dist/index.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/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/manifest.js +1 -1
- package/dist/lib/manifest.js.map +1 -1
- 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 +181 -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/types.d.ts +10 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/versions.d.ts +84 -0
- package/dist/lib/versions.d.ts.map +1 -0
- package/dist/lib/versions.js +297 -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,9 +16,9 @@ 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
|
-
import { readState,
|
|
21
|
+
import { readState, getRepoLocalPath, getScope, setScope, removeScope, getScopesByPriority, getScopePriority, } from './lib/state.js';
|
|
22
22
|
import { SCOPE_PRIORITIES, DEFAULT_SYSTEM_REPO } from './lib/types.js';
|
|
23
23
|
import { cloneRepo, parseSource } from './lib/git.js';
|
|
24
24
|
import { discoverCommands, resolveCommandSource, installCommand, uninstallCommand, listInstalledCommandsWithScope, promoteCommandToUser, commandExists, commandContentMatches, } from './lib/commands.js';
|
|
@@ -27,6 +27,8 @@ import { discoverSkillsFromRepo, installSkill, uninstallSkill, listInstalledSkil
|
|
|
27
27
|
import { discoverInstructionsFromRepo, resolveInstructionsSource, installInstructions, uninstallInstructions, listInstalledInstructionsWithScope, promoteInstructionsToUser, instructionsExists, instructionsContentMatches, getInstructionsContent, } from './lib/instructions.js';
|
|
28
28
|
import { DEFAULT_REGISTRIES } from './lib/types.js';
|
|
29
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';
|
|
30
32
|
const program = new Command();
|
|
31
33
|
/**
|
|
32
34
|
* Ensure at least one scope is configured.
|
|
@@ -387,6 +389,8 @@ program
|
|
|
387
389
|
const allSkills = discoverSkillsFromRepo(localPath);
|
|
388
390
|
const discoveredHooks = discoverHooksFromRepo(localPath);
|
|
389
391
|
const allInstructions = discoverInstructionsFromRepo(localPath);
|
|
392
|
+
const allDiscoveredJobs = discoverJobsFromRepo(localPath);
|
|
393
|
+
const allDiscoveredDrives = discoverDrivesFromRepo(localPath);
|
|
390
394
|
// Determine which agents to sync
|
|
391
395
|
const cliStates = await getAllCliStates();
|
|
392
396
|
let selectedAgents;
|
|
@@ -396,7 +400,7 @@ program
|
|
|
396
400
|
console.log(chalk.gray(`\nFiltering for ${AGENTS[agentFilter].name} only\n`));
|
|
397
401
|
}
|
|
398
402
|
else if (options.yes || options.force) {
|
|
399
|
-
selectedAgents = (manifest?.defaults?.agents ||
|
|
403
|
+
selectedAgents = (manifest?.defaults?.agents || ALL_AGENT_IDS);
|
|
400
404
|
}
|
|
401
405
|
else {
|
|
402
406
|
const installedAgents = ALL_AGENT_IDS.filter((id) => cliStates[id]?.installed || id === 'cursor');
|
|
@@ -405,7 +409,7 @@ program
|
|
|
405
409
|
choices: installedAgents.map((id) => ({
|
|
406
410
|
name: AGENTS[id].name,
|
|
407
411
|
value: id,
|
|
408
|
-
checked: (manifest?.defaults?.agents ||
|
|
412
|
+
checked: (manifest?.defaults?.agents || ALL_AGENT_IDS).includes(id),
|
|
409
413
|
})),
|
|
410
414
|
});
|
|
411
415
|
}
|
|
@@ -544,12 +548,36 @@ program
|
|
|
544
548
|
existingItems.push({ type: 'instructions', name: AGENTS[instr.agentId].instructionsFile, agents: [instr.agentId], isNew: false });
|
|
545
549
|
}
|
|
546
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
|
+
}
|
|
547
575
|
// Display overview
|
|
548
576
|
console.log(chalk.bold('\nOverview\n'));
|
|
549
577
|
const formatAgentList = (agents) => agents.map((id) => AGENTS[id].name).join(', ');
|
|
550
578
|
if (newItems.length > 0) {
|
|
551
579
|
console.log(chalk.green(' NEW (will install):\n'));
|
|
552
|
-
const byType = { command: [], skill: [], hook: [], mcp: [], instructions: [] };
|
|
580
|
+
const byType = { command: [], skill: [], hook: [], mcp: [], instructions: [], job: [], drive: [] };
|
|
553
581
|
for (const item of newItems)
|
|
554
582
|
byType[item.type].push(item);
|
|
555
583
|
if (byType.command.length > 0) {
|
|
@@ -582,11 +610,23 @@ program
|
|
|
582
610
|
console.log(` ${chalk.cyan(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
|
|
583
611
|
}
|
|
584
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
|
+
}
|
|
585
625
|
console.log();
|
|
586
626
|
}
|
|
587
627
|
if (upToDateItems.length > 0) {
|
|
588
628
|
console.log(chalk.gray(' UP TO DATE (no changes):\n'));
|
|
589
|
-
const byType = { command: [], skill: [], hook: [], mcp: [], instructions: [] };
|
|
629
|
+
const byType = { command: [], skill: [], hook: [], mcp: [], instructions: [], job: [], drive: [] };
|
|
590
630
|
for (const item of upToDateItems)
|
|
591
631
|
byType[item.type].push(item);
|
|
592
632
|
if (byType.command.length > 0) {
|
|
@@ -613,11 +653,23 @@ program
|
|
|
613
653
|
console.log(` ${chalk.dim(item.name.padEnd(20))} ${chalk.dim(formatAgentList(item.agents))}`);
|
|
614
654
|
}
|
|
615
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
|
+
}
|
|
616
668
|
console.log();
|
|
617
669
|
}
|
|
618
670
|
if (existingItems.length > 0) {
|
|
619
671
|
console.log(chalk.yellow(' EXISTING (conflicts):\n'));
|
|
620
|
-
const byType = { command: [], skill: [], hook: [], mcp: [], instructions: [] };
|
|
672
|
+
const byType = { command: [], skill: [], hook: [], mcp: [], instructions: [], job: [], drive: [] };
|
|
621
673
|
for (const item of existingItems)
|
|
622
674
|
byType[item.type].push(item);
|
|
623
675
|
if (byType.command.length > 0) {
|
|
@@ -650,6 +702,18 @@ program
|
|
|
650
702
|
console.log(` ${chalk.yellow(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
|
|
651
703
|
}
|
|
652
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
|
+
}
|
|
653
717
|
console.log();
|
|
654
718
|
}
|
|
655
719
|
if (newItems.length === 0 && existingItems.length === 0) {
|
|
@@ -722,8 +786,8 @@ program
|
|
|
722
786
|
}
|
|
723
787
|
// Install new items (no conflicts)
|
|
724
788
|
console.log();
|
|
725
|
-
let installed = { commands: 0, skills: 0, hooks: 0, mcps: 0, instructions: 0 };
|
|
726
|
-
let skipped = { commands: 0, skills: 0, hooks: 0, mcps: 0, instructions: 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 };
|
|
727
791
|
// Install commands
|
|
728
792
|
const cmdSpinner = ora('Installing commands...').start();
|
|
729
793
|
for (const item of [...newItems, ...existingItems].filter((i) => i.type === 'command')) {
|
|
@@ -853,6 +917,71 @@ program
|
|
|
853
917
|
instrSpinner.info('No instructions to install');
|
|
854
918
|
}
|
|
855
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
|
+
}
|
|
856
985
|
// Sync CLI versions (user scope only)
|
|
857
986
|
if (isUserScope && !options.skipClis && manifest?.clis) {
|
|
858
987
|
const cliSpinner = ora('Checking CLI versions...').start();
|
|
@@ -1075,7 +1204,7 @@ commandsCmd
|
|
|
1075
1204
|
spinner.succeed(`Found ${commands.length} commands`);
|
|
1076
1205
|
const agents = options.agents
|
|
1077
1206
|
? options.agents.split(',')
|
|
1078
|
-
:
|
|
1207
|
+
: ALL_AGENT_IDS;
|
|
1079
1208
|
const cliStates = await getAllCliStates();
|
|
1080
1209
|
for (const command of commands) {
|
|
1081
1210
|
console.log(`\n ${chalk.cyan(command.name)}: ${command.description}`);
|
|
@@ -1404,7 +1533,7 @@ skillsCmd
|
|
|
1404
1533
|
choices: SKILLS_CAPABLE_AGENTS.filter((id) => cliStates[id]?.installed || id === 'cursor').map((id) => ({
|
|
1405
1534
|
name: AGENTS[id].name,
|
|
1406
1535
|
value: id,
|
|
1407
|
-
checked:
|
|
1536
|
+
checked: true,
|
|
1408
1537
|
})),
|
|
1409
1538
|
});
|
|
1410
1539
|
if (agents.length === 0) {
|
|
@@ -1924,204 +2053,290 @@ mcpCmd
|
|
|
1924
2053
|
}
|
|
1925
2054
|
});
|
|
1926
2055
|
// =============================================================================
|
|
1927
|
-
//
|
|
2056
|
+
// VERSION MANAGEMENT COMMANDS (add, remove, use, list, upgrade)
|
|
1928
2057
|
// =============================================================================
|
|
1929
|
-
|
|
1930
|
-
.command('
|
|
1931
|
-
.description('
|
|
1932
|
-
|
|
1933
|
-
.
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
const agent = AGENTS[agentId];
|
|
1942
|
-
const state = states[agentId];
|
|
1943
|
-
if (state?.installed) {
|
|
1944
|
-
console.log(` ${agent.name.padEnd(14)} ${chalk.green(state.version || 'installed')}`);
|
|
1945
|
-
if (state.path) {
|
|
1946
|
-
console.log(` ${''.padEnd(14)} ${chalk.gray(state.path)}`);
|
|
1947
|
-
}
|
|
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;
|
|
1948
2070
|
}
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
cliCmd
|
|
1955
|
-
.command('add <agents...>')
|
|
1956
|
-
.description('Install agent CLI(s)')
|
|
1957
|
-
.option('-v, --version <version>', 'Version to install', 'latest')
|
|
1958
|
-
.option('--manifest-only', 'Only add to manifest, do not install')
|
|
1959
|
-
.action(async (agents, options) => {
|
|
1960
|
-
const validAgents = [];
|
|
1961
|
-
for (const agent of agents) {
|
|
1962
|
-
const agentId = agent.toLowerCase();
|
|
1963
|
-
if (!AGENTS[agentId]) {
|
|
1964
|
-
console.log(chalk.red(`Unknown agent: ${agent}`));
|
|
1965
|
-
console.log(chalk.gray(`Available: ${ALL_AGENT_IDS.join(', ')}`));
|
|
1966
|
-
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;
|
|
1967
2076
|
}
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
spinner.succeed(`Installed ${agentConfig.name}@${version}`);
|
|
1984
|
-
}
|
|
1985
|
-
catch (err) {
|
|
1986
|
-
spinner.fail(`Failed to install ${agentConfig.name}`);
|
|
1987
|
-
console.error(chalk.gray(err.message));
|
|
1988
|
-
continue;
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
else if (installScript) {
|
|
1992
|
-
const spinner = ora(`Installing ${agentConfig.name}...`).start();
|
|
1993
|
-
try {
|
|
1994
|
-
await execAsync(installScript, { shell: '/bin/bash' });
|
|
1995
|
-
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}`));
|
|
1996
2092
|
}
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
console.
|
|
2000
|
-
|
|
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();
|
|
2001
2099
|
}
|
|
2002
2100
|
}
|
|
2003
2101
|
else {
|
|
2004
|
-
|
|
2102
|
+
spinner.fail(`Failed to install ${agentConfig.name}@${version}`);
|
|
2103
|
+
console.error(chalk.gray(result.error || 'Unknown error'));
|
|
2104
|
+
continue;
|
|
2005
2105
|
}
|
|
2006
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
|
+
}
|
|
2007
2125
|
}
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
version: version,
|
|
2017
|
-
};
|
|
2018
|
-
}
|
|
2019
|
-
writeManifest(localPath, manifest);
|
|
2020
|
-
if (validAgents.length === 1) {
|
|
2021
|
-
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];
|
|
2022
2134
|
}
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
.
|
|
2029
|
-
.
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
console.log(chalk.gray(`Available: ${ALL_AGENT_IDS.join(', ')}`));
|
|
2038
|
-
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;
|
|
2039
2149
|
}
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
const pkg = agentConfig.npmPackage;
|
|
2048
|
-
if (!options.manifestOnly) {
|
|
2049
|
-
if (!pkg) {
|
|
2050
|
-
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`));
|
|
2051
2157
|
}
|
|
2052
|
-
else
|
|
2053
|
-
|
|
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`));
|
|
2054
2168
|
}
|
|
2055
2169
|
else {
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
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);
|
|
2060
2176
|
}
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
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`));
|
|
2064
2188
|
}
|
|
2065
2189
|
}
|
|
2066
2190
|
}
|
|
2067
2191
|
}
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
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(', ')}`));
|
|
2076
2211
|
}
|
|
2212
|
+
return;
|
|
2077
2213
|
}
|
|
2078
|
-
if (
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
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)`));
|
|
2082
2279
|
}
|
|
2083
2280
|
else {
|
|
2084
|
-
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'));
|
|
2085
2283
|
}
|
|
2086
2284
|
}
|
|
2087
2285
|
});
|
|
2088
|
-
|
|
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
|
|
2089
2303
|
.command('upgrade [agent]')
|
|
2090
|
-
.description('Upgrade agent CLI(s) to version
|
|
2091
|
-
.option('-
|
|
2092
|
-
.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')
|
|
2093
2306
|
.action(async (agent, options) => {
|
|
2094
|
-
const scopeName = options.scope;
|
|
2095
|
-
const scope = getScope(scopeName);
|
|
2096
|
-
const localPath = scope ? getRepoLocalPath(scope.source) : null;
|
|
2097
|
-
const manifest = localPath ? readManifest(localPath) : null;
|
|
2098
2307
|
const agentsToUpgrade = agent
|
|
2099
2308
|
? [agent.toLowerCase()]
|
|
2100
|
-
: ALL_AGENT_IDS.filter((id) =>
|
|
2309
|
+
: ALL_AGENT_IDS.filter((id) => listInstalledVersions(id).length > 0);
|
|
2101
2310
|
if (agentsToUpgrade.length === 0) {
|
|
2102
|
-
console.log(chalk.yellow('No CLIs
|
|
2311
|
+
console.log(chalk.yellow('No agent CLIs installed. Run: agents add <agent>@<version>'));
|
|
2103
2312
|
return;
|
|
2104
2313
|
}
|
|
2105
|
-
const { exec } = await import('child_process');
|
|
2106
|
-
const { promisify } = await import('util');
|
|
2107
|
-
const execAsync = promisify(exec);
|
|
2108
2314
|
for (const agentId of agentsToUpgrade) {
|
|
2109
2315
|
const agentConfig = AGENTS[agentId];
|
|
2110
2316
|
if (!agentConfig) {
|
|
2111
2317
|
console.log(chalk.red(`Unknown agent: ${agentId}`));
|
|
2112
2318
|
continue;
|
|
2113
2319
|
}
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2320
|
+
// Determine target version
|
|
2321
|
+
let targetVersion = 'latest';
|
|
2322
|
+
if (options.project) {
|
|
2323
|
+
const projectVersion = getProjectVersionFromCwd(agentId);
|
|
2324
|
+
if (projectVersion) {
|
|
2325
|
+
targetVersion = projectVersion;
|
|
2326
|
+
}
|
|
2121
2327
|
}
|
|
2122
|
-
|
|
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 {
|
|
2123
2338
|
spinner.fail(`Failed to upgrade ${agentConfig.name}`);
|
|
2124
|
-
console.error(chalk.gray(
|
|
2339
|
+
console.error(chalk.gray(result.error || 'Unknown error'));
|
|
2125
2340
|
}
|
|
2126
2341
|
}
|
|
2127
2342
|
});
|
|
@@ -2265,31 +2480,6 @@ repoCmd
|
|
|
2265
2480
|
console.log(chalk.green('\nSync complete.'));
|
|
2266
2481
|
});
|
|
2267
2482
|
// =============================================================================
|
|
2268
|
-
// INIT COMMAND
|
|
2269
|
-
// =============================================================================
|
|
2270
|
-
program
|
|
2271
|
-
.command('init')
|
|
2272
|
-
.description('Initialize a new .agents repo')
|
|
2273
|
-
.action(() => {
|
|
2274
|
-
ensureAgentsDir();
|
|
2275
|
-
const manifest = createDefaultManifest();
|
|
2276
|
-
console.log(chalk.bold('\nDefault agents.yaml:\n'));
|
|
2277
|
-
console.log(chalk.gray('clis:'));
|
|
2278
|
-
console.log(chalk.gray(' claude:'));
|
|
2279
|
-
console.log(chalk.gray(' package: "@anthropic-ai/claude-code"'));
|
|
2280
|
-
console.log(chalk.gray(' version: "latest"'));
|
|
2281
|
-
console.log(chalk.gray(' codex:'));
|
|
2282
|
-
console.log(chalk.gray(' package: "@openai/codex"'));
|
|
2283
|
-
console.log(chalk.gray(' version: "latest"'));
|
|
2284
|
-
console.log();
|
|
2285
|
-
console.log(chalk.green('Create a new repo with this structure:'));
|
|
2286
|
-
console.log(chalk.gray(' .agents/'));
|
|
2287
|
-
console.log(chalk.gray(' agents.yaml'));
|
|
2288
|
-
console.log(chalk.gray(' shared/commands/'));
|
|
2289
|
-
console.log(chalk.gray(' claude/hooks/'));
|
|
2290
|
-
console.log();
|
|
2291
|
-
});
|
|
2292
|
-
// =============================================================================
|
|
2293
2483
|
// REGISTRY COMMANDS
|
|
2294
2484
|
// =============================================================================
|
|
2295
2485
|
const registryCmd = program
|
|
@@ -2472,11 +2662,11 @@ program
|
|
|
2472
2662
|
}
|
|
2473
2663
|
});
|
|
2474
2664
|
// =============================================================================
|
|
2475
|
-
//
|
|
2665
|
+
// INSTALL COMMAND (unified package installation)
|
|
2476
2666
|
// =============================================================================
|
|
2477
2667
|
program
|
|
2478
|
-
.command('
|
|
2479
|
-
.description('
|
|
2668
|
+
.command('install <identifier>')
|
|
2669
|
+
.description('Install a package (mcp:name, skill:user/repo, or gh:user/repo)')
|
|
2480
2670
|
.option('-a, --agents <list>', 'Comma-separated agents to install to')
|
|
2481
2671
|
.action(async (identifier, options) => {
|
|
2482
2672
|
const spinner = ora('Resolving package...').start();
|
|
@@ -2577,7 +2767,7 @@ program
|
|
|
2577
2767
|
console.log(` ${hooks.shared.length + Object.values(hooks.agentSpecific).flat().length} hooks`);
|
|
2578
2768
|
const agents = options.agents
|
|
2579
2769
|
? options.agents.split(',')
|
|
2580
|
-
:
|
|
2770
|
+
: ALL_AGENT_IDS;
|
|
2581
2771
|
const gitCliStates = await getAllCliStates();
|
|
2582
2772
|
// Install commands
|
|
2583
2773
|
if (hasCommands) {
|
|
@@ -2638,7 +2828,7 @@ program
|
|
|
2638
2828
|
});
|
|
2639
2829
|
// Self-upgrade command
|
|
2640
2830
|
program
|
|
2641
|
-
.command('upgrade')
|
|
2831
|
+
.command('self-upgrade')
|
|
2642
2832
|
.description('Upgrade agents-cli to the latest version')
|
|
2643
2833
|
.action(async () => {
|
|
2644
2834
|
const spinner = ora('Checking for updates...').start();
|
|
@@ -2700,6 +2890,498 @@ program
|
|
|
2700
2890
|
process.exit(1);
|
|
2701
2891
|
}
|
|
2702
2892
|
});
|
|
2893
|
+
// =============================================================================
|
|
2894
|
+
// DAEMON COMMANDS
|
|
2895
|
+
// =============================================================================
|
|
2896
|
+
import { startDaemon, stopDaemon, isDaemonRunning, readDaemonPid, readDaemonLog, runDaemon, signalDaemonReload, } from './lib/daemon.js';
|
|
2897
|
+
import { listJobs as listAllJobs, readJob, validateJob, writeJob, setJobEnabled, getLatestRun, getRunDir, discoverJobsFromRepo, jobExists, jobContentMatches, installJobFromSource, } from './lib/jobs.js';
|
|
2898
|
+
import { executeJob } from './lib/runner.js';
|
|
2899
|
+
import { JobScheduler } from './lib/scheduler.js';
|
|
2900
|
+
const daemonCmd = program.command('daemon').description('Manage the jobs daemon');
|
|
2901
|
+
daemonCmd
|
|
2902
|
+
.command('start')
|
|
2903
|
+
.description('Start the daemon')
|
|
2904
|
+
.action(() => {
|
|
2905
|
+
const result = startDaemon();
|
|
2906
|
+
if (result.method === 'already-running') {
|
|
2907
|
+
console.log(chalk.yellow(`Daemon already running (PID: ${result.pid})`));
|
|
2908
|
+
}
|
|
2909
|
+
else {
|
|
2910
|
+
console.log(chalk.green(`Daemon started (PID: ${result.pid}, method: ${result.method})`));
|
|
2911
|
+
}
|
|
2912
|
+
});
|
|
2913
|
+
daemonCmd
|
|
2914
|
+
.command('stop')
|
|
2915
|
+
.description('Stop the daemon')
|
|
2916
|
+
.action(() => {
|
|
2917
|
+
if (!isDaemonRunning()) {
|
|
2918
|
+
console.log(chalk.yellow('Daemon is not running'));
|
|
2919
|
+
return;
|
|
2920
|
+
}
|
|
2921
|
+
stopDaemon();
|
|
2922
|
+
console.log(chalk.green('Daemon stopped'));
|
|
2923
|
+
});
|
|
2924
|
+
daemonCmd
|
|
2925
|
+
.command('status')
|
|
2926
|
+
.description('Show daemon status')
|
|
2927
|
+
.action(() => {
|
|
2928
|
+
const running = isDaemonRunning();
|
|
2929
|
+
const pid = readDaemonPid();
|
|
2930
|
+
console.log(chalk.bold('Daemon Status\n'));
|
|
2931
|
+
console.log(` Status: ${running ? chalk.green('running') : chalk.gray('stopped')}`);
|
|
2932
|
+
if (pid)
|
|
2933
|
+
console.log(` PID: ${pid}`);
|
|
2934
|
+
const jobs = listAllJobs();
|
|
2935
|
+
const enabled = jobs.filter((j) => j.enabled);
|
|
2936
|
+
console.log(` Jobs: ${enabled.length} enabled / ${jobs.length} total`);
|
|
2937
|
+
if (running && enabled.length > 0) {
|
|
2938
|
+
const scheduler = new JobScheduler(async () => { });
|
|
2939
|
+
scheduler.loadAll();
|
|
2940
|
+
const scheduled = scheduler.listScheduled();
|
|
2941
|
+
console.log(chalk.bold('\n Scheduled Jobs\n'));
|
|
2942
|
+
for (const job of scheduled) {
|
|
2943
|
+
const next = job.nextRun ? job.nextRun.toLocaleString() : 'unknown';
|
|
2944
|
+
console.log(` ${chalk.cyan(job.name.padEnd(24))} next: ${chalk.gray(next)}`);
|
|
2945
|
+
}
|
|
2946
|
+
scheduler.stopAll();
|
|
2947
|
+
}
|
|
2948
|
+
});
|
|
2949
|
+
daemonCmd
|
|
2950
|
+
.command('logs')
|
|
2951
|
+
.description('Show daemon logs')
|
|
2952
|
+
.option('-n, --lines <number>', 'Number of lines to show', '50')
|
|
2953
|
+
.option('-f, --follow', 'Follow log output')
|
|
2954
|
+
.action(async (options) => {
|
|
2955
|
+
if (options.follow) {
|
|
2956
|
+
const { exec: execCb } = await import('child_process');
|
|
2957
|
+
const { getAgentsDir } = await import('./lib/state.js');
|
|
2958
|
+
const logPath = path.join(getAgentsDir(), 'daemon.log');
|
|
2959
|
+
const child = execCb(`tail -f "${logPath}"`);
|
|
2960
|
+
child.stdout?.pipe(process.stdout);
|
|
2961
|
+
child.stderr?.pipe(process.stderr);
|
|
2962
|
+
child.on('exit', () => process.exit(0));
|
|
2963
|
+
process.on('SIGINT', () => { child.kill(); process.exit(0); });
|
|
2964
|
+
return;
|
|
2965
|
+
}
|
|
2966
|
+
const lines = parseInt(options.lines, 10);
|
|
2967
|
+
const output = readDaemonLog(lines);
|
|
2968
|
+
if (output) {
|
|
2969
|
+
console.log(output);
|
|
2970
|
+
}
|
|
2971
|
+
else {
|
|
2972
|
+
console.log(chalk.gray('No daemon logs'));
|
|
2973
|
+
}
|
|
2974
|
+
});
|
|
2975
|
+
daemonCmd
|
|
2976
|
+
.command('_run')
|
|
2977
|
+
.description('Run daemon in foreground (internal)')
|
|
2978
|
+
.action(async () => {
|
|
2979
|
+
await runDaemon();
|
|
2980
|
+
});
|
|
2981
|
+
// =============================================================================
|
|
2982
|
+
// JOBS COMMANDS
|
|
2983
|
+
// =============================================================================
|
|
2984
|
+
const jobsCmd = program.command('jobs').description('Manage scheduled jobs');
|
|
2985
|
+
jobsCmd
|
|
2986
|
+
.command('list')
|
|
2987
|
+
.description('List all jobs')
|
|
2988
|
+
.action(() => {
|
|
2989
|
+
const jobs = listAllJobs();
|
|
2990
|
+
if (jobs.length === 0) {
|
|
2991
|
+
console.log(chalk.gray('No jobs configured'));
|
|
2992
|
+
console.log(chalk.gray(' Add a job: agents jobs add <path-to-job.yml>'));
|
|
2993
|
+
return;
|
|
2994
|
+
}
|
|
2995
|
+
const scheduler = new JobScheduler(async () => { });
|
|
2996
|
+
scheduler.loadAll();
|
|
2997
|
+
console.log(chalk.bold('Scheduled Jobs\n'));
|
|
2998
|
+
const header = ` ${'Name'.padEnd(24)} ${'Agent'.padEnd(10)} ${'Schedule'.padEnd(20)} ${'Enabled'.padEnd(10)} ${'Next Run'.padEnd(24)} ${'Last Status'}`;
|
|
2999
|
+
console.log(chalk.gray(header));
|
|
3000
|
+
console.log(chalk.gray(' ' + '-'.repeat(110)));
|
|
3001
|
+
for (const job of jobs) {
|
|
3002
|
+
const nextRun = scheduler.getNextRun(job.name);
|
|
3003
|
+
const nextStr = nextRun ? nextRun.toLocaleString() : '-';
|
|
3004
|
+
const latestRun = getLatestRun(job.name);
|
|
3005
|
+
const lastStatus = latestRun?.status || '-';
|
|
3006
|
+
const enabledStr = job.enabled ? chalk.green('yes') : chalk.gray('no');
|
|
3007
|
+
const statusColor = lastStatus === 'completed' ? chalk.green : lastStatus === 'failed' ? chalk.red : lastStatus === 'timeout' ? chalk.yellow : chalk.gray;
|
|
3008
|
+
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)}`);
|
|
3009
|
+
}
|
|
3010
|
+
scheduler.stopAll();
|
|
3011
|
+
console.log();
|
|
3012
|
+
});
|
|
3013
|
+
jobsCmd
|
|
3014
|
+
.command('add <path>')
|
|
3015
|
+
.description('Add a job from a YAML file')
|
|
3016
|
+
.action(async (filePath) => {
|
|
3017
|
+
const resolved = path.resolve(filePath);
|
|
3018
|
+
if (!fs.existsSync(resolved)) {
|
|
3019
|
+
console.log(chalk.red(`File not found: ${resolved}`));
|
|
3020
|
+
process.exit(1);
|
|
3021
|
+
}
|
|
3022
|
+
const content = fs.readFileSync(resolved, 'utf-8');
|
|
3023
|
+
let parsed;
|
|
3024
|
+
try {
|
|
3025
|
+
const yamlMod = await import('yaml');
|
|
3026
|
+
parsed = yamlMod.parse(content);
|
|
3027
|
+
}
|
|
3028
|
+
catch (err) {
|
|
3029
|
+
console.log(chalk.red(`Invalid YAML: ${err.message}`));
|
|
3030
|
+
process.exit(1);
|
|
3031
|
+
}
|
|
3032
|
+
const name = parsed.name || path.basename(resolved).replace(/\.ya?ml$/, '');
|
|
3033
|
+
parsed.name = name;
|
|
3034
|
+
const errors = validateJob(parsed);
|
|
3035
|
+
if (errors.length > 0) {
|
|
3036
|
+
console.log(chalk.red('Validation errors:'));
|
|
3037
|
+
for (const err of errors) {
|
|
3038
|
+
console.log(chalk.red(` - ${err}`));
|
|
3039
|
+
}
|
|
3040
|
+
process.exit(1);
|
|
3041
|
+
}
|
|
3042
|
+
const config = {
|
|
3043
|
+
mode: 'plan',
|
|
3044
|
+
effort: 'default',
|
|
3045
|
+
timeout: '30m',
|
|
3046
|
+
enabled: true,
|
|
3047
|
+
...parsed,
|
|
3048
|
+
};
|
|
3049
|
+
writeJob(config);
|
|
3050
|
+
console.log(chalk.green(`Job '${name}' added`));
|
|
3051
|
+
if (isDaemonRunning()) {
|
|
3052
|
+
signalDaemonReload();
|
|
3053
|
+
console.log(chalk.gray('Daemon reloaded'));
|
|
3054
|
+
}
|
|
3055
|
+
});
|
|
3056
|
+
jobsCmd
|
|
3057
|
+
.command('run <name>')
|
|
3058
|
+
.description('Run a job immediately in the foreground')
|
|
3059
|
+
.action(async (name) => {
|
|
3060
|
+
const job = readJob(name);
|
|
3061
|
+
if (!job) {
|
|
3062
|
+
console.log(chalk.red(`Job '${name}' not found`));
|
|
3063
|
+
process.exit(1);
|
|
3064
|
+
}
|
|
3065
|
+
console.log(chalk.bold(`Running job '${name}' (agent: ${job.agent}, mode: ${job.mode})\n`));
|
|
3066
|
+
const spinner = ora('Executing...').start();
|
|
3067
|
+
try {
|
|
3068
|
+
const result = await executeJob(job);
|
|
3069
|
+
if (result.meta.status === 'completed') {
|
|
3070
|
+
spinner.succeed(`Job completed (exit code: ${result.meta.exitCode})`);
|
|
3071
|
+
}
|
|
3072
|
+
else if (result.meta.status === 'timeout') {
|
|
3073
|
+
spinner.warn(`Job timed out after ${job.timeout}`);
|
|
3074
|
+
}
|
|
3075
|
+
else {
|
|
3076
|
+
spinner.fail(`Job failed (exit code: ${result.meta.exitCode})`);
|
|
3077
|
+
}
|
|
3078
|
+
console.log(chalk.gray(` Run: ${result.meta.runId}`));
|
|
3079
|
+
console.log(chalk.gray(` Log: ${getRunDir(name, result.meta.runId)}/stdout.log`));
|
|
3080
|
+
if (result.reportPath) {
|
|
3081
|
+
console.log(chalk.bold('\nReport:\n'));
|
|
3082
|
+
console.log(fs.readFileSync(result.reportPath, 'utf-8'));
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
catch (err) {
|
|
3086
|
+
spinner.fail('Execution failed');
|
|
3087
|
+
console.error(chalk.red(err.message));
|
|
3088
|
+
process.exit(1);
|
|
3089
|
+
}
|
|
3090
|
+
});
|
|
3091
|
+
jobsCmd
|
|
3092
|
+
.command('logs <name>')
|
|
3093
|
+
.description('Show stdout from the latest (or specific) run')
|
|
3094
|
+
.option('-r, --run <runId>', 'Specific run ID')
|
|
3095
|
+
.action((name, options) => {
|
|
3096
|
+
let runId = options.run;
|
|
3097
|
+
if (!runId) {
|
|
3098
|
+
const latest = getLatestRun(name);
|
|
3099
|
+
if (!latest) {
|
|
3100
|
+
console.log(chalk.yellow(`No runs found for job '${name}'`));
|
|
3101
|
+
return;
|
|
3102
|
+
}
|
|
3103
|
+
runId = latest.runId;
|
|
3104
|
+
}
|
|
3105
|
+
const logPath = path.join(getRunDir(name, runId), 'stdout.log');
|
|
3106
|
+
if (!fs.existsSync(logPath)) {
|
|
3107
|
+
console.log(chalk.yellow(`Log not found: ${logPath}`));
|
|
3108
|
+
return;
|
|
3109
|
+
}
|
|
3110
|
+
console.log(chalk.gray(`Run: ${runId}\n`));
|
|
3111
|
+
console.log(fs.readFileSync(logPath, 'utf-8'));
|
|
3112
|
+
});
|
|
3113
|
+
jobsCmd
|
|
3114
|
+
.command('report <name>')
|
|
3115
|
+
.description('Show report from the latest (or specific) run')
|
|
3116
|
+
.option('-r, --run <runId>', 'Specific run ID')
|
|
3117
|
+
.action((name, options) => {
|
|
3118
|
+
let runId = options.run;
|
|
3119
|
+
if (!runId) {
|
|
3120
|
+
const latest = getLatestRun(name);
|
|
3121
|
+
if (!latest) {
|
|
3122
|
+
console.log(chalk.yellow(`No runs found for job '${name}'`));
|
|
3123
|
+
return;
|
|
3124
|
+
}
|
|
3125
|
+
runId = latest.runId;
|
|
3126
|
+
}
|
|
3127
|
+
const reportPath = path.join(getRunDir(name, runId), 'report.md');
|
|
3128
|
+
if (!fs.existsSync(reportPath)) {
|
|
3129
|
+
console.log(chalk.yellow(`No report found for run ${runId}`));
|
|
3130
|
+
console.log(chalk.gray(` Reports are extracted from agent output on completion`));
|
|
3131
|
+
return;
|
|
3132
|
+
}
|
|
3133
|
+
console.log(chalk.gray(`Run: ${runId}\n`));
|
|
3134
|
+
console.log(fs.readFileSync(reportPath, 'utf-8'));
|
|
3135
|
+
});
|
|
3136
|
+
jobsCmd
|
|
3137
|
+
.command('enable <name>')
|
|
3138
|
+
.description('Enable a job')
|
|
3139
|
+
.action((name) => {
|
|
3140
|
+
try {
|
|
3141
|
+
setJobEnabled(name, true);
|
|
3142
|
+
console.log(chalk.green(`Job '${name}' enabled`));
|
|
3143
|
+
if (isDaemonRunning()) {
|
|
3144
|
+
signalDaemonReload();
|
|
3145
|
+
console.log(chalk.gray('Daemon reloaded'));
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
catch (err) {
|
|
3149
|
+
console.log(chalk.red(err.message));
|
|
3150
|
+
process.exit(1);
|
|
3151
|
+
}
|
|
3152
|
+
});
|
|
3153
|
+
jobsCmd
|
|
3154
|
+
.command('disable <name>')
|
|
3155
|
+
.description('Disable a job')
|
|
3156
|
+
.action((name) => {
|
|
3157
|
+
try {
|
|
3158
|
+
setJobEnabled(name, false);
|
|
3159
|
+
console.log(chalk.green(`Job '${name}' disabled`));
|
|
3160
|
+
if (isDaemonRunning()) {
|
|
3161
|
+
signalDaemonReload();
|
|
3162
|
+
console.log(chalk.gray('Daemon reloaded'));
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
catch (err) {
|
|
3166
|
+
console.log(chalk.red(err.message));
|
|
3167
|
+
process.exit(1);
|
|
3168
|
+
}
|
|
3169
|
+
});
|
|
3170
|
+
// =============================================================================
|
|
3171
|
+
// DRIVE COMMANDS
|
|
3172
|
+
// =============================================================================
|
|
3173
|
+
import { createDrive, readDrive, listDrives as listAllDrives, deleteDrive, updateDriveFrontmatter, driveExists, discoverDrivesFromRepo, installDriveFromSource, driveContentMatches, } from './lib/drives.js';
|
|
3174
|
+
import { runDriveServer } from './lib/drive-server.js';
|
|
3175
|
+
const driveCmd = program.command('drive').description('Manage context drives');
|
|
3176
|
+
driveCmd
|
|
3177
|
+
.command('create <name>')
|
|
3178
|
+
.description('Create a new empty drive')
|
|
3179
|
+
.option('-d, --description <desc>', 'Drive description')
|
|
3180
|
+
.option('-p, --project <path>', 'Link to a project directory')
|
|
3181
|
+
.action((name, options) => {
|
|
3182
|
+
try {
|
|
3183
|
+
const filePath = createDrive(name, options.description);
|
|
3184
|
+
if (options.project) {
|
|
3185
|
+
updateDriveFrontmatter(name, { project: path.resolve(options.project) });
|
|
3186
|
+
}
|
|
3187
|
+
console.log(chalk.green(`Drive '${name}' created`));
|
|
3188
|
+
console.log(chalk.gray(` ${filePath}`));
|
|
3189
|
+
}
|
|
3190
|
+
catch (err) {
|
|
3191
|
+
console.log(chalk.red(err.message));
|
|
3192
|
+
process.exit(1);
|
|
3193
|
+
}
|
|
3194
|
+
});
|
|
3195
|
+
driveCmd
|
|
3196
|
+
.command('list')
|
|
3197
|
+
.description('List all drives')
|
|
3198
|
+
.action(() => {
|
|
3199
|
+
const drives = listAllDrives();
|
|
3200
|
+
if (drives.length === 0) {
|
|
3201
|
+
console.log(chalk.gray('No drives configured'));
|
|
3202
|
+
console.log(chalk.gray(' Create a drive: agents drive create <name>'));
|
|
3203
|
+
return;
|
|
3204
|
+
}
|
|
3205
|
+
console.log(chalk.bold('Context Drives\n'));
|
|
3206
|
+
const header = ` ${'Name'.padEnd(24)} ${'Description'.padEnd(40)} ${'Project'}`;
|
|
3207
|
+
console.log(chalk.gray(header));
|
|
3208
|
+
console.log(chalk.gray(' ' + '-'.repeat(90)));
|
|
3209
|
+
for (const drive of drives) {
|
|
3210
|
+
const desc = (drive.description || '-').slice(0, 38);
|
|
3211
|
+
const proj = drive.project || '-';
|
|
3212
|
+
console.log(` ${chalk.cyan(drive.name.padEnd(24))} ${desc.padEnd(40)} ${chalk.gray(proj)}`);
|
|
3213
|
+
}
|
|
3214
|
+
console.log();
|
|
3215
|
+
});
|
|
3216
|
+
driveCmd
|
|
3217
|
+
.command('info <name>')
|
|
3218
|
+
.description('Show drive metadata and content preview')
|
|
3219
|
+
.action((name) => {
|
|
3220
|
+
const drive = readDrive(name);
|
|
3221
|
+
if (!drive) {
|
|
3222
|
+
console.log(chalk.red(`Drive '${name}' not found`));
|
|
3223
|
+
process.exit(1);
|
|
3224
|
+
}
|
|
3225
|
+
console.log(chalk.bold(`Drive: ${drive.frontmatter.name}\n`));
|
|
3226
|
+
if (drive.frontmatter.description) {
|
|
3227
|
+
console.log(chalk.gray(` Description: ${drive.frontmatter.description}`));
|
|
3228
|
+
}
|
|
3229
|
+
if (drive.frontmatter.project) {
|
|
3230
|
+
console.log(chalk.gray(` Project: ${drive.frontmatter.project}`));
|
|
3231
|
+
}
|
|
3232
|
+
if (drive.frontmatter.repo) {
|
|
3233
|
+
console.log(chalk.gray(` Repo: ${drive.frontmatter.repo}`));
|
|
3234
|
+
}
|
|
3235
|
+
if (drive.frontmatter.updated) {
|
|
3236
|
+
console.log(chalk.gray(` Updated: ${drive.frontmatter.updated}`));
|
|
3237
|
+
}
|
|
3238
|
+
console.log(chalk.gray(` Path: ${drive.path}`));
|
|
3239
|
+
const content = drive.content.trim();
|
|
3240
|
+
if (content) {
|
|
3241
|
+
console.log(chalk.bold('\nContent:\n'));
|
|
3242
|
+
const lines = content.split('\n');
|
|
3243
|
+
const preview = lines.slice(0, 30);
|
|
3244
|
+
for (const line of preview) {
|
|
3245
|
+
console.log(` ${line}`);
|
|
3246
|
+
}
|
|
3247
|
+
if (lines.length > 30) {
|
|
3248
|
+
console.log(chalk.gray(`\n ... ${lines.length - 30} more lines`));
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
console.log();
|
|
3252
|
+
});
|
|
3253
|
+
driveCmd
|
|
3254
|
+
.command('edit <name>')
|
|
3255
|
+
.description('Open drive in $EDITOR')
|
|
3256
|
+
.action((name) => {
|
|
3257
|
+
const drive = readDrive(name);
|
|
3258
|
+
if (!drive) {
|
|
3259
|
+
console.log(chalk.red(`Drive '${name}' not found`));
|
|
3260
|
+
process.exit(1);
|
|
3261
|
+
}
|
|
3262
|
+
const editor = process.env.EDITOR || process.env.VISUAL || 'vi';
|
|
3263
|
+
const { execSync } = require('child_process');
|
|
3264
|
+
try {
|
|
3265
|
+
execSync(`${editor} "${drive.path}"`, { stdio: 'inherit' });
|
|
3266
|
+
}
|
|
3267
|
+
catch {
|
|
3268
|
+
console.log(chalk.red(`Failed to open editor: ${editor}`));
|
|
3269
|
+
process.exit(1);
|
|
3270
|
+
}
|
|
3271
|
+
});
|
|
3272
|
+
driveCmd
|
|
3273
|
+
.command('delete <name>')
|
|
3274
|
+
.description('Delete a drive')
|
|
3275
|
+
.action(async (name) => {
|
|
3276
|
+
if (!driveExists(name)) {
|
|
3277
|
+
console.log(chalk.red(`Drive '${name}' not found`));
|
|
3278
|
+
process.exit(1);
|
|
3279
|
+
}
|
|
3280
|
+
try {
|
|
3281
|
+
const answer = await confirm({ message: `Delete drive '${name}'?` });
|
|
3282
|
+
if (!answer)
|
|
3283
|
+
return;
|
|
3284
|
+
}
|
|
3285
|
+
catch (err) {
|
|
3286
|
+
if (isPromptCancelled(err))
|
|
3287
|
+
return;
|
|
3288
|
+
throw err;
|
|
3289
|
+
}
|
|
3290
|
+
deleteDrive(name);
|
|
3291
|
+
console.log(chalk.green(`Drive '${name}' deleted`));
|
|
3292
|
+
});
|
|
3293
|
+
driveCmd
|
|
3294
|
+
.command('link <name> <path>')
|
|
3295
|
+
.description('Link a drive to a project directory')
|
|
3296
|
+
.action((name, projectPath) => {
|
|
3297
|
+
if (!driveExists(name)) {
|
|
3298
|
+
console.log(chalk.red(`Drive '${name}' not found`));
|
|
3299
|
+
process.exit(1);
|
|
3300
|
+
}
|
|
3301
|
+
const resolved = path.resolve(projectPath);
|
|
3302
|
+
if (!fs.existsSync(resolved)) {
|
|
3303
|
+
console.log(chalk.red(`Directory not found: ${resolved}`));
|
|
3304
|
+
process.exit(1);
|
|
3305
|
+
}
|
|
3306
|
+
updateDriveFrontmatter(name, { project: resolved });
|
|
3307
|
+
console.log(chalk.green(`Drive '${name}' linked to ${resolved}`));
|
|
3308
|
+
});
|
|
3309
|
+
driveCmd
|
|
3310
|
+
.command('sync')
|
|
3311
|
+
.description('Sync drives with .agents repo')
|
|
3312
|
+
.action(async () => {
|
|
3313
|
+
const source = await ensureSource();
|
|
3314
|
+
const repoPath = getRepoLocalPath(source);
|
|
3315
|
+
const discovered = discoverDrivesFromRepo(repoPath);
|
|
3316
|
+
if (discovered.length === 0) {
|
|
3317
|
+
console.log(chalk.gray('No drives found in repo'));
|
|
3318
|
+
return;
|
|
3319
|
+
}
|
|
3320
|
+
let installed = 0;
|
|
3321
|
+
let skipped = 0;
|
|
3322
|
+
for (const d of discovered) {
|
|
3323
|
+
if (driveExists(d.name) && driveContentMatches(d.name, d.path)) {
|
|
3324
|
+
skipped++;
|
|
3325
|
+
continue;
|
|
3326
|
+
}
|
|
3327
|
+
const result = installDriveFromSource(d.path, d.name);
|
|
3328
|
+
if (result.success) {
|
|
3329
|
+
installed++;
|
|
3330
|
+
console.log(chalk.green(` + ${d.name}`));
|
|
3331
|
+
}
|
|
3332
|
+
else {
|
|
3333
|
+
console.log(chalk.red(` x ${d.name}: ${result.error}`));
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
if (installed === 0 && skipped > 0) {
|
|
3337
|
+
console.log(chalk.gray(`All ${skipped} drives up to date`));
|
|
3338
|
+
}
|
|
3339
|
+
else if (installed > 0) {
|
|
3340
|
+
console.log(chalk.green(`\nSynced ${installed} drive(s)`));
|
|
3341
|
+
}
|
|
3342
|
+
});
|
|
3343
|
+
driveCmd
|
|
3344
|
+
.command('generate <name>')
|
|
3345
|
+
.description('Run a drive generation job now')
|
|
3346
|
+
.action(async (name) => {
|
|
3347
|
+
if (!driveExists(name)) {
|
|
3348
|
+
console.log(chalk.red(`Drive '${name}' not found`));
|
|
3349
|
+
process.exit(1);
|
|
3350
|
+
}
|
|
3351
|
+
const jobName = `update-drive-${name}`;
|
|
3352
|
+
const job = readJob(jobName);
|
|
3353
|
+
if (!job) {
|
|
3354
|
+
console.log(chalk.red(`No generation job found for drive '${name}'`));
|
|
3355
|
+
console.log(chalk.gray(` Expected job name: ${jobName}`));
|
|
3356
|
+
console.log(chalk.gray(` Create one at: ~/.agents/jobs/${jobName}.yml`));
|
|
3357
|
+
process.exit(1);
|
|
3358
|
+
}
|
|
3359
|
+
console.log(chalk.bold(`Generating drive '${name}' (job: ${jobName})\n`));
|
|
3360
|
+
const spinner = ora('Executing...').start();
|
|
3361
|
+
try {
|
|
3362
|
+
const result = await executeJob(job);
|
|
3363
|
+
if (result.meta.status === 'completed') {
|
|
3364
|
+
spinner.succeed(`Drive '${name}' updated`);
|
|
3365
|
+
}
|
|
3366
|
+
else if (result.meta.status === 'timeout') {
|
|
3367
|
+
spinner.warn(`Generation timed out after ${job.timeout}`);
|
|
3368
|
+
}
|
|
3369
|
+
else {
|
|
3370
|
+
spinner.fail(`Generation failed (exit code: ${result.meta.exitCode})`);
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
catch (err) {
|
|
3374
|
+
spinner.fail('Generation failed');
|
|
3375
|
+
console.error(chalk.red(err.message));
|
|
3376
|
+
process.exit(1);
|
|
3377
|
+
}
|
|
3378
|
+
});
|
|
3379
|
+
driveCmd
|
|
3380
|
+
.command('serve')
|
|
3381
|
+
.description('Start the drive MCP server (stdio)')
|
|
3382
|
+
.action(async () => {
|
|
3383
|
+
await runDriveServer();
|
|
3384
|
+
});
|
|
2703
3385
|
async function showWhatsNew(fromVersion, toVersion) {
|
|
2704
3386
|
try {
|
|
2705
3387
|
// Fetch changelog from npm package
|