opencode-swarm-plugin 0.61.0 → 0.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/swarm.ts +303 -2339
- package/claude-plugin/dist/index.js +830 -635
- package/claude-plugin/dist/schemas/cell.d.ts +1 -0
- package/claude-plugin/dist/schemas/cell.d.ts.map +1 -1
- package/claude-plugin/hooks/hooks.json +9 -0
- package/dist/bin/swarm.js +78737 -81712
- package/dist/examples/plugin-wrapper-template.ts +8 -3
- package/dist/hive.d.ts.map +1 -1
- package/dist/hive.js +3 -1
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +186 -2
- package/dist/marketplace/index.js +830 -635
- package/dist/plugin.js +186 -2
- package/dist/schemas/cell.d.ts +1 -0
- package/dist/schemas/cell.d.ts.map +1 -1
- package/dist/skills.d.ts +16 -0
- package/dist/skills.d.ts.map +1 -1
- package/dist/swarm-orchestrate.d.ts +110 -0
- package/dist/swarm-orchestrate.d.ts.map +1 -1
- package/dist/swarm-prompts.js +184 -2
- package/dist/swarm.d.ts +40 -0
- package/dist/swarm.d.ts.map +1 -1
- package/examples/plugin-wrapper-template.ts +8 -3
- package/global-skills/skill-generator/SKILL.md +155 -0
- package/global-skills/skill-generator/references/conventions.md +361 -0
- package/global-skills/skill-generator/scripts/generate-skill.sh +193 -0
- package/package.json +1 -1
package/bin/swarm.ts
CHANGED
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
* swarm setup - Interactive installer for all dependencies
|
|
9
9
|
* swarm doctor - Check dependency health with detailed status
|
|
10
10
|
* swarm init - Initialize swarm in current project
|
|
11
|
-
* swarm claude - Claude Code integration commands
|
|
12
|
-
* swarm mcp-serve - Debug-only MCP server (Claude auto-launches)
|
|
13
11
|
* swarm version - Show version info
|
|
14
12
|
* swarm - Interactive mode (same as setup)
|
|
15
13
|
*/
|
|
@@ -19,20 +17,17 @@ import {
|
|
|
19
17
|
chmodSync,
|
|
20
18
|
copyFileSync,
|
|
21
19
|
existsSync,
|
|
22
|
-
lstatSync,
|
|
23
20
|
mkdirSync,
|
|
24
21
|
readFileSync,
|
|
25
|
-
readlinkSync,
|
|
26
22
|
readdirSync,
|
|
27
23
|
renameSync,
|
|
28
24
|
rmdirSync,
|
|
29
25
|
rmSync,
|
|
30
26
|
statSync,
|
|
31
|
-
symlinkSync,
|
|
32
27
|
writeFileSync,
|
|
33
28
|
} from "fs";
|
|
34
29
|
import { homedir } from "os";
|
|
35
|
-
import { basename, dirname, join
|
|
30
|
+
import { basename, dirname, join } from "path";
|
|
36
31
|
import { fileURLToPath } from "url";
|
|
37
32
|
import {
|
|
38
33
|
checkBeadsMigrationNeeded,
|
|
@@ -55,21 +50,17 @@ import {
|
|
|
55
50
|
resolvePartialId,
|
|
56
51
|
createDurableStreamAdapter,
|
|
57
52
|
createDurableStreamServer,
|
|
58
|
-
consolidateDatabases,
|
|
59
|
-
getGlobalDbPath,
|
|
60
53
|
} from "swarm-mail";
|
|
61
|
-
import { createMemoryAdapter } from "../src/memory";
|
|
62
54
|
import { execSync, spawn } from "child_process";
|
|
63
55
|
import { tmpdir } from "os";
|
|
64
56
|
|
|
65
57
|
// Query & observability tools
|
|
66
58
|
import {
|
|
67
|
-
|
|
59
|
+
executeQuery,
|
|
68
60
|
executePreset,
|
|
69
61
|
formatAsTable,
|
|
70
62
|
formatAsCSV,
|
|
71
63
|
formatAsJSON,
|
|
72
|
-
type QueryResult,
|
|
73
64
|
} from "../src/query-tools.js";
|
|
74
65
|
import {
|
|
75
66
|
getWorkerStatus,
|
|
@@ -89,11 +80,6 @@ import {
|
|
|
89
80
|
exportToCSV,
|
|
90
81
|
exportToJSON,
|
|
91
82
|
} from "../src/export-tools.js";
|
|
92
|
-
import { tree } from "./commands/tree.js";
|
|
93
|
-
import { session } from "./commands/session.js";
|
|
94
|
-
import { log } from "./commands/log.js";
|
|
95
|
-
import { status } from "./commands/status.js";
|
|
96
|
-
import { queue } from "./commands/queue.js";
|
|
97
83
|
import {
|
|
98
84
|
querySwarmHistory,
|
|
99
85
|
formatSwarmHistory,
|
|
@@ -106,9 +92,6 @@ import {
|
|
|
106
92
|
formatHealthDashboard,
|
|
107
93
|
} from "../src/observability-health.js";
|
|
108
94
|
|
|
109
|
-
// Swarm insights
|
|
110
|
-
import { getRejectionAnalytics, getCompactionAnalytics } from "../src/swarm-insights.js";
|
|
111
|
-
|
|
112
95
|
// Eval tools
|
|
113
96
|
import { getPhase, getScoreHistory, recordEvalRun, getEvalHistoryPath } from "../src/eval-history.js";
|
|
114
97
|
import { DEFAULT_THRESHOLDS, checkGate } from "../src/eval-gates.js";
|
|
@@ -119,15 +102,10 @@ import { detectRegressions } from "../src/regression-detection.js";
|
|
|
119
102
|
import { allTools } from "../src/index.js";
|
|
120
103
|
|
|
121
104
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
122
|
-
// When
|
|
123
|
-
|
|
124
|
-
const pkgPath = existsSync(join(__dirname, "..", "package.json"))
|
|
125
|
-
? join(__dirname, "..", "package.json")
|
|
126
|
-
: join(__dirname, "..", "..", "package.json");
|
|
105
|
+
// When bundled to dist/bin/swarm.js, need to go up two levels to find package.json
|
|
106
|
+
const pkgPath = join(__dirname, "..", "..", "package.json");
|
|
127
107
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
128
108
|
const VERSION: string = pkg.version;
|
|
129
|
-
const PACKAGE_ROOT = dirname(pkgPath);
|
|
130
|
-
const CLAUDE_PLUGIN_NAME = "swarm";
|
|
131
109
|
|
|
132
110
|
// ============================================================================
|
|
133
111
|
// ASCII Art & Branding
|
|
@@ -465,13 +443,24 @@ function showUpdateNotification(info: UpdateInfo) {
|
|
|
465
443
|
// Types
|
|
466
444
|
// ============================================================================
|
|
467
445
|
|
|
446
|
+
type InstallType = "brew" | "npm" | "bun" | "pnpm" | "yarn" | "paru" | "choco" | "scoop" | "manual";
|
|
447
|
+
|
|
448
|
+
interface InstallMethod {
|
|
449
|
+
name: string;
|
|
450
|
+
command: string;
|
|
451
|
+
type: InstallType;
|
|
452
|
+
platforms?: ("darwin" | "linux" | "win32")[];
|
|
453
|
+
requires?: string; // Command that must exist for this method to work
|
|
454
|
+
}
|
|
455
|
+
|
|
468
456
|
interface Dependency {
|
|
469
457
|
name: string;
|
|
470
458
|
command: string;
|
|
471
459
|
checkArgs: string[];
|
|
472
460
|
required: boolean;
|
|
473
461
|
install: string;
|
|
474
|
-
installType:
|
|
462
|
+
installType: InstallType;
|
|
463
|
+
installMethods?: InstallMethod[]; // Multiple installation options
|
|
475
464
|
description: string;
|
|
476
465
|
}
|
|
477
466
|
|
|
@@ -485,6 +474,77 @@ interface CheckResult {
|
|
|
485
474
|
// Dependencies
|
|
486
475
|
// ============================================================================
|
|
487
476
|
|
|
477
|
+
/**
|
|
478
|
+
* All available installation methods for OpenCode.
|
|
479
|
+
* Ordered by recommendation (curl first as it's most universal).
|
|
480
|
+
*/
|
|
481
|
+
const OPENCODE_INSTALL_METHODS: InstallMethod[] = [
|
|
482
|
+
// Universal (works on macOS and Linux)
|
|
483
|
+
{
|
|
484
|
+
name: "curl (recommended)",
|
|
485
|
+
command: "curl -fsSL https://opencode.ai/install | bash",
|
|
486
|
+
type: "manual",
|
|
487
|
+
platforms: ["darwin", "linux"],
|
|
488
|
+
requires: "curl",
|
|
489
|
+
},
|
|
490
|
+
// macOS and Linux package managers
|
|
491
|
+
{
|
|
492
|
+
name: "Homebrew",
|
|
493
|
+
command: "brew install opencode",
|
|
494
|
+
type: "brew",
|
|
495
|
+
platforms: ["darwin", "linux"],
|
|
496
|
+
requires: "brew",
|
|
497
|
+
},
|
|
498
|
+
// Node.js package managers (cross-platform)
|
|
499
|
+
{
|
|
500
|
+
name: "npm",
|
|
501
|
+
command: "npm install -g opencode-ai",
|
|
502
|
+
type: "npm",
|
|
503
|
+
requires: "npm",
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
name: "Bun",
|
|
507
|
+
command: "bun install -g opencode-ai",
|
|
508
|
+
type: "bun",
|
|
509
|
+
requires: "bun",
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
name: "pnpm",
|
|
513
|
+
command: "pnpm install -g opencode-ai",
|
|
514
|
+
type: "pnpm",
|
|
515
|
+
requires: "pnpm",
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
name: "Yarn",
|
|
519
|
+
command: "yarn global add opencode-ai",
|
|
520
|
+
type: "yarn",
|
|
521
|
+
requires: "yarn",
|
|
522
|
+
},
|
|
523
|
+
// Linux-specific
|
|
524
|
+
{
|
|
525
|
+
name: "paru (Arch Linux)",
|
|
526
|
+
command: "paru -S opencode-bin",
|
|
527
|
+
type: "paru",
|
|
528
|
+
platforms: ["linux"],
|
|
529
|
+
requires: "paru",
|
|
530
|
+
},
|
|
531
|
+
// Windows-specific
|
|
532
|
+
{
|
|
533
|
+
name: "Chocolatey",
|
|
534
|
+
command: "choco install opencode",
|
|
535
|
+
type: "choco",
|
|
536
|
+
platforms: ["win32"],
|
|
537
|
+
requires: "choco",
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
name: "Scoop",
|
|
541
|
+
command: "scoop bucket add extras && scoop install extras/opencode",
|
|
542
|
+
type: "scoop",
|
|
543
|
+
platforms: ["win32"],
|
|
544
|
+
requires: "scoop",
|
|
545
|
+
},
|
|
546
|
+
];
|
|
547
|
+
|
|
488
548
|
const DEPENDENCIES: Dependency[] = [
|
|
489
549
|
{
|
|
490
550
|
name: "Bun",
|
|
@@ -500,18 +560,10 @@ const DEPENDENCIES: Dependency[] = [
|
|
|
500
560
|
command: "opencode",
|
|
501
561
|
checkArgs: ["--version"],
|
|
502
562
|
required: true,
|
|
503
|
-
install: "
|
|
504
|
-
installType: "brew",
|
|
505
|
-
description: "AI coding assistant (plugin host)",
|
|
506
|
-
},
|
|
507
|
-
{
|
|
508
|
-
name: "Claude Code",
|
|
509
|
-
command: "claude",
|
|
510
|
-
checkArgs: ["--version"],
|
|
511
|
-
required: false,
|
|
512
|
-
install: "https://docs.anthropic.com/claude-code",
|
|
563
|
+
install: "curl -fsSL https://opencode.ai/install | bash", // Default to curl
|
|
513
564
|
installType: "manual",
|
|
514
|
-
|
|
565
|
+
installMethods: OPENCODE_INSTALL_METHODS,
|
|
566
|
+
description: "AI coding assistant (plugin host)",
|
|
515
567
|
},
|
|
516
568
|
// Note: Beads CLI (bd) is NO LONGER required - we use HiveAdapter from swarm-mail
|
|
517
569
|
// which provides the same functionality programmatically without external dependencies
|
|
@@ -524,6 +576,15 @@ const DEPENDENCIES: Dependency[] = [
|
|
|
524
576
|
installType: "manual",
|
|
525
577
|
description: "Indexes and searches AI coding agent history for context",
|
|
526
578
|
},
|
|
579
|
+
{
|
|
580
|
+
name: "UBS (Ultimate Bug Scanner)",
|
|
581
|
+
command: "ubs",
|
|
582
|
+
checkArgs: ["--help"],
|
|
583
|
+
required: false,
|
|
584
|
+
install: "https://github.com/Dicklesworthstone/ultimate_bug_scanner",
|
|
585
|
+
installType: "manual",
|
|
586
|
+
description: "AI-powered static analysis for pre-completion bug scanning",
|
|
587
|
+
},
|
|
527
588
|
{
|
|
528
589
|
name: "Ollama",
|
|
529
590
|
command: "ollama",
|
|
@@ -604,154 +665,79 @@ async function checkAllDependencies(): Promise<CheckResult[]> {
|
|
|
604
665
|
return results;
|
|
605
666
|
}
|
|
606
667
|
|
|
607
|
-
// ============================================================================
|
|
608
|
-
// Claude Code Helpers
|
|
609
|
-
// ============================================================================
|
|
610
|
-
|
|
611
|
-
interface ClaudeHookInput {
|
|
612
|
-
project?: { path?: string };
|
|
613
|
-
cwd?: string;
|
|
614
|
-
session?: { id?: string };
|
|
615
|
-
metadata?: { cwd?: string };
|
|
616
|
-
prompt?: string; // UserPromptSubmit includes the user's prompt text
|
|
617
|
-
}
|
|
618
|
-
|
|
619
668
|
/**
|
|
620
|
-
*
|
|
669
|
+
* Get available installation methods for a dependency based on:
|
|
670
|
+
* - Current platform (darwin, linux, win32)
|
|
671
|
+
* - Available package managers on the system
|
|
621
672
|
*/
|
|
622
|
-
function
|
|
623
|
-
|
|
624
|
-
|
|
673
|
+
async function getAvailableInstallMethods(
|
|
674
|
+
methods: InstallMethod[],
|
|
675
|
+
): Promise<InstallMethod[]> {
|
|
676
|
+
const platform = process.platform as "darwin" | "linux" | "win32";
|
|
677
|
+
const available: InstallMethod[] = [];
|
|
678
|
+
|
|
679
|
+
for (const method of methods) {
|
|
680
|
+
// Check platform compatibility
|
|
681
|
+
if (method.platforms && !method.platforms.includes(platform)) {
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
625
684
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
}
|
|
685
|
+
// Check if required command is available
|
|
686
|
+
if (method.requires) {
|
|
687
|
+
const { available: cmdAvailable } = await checkCommand(method.requires, ["--version"]);
|
|
688
|
+
// Some commands don't support --version, try --help as fallback
|
|
689
|
+
if (!cmdAvailable) {
|
|
690
|
+
const { available: helpAvailable } = await checkCommand(method.requires, ["--help"]);
|
|
691
|
+
if (!helpAvailable) {
|
|
692
|
+
// Special case: curl doesn't have --version on some systems, check with -V
|
|
693
|
+
if (method.requires === "curl") {
|
|
694
|
+
const { available: curlAvailable } = await checkCommand("curl", ["-V"]);
|
|
695
|
+
if (!curlAvailable) continue;
|
|
696
|
+
} else {
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
632
702
|
|
|
633
|
-
|
|
634
|
-
* Read JSON input from stdin for Claude Code hooks.
|
|
635
|
-
*/
|
|
636
|
-
async function readHookInput<T>(): Promise<T | null> {
|
|
637
|
-
if (process.stdin.isTTY) return null;
|
|
638
|
-
const chunks: string[] = [];
|
|
639
|
-
for await (const chunk of process.stdin) {
|
|
640
|
-
chunks.push(chunk.toString());
|
|
641
|
-
}
|
|
642
|
-
const raw = chunks.join("").trim();
|
|
643
|
-
if (!raw) return null;
|
|
644
|
-
try {
|
|
645
|
-
return JSON.parse(raw) as T;
|
|
646
|
-
} catch {
|
|
647
|
-
return null;
|
|
703
|
+
available.push(method);
|
|
648
704
|
}
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
/**
|
|
652
|
-
* Resolve the project path for Claude Code hook executions.
|
|
653
|
-
*/
|
|
654
|
-
function resolveClaudeProjectPath(input: ClaudeHookInput | null): string {
|
|
655
|
-
return (
|
|
656
|
-
input?.project?.path ||
|
|
657
|
-
input?.cwd ||
|
|
658
|
-
input?.metadata?.cwd ||
|
|
659
|
-
process.env.CLAUDE_PROJECT_DIR ||
|
|
660
|
-
process.env.PWD ||
|
|
661
|
-
process.cwd()
|
|
662
|
-
);
|
|
663
|
-
}
|
|
664
705
|
|
|
665
|
-
|
|
666
|
-
* Format hook output for Claude Code context injection.
|
|
667
|
-
* Uses the proper JSON format with hookSpecificOutput for structured feedback.
|
|
668
|
-
*/
|
|
669
|
-
function writeClaudeHookOutput(
|
|
670
|
-
hookEventName: string,
|
|
671
|
-
additionalContext: string,
|
|
672
|
-
options?: { suppressOutput?: boolean }
|
|
673
|
-
): void {
|
|
674
|
-
if (!additionalContext.trim()) return;
|
|
675
|
-
process.stdout.write(
|
|
676
|
-
`${JSON.stringify({
|
|
677
|
-
suppressOutput: options?.suppressOutput,
|
|
678
|
-
hookSpecificOutput: {
|
|
679
|
-
hookEventName,
|
|
680
|
-
additionalContext,
|
|
681
|
-
},
|
|
682
|
-
})}\n`,
|
|
683
|
-
);
|
|
706
|
+
return available;
|
|
684
707
|
}
|
|
685
708
|
|
|
686
709
|
/**
|
|
687
|
-
*
|
|
710
|
+
* Prompt user to select an installation method for a dependency
|
|
688
711
|
*/
|
|
689
|
-
function
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
})
|
|
695
|
-
);
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
interface ClaudeInstallStatus {
|
|
699
|
-
pluginRoot: string;
|
|
700
|
-
globalPluginPath: string;
|
|
701
|
-
globalPluginTarget?: string;
|
|
702
|
-
globalPluginExists: boolean;
|
|
703
|
-
globalPluginLinked: boolean;
|
|
704
|
-
projectClaudeDir: string;
|
|
705
|
-
projectConfigExists: boolean;
|
|
706
|
-
projectConfigPaths: string[];
|
|
707
|
-
}
|
|
712
|
+
async function promptInstallMethod(
|
|
713
|
+
dep: Dependency,
|
|
714
|
+
availableMethods: InstallMethod[],
|
|
715
|
+
): Promise<InstallMethod | null> {
|
|
716
|
+
if (availableMethods.length === 0) {
|
|
717
|
+
p.log.error(`No installation methods available for ${dep.name} on this system`);
|
|
718
|
+
p.log.message(dim(" You may need to install it manually"));
|
|
719
|
+
return null;
|
|
720
|
+
}
|
|
708
721
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
const pluginRoot = getClaudePluginRoot();
|
|
714
|
-
const claudeConfigDir = getClaudeConfigDir();
|
|
715
|
-
const globalPluginPath = join(claudeConfigDir, "plugins", CLAUDE_PLUGIN_NAME);
|
|
722
|
+
if (availableMethods.length === 1) {
|
|
723
|
+
// Only one option, use it directly
|
|
724
|
+
return availableMethods[0];
|
|
725
|
+
}
|
|
716
726
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
727
|
+
const selected = await p.select({
|
|
728
|
+
message: `How would you like to install ${dep.name}?`,
|
|
729
|
+
options: availableMethods.map((method) => ({
|
|
730
|
+
value: method,
|
|
731
|
+
label: method.name,
|
|
732
|
+
hint: method.command,
|
|
733
|
+
})),
|
|
734
|
+
});
|
|
720
735
|
|
|
721
|
-
if (
|
|
722
|
-
|
|
723
|
-
try {
|
|
724
|
-
const stat = lstatSync(globalPluginPath);
|
|
725
|
-
if (stat.isSymbolicLink()) {
|
|
726
|
-
globalPluginLinked = true;
|
|
727
|
-
const target = readlinkSync(globalPluginPath);
|
|
728
|
-
globalPluginTarget = resolve(dirname(globalPluginPath), target);
|
|
729
|
-
}
|
|
730
|
-
} catch {
|
|
731
|
-
// Ignore errors
|
|
732
|
-
}
|
|
736
|
+
if (p.isCancel(selected)) {
|
|
737
|
+
return null;
|
|
733
738
|
}
|
|
734
739
|
|
|
735
|
-
|
|
736
|
-
const projectConfigPaths = [
|
|
737
|
-
join(projectClaudeDir, "commands"),
|
|
738
|
-
join(projectClaudeDir, "agents"),
|
|
739
|
-
join(projectClaudeDir, "skills"),
|
|
740
|
-
join(projectClaudeDir, "hooks"),
|
|
741
|
-
join(projectClaudeDir, ".mcp.json"),
|
|
742
|
-
];
|
|
743
|
-
const projectConfigExists = projectConfigPaths.some((path) => existsSync(path));
|
|
744
|
-
|
|
745
|
-
return {
|
|
746
|
-
pluginRoot,
|
|
747
|
-
globalPluginPath,
|
|
748
|
-
globalPluginTarget,
|
|
749
|
-
globalPluginExists,
|
|
750
|
-
globalPluginLinked,
|
|
751
|
-
projectClaudeDir,
|
|
752
|
-
projectConfigExists,
|
|
753
|
-
projectConfigPaths,
|
|
754
|
-
};
|
|
740
|
+
return selected;
|
|
755
741
|
}
|
|
756
742
|
|
|
757
743
|
// ============================================================================
|
|
@@ -1570,7 +1556,7 @@ skills_list()
|
|
|
1570
1556
|
# Check what MCP servers are available (look for context7, pdf-brain, fetch, etc.)
|
|
1571
1557
|
# Note: No direct MCP listing tool - infer from task context or ask coordinator
|
|
1572
1558
|
|
|
1573
|
-
# Check for CLI tools if relevant (bd, cass, ollama)
|
|
1559
|
+
# Check for CLI tools if relevant (bd, cass, ubs, ollama)
|
|
1574
1560
|
# Use Bash tool to check: which <tool-name>
|
|
1575
1561
|
\`\`\`
|
|
1576
1562
|
|
|
@@ -1699,6 +1685,7 @@ bash("which <tool>", description="Check if <tool> is available")
|
|
|
1699
1685
|
|
|
1700
1686
|
# Examples:
|
|
1701
1687
|
bash("which cass", description="Check CASS availability")
|
|
1688
|
+
bash("which ubs", description="Check UBS availability")
|
|
1702
1689
|
bash("ollama --version", description="Check Ollama availability")
|
|
1703
1690
|
\`\`\`
|
|
1704
1691
|
|
|
@@ -1758,20 +1745,28 @@ Begin by executing Step 1 (swarmmail_init).
|
|
|
1758
1745
|
|
|
1759
1746
|
/**
|
|
1760
1747
|
* Get the fix command for a dependency
|
|
1761
|
-
* Returns
|
|
1748
|
+
* Returns platform-appropriate install suggestions
|
|
1762
1749
|
*/
|
|
1763
1750
|
function getFixCommand(dep: Dependency): string | null {
|
|
1751
|
+
const platform = process.platform;
|
|
1752
|
+
|
|
1764
1753
|
switch (dep.name) {
|
|
1765
1754
|
case "OpenCode":
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1755
|
+
// Show platform-appropriate suggestions
|
|
1756
|
+
if (platform === "win32") {
|
|
1757
|
+
return "choco install opencode OR scoop bucket add extras && scoop install extras/opencode OR npm install -g opencode-ai";
|
|
1758
|
+
} else {
|
|
1759
|
+
return "curl -fsSL https://opencode.ai/install | bash OR brew install opencode OR npm install -g opencode-ai";
|
|
1760
|
+
}
|
|
1769
1761
|
case "Ollama":
|
|
1762
|
+
if (platform === "win32") {
|
|
1763
|
+
return "See: https://ollama.ai/download (then: ollama pull mxbai-embed-large)";
|
|
1764
|
+
}
|
|
1770
1765
|
return "brew install ollama && ollama pull mxbai-embed-large";
|
|
1771
|
-
case "Redis":
|
|
1772
|
-
return "brew install redis && brew services start redis";
|
|
1773
1766
|
case "CASS (Coding Agent Session Search)":
|
|
1774
1767
|
return "See: https://github.com/Dicklesworthstone/coding_agent_session_search";
|
|
1768
|
+
case "UBS (Ultimate Bug Scanner)":
|
|
1769
|
+
return "See: https://github.com/Dicklesworthstone/ultimate_bug_scanner";
|
|
1775
1770
|
default:
|
|
1776
1771
|
// Fallback to generic install command if available
|
|
1777
1772
|
return dep.installType !== "manual" ? dep.install : null;
|
|
@@ -1843,7 +1838,7 @@ async function doctor(debug = false) {
|
|
|
1843
1838
|
// Check skills
|
|
1844
1839
|
p.log.step("Skills:");
|
|
1845
1840
|
const configDir = join(homedir(), ".config", "opencode");
|
|
1846
|
-
const globalSkillsPath = join(configDir, "
|
|
1841
|
+
const globalSkillsPath = join(configDir, "skills");
|
|
1847
1842
|
const bundledSkillsPath = join(__dirname, "..", "global-skills");
|
|
1848
1843
|
|
|
1849
1844
|
// Global skills directory
|
|
@@ -1901,42 +1896,6 @@ async function doctor(debug = false) {
|
|
|
1901
1896
|
}
|
|
1902
1897
|
}
|
|
1903
1898
|
|
|
1904
|
-
// Claude Code checks
|
|
1905
|
-
p.log.step("Claude Code:");
|
|
1906
|
-
const claudeResult = results.find((result) => result.dep.name === "Claude Code");
|
|
1907
|
-
const claudeStatus = getClaudeInstallStatus(process.cwd());
|
|
1908
|
-
|
|
1909
|
-
if (!claudeResult?.available) {
|
|
1910
|
-
p.log.warn("Claude Code CLI not detected (optional)");
|
|
1911
|
-
p.log.message(dim(" Install: https://docs.anthropic.com/claude-code"));
|
|
1912
|
-
} else {
|
|
1913
|
-
p.log.success(`Claude Code CLI detected${claudeResult.version ? ` v${claudeResult.version}` : ""}`);
|
|
1914
|
-
|
|
1915
|
-
if (existsSync(claudeStatus.pluginRoot)) {
|
|
1916
|
-
p.log.message(dim(` Plugin bundle: ${claudeStatus.pluginRoot}`));
|
|
1917
|
-
} else {
|
|
1918
|
-
p.log.warn(`Claude plugin bundle missing: ${claudeStatus.pluginRoot}`);
|
|
1919
|
-
}
|
|
1920
|
-
|
|
1921
|
-
if (claudeStatus.globalPluginExists) {
|
|
1922
|
-
if (claudeStatus.globalPluginLinked) {
|
|
1923
|
-
p.log.success(`Global plugin symlink: ${claudeStatus.globalPluginPath}`);
|
|
1924
|
-
} else {
|
|
1925
|
-
p.log.warn(`Global plugin exists but is not a symlink: ${claudeStatus.globalPluginPath}`);
|
|
1926
|
-
}
|
|
1927
|
-
} else {
|
|
1928
|
-
p.log.warn("Global Claude plugin not installed");
|
|
1929
|
-
p.log.message(dim(" Run: swarm claude install"));
|
|
1930
|
-
}
|
|
1931
|
-
|
|
1932
|
-
if (claudeStatus.projectConfigExists) {
|
|
1933
|
-
p.log.success(`Project Claude config: ${claudeStatus.projectClaudeDir}`);
|
|
1934
|
-
} else {
|
|
1935
|
-
p.log.warn("Project Claude config not found");
|
|
1936
|
-
p.log.message(dim(" Run: swarm claude init"));
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
|
-
|
|
1940
1899
|
if (requiredMissing.length > 0) {
|
|
1941
1900
|
p.outro(
|
|
1942
1901
|
"Missing " +
|
|
@@ -2173,8 +2132,7 @@ async function setup(forceReinstall = false, nonInteractive = false) {
|
|
|
2173
2132
|
p.log.step("Missing " + requiredMissing.length + " required dependencies");
|
|
2174
2133
|
|
|
2175
2134
|
for (const { dep } of requiredMissing) {
|
|
2176
|
-
|
|
2177
|
-
const shouldInstall = nonInteractive ? true : await p.confirm({
|
|
2135
|
+
const shouldInstall = await p.confirm({
|
|
2178
2136
|
message: "Install " + dep.name + "? (" + dep.description + ")",
|
|
2179
2137
|
initialValue: true,
|
|
2180
2138
|
});
|
|
@@ -2185,16 +2143,47 @@ async function setup(forceReinstall = false, nonInteractive = false) {
|
|
|
2185
2143
|
}
|
|
2186
2144
|
|
|
2187
2145
|
if (shouldInstall) {
|
|
2188
|
-
|
|
2189
|
-
|
|
2146
|
+
// Check if this dependency has multiple installation methods
|
|
2147
|
+
if (dep.installMethods && dep.installMethods.length > 0) {
|
|
2148
|
+
// Detect available methods on this system
|
|
2149
|
+
const detectSpinner = p.spinner();
|
|
2150
|
+
detectSpinner.start("Detecting available installation methods...");
|
|
2151
|
+
const availableMethods = await getAvailableInstallMethods(dep.installMethods);
|
|
2152
|
+
detectSpinner.stop(`Found ${availableMethods.length} installation method(s)`);
|
|
2153
|
+
|
|
2154
|
+
// Prompt user to select method
|
|
2155
|
+
const selectedMethod = await promptInstallMethod(dep, availableMethods);
|
|
2156
|
+
|
|
2157
|
+
if (!selectedMethod) {
|
|
2158
|
+
p.log.warn("Skipping " + dep.name + " - no installation method selected");
|
|
2159
|
+
continue;
|
|
2160
|
+
}
|
|
2190
2161
|
|
|
2191
|
-
|
|
2162
|
+
const installSpinner = p.spinner();
|
|
2163
|
+
installSpinner.start(`Installing ${dep.name} via ${selectedMethod.name}...`);
|
|
2164
|
+
|
|
2165
|
+
const success = await runInstall(selectedMethod.command);
|
|
2192
2166
|
|
|
2193
|
-
|
|
2194
|
-
|
|
2167
|
+
if (success) {
|
|
2168
|
+
installSpinner.stop(dep.name + " installed");
|
|
2169
|
+
} else {
|
|
2170
|
+
installSpinner.stop("Failed to install " + dep.name);
|
|
2171
|
+
p.log.error("Command failed: " + selectedMethod.command);
|
|
2172
|
+
p.log.message(dim(" Try running the command manually in your terminal"));
|
|
2173
|
+
}
|
|
2195
2174
|
} else {
|
|
2196
|
-
|
|
2197
|
-
|
|
2175
|
+
// Fallback to single install command
|
|
2176
|
+
const installSpinner = p.spinner();
|
|
2177
|
+
installSpinner.start("Installing " + dep.name + "...");
|
|
2178
|
+
|
|
2179
|
+
const success = await runInstall(dep.install);
|
|
2180
|
+
|
|
2181
|
+
if (success) {
|
|
2182
|
+
installSpinner.stop(dep.name + " installed");
|
|
2183
|
+
} else {
|
|
2184
|
+
installSpinner.stop("Failed to install " + dep.name);
|
|
2185
|
+
p.log.error("Manual install: " + dep.install);
|
|
2186
|
+
}
|
|
2198
2187
|
}
|
|
2199
2188
|
} else {
|
|
2200
2189
|
p.log.warn("Skipping " + dep.name + " - swarm may not work correctly");
|
|
@@ -2370,85 +2359,6 @@ async function setup(forceReinstall = false, nonInteractive = false) {
|
|
|
2370
2359
|
p.log.message(dim(' No OpenCode config found (skipping MCP check)'));
|
|
2371
2360
|
}
|
|
2372
2361
|
|
|
2373
|
-
// Check for stray databases and consolidate to global database
|
|
2374
|
-
p.log.step("Checking for stray databases...");
|
|
2375
|
-
const globalDbPath = getGlobalDbPath();
|
|
2376
|
-
|
|
2377
|
-
try {
|
|
2378
|
-
const report = await consolidateDatabases(cwd, globalDbPath, {
|
|
2379
|
-
yes: nonInteractive,
|
|
2380
|
-
interactive: !nonInteractive,
|
|
2381
|
-
});
|
|
2382
|
-
|
|
2383
|
-
if (report.straysFound > 0) {
|
|
2384
|
-
if (report.totalRowsMigrated > 0) {
|
|
2385
|
-
p.log.success(
|
|
2386
|
-
`Migrated ${report.totalRowsMigrated} records from ${report.straysMigrated} stray database(s)`
|
|
2387
|
-
);
|
|
2388
|
-
for (const migration of report.migrations) {
|
|
2389
|
-
const { migrated, skipped } = migration.result;
|
|
2390
|
-
if (migrated > 0 || skipped > 0) {
|
|
2391
|
-
p.log.message(
|
|
2392
|
-
dim(
|
|
2393
|
-
` ${migration.path}: ${migrated} migrated, ${skipped} skipped`
|
|
2394
|
-
)
|
|
2395
|
-
);
|
|
2396
|
-
}
|
|
2397
|
-
}
|
|
2398
|
-
} else {
|
|
2399
|
-
p.log.message(
|
|
2400
|
-
dim(" All data already in global database (no migration needed)")
|
|
2401
|
-
);
|
|
2402
|
-
}
|
|
2403
|
-
} else {
|
|
2404
|
-
p.log.message(dim(" No stray databases found"));
|
|
2405
|
-
}
|
|
2406
|
-
|
|
2407
|
-
if (report.errors.length > 0) {
|
|
2408
|
-
p.log.warn(`${report.errors.length} error(s) during consolidation`);
|
|
2409
|
-
for (const error of report.errors) {
|
|
2410
|
-
p.log.message(dim(` ${error}`));
|
|
2411
|
-
}
|
|
2412
|
-
}
|
|
2413
|
-
} catch (error) {
|
|
2414
|
-
p.log.warn("Database consolidation check failed");
|
|
2415
|
-
if (error instanceof Error) {
|
|
2416
|
-
p.log.message(dim(` ${error.message}`));
|
|
2417
|
-
}
|
|
2418
|
-
// Don't fail setup - this is non-critical
|
|
2419
|
-
}
|
|
2420
|
-
|
|
2421
|
-
// Run database repair after consolidation
|
|
2422
|
-
p.log.step("Running database integrity check...");
|
|
2423
|
-
try {
|
|
2424
|
-
const repairResult = await runDbRepair({ dryRun: false });
|
|
2425
|
-
|
|
2426
|
-
if (repairResult.totalCleaned === 0) {
|
|
2427
|
-
p.log.success("Database integrity verified - no issues found");
|
|
2428
|
-
} else {
|
|
2429
|
-
p.log.success(`Cleaned ${repairResult.totalCleaned} orphaned/invalid records`);
|
|
2430
|
-
|
|
2431
|
-
if (repairResult.nullBeads > 0) {
|
|
2432
|
-
p.log.message(dim(` - ${repairResult.nullBeads} beads with NULL IDs`));
|
|
2433
|
-
}
|
|
2434
|
-
if (repairResult.orphanedRecipients > 0) {
|
|
2435
|
-
p.log.message(dim(` - ${repairResult.orphanedRecipients} orphaned message recipients`));
|
|
2436
|
-
}
|
|
2437
|
-
if (repairResult.messagesWithoutRecipients > 0) {
|
|
2438
|
-
p.log.message(dim(` - ${repairResult.messagesWithoutRecipients} messages without recipients`));
|
|
2439
|
-
}
|
|
2440
|
-
if (repairResult.expiredReservations > 0) {
|
|
2441
|
-
p.log.message(dim(` - ${repairResult.expiredReservations} expired reservations`));
|
|
2442
|
-
}
|
|
2443
|
-
}
|
|
2444
|
-
} catch (error) {
|
|
2445
|
-
p.log.warn("Database repair check failed (non-critical)");
|
|
2446
|
-
if (error instanceof Error) {
|
|
2447
|
-
p.log.message(dim(` ${error.message}`));
|
|
2448
|
-
}
|
|
2449
|
-
// Don't fail setup - this is non-critical
|
|
2450
|
-
}
|
|
2451
|
-
|
|
2452
2362
|
// Model defaults: opus for coordinator, sonnet for worker, haiku for lite
|
|
2453
2363
|
const DEFAULT_COORDINATOR = "anthropic/claude-opus-4-5";
|
|
2454
2364
|
const DEFAULT_WORKER = "anthropic/claude-sonnet-4-5";
|
|
@@ -2753,36 +2663,6 @@ async function setup(forceReinstall = false, nonInteractive = false) {
|
|
|
2753
2663
|
}
|
|
2754
2664
|
}
|
|
2755
2665
|
|
|
2756
|
-
// Claude Code checks
|
|
2757
|
-
p.log.step("Claude Code integration (optional)...");
|
|
2758
|
-
const claudeResult = results.find((result) => result.dep.name === "Claude Code");
|
|
2759
|
-
const claudeStatus = getClaudeInstallStatus(cwd);
|
|
2760
|
-
|
|
2761
|
-
if (!claudeResult?.available) {
|
|
2762
|
-
p.log.warn("Claude Code not detected (optional)");
|
|
2763
|
-
p.log.message(dim(" Install: https://docs.anthropic.com/claude-code"));
|
|
2764
|
-
} else {
|
|
2765
|
-
const versionInfo = claudeResult.version ? ` v${claudeResult.version}` : "";
|
|
2766
|
-
p.log.success(`Claude Code detected${versionInfo}`);
|
|
2767
|
-
p.log.message(dim(` Plugin bundle: ${claudeStatus.pluginRoot}`));
|
|
2768
|
-
|
|
2769
|
-
if (claudeStatus.globalPluginExists) {
|
|
2770
|
-
if (claudeStatus.globalPluginLinked) {
|
|
2771
|
-
p.log.success(`Claude plugin linked: ${claudeStatus.globalPluginPath}`);
|
|
2772
|
-
} else {
|
|
2773
|
-
p.log.warn(`Claude plugin exists but is not a symlink: ${claudeStatus.globalPluginPath}`);
|
|
2774
|
-
}
|
|
2775
|
-
} else {
|
|
2776
|
-
p.log.message(dim(" Run 'swarm claude install' for a dev symlink"));
|
|
2777
|
-
}
|
|
2778
|
-
|
|
2779
|
-
if (claudeStatus.projectConfigExists) {
|
|
2780
|
-
p.log.success(`Project Claude config: ${claudeStatus.projectClaudeDir}`);
|
|
2781
|
-
} else {
|
|
2782
|
-
p.log.message(dim(" Run 'swarm claude init' to create .claude/ config"));
|
|
2783
|
-
}
|
|
2784
|
-
}
|
|
2785
|
-
|
|
2786
2666
|
// Show setup summary
|
|
2787
2667
|
const totalFiles = stats.created + stats.updated + stats.unchanged;
|
|
2788
2668
|
const summaryParts: string[] = [];
|
|
@@ -2801,903 +2681,43 @@ async function setup(forceReinstall = false, nonInteractive = false) {
|
|
|
2801
2681
|
p.outro("Run 'swarm doctor' to verify installation.");
|
|
2802
2682
|
}
|
|
2803
2683
|
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
// ============================================================================
|
|
2807
|
-
|
|
2808
|
-
/**
|
|
2809
|
-
* Handle Claude Code subcommands.
|
|
2810
|
-
*/
|
|
2811
|
-
async function claudeCommand() {
|
|
2812
|
-
const args = process.argv.slice(3);
|
|
2813
|
-
const subcommand = args[0];
|
|
2814
|
-
|
|
2815
|
-
if (!subcommand || ["help", "--help", "-h"].includes(subcommand)) {
|
|
2816
|
-
showClaudeHelp();
|
|
2817
|
-
return;
|
|
2818
|
-
}
|
|
2819
|
-
|
|
2820
|
-
switch (subcommand) {
|
|
2821
|
-
case "path":
|
|
2822
|
-
claudePath();
|
|
2823
|
-
break;
|
|
2824
|
-
case "install":
|
|
2825
|
-
await claudeInstall();
|
|
2826
|
-
break;
|
|
2827
|
-
case "uninstall":
|
|
2828
|
-
await claudeUninstall();
|
|
2829
|
-
break;
|
|
2830
|
-
case "init":
|
|
2831
|
-
await claudeInit();
|
|
2832
|
-
break;
|
|
2833
|
-
case "session-start":
|
|
2834
|
-
await claudeSessionStart();
|
|
2835
|
-
break;
|
|
2836
|
-
case "user-prompt":
|
|
2837
|
-
await claudeUserPrompt();
|
|
2838
|
-
break;
|
|
2839
|
-
case "pre-edit":
|
|
2840
|
-
await claudePreEdit();
|
|
2841
|
-
break;
|
|
2842
|
-
case "pre-complete":
|
|
2843
|
-
await claudePreComplete();
|
|
2844
|
-
break;
|
|
2845
|
-
case "post-complete":
|
|
2846
|
-
await claudePostComplete();
|
|
2847
|
-
break;
|
|
2848
|
-
case "pre-compact":
|
|
2849
|
-
await claudePreCompact();
|
|
2850
|
-
break;
|
|
2851
|
-
case "session-end":
|
|
2852
|
-
await claudeSessionEnd();
|
|
2853
|
-
break;
|
|
2854
|
-
case "track-tool":
|
|
2855
|
-
await claudeTrackTool(Bun.argv[4]); // tool name is 4th arg
|
|
2856
|
-
break;
|
|
2857
|
-
case "compliance":
|
|
2858
|
-
await claudeCompliance();
|
|
2859
|
-
break;
|
|
2860
|
-
default:
|
|
2861
|
-
console.error(`Unknown subcommand: ${subcommand}`);
|
|
2862
|
-
showClaudeHelp();
|
|
2863
|
-
process.exit(1);
|
|
2864
|
-
}
|
|
2865
|
-
}
|
|
2866
|
-
|
|
2867
|
-
function showClaudeHelp() {
|
|
2868
|
-
console.log(`
|
|
2869
|
-
Usage: swarm claude <command>
|
|
2870
|
-
|
|
2871
|
-
Commands:
|
|
2872
|
-
path Print Claude plugin path (for --plugin-dir)
|
|
2873
|
-
install Symlink plugin into ~/.claude/plugins/${CLAUDE_PLUGIN_NAME}
|
|
2874
|
-
uninstall Remove Claude plugin symlink
|
|
2875
|
-
init Create project-local .claude/ config
|
|
2876
|
-
session-start Hook: session start context (JSON output)
|
|
2877
|
-
user-prompt Hook: prompt submit context (JSON output)
|
|
2878
|
-
pre-edit Hook: pre-Edit/Write reminder (hivemind check)
|
|
2879
|
-
pre-complete Hook: pre-swarm_complete checklist
|
|
2880
|
-
post-complete Hook: post-swarm_complete learnings reminder
|
|
2881
|
-
pre-compact Hook: pre-compaction handler
|
|
2882
|
-
session-end Hook: session cleanup
|
|
2883
|
-
`);
|
|
2884
|
-
}
|
|
2684
|
+
async function init() {
|
|
2685
|
+
p.intro("swarm init v" + VERSION);
|
|
2885
2686
|
|
|
2886
|
-
|
|
2887
|
-
* Print the bundled Claude plugin path.
|
|
2888
|
-
*/
|
|
2889
|
-
function claudePath() {
|
|
2890
|
-
const pluginRoot = getClaudePluginRoot();
|
|
2891
|
-
if (!existsSync(pluginRoot)) {
|
|
2892
|
-
console.error(`Claude plugin not found at ${pluginRoot}`);
|
|
2893
|
-
process.exit(1);
|
|
2894
|
-
}
|
|
2895
|
-
console.log(pluginRoot);
|
|
2896
|
-
}
|
|
2687
|
+
const projectPath = process.cwd();
|
|
2897
2688
|
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
p.intro("swarm claude install");
|
|
2903
|
-
const pluginRoot = getClaudePluginRoot();
|
|
2904
|
-
if (!existsSync(pluginRoot)) {
|
|
2905
|
-
p.log.error(`Claude plugin not found at ${pluginRoot}`);
|
|
2689
|
+
const gitDir = existsSync(".git");
|
|
2690
|
+
if (!gitDir) {
|
|
2691
|
+
p.log.error("Not in a git repository");
|
|
2692
|
+
p.log.message("Run 'git init' first, or cd to a git repo");
|
|
2906
2693
|
p.outro("Aborted");
|
|
2907
2694
|
process.exit(1);
|
|
2908
2695
|
}
|
|
2909
2696
|
|
|
2910
|
-
|
|
2911
|
-
const
|
|
2912
|
-
const
|
|
2697
|
+
// Check for existing .hive or .beads directories
|
|
2698
|
+
const hiveDir = existsSync(".hive");
|
|
2699
|
+
const beadsDir = existsSync(".beads");
|
|
2700
|
+
|
|
2701
|
+
if (hiveDir) {
|
|
2702
|
+
p.log.warn("Hive already initialized in this project (.hive/ exists)");
|
|
2913
2703
|
|
|
2914
|
-
|
|
2704
|
+
const reinit = await p.confirm({
|
|
2705
|
+
message: "Continue anyway?",
|
|
2706
|
+
initialValue: false,
|
|
2707
|
+
});
|
|
2915
2708
|
|
|
2916
|
-
|
|
2917
|
-
const stat = lstatSync(pluginPath);
|
|
2918
|
-
if (!stat.isSymbolicLink()) {
|
|
2919
|
-
p.log.error(`Existing path is not a symlink: ${pluginPath}`);
|
|
2709
|
+
if (p.isCancel(reinit) || !reinit) {
|
|
2920
2710
|
p.outro("Aborted");
|
|
2921
|
-
process.exit(
|
|
2922
|
-
}
|
|
2923
|
-
|
|
2924
|
-
const target = readlinkSync(pluginPath);
|
|
2925
|
-
const resolved = resolve(dirname(pluginPath), target);
|
|
2926
|
-
if (resolved === pluginRoot) {
|
|
2927
|
-
p.log.success("Claude plugin already linked");
|
|
2928
|
-
p.outro("Done");
|
|
2929
|
-
return;
|
|
2711
|
+
process.exit(0);
|
|
2930
2712
|
}
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
}
|
|
2940
|
-
|
|
2941
|
-
/**
|
|
2942
|
-
* Remove the Claude plugin symlink.
|
|
2943
|
-
*/
|
|
2944
|
-
async function claudeUninstall() {
|
|
2945
|
-
p.intro("swarm claude uninstall");
|
|
2946
|
-
const pluginPath = join(getClaudeConfigDir(), "plugins", CLAUDE_PLUGIN_NAME);
|
|
2947
|
-
|
|
2948
|
-
if (!existsSync(pluginPath)) {
|
|
2949
|
-
p.log.warn("Claude plugin symlink not found");
|
|
2950
|
-
p.outro("Nothing to remove");
|
|
2951
|
-
return;
|
|
2952
|
-
}
|
|
2953
|
-
|
|
2954
|
-
rmSync(pluginPath, { recursive: true, force: true });
|
|
2955
|
-
p.log.success(`Removed ${pluginPath}`);
|
|
2956
|
-
p.outro("Claude plugin uninstalled");
|
|
2957
|
-
}
|
|
2958
|
-
|
|
2959
|
-
/**
|
|
2960
|
-
* Create project-local Claude Code config from bundled plugin assets.
|
|
2961
|
-
*/
|
|
2962
|
-
async function claudeInit() {
|
|
2963
|
-
p.intro("swarm claude init");
|
|
2964
|
-
const projectPath = process.cwd();
|
|
2965
|
-
const pluginRoot = getClaudePluginRoot();
|
|
2966
|
-
|
|
2967
|
-
if (!existsSync(pluginRoot)) {
|
|
2968
|
-
p.log.error(`Claude plugin not found at ${pluginRoot}`);
|
|
2969
|
-
p.outro("Aborted");
|
|
2970
|
-
process.exit(1);
|
|
2971
|
-
}
|
|
2972
|
-
|
|
2973
|
-
const projectClaudeDir = join(projectPath, ".claude");
|
|
2974
|
-
const commandDir = join(projectClaudeDir, "commands");
|
|
2975
|
-
const agentDir = join(projectClaudeDir, "agents");
|
|
2976
|
-
const skillsDir = join(projectClaudeDir, "skills");
|
|
2977
|
-
const hooksDir = join(projectClaudeDir, "hooks");
|
|
2978
|
-
|
|
2979
|
-
for (const dir of [projectClaudeDir, commandDir, agentDir, skillsDir, hooksDir]) {
|
|
2980
|
-
mkdirWithStatus(dir);
|
|
2981
|
-
}
|
|
2982
|
-
|
|
2983
|
-
const copyMap = [
|
|
2984
|
-
{ src: join(pluginRoot, "commands"), dest: commandDir, label: "Commands" },
|
|
2985
|
-
{ src: join(pluginRoot, "agents"), dest: agentDir, label: "Agents" },
|
|
2986
|
-
{ src: join(pluginRoot, "skills"), dest: skillsDir, label: "Skills" },
|
|
2987
|
-
{ src: join(pluginRoot, "hooks"), dest: hooksDir, label: "Hooks" },
|
|
2988
|
-
];
|
|
2989
|
-
|
|
2990
|
-
for (const { src, dest, label } of copyMap) {
|
|
2991
|
-
if (existsSync(src)) {
|
|
2992
|
-
copyDirRecursiveSync(src, dest);
|
|
2993
|
-
p.log.success(`${label}: ${dest}`);
|
|
2994
|
-
}
|
|
2995
|
-
}
|
|
2996
|
-
|
|
2997
|
-
const mcpSourcePath = join(pluginRoot, ".mcp.json");
|
|
2998
|
-
if (existsSync(mcpSourcePath)) {
|
|
2999
|
-
const mcpDestPath = join(projectClaudeDir, ".mcp.json");
|
|
3000
|
-
const content = readFileSync(mcpSourcePath, "utf-8");
|
|
3001
|
-
writeFileWithStatus(mcpDestPath, content, "MCP config");
|
|
3002
|
-
}
|
|
3003
|
-
|
|
3004
|
-
const lspSourcePath = join(pluginRoot, ".lsp.json");
|
|
3005
|
-
if (existsSync(lspSourcePath)) {
|
|
3006
|
-
const lspDestPath = join(projectClaudeDir, ".lsp.json");
|
|
3007
|
-
const content = readFileSync(lspSourcePath, "utf-8");
|
|
3008
|
-
writeFileWithStatus(lspDestPath, content, "LSP config");
|
|
3009
|
-
}
|
|
3010
|
-
|
|
3011
|
-
p.log.message(dim(" Uses ${CLAUDE_PLUGIN_ROOT} in MCP config"));
|
|
3012
|
-
p.outro("Claude project config ready");
|
|
3013
|
-
}
|
|
3014
|
-
|
|
3015
|
-
/**
|
|
3016
|
-
* Claude hook: start a session and emit comprehensive context for Claude Code.
|
|
3017
|
-
*
|
|
3018
|
-
* Gathers:
|
|
3019
|
-
* - Session info + previous handoff notes
|
|
3020
|
-
* - In-progress cells (work that was mid-flight)
|
|
3021
|
-
* - Open epics with their children
|
|
3022
|
-
* - Recent activity summary
|
|
3023
|
-
*/
|
|
3024
|
-
async function claudeSessionStart() {
|
|
3025
|
-
try {
|
|
3026
|
-
const input = await readHookInput<ClaudeHookInput>();
|
|
3027
|
-
const projectPath = resolveClaudeProjectPath(input);
|
|
3028
|
-
const swarmMail = await getSwarmMailLibSQL(projectPath);
|
|
3029
|
-
const db = await swarmMail.getDatabase(projectPath);
|
|
3030
|
-
const adapter = createHiveAdapter(db, projectPath);
|
|
3031
|
-
|
|
3032
|
-
await adapter.runMigrations();
|
|
3033
|
-
|
|
3034
|
-
const session = await adapter.startSession(projectPath, {});
|
|
3035
|
-
const contextLines: string[] = [];
|
|
3036
|
-
|
|
3037
|
-
// Session basics
|
|
3038
|
-
contextLines.push(`## Swarm Session: ${session.id}`);
|
|
3039
|
-
contextLines.push(`Source: ${(input as { source?: string }).source || "startup"}`);
|
|
3040
|
-
contextLines.push("");
|
|
3041
|
-
|
|
3042
|
-
// Previous handoff notes (critical for continuation)
|
|
3043
|
-
if (session.previous_handoff_notes) {
|
|
3044
|
-
contextLines.push("## Previous Handoff Notes");
|
|
3045
|
-
contextLines.push(session.previous_handoff_notes);
|
|
3046
|
-
contextLines.push("");
|
|
3047
|
-
}
|
|
3048
|
-
|
|
3049
|
-
// Active cell from previous session
|
|
3050
|
-
if (session.active_cell_id) {
|
|
3051
|
-
const activeCell = await adapter.getCell(projectPath, session.active_cell_id);
|
|
3052
|
-
if (activeCell) {
|
|
3053
|
-
contextLines.push("## Active Cell (from previous session)");
|
|
3054
|
-
contextLines.push(`- **${activeCell.id}**: ${activeCell.title}`);
|
|
3055
|
-
contextLines.push(` Status: ${activeCell.status}, Priority: ${activeCell.priority}`);
|
|
3056
|
-
if (activeCell.description) {
|
|
3057
|
-
contextLines.push(` ${activeCell.description.slice(0, 200)}...`);
|
|
3058
|
-
}
|
|
3059
|
-
contextLines.push("");
|
|
3060
|
-
}
|
|
3061
|
-
}
|
|
3062
|
-
|
|
3063
|
-
// In-progress cells (work that was mid-flight)
|
|
3064
|
-
const inProgressCells = await adapter.getInProgressCells(projectPath);
|
|
3065
|
-
if (inProgressCells.length > 0) {
|
|
3066
|
-
contextLines.push("## In-Progress Work");
|
|
3067
|
-
for (const cell of inProgressCells.slice(0, 5)) {
|
|
3068
|
-
contextLines.push(`- **${cell.id}**: ${cell.title} (${cell.type}, P${cell.priority})`);
|
|
3069
|
-
if (cell.description) {
|
|
3070
|
-
contextLines.push(` ${cell.description.slice(0, 150)}`);
|
|
3071
|
-
}
|
|
3072
|
-
}
|
|
3073
|
-
if (inProgressCells.length > 5) {
|
|
3074
|
-
contextLines.push(` ... and ${inProgressCells.length - 5} more`);
|
|
3075
|
-
}
|
|
3076
|
-
contextLines.push("");
|
|
3077
|
-
}
|
|
3078
|
-
|
|
3079
|
-
// Open epics (high-level context)
|
|
3080
|
-
const openEpics = await adapter.queryCells(projectPath, {
|
|
3081
|
-
status: "open",
|
|
3082
|
-
type: "epic",
|
|
3083
|
-
limit: 3
|
|
3084
|
-
});
|
|
3085
|
-
if (openEpics.length > 0) {
|
|
3086
|
-
contextLines.push("## Open Epics");
|
|
3087
|
-
for (const epic of openEpics) {
|
|
3088
|
-
const children = await adapter.getEpicChildren(projectPath, epic.id);
|
|
3089
|
-
const openChildren = children.filter(c => c.status !== "closed");
|
|
3090
|
-
contextLines.push(`- **${epic.id}**: ${epic.title}`);
|
|
3091
|
-
contextLines.push(` ${openChildren.length}/${children.length} subtasks remaining`);
|
|
3092
|
-
}
|
|
3093
|
-
contextLines.push("");
|
|
3094
|
-
}
|
|
3095
|
-
|
|
3096
|
-
// Stats summary
|
|
3097
|
-
const stats = await adapter.getCellsStats(projectPath);
|
|
3098
|
-
contextLines.push("## Hive Stats");
|
|
3099
|
-
contextLines.push(`Open: ${stats.open} | In Progress: ${stats.in_progress} | Blocked: ${stats.blocked} | Closed: ${stats.closed}`);
|
|
3100
|
-
|
|
3101
|
-
writeClaudeHookOutput("SessionStart", contextLines.join("\n"));
|
|
3102
|
-
} catch (error) {
|
|
3103
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
3104
|
-
}
|
|
3105
|
-
}
|
|
3106
|
-
|
|
3107
|
-
/**
|
|
3108
|
-
* Claude hook: provide active context on prompt submission.
|
|
3109
|
-
*
|
|
3110
|
-
* Lightweight hook that runs on every prompt. Provides:
|
|
3111
|
-
* - Active session info
|
|
3112
|
-
* - Current in-progress cell (if any)
|
|
3113
|
-
* - Quick stats for awareness
|
|
3114
|
-
*
|
|
3115
|
-
* Output is suppressed from verbose mode to avoid noise.
|
|
3116
|
-
*/
|
|
3117
|
-
async function claudeUserPrompt() {
|
|
3118
|
-
try {
|
|
3119
|
-
const input = await readHookInput<ClaudeHookInput>();
|
|
3120
|
-
const projectPath = resolveClaudeProjectPath(input);
|
|
3121
|
-
const swarmMail = await getSwarmMailLibSQL(projectPath);
|
|
3122
|
-
const db = await swarmMail.getDatabase(projectPath);
|
|
3123
|
-
const adapter = createHiveAdapter(db, projectPath);
|
|
3124
|
-
|
|
3125
|
-
await adapter.runMigrations();
|
|
3126
|
-
|
|
3127
|
-
const session = await adapter.getCurrentSession(projectPath);
|
|
3128
|
-
if (!session) return;
|
|
3129
|
-
|
|
3130
|
-
const contextLines: string[] = [];
|
|
3131
|
-
|
|
3132
|
-
// Always inject current timestamp for temporal awareness
|
|
3133
|
-
const now = new Date();
|
|
3134
|
-
const timestamp = now.toLocaleString("en-US", {
|
|
3135
|
-
weekday: "short",
|
|
3136
|
-
year: "numeric",
|
|
3137
|
-
month: "short",
|
|
3138
|
-
day: "numeric",
|
|
3139
|
-
hour: "2-digit",
|
|
3140
|
-
minute: "2-digit",
|
|
3141
|
-
timeZoneName: "short",
|
|
3142
|
-
});
|
|
3143
|
-
contextLines.push(`**Now**: ${timestamp}`);
|
|
3144
|
-
|
|
3145
|
-
// Semantic memory recall - search hivemind for relevant context
|
|
3146
|
-
if (input.prompt && input.prompt.trim().length > 10) {
|
|
3147
|
-
try {
|
|
3148
|
-
const memoryAdapter = await createMemoryAdapter(db);
|
|
3149
|
-
const findResult = await memoryAdapter.find({ query: input.prompt, limit: 3 });
|
|
3150
|
-
const memories = findResult.results;
|
|
3151
|
-
|
|
3152
|
-
if (memories && memories.length > 0) {
|
|
3153
|
-
// Only include high-confidence matches (score > 0.5)
|
|
3154
|
-
const relevant = memories.filter((m: { score?: number }) => (m.score ?? 0) > 0.5);
|
|
3155
|
-
if (relevant.length > 0) {
|
|
3156
|
-
const memorySnippets = relevant
|
|
3157
|
-
.slice(0, 2) // Max 2 memories to keep context light
|
|
3158
|
-
.map((m: { content?: string; information?: string }) => {
|
|
3159
|
-
const content = m.content || m.information || "";
|
|
3160
|
-
return content.length > 200 ? content.slice(0, 200) + "..." : content;
|
|
3161
|
-
})
|
|
3162
|
-
.join(" | ");
|
|
3163
|
-
contextLines.push(`**Recall**: ${memorySnippets}`);
|
|
3164
|
-
}
|
|
3165
|
-
}
|
|
3166
|
-
} catch {
|
|
3167
|
-
// Memory recall is optional - don't fail the hook
|
|
3168
|
-
}
|
|
3169
|
-
}
|
|
3170
|
-
|
|
3171
|
-
// Current active cell (most relevant context)
|
|
3172
|
-
if (session.active_cell_id) {
|
|
3173
|
-
const activeCell = await adapter.getCell(projectPath, session.active_cell_id);
|
|
3174
|
-
if (activeCell && activeCell.status !== "closed") {
|
|
3175
|
-
contextLines.push(`**Active**: ${activeCell.id} - ${activeCell.title}`);
|
|
3176
|
-
}
|
|
3177
|
-
}
|
|
3178
|
-
|
|
3179
|
-
// Quick in-progress count for awareness
|
|
3180
|
-
const inProgress = await adapter.getInProgressCells(projectPath);
|
|
3181
|
-
if (inProgress.length > 0) {
|
|
3182
|
-
const titles = inProgress.slice(0, 3).map(c => c.title.slice(0, 40)).join(", ");
|
|
3183
|
-
contextLines.push(`**WIP (${inProgress.length})**: ${titles}${inProgress.length > 3 ? "..." : ""}`);
|
|
3184
|
-
}
|
|
3185
|
-
|
|
3186
|
-
// Only output if there's meaningful context
|
|
3187
|
-
if (contextLines.length > 0) {
|
|
3188
|
-
writeClaudeHookOutput("UserPromptSubmit", contextLines.join(" | "), { suppressOutput: true });
|
|
3189
|
-
}
|
|
3190
|
-
} catch (error) {
|
|
3191
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
3192
|
-
}
|
|
3193
|
-
}
|
|
3194
|
-
|
|
3195
|
-
/**
|
|
3196
|
-
* Claude hook: capture comprehensive state before compaction.
|
|
3197
|
-
*
|
|
3198
|
-
* This is the CRITICAL hook for continuation. It captures:
|
|
3199
|
-
* - All in-progress work with details
|
|
3200
|
-
* - Active epic state and progress
|
|
3201
|
-
* - Any pending/blocked cells
|
|
3202
|
-
* - Reserved files
|
|
3203
|
-
* - Session context
|
|
3204
|
-
*
|
|
3205
|
-
* The output becomes part of the compacted summary, enabling
|
|
3206
|
-
* Claude to continue work seamlessly after context window fills.
|
|
3207
|
-
*/
|
|
3208
|
-
async function claudePreCompact() {
|
|
3209
|
-
try {
|
|
3210
|
-
const input = await readHookInput<ClaudeHookInput & { trigger?: string; custom_instructions?: string }>();
|
|
3211
|
-
const projectPath = resolveClaudeProjectPath(input);
|
|
3212
|
-
const swarmMail = await getSwarmMailLibSQL(projectPath);
|
|
3213
|
-
const db = await swarmMail.getDatabase(projectPath);
|
|
3214
|
-
const adapter = createHiveAdapter(db, projectPath);
|
|
3215
|
-
|
|
3216
|
-
await adapter.runMigrations();
|
|
3217
|
-
|
|
3218
|
-
const session = await adapter.getCurrentSession(projectPath);
|
|
3219
|
-
const contextLines: string[] = [];
|
|
3220
|
-
|
|
3221
|
-
contextLines.push("# Swarm State Snapshot (Pre-Compaction)");
|
|
3222
|
-
contextLines.push(`Trigger: ${input.trigger || "auto"}`);
|
|
3223
|
-
if (input.custom_instructions) {
|
|
3224
|
-
contextLines.push(`Instructions: ${input.custom_instructions}`);
|
|
3225
|
-
}
|
|
3226
|
-
contextLines.push("");
|
|
3227
|
-
|
|
3228
|
-
// Session info
|
|
3229
|
-
if (session) {
|
|
3230
|
-
contextLines.push("## Session");
|
|
3231
|
-
contextLines.push(`ID: ${session.id}`);
|
|
3232
|
-
if (session.active_cell_id) {
|
|
3233
|
-
contextLines.push(`Active cell: ${session.active_cell_id}`);
|
|
3234
|
-
}
|
|
3235
|
-
}
|
|
3236
|
-
contextLines.push("");
|
|
3237
|
-
|
|
3238
|
-
// In-progress work (CRITICAL for continuation)
|
|
3239
|
-
const inProgressCells = await adapter.getInProgressCells(projectPath);
|
|
3240
|
-
if (inProgressCells.length > 0) {
|
|
3241
|
-
contextLines.push("## In-Progress Work (CONTINUE THESE)");
|
|
3242
|
-
for (const cell of inProgressCells) {
|
|
3243
|
-
contextLines.push(`### ${cell.id}: ${cell.title}`);
|
|
3244
|
-
contextLines.push(`Type: ${cell.type} | Priority: ${cell.priority} | Status: ${cell.status}`);
|
|
3245
|
-
if (cell.parent_id) {
|
|
3246
|
-
contextLines.push(`Parent: ${cell.parent_id}`);
|
|
3247
|
-
}
|
|
3248
|
-
if (cell.description) {
|
|
3249
|
-
contextLines.push(`Description: ${cell.description}`);
|
|
3250
|
-
}
|
|
3251
|
-
// Get comments for context on progress
|
|
3252
|
-
const comments = await adapter.getComments(projectPath, cell.id);
|
|
3253
|
-
if (comments.length > 0) {
|
|
3254
|
-
const recent = comments.slice(-3);
|
|
3255
|
-
contextLines.push("Recent notes:");
|
|
3256
|
-
for (const comment of recent) {
|
|
3257
|
-
contextLines.push(`- ${comment.body.slice(0, 200)}`);
|
|
3258
|
-
}
|
|
3259
|
-
}
|
|
3260
|
-
contextLines.push("");
|
|
3261
|
-
}
|
|
3262
|
-
}
|
|
3263
|
-
|
|
3264
|
-
// Open epics with children status
|
|
3265
|
-
const openEpics = await adapter.queryCells(projectPath, {
|
|
3266
|
-
status: ["open", "in_progress"],
|
|
3267
|
-
type: "epic",
|
|
3268
|
-
limit: 5
|
|
3269
|
-
});
|
|
3270
|
-
if (openEpics.length > 0) {
|
|
3271
|
-
contextLines.push("## Active Epics");
|
|
3272
|
-
for (const epic of openEpics) {
|
|
3273
|
-
const children = await adapter.getEpicChildren(projectPath, epic.id);
|
|
3274
|
-
const completed = children.filter(c => c.status === "closed").length;
|
|
3275
|
-
const inProgress = children.filter(c => c.status === "in_progress");
|
|
3276
|
-
const open = children.filter(c => c.status === "open");
|
|
3277
|
-
|
|
3278
|
-
contextLines.push(`### ${epic.id}: ${epic.title}`);
|
|
3279
|
-
contextLines.push(`Progress: ${completed}/${children.length} completed`);
|
|
3280
|
-
|
|
3281
|
-
if (inProgress.length > 0) {
|
|
3282
|
-
contextLines.push("In progress:");
|
|
3283
|
-
for (const c of inProgress) {
|
|
3284
|
-
contextLines.push(`- ${c.id}: ${c.title}`);
|
|
3285
|
-
}
|
|
3286
|
-
}
|
|
3287
|
-
if (open.length > 0 && open.length <= 5) {
|
|
3288
|
-
contextLines.push("Remaining:");
|
|
3289
|
-
for (const c of open) {
|
|
3290
|
-
contextLines.push(`- ${c.id}: ${c.title}`);
|
|
3291
|
-
}
|
|
3292
|
-
} else if (open.length > 5) {
|
|
3293
|
-
contextLines.push(`Remaining: ${open.length} tasks`);
|
|
3294
|
-
}
|
|
3295
|
-
contextLines.push("");
|
|
3296
|
-
}
|
|
3297
|
-
}
|
|
3298
|
-
|
|
3299
|
-
// Blocked cells (so Claude knows what's waiting)
|
|
3300
|
-
const blockedCells = await adapter.getBlockedCells(projectPath);
|
|
3301
|
-
if (blockedCells.length > 0) {
|
|
3302
|
-
contextLines.push("## Blocked Work");
|
|
3303
|
-
for (const { cell, blockers } of blockedCells.slice(0, 5)) {
|
|
3304
|
-
contextLines.push(`- ${cell.id}: ${cell.title}`);
|
|
3305
|
-
contextLines.push(` Blocked by: ${blockers.join(", ")}`);
|
|
3306
|
-
}
|
|
3307
|
-
contextLines.push("");
|
|
3308
|
-
}
|
|
3309
|
-
|
|
3310
|
-
// Ready cells (next work available)
|
|
3311
|
-
const readyCell = await adapter.getNextReadyCell(projectPath);
|
|
3312
|
-
if (readyCell) {
|
|
3313
|
-
contextLines.push("## Next Ready Task");
|
|
3314
|
-
contextLines.push(`${readyCell.id}: ${readyCell.title} (P${readyCell.priority})`);
|
|
3315
|
-
contextLines.push("");
|
|
3316
|
-
}
|
|
3317
|
-
|
|
3318
|
-
// Stats
|
|
3319
|
-
const stats = await adapter.getCellsStats(projectPath);
|
|
3320
|
-
contextLines.push("## Hive Stats");
|
|
3321
|
-
contextLines.push(`Open: ${stats.open} | In Progress: ${stats.in_progress} | Blocked: ${stats.blocked} | Closed: ${stats.closed}`);
|
|
3322
|
-
|
|
3323
|
-
writeClaudeHookOutput("PreCompact", contextLines.join("\n"));
|
|
3324
|
-
} catch (error) {
|
|
3325
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
3326
|
-
}
|
|
3327
|
-
}
|
|
3328
|
-
|
|
3329
|
-
/**
|
|
3330
|
-
* Claude hook: end the active session.
|
|
3331
|
-
*/
|
|
3332
|
-
async function claudeSessionEnd() {
|
|
3333
|
-
try {
|
|
3334
|
-
const input = await readHookInput<ClaudeHookInput>();
|
|
3335
|
-
const projectPath = resolveClaudeProjectPath(input);
|
|
3336
|
-
const swarmMail = await getSwarmMailLibSQL(projectPath);
|
|
3337
|
-
const db = await swarmMail.getDatabase(projectPath);
|
|
3338
|
-
const adapter = createHiveAdapter(db, projectPath);
|
|
3339
|
-
|
|
3340
|
-
await adapter.runMigrations();
|
|
3341
|
-
|
|
3342
|
-
const currentSession = await adapter.getCurrentSession(projectPath);
|
|
3343
|
-
if (!currentSession) return;
|
|
3344
|
-
|
|
3345
|
-
await adapter.endSession(projectPath, currentSession.id, {});
|
|
3346
|
-
} catch (error) {
|
|
3347
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
3348
|
-
}
|
|
3349
|
-
}
|
|
3350
|
-
|
|
3351
|
-
/**
|
|
3352
|
-
* Claude hook: pre-edit reminder for workers
|
|
3353
|
-
*
|
|
3354
|
-
* Runs BEFORE Edit/Write tool calls. Reminds worker to query hivemind first.
|
|
3355
|
-
* This is a gentle nudge, not a blocker.
|
|
3356
|
-
*/
|
|
3357
|
-
async function claudePreEdit() {
|
|
3358
|
-
try {
|
|
3359
|
-
const input = await readHookInput<ClaudeHookInput & { tool_name?: string; tool_input?: Record<string, unknown> }>();
|
|
3360
|
-
|
|
3361
|
-
// Only inject reminder if this looks like a worker context
|
|
3362
|
-
// (workers have initialized via swarmmail_init)
|
|
3363
|
-
const projectPath = resolveClaudeProjectPath(input);
|
|
3364
|
-
|
|
3365
|
-
// Check if we've seen hivemind_find in this session
|
|
3366
|
-
// For now, we always remind - tracking will come later
|
|
3367
|
-
const contextLines: string[] = [];
|
|
3368
|
-
|
|
3369
|
-
contextLines.push("⚠️ **Before this edit**: Did you run `hivemind_find` to check for existing solutions?");
|
|
3370
|
-
contextLines.push("If you haven't queried hivemind yet, consider doing so to avoid re-solving problems.");
|
|
3371
|
-
|
|
3372
|
-
writeClaudeHookOutput("PreToolUse:Edit", contextLines.join("\n"), { suppressOutput: true });
|
|
3373
|
-
} catch (error) {
|
|
3374
|
-
// Non-fatal - don't block the edit
|
|
3375
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
3376
|
-
}
|
|
3377
|
-
}
|
|
3378
|
-
|
|
3379
|
-
/**
|
|
3380
|
-
* Claude hook: pre-complete check for workers
|
|
3381
|
-
*
|
|
3382
|
-
* Runs BEFORE swarm_complete. Checks compliance with mandatory steps
|
|
3383
|
-
* and warns if any were skipped.
|
|
3384
|
-
*/
|
|
3385
|
-
async function claudePreComplete() {
|
|
3386
|
-
try {
|
|
3387
|
-
const input = await readHookInput<ClaudeHookInput & { tool_input?: Record<string, unknown> }>();
|
|
3388
|
-
const projectPath = resolveClaudeProjectPath(input);
|
|
3389
|
-
const contextLines: string[] = [];
|
|
3390
|
-
|
|
3391
|
-
// Check session tracking markers
|
|
3392
|
-
const trackingDir = join(projectPath, ".claude", ".worker-tracking");
|
|
3393
|
-
const sessionId = (input as { session_id?: string }).session_id || "";
|
|
3394
|
-
const sessionDir = join(trackingDir, sessionId.slice(0, 8));
|
|
3395
|
-
|
|
3396
|
-
const mandatoryTools = [
|
|
3397
|
-
{ name: "swarmmail_init", label: "Initialize coordination" },
|
|
3398
|
-
{ name: "hivemind_find", label: "Query past learnings" },
|
|
3399
|
-
];
|
|
3400
|
-
const recommendedTools = [
|
|
3401
|
-
{ name: "skills_use", label: "Load relevant skills" },
|
|
3402
|
-
{ name: "hivemind_store", label: "Store new learnings" },
|
|
3403
|
-
];
|
|
3404
|
-
|
|
3405
|
-
const missing: string[] = [];
|
|
3406
|
-
const skippedRecommended: string[] = [];
|
|
3407
|
-
|
|
3408
|
-
for (const tool of mandatoryTools) {
|
|
3409
|
-
const markerPath = join(sessionDir, `${tool.name}.marker`);
|
|
3410
|
-
if (!existsSync(markerPath)) {
|
|
3411
|
-
missing.push(tool.label);
|
|
3412
|
-
}
|
|
3413
|
-
}
|
|
3414
|
-
|
|
3415
|
-
for (const tool of recommendedTools) {
|
|
3416
|
-
const markerPath = join(sessionDir, `${tool.name}.marker`);
|
|
3417
|
-
if (!existsSync(markerPath)) {
|
|
3418
|
-
skippedRecommended.push(tool.label);
|
|
3419
|
-
}
|
|
3420
|
-
}
|
|
3421
|
-
|
|
3422
|
-
if (missing.length > 0) {
|
|
3423
|
-
contextLines.push("⚠️ **MANDATORY STEPS SKIPPED:**");
|
|
3424
|
-
for (const step of missing) {
|
|
3425
|
-
contextLines.push(` ❌ ${step}`);
|
|
3426
|
-
}
|
|
3427
|
-
contextLines.push("");
|
|
3428
|
-
contextLines.push("These steps are critical for swarm coordination.");
|
|
3429
|
-
contextLines.push("Consider running `hivemind_find` before future completions.");
|
|
3430
|
-
}
|
|
3431
|
-
|
|
3432
|
-
if (skippedRecommended.length > 0 && missing.length === 0) {
|
|
3433
|
-
contextLines.push("📝 **Recommended steps not observed:**");
|
|
3434
|
-
for (const step of skippedRecommended) {
|
|
3435
|
-
contextLines.push(` - ${step}`);
|
|
3436
|
-
}
|
|
3437
|
-
}
|
|
3438
|
-
|
|
3439
|
-
if (missing.length === 0 && skippedRecommended.length === 0) {
|
|
3440
|
-
contextLines.push("✅ **All mandatory and recommended steps completed!**");
|
|
3441
|
-
}
|
|
3442
|
-
|
|
3443
|
-
// Emit compliance event for analytics
|
|
3444
|
-
try {
|
|
3445
|
-
const swarmMail = await getSwarmMailLibSQL(projectPath);
|
|
3446
|
-
const db = await swarmMail.getDatabase(projectPath);
|
|
3447
|
-
|
|
3448
|
-
await db.execute({
|
|
3449
|
-
sql: `INSERT INTO swarm_events (event_type, project_path, agent_name, epic_id, bead_id, payload, created_at)
|
|
3450
|
-
VALUES (?, ?, ?, ?, ?, ?, datetime('now'))`,
|
|
3451
|
-
args: [
|
|
3452
|
-
"worker_compliance",
|
|
3453
|
-
projectPath,
|
|
3454
|
-
"worker",
|
|
3455
|
-
"",
|
|
3456
|
-
"",
|
|
3457
|
-
JSON.stringify({
|
|
3458
|
-
session_id: sessionId,
|
|
3459
|
-
mandatory_skipped: missing.length,
|
|
3460
|
-
recommended_skipped: skippedRecommended.length,
|
|
3461
|
-
score: Math.round(((mandatoryTools.length - missing.length) / mandatoryTools.length) * 100),
|
|
3462
|
-
}),
|
|
3463
|
-
],
|
|
3464
|
-
});
|
|
3465
|
-
} catch {
|
|
3466
|
-
// Non-fatal
|
|
3467
|
-
}
|
|
3468
|
-
|
|
3469
|
-
if (contextLines.length > 0) {
|
|
3470
|
-
writeClaudeHookOutput("PreToolUse:swarm_complete", contextLines.join("\n"), { suppressOutput: missing.length === 0 });
|
|
3471
|
-
}
|
|
3472
|
-
} catch (error) {
|
|
3473
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
3474
|
-
}
|
|
3475
|
-
}
|
|
3476
|
-
|
|
3477
|
-
/**
|
|
3478
|
-
* Claude hook: post-complete reminder for workers
|
|
3479
|
-
*
|
|
3480
|
-
* Runs AFTER swarm_complete. Reminds to store learnings if any were discovered.
|
|
3481
|
-
*/
|
|
3482
|
-
async function claudePostComplete() {
|
|
3483
|
-
try {
|
|
3484
|
-
const input = await readHookInput<ClaudeHookInput & { tool_output?: string }>();
|
|
3485
|
-
const contextLines: string[] = [];
|
|
3486
|
-
|
|
3487
|
-
contextLines.push("✅ Task completed. If you discovered anything valuable during this work:");
|
|
3488
|
-
contextLines.push("```");
|
|
3489
|
-
contextLines.push("hivemind_store(");
|
|
3490
|
-
contextLines.push(' information="<what you learned, WHY it matters>",');
|
|
3491
|
-
contextLines.push(' tags="<domain, pattern-type>"');
|
|
3492
|
-
contextLines.push(")");
|
|
3493
|
-
contextLines.push("```");
|
|
3494
|
-
contextLines.push("**The swarm's collective intelligence grows when agents share learnings.**");
|
|
3495
|
-
|
|
3496
|
-
writeClaudeHookOutput("PostToolUse:swarm_complete", contextLines.join("\n"), { suppressOutput: true });
|
|
3497
|
-
} catch (error) {
|
|
3498
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
3499
|
-
}
|
|
3500
|
-
}
|
|
3501
|
-
|
|
3502
|
-
/**
|
|
3503
|
-
* Track when a mandatory tool is called.
|
|
3504
|
-
*
|
|
3505
|
-
* Creates a session-specific marker file that records tool usage.
|
|
3506
|
-
* These markers are checked at swarm_complete to calculate compliance.
|
|
3507
|
-
*/
|
|
3508
|
-
async function claudeTrackTool(toolName: string) {
|
|
3509
|
-
if (!toolName) return;
|
|
3510
|
-
|
|
3511
|
-
try {
|
|
3512
|
-
const input = await readHookInput<ClaudeHookInput>();
|
|
3513
|
-
const projectPath = resolveClaudeProjectPath(input);
|
|
3514
|
-
|
|
3515
|
-
// Get or create session tracking directory
|
|
3516
|
-
const trackingDir = join(projectPath, ".claude", ".worker-tracking");
|
|
3517
|
-
const sessionId = (input as { session_id?: string }).session_id || `unknown-${Date.now()}`;
|
|
3518
|
-
const sessionDir = join(trackingDir, sessionId.slice(0, 8)); // Use first 8 chars of session ID
|
|
3519
|
-
|
|
3520
|
-
if (!existsSync(sessionDir)) {
|
|
3521
|
-
mkdirSync(sessionDir, { recursive: true });
|
|
3522
|
-
}
|
|
3523
|
-
|
|
3524
|
-
// Write marker file for this tool
|
|
3525
|
-
const markerPath = join(sessionDir, `${toolName}.marker`);
|
|
3526
|
-
writeFileSync(markerPath, new Date().toISOString());
|
|
3527
|
-
|
|
3528
|
-
// Also emit an event for long-term analytics (non-blocking)
|
|
3529
|
-
try {
|
|
3530
|
-
const swarmMail = await getSwarmMailLibSQL(projectPath);
|
|
3531
|
-
const db = await swarmMail.getDatabase(projectPath);
|
|
3532
|
-
|
|
3533
|
-
await db.execute({
|
|
3534
|
-
sql: `INSERT INTO swarm_events (event_type, project_path, agent_name, epic_id, bead_id, payload, created_at)
|
|
3535
|
-
VALUES (?, ?, ?, ?, ?, ?, datetime('now'))`,
|
|
3536
|
-
args: [
|
|
3537
|
-
"worker_tool_call",
|
|
3538
|
-
projectPath,
|
|
3539
|
-
"worker", // We don't have agent name in hook context
|
|
3540
|
-
"",
|
|
3541
|
-
"",
|
|
3542
|
-
JSON.stringify({ tool: toolName, session_id: sessionId }),
|
|
3543
|
-
],
|
|
3544
|
-
});
|
|
3545
|
-
} catch {
|
|
3546
|
-
// Non-fatal - tracking is best-effort
|
|
3547
|
-
}
|
|
3548
|
-
} catch (error) {
|
|
3549
|
-
// Silent failure - don't interrupt the tool call
|
|
3550
|
-
console.error(`[track-tool] ${error instanceof Error ? error.message : String(error)}`);
|
|
3551
|
-
}
|
|
3552
|
-
}
|
|
3553
|
-
|
|
3554
|
-
/**
|
|
3555
|
-
* Check worker compliance - which mandatory tools were called.
|
|
3556
|
-
*
|
|
3557
|
-
* Returns compliance data based on session tracking markers.
|
|
3558
|
-
*/
|
|
3559
|
-
async function claudeCompliance() {
|
|
3560
|
-
try {
|
|
3561
|
-
const input = await readHookInput<ClaudeHookInput>();
|
|
3562
|
-
const projectPath = resolveClaudeProjectPath(input);
|
|
3563
|
-
|
|
3564
|
-
const trackingDir = join(projectPath, ".claude", ".worker-tracking");
|
|
3565
|
-
const sessionId = (input as { session_id?: string }).session_id || "";
|
|
3566
|
-
const sessionDir = join(trackingDir, sessionId.slice(0, 8));
|
|
3567
|
-
|
|
3568
|
-
const mandatoryTools = ["swarmmail_init", "hivemind_find", "skills_use"];
|
|
3569
|
-
const recommendedTools = ["hivemind_store"];
|
|
3570
|
-
|
|
3571
|
-
const compliance: Record<string, boolean> = {};
|
|
3572
|
-
|
|
3573
|
-
for (const tool of [...mandatoryTools, ...recommendedTools]) {
|
|
3574
|
-
const markerPath = join(sessionDir, `${tool}.marker`);
|
|
3575
|
-
compliance[tool] = existsSync(markerPath);
|
|
3576
|
-
}
|
|
3577
|
-
|
|
3578
|
-
const mandatoryCount = mandatoryTools.filter(t => compliance[t]).length;
|
|
3579
|
-
const score = Math.round((mandatoryCount / mandatoryTools.length) * 100);
|
|
3580
|
-
|
|
3581
|
-
console.log(JSON.stringify({
|
|
3582
|
-
session_id: sessionId,
|
|
3583
|
-
compliance,
|
|
3584
|
-
mandatory_score: score,
|
|
3585
|
-
mandatory_met: mandatoryCount,
|
|
3586
|
-
mandatory_total: mandatoryTools.length,
|
|
3587
|
-
stored_learnings: compliance.hivemind_store,
|
|
3588
|
-
}));
|
|
3589
|
-
} catch (error) {
|
|
3590
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
3591
|
-
}
|
|
3592
|
-
}
|
|
3593
|
-
|
|
3594
|
-
/**
|
|
3595
|
-
* Query worker compliance stats across all sessions.
|
|
3596
|
-
*/
|
|
3597
|
-
async function showWorkerCompliance() {
|
|
3598
|
-
const projectPath = process.cwd();
|
|
3599
|
-
|
|
3600
|
-
try {
|
|
3601
|
-
// Use shared executeQuery (handles DB connection internally)
|
|
3602
|
-
const toolUsageSql = `SELECT
|
|
3603
|
-
json_extract(payload, '$.tool') as tool,
|
|
3604
|
-
COUNT(*) as count,
|
|
3605
|
-
COUNT(DISTINCT json_extract(payload, '$.session_id')) as sessions
|
|
3606
|
-
FROM swarm_events
|
|
3607
|
-
WHERE event_type = 'worker_tool_call'
|
|
3608
|
-
AND created_at > datetime('now', '-7 days')
|
|
3609
|
-
GROUP BY tool
|
|
3610
|
-
ORDER BY count DESC`;
|
|
3611
|
-
|
|
3612
|
-
const toolResult = await executeQueryCLI(projectPath, toolUsageSql);
|
|
3613
|
-
const rows = toolResult.rows;
|
|
3614
|
-
|
|
3615
|
-
console.log(yellow(BANNER));
|
|
3616
|
-
console.log(cyan("\n📊 Worker Tool Usage (Last 7 Days)\n"));
|
|
3617
|
-
|
|
3618
|
-
if (rows.length === 0) {
|
|
3619
|
-
console.log(dim("No worker tool usage data found."));
|
|
3620
|
-
console.log(dim("Data is collected when workers run with the Claude Code plugin."));
|
|
3621
|
-
return;
|
|
3622
|
-
}
|
|
3623
|
-
|
|
3624
|
-
console.log(dim("Tool Calls Sessions"));
|
|
3625
|
-
console.log(dim("─".repeat(45)));
|
|
3626
|
-
|
|
3627
|
-
for (const row of rows) {
|
|
3628
|
-
const tool = String(row.tool).padEnd(22);
|
|
3629
|
-
const count = String(row.count).padStart(6);
|
|
3630
|
-
const sessions = String(row.sessions).padStart(8);
|
|
3631
|
-
console.log(`${tool} ${count} ${sessions}`);
|
|
3632
|
-
}
|
|
3633
|
-
|
|
3634
|
-
// Calculate compliance rate
|
|
3635
|
-
const hivemindFinds = rows.find(r => r.tool === "hivemind_find");
|
|
3636
|
-
const completesSql = `SELECT COUNT(*) as count FROM swarm_events
|
|
3637
|
-
WHERE event_type = 'worker_completed'
|
|
3638
|
-
AND created_at > datetime('now', '-7 days')`;
|
|
3639
|
-
const completesResult = await executeQueryCLI(projectPath, completesSql);
|
|
3640
|
-
|
|
3641
|
-
const completes = Number(completesResult.rows[0]?.count || 0);
|
|
3642
|
-
const finds = Number(hivemindFinds?.count || 0);
|
|
3643
|
-
|
|
3644
|
-
if (completes > 0) {
|
|
3645
|
-
const rate = Math.round((Math.min(finds, completes) / completes) * 100);
|
|
3646
|
-
console.log(dim("\n─".repeat(45)));
|
|
3647
|
-
console.log(`\n${green("Hivemind compliance rate:")} ${rate}% (${finds} queries / ${completes} completions)`);
|
|
3648
|
-
}
|
|
3649
|
-
|
|
3650
|
-
} catch (error) {
|
|
3651
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
3652
|
-
if (msg.includes("no such table")) {
|
|
3653
|
-
console.log(yellow(BANNER));
|
|
3654
|
-
console.log(cyan("\n📊 Worker Tool Usage\n"));
|
|
3655
|
-
console.log(dim("No tracking data yet."));
|
|
3656
|
-
console.log(dim("Compliance tracking starts when workers run with the Claude Code plugin hooks."));
|
|
3657
|
-
console.log(dim("\nThe swarm_events table will be created on first tracked tool call."));
|
|
3658
|
-
} else {
|
|
3659
|
-
console.error("Error fetching compliance data:", msg);
|
|
3660
|
-
}
|
|
3661
|
-
}
|
|
3662
|
-
}
|
|
3663
|
-
|
|
3664
|
-
async function init() {
|
|
3665
|
-
p.intro("swarm init v" + VERSION);
|
|
3666
|
-
|
|
3667
|
-
const projectPath = process.cwd();
|
|
3668
|
-
|
|
3669
|
-
const gitDir = existsSync(".git");
|
|
3670
|
-
if (!gitDir) {
|
|
3671
|
-
p.log.error("Not in a git repository");
|
|
3672
|
-
p.log.message("Run 'git init' first, or cd to a git repo");
|
|
3673
|
-
p.outro("Aborted");
|
|
3674
|
-
process.exit(1);
|
|
3675
|
-
}
|
|
3676
|
-
|
|
3677
|
-
// Check for existing .hive or .beads directories
|
|
3678
|
-
const hiveDir = existsSync(".hive");
|
|
3679
|
-
const beadsDir = existsSync(".beads");
|
|
3680
|
-
|
|
3681
|
-
if (hiveDir) {
|
|
3682
|
-
p.log.warn("Hive already initialized in this project (.hive/ exists)");
|
|
3683
|
-
|
|
3684
|
-
const reinit = await p.confirm({
|
|
3685
|
-
message: "Continue anyway?",
|
|
3686
|
-
initialValue: false,
|
|
3687
|
-
});
|
|
3688
|
-
|
|
3689
|
-
if (p.isCancel(reinit) || !reinit) {
|
|
3690
|
-
p.outro("Aborted");
|
|
3691
|
-
process.exit(0);
|
|
3692
|
-
}
|
|
3693
|
-
} else if (beadsDir) {
|
|
3694
|
-
// Offer migration from .beads to .hive
|
|
3695
|
-
p.log.warn("Found legacy .beads/ directory");
|
|
3696
|
-
|
|
3697
|
-
const migrate = await p.confirm({
|
|
3698
|
-
message: "Migrate .beads/ to .hive/?",
|
|
3699
|
-
initialValue: true,
|
|
3700
|
-
});
|
|
2713
|
+
} else if (beadsDir) {
|
|
2714
|
+
// Offer migration from .beads to .hive
|
|
2715
|
+
p.log.warn("Found legacy .beads/ directory");
|
|
2716
|
+
|
|
2717
|
+
const migrate = await p.confirm({
|
|
2718
|
+
message: "Migrate .beads/ to .hive/?",
|
|
2719
|
+
initialValue: true,
|
|
2720
|
+
});
|
|
3701
2721
|
|
|
3702
2722
|
if (!p.isCancel(migrate) && migrate) {
|
|
3703
2723
|
const s = p.spinner();
|
|
@@ -3829,7 +2849,7 @@ function config() {
|
|
|
3829
2849
|
const plannerAgentPath = join(agentDir, "swarm-planner.md");
|
|
3830
2850
|
const workerAgentPath = join(agentDir, "swarm-worker.md");
|
|
3831
2851
|
const researcherAgentPath = join(agentDir, "swarm-researcher.md");
|
|
3832
|
-
const globalSkillsPath = join(configDir, "
|
|
2852
|
+
const globalSkillsPath = join(configDir, "skills");
|
|
3833
2853
|
|
|
3834
2854
|
console.log(yellow(BANNER));
|
|
3835
2855
|
console.log(dim(" " + TAGLINE + " v" + VERSION));
|
|
@@ -4008,16 +3028,16 @@ async function query() {
|
|
|
4008
3028
|
const projectPath = process.cwd();
|
|
4009
3029
|
|
|
4010
3030
|
try {
|
|
4011
|
-
let
|
|
3031
|
+
let rows: any[];
|
|
4012
3032
|
|
|
4013
3033
|
if (parsed.preset) {
|
|
4014
3034
|
// Execute preset query
|
|
4015
3035
|
p.log.step(`Executing preset: ${parsed.preset}`);
|
|
4016
|
-
|
|
3036
|
+
rows = await executePreset(projectPath, parsed.preset);
|
|
4017
3037
|
} else if (parsed.query) {
|
|
4018
3038
|
// Execute custom SQL
|
|
4019
3039
|
p.log.step("Executing custom SQL");
|
|
4020
|
-
|
|
3040
|
+
rows = await executeQuery(projectPath, parsed.query);
|
|
4021
3041
|
} else {
|
|
4022
3042
|
p.log.error("No query specified. Use --sql or --preset");
|
|
4023
3043
|
p.outro("Aborted");
|
|
@@ -4028,14 +3048,14 @@ async function query() {
|
|
|
4028
3048
|
let output: string;
|
|
4029
3049
|
switch (parsed.format) {
|
|
4030
3050
|
case "csv":
|
|
4031
|
-
output = formatAsCSV(
|
|
3051
|
+
output = formatAsCSV(rows);
|
|
4032
3052
|
break;
|
|
4033
3053
|
case "json":
|
|
4034
|
-
output = formatAsJSON(
|
|
3054
|
+
output = formatAsJSON(rows);
|
|
4035
3055
|
break;
|
|
4036
3056
|
case "table":
|
|
4037
3057
|
default:
|
|
4038
|
-
output = formatAsTable(
|
|
3058
|
+
output = formatAsTable(rows);
|
|
4039
3059
|
break;
|
|
4040
3060
|
}
|
|
4041
3061
|
|
|
@@ -4043,7 +3063,7 @@ async function query() {
|
|
|
4043
3063
|
console.log(output);
|
|
4044
3064
|
console.log();
|
|
4045
3065
|
|
|
4046
|
-
p.outro(`Found ${
|
|
3066
|
+
p.outro(`Found ${rows.length} result(s)`);
|
|
4047
3067
|
} catch (error) {
|
|
4048
3068
|
p.log.error("Query failed");
|
|
4049
3069
|
p.log.message(error instanceof Error ? error.message : String(error));
|
|
@@ -4372,11 +3392,6 @@ async function exportEvents() {
|
|
|
4372
3392
|
}
|
|
4373
3393
|
}
|
|
4374
3394
|
|
|
4375
|
-
async function treeCommand() {
|
|
4376
|
-
const args = process.argv.slice(3);
|
|
4377
|
-
await tree(args);
|
|
4378
|
-
}
|
|
4379
|
-
|
|
4380
3395
|
async function help() {
|
|
4381
3396
|
console.log(yellow(BANNER));
|
|
4382
3397
|
console.log(dim(" " + TAGLINE + " v" + VERSION));
|
|
@@ -4384,24 +3399,16 @@ async function help() {
|
|
|
4384
3399
|
console.log(magenta(" " + getRandomMessage()));
|
|
4385
3400
|
console.log(`
|
|
4386
3401
|
${cyan("Commands:")}
|
|
4387
|
-
swarm Status dashboard (default when no subcommand)
|
|
4388
|
-
swarm status Status dashboard (same as running swarm with no args)
|
|
4389
|
-
--json Machine-readable JSON output
|
|
4390
3402
|
swarm setup Interactive installer - checks and installs dependencies
|
|
4391
3403
|
--reinstall, -r Skip prompt, go straight to reinstall
|
|
4392
3404
|
--yes, -y Non-interactive with defaults (opus/sonnet/haiku)
|
|
4393
3405
|
swarm doctor Health check - shows status of all dependencies
|
|
4394
|
-
--deep Deep DB health checks (integrity, orphans, cycles, zombies)
|
|
4395
|
-
--deep --fix Auto-repair fixable issues
|
|
4396
|
-
--deep --json Machine-readable JSON output
|
|
4397
3406
|
swarm init Initialize beads in current project
|
|
4398
3407
|
swarm config Show paths to generated config files
|
|
4399
|
-
swarm claude Claude Code integration (path/install/uninstall/init/hooks)
|
|
4400
3408
|
swarm agents Update AGENTS.md with skill awareness
|
|
4401
3409
|
swarm migrate Migrate PGlite database to libSQL
|
|
4402
3410
|
swarm serve Start SSE server for real-time event streaming (port 4483 - HIVE)
|
|
4403
3411
|
--port <n> Port to listen on (default: 4483)
|
|
4404
|
-
swarm mcp-serve Debug-only MCP server (Claude auto-launches via .mcp.json)
|
|
4405
3412
|
swarm viz Alias for 'swarm serve' (deprecated, use serve)
|
|
4406
3413
|
--port <n> Port to listen on (default: 4483)
|
|
4407
3414
|
swarm cells List or get cells from database (replaces 'swarm tool hive_query')
|
|
@@ -4413,12 +3420,8 @@ ${cyan("Commands:")}
|
|
|
4413
3420
|
swarm eval Eval-driven development commands
|
|
4414
3421
|
swarm query SQL analytics with presets (--sql, --preset, --format)
|
|
4415
3422
|
swarm dashboard Live terminal UI with worker status (--epic, --refresh)
|
|
4416
|
-
swarm compliance Worker tool usage compliance stats (hivemind, skills, etc)
|
|
4417
3423
|
swarm replay Event replay with timing (--speed, --type, --agent, --since, --until)
|
|
4418
3424
|
swarm export Export events (--format otlp/csv/json, --epic, --output)
|
|
4419
|
-
swarm tree Visualize cell hierarchy as ASCII tree (--status, --epic, --json)
|
|
4420
|
-
swarm session Manage work sessions with handoff notes (start, end, status, history)
|
|
4421
|
-
swarm queue Manage distributed job queue (submit, status, list, worker)
|
|
4422
3425
|
swarm update Update to latest version
|
|
4423
3426
|
swarm version Show version and banner
|
|
4424
3427
|
swarm tool Execute a tool (for plugin wrapper)
|
|
@@ -4438,21 +3441,15 @@ ${cyan("Cell Management:")}
|
|
|
4438
3441
|
swarm cells --json Raw JSON output (array, no wrapper)
|
|
4439
3442
|
|
|
4440
3443
|
${cyan("Memory Management (Hivemind):")}
|
|
4441
|
-
swarm memory store <info> [
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
swarm memory
|
|
4446
|
-
swarm memory
|
|
4447
|
-
swarm memory
|
|
4448
|
-
swarm memory
|
|
4449
|
-
swarm memory
|
|
4450
|
-
swarm memory index Index AI sessions (use hivemind_index tool)
|
|
4451
|
-
swarm memory sync Sync to .hive/memories.jsonl (use hivemind_sync tool)
|
|
4452
|
-
swarm memory entities [--type <type>] List all entities (optionally filtered by type)
|
|
4453
|
-
swarm memory entity <name> Show entity details with taxonomy relationships
|
|
4454
|
-
swarm memory taxonomy <entity> [--direction <dir>] Show taxonomy tree (broader|narrower|related)
|
|
4455
|
-
swarm memory <command> --json Output JSON for all commands
|
|
3444
|
+
swarm memory store <info> [--tags <tags>] Store a learning/memory
|
|
3445
|
+
swarm memory find <query> [--limit <n>] Search all memories (semantic + FTS)
|
|
3446
|
+
swarm memory get <id> Get specific memory by ID
|
|
3447
|
+
swarm memory remove <id> Delete outdated/incorrect memory
|
|
3448
|
+
swarm memory validate <id> Confirm accuracy (resets 90-day decay)
|
|
3449
|
+
swarm memory stats Show database statistics
|
|
3450
|
+
swarm memory index Index AI sessions (use hivemind_index tool)
|
|
3451
|
+
swarm memory sync Sync to .hive/memories.jsonl (use hivemind_sync tool)
|
|
3452
|
+
swarm memory <command> --json Output JSON for all commands
|
|
4456
3453
|
|
|
4457
3454
|
${cyan("Log Viewing:")}
|
|
4458
3455
|
swarm log Tail recent logs (last 50 lines)
|
|
@@ -4473,8 +3470,6 @@ ${cyan("Stats & History:")}
|
|
|
4473
3470
|
swarm stats Show swarm health metrics powered by swarm-insights (last 7 days)
|
|
4474
3471
|
swarm stats --since 24h Show stats for custom time period
|
|
4475
3472
|
swarm stats --regressions Show eval regressions (>10% score drops)
|
|
4476
|
-
swarm stats --rejections Show rejection reason analytics
|
|
4477
|
-
swarm stats --compaction-prompts Show compaction prompt analytics (visibility into generated prompts)
|
|
4478
3473
|
swarm stats --json Output as JSON for scripting
|
|
4479
3474
|
swarm o11y Show observability health dashboard (hook coverage, events, sessions)
|
|
4480
3475
|
swarm o11y --since 7d Custom time period for event stats (default: 7 days)
|
|
@@ -4507,28 +3502,6 @@ ${cyan("Observability Commands:")}
|
|
|
4507
3502
|
swarm export --format csv Export as CSV
|
|
4508
3503
|
swarm export --epic <id> Export specific epic only
|
|
4509
3504
|
swarm export --output <file> Write to file instead of stdout
|
|
4510
|
-
swarm compliance Show worker tool usage compliance stats
|
|
4511
|
-
swarm tree Show all cells as tree
|
|
4512
|
-
swarm tree --status open Show only open cells
|
|
4513
|
-
swarm tree --epic <id> Show specific epic subtree
|
|
4514
|
-
swarm tree --json Output as JSON
|
|
4515
|
-
|
|
4516
|
-
${cyan("Session Management (Chainlink-inspired):")}
|
|
4517
|
-
swarm session start [--cell <id>] Start new session (shows previous handoff notes)
|
|
4518
|
-
swarm session end [--notes "..."] End session with handoff notes for next session
|
|
4519
|
-
swarm session status Show current session info
|
|
4520
|
-
swarm session history [--limit n] Show session history (default: 10)
|
|
4521
|
-
|
|
4522
|
-
${cyan("Queue Management (BullMQ + Redis):")}
|
|
4523
|
-
swarm queue submit <type> [options] Submit job to distributed queue
|
|
4524
|
-
--payload '{...}' Job payload (JSON)
|
|
4525
|
-
--priority <n> Priority (lower = higher, default: 5)
|
|
4526
|
-
--delay <ms> Delay before processing (default: 0)
|
|
4527
|
-
swarm queue status <jobId> Show job status
|
|
4528
|
-
swarm queue list [--state <state>] List jobs and metrics (waiting|active|completed|failed|delayed)
|
|
4529
|
-
swarm queue worker [options] Start worker to process jobs
|
|
4530
|
-
--concurrency <n> Concurrent jobs (default: 5)
|
|
4531
|
-
--sandbox Enable resource limits via systemd
|
|
4532
3505
|
|
|
4533
3506
|
${cyan("Usage in OpenCode:")}
|
|
4534
3507
|
/swarm "Add user authentication with OAuth"
|
|
@@ -4536,21 +3509,6 @@ ${cyan("Usage in OpenCode:")}
|
|
|
4536
3509
|
@swarm-worker "Execute this specific subtask"
|
|
4537
3510
|
@swarm-researcher "Research Next.js caching APIs"
|
|
4538
3511
|
|
|
4539
|
-
${cyan("Claude Code:")}
|
|
4540
|
-
swarm claude path Show bundled Claude plugin path
|
|
4541
|
-
swarm claude install Symlink plugin into ~/.claude/plugins
|
|
4542
|
-
swarm claude uninstall Remove Claude plugin symlink
|
|
4543
|
-
swarm claude init Create project-local .claude config
|
|
4544
|
-
swarm claude session-start Hook: session start context
|
|
4545
|
-
swarm claude user-prompt Hook: prompt submit context
|
|
4546
|
-
swarm claude pre-edit Hook: pre-Edit/Write (hivemind reminder)
|
|
4547
|
-
swarm claude pre-complete Hook: pre-swarm_complete (compliance check)
|
|
4548
|
-
swarm claude post-complete Hook: post-swarm_complete (store learnings)
|
|
4549
|
-
swarm claude track-tool <name> Hook: track mandatory tool usage
|
|
4550
|
-
swarm claude compliance Hook: show session compliance data
|
|
4551
|
-
swarm claude pre-compact Hook: pre-compaction handler
|
|
4552
|
-
swarm claude session-end Hook: session cleanup
|
|
4553
|
-
|
|
4554
3512
|
${cyan("Customization:")}
|
|
4555
3513
|
Edit the generated files to customize behavior:
|
|
4556
3514
|
${dim("~/.config/opencode/command/swarm.md")} - /swarm command prompt
|
|
@@ -4675,102 +3633,13 @@ async function executeTool(toolName: string, argsJson?: string) {
|
|
|
4675
3633
|
}
|
|
4676
3634
|
}
|
|
4677
3635
|
|
|
4678
|
-
/**
|
|
4679
|
-
* Convert OpenCode plugin schema (Zod standard schema format) to JSON Schema
|
|
4680
|
-
*/
|
|
4681
|
-
function zodToJsonSchema(args: Record<string, unknown> | undefined): {
|
|
4682
|
-
type: "object";
|
|
4683
|
-
properties: Record<string, unknown>;
|
|
4684
|
-
required?: string[];
|
|
4685
|
-
} {
|
|
4686
|
-
if (!args) return { type: "object", properties: {} };
|
|
4687
|
-
|
|
4688
|
-
const properties: Record<string, unknown> = {};
|
|
4689
|
-
const required: string[] = [];
|
|
4690
|
-
|
|
4691
|
-
for (const [key, schema] of Object.entries(args)) {
|
|
4692
|
-
const s = schema as { type?: string; def?: { type?: string; innerType?: unknown; entries?: Record<string, string>; element?: unknown; shape?: Record<string, unknown> } };
|
|
4693
|
-
const def = s.def;
|
|
4694
|
-
|
|
4695
|
-
// Handle optional wrapper
|
|
4696
|
-
const isOptional = def?.type === "optional";
|
|
4697
|
-
const innerSchema = isOptional ? (def?.innerType as typeof s) : s;
|
|
4698
|
-
const innerDef = (innerSchema as typeof s).def;
|
|
4699
|
-
|
|
4700
|
-
if (!isOptional) {
|
|
4701
|
-
required.push(key);
|
|
4702
|
-
}
|
|
4703
|
-
|
|
4704
|
-
// Convert type
|
|
4705
|
-
const schemaType = innerDef?.type || (innerSchema as typeof s).type;
|
|
4706
|
-
switch (schemaType) {
|
|
4707
|
-
case "string":
|
|
4708
|
-
properties[key] = { type: "string" };
|
|
4709
|
-
break;
|
|
4710
|
-
case "number":
|
|
4711
|
-
properties[key] = { type: "number" };
|
|
4712
|
-
break;
|
|
4713
|
-
case "boolean":
|
|
4714
|
-
properties[key] = { type: "boolean" };
|
|
4715
|
-
break;
|
|
4716
|
-
case "enum":
|
|
4717
|
-
properties[key] = {
|
|
4718
|
-
type: "string",
|
|
4719
|
-
enum: Object.keys(innerDef?.entries || {}),
|
|
4720
|
-
};
|
|
4721
|
-
break;
|
|
4722
|
-
case "array": {
|
|
4723
|
-
const element = innerDef?.element as typeof s | undefined;
|
|
4724
|
-
const elementType = element?.def?.type || element?.type;
|
|
4725
|
-
if (elementType === "object") {
|
|
4726
|
-
properties[key] = {
|
|
4727
|
-
type: "array",
|
|
4728
|
-
items: zodToJsonSchema(element?.def?.shape as Record<string, unknown>),
|
|
4729
|
-
};
|
|
4730
|
-
} else {
|
|
4731
|
-
properties[key] = {
|
|
4732
|
-
type: "array",
|
|
4733
|
-
items: { type: elementType || "string" },
|
|
4734
|
-
};
|
|
4735
|
-
}
|
|
4736
|
-
break;
|
|
4737
|
-
}
|
|
4738
|
-
case "object":
|
|
4739
|
-
properties[key] = zodToJsonSchema(innerDef?.shape as Record<string, unknown>);
|
|
4740
|
-
break;
|
|
4741
|
-
default:
|
|
4742
|
-
properties[key] = { type: "string" }; // fallback
|
|
4743
|
-
}
|
|
4744
|
-
}
|
|
4745
|
-
|
|
4746
|
-
return {
|
|
4747
|
-
type: "object",
|
|
4748
|
-
properties,
|
|
4749
|
-
...(required.length > 0 ? { required } : {}),
|
|
4750
|
-
};
|
|
4751
|
-
}
|
|
4752
|
-
|
|
4753
3636
|
/**
|
|
4754
3637
|
* List all available tools
|
|
4755
3638
|
*/
|
|
4756
|
-
async function listTools(
|
|
3639
|
+
async function listTools() {
|
|
4757
3640
|
// Static import at top of file
|
|
4758
3641
|
const tools = Object.keys(allTools).sort();
|
|
4759
3642
|
|
|
4760
|
-
// JSON output for MCP server discovery
|
|
4761
|
-
if (jsonOutput) {
|
|
4762
|
-
const toolList = tools.map((name) => {
|
|
4763
|
-
const tool = allTools[name as keyof typeof allTools];
|
|
4764
|
-
return {
|
|
4765
|
-
name,
|
|
4766
|
-
description: tool?.description || `Swarm tool: ${name}`,
|
|
4767
|
-
inputSchema: zodToJsonSchema(tool?.args as Record<string, unknown> | undefined),
|
|
4768
|
-
};
|
|
4769
|
-
});
|
|
4770
|
-
console.log(JSON.stringify(toolList));
|
|
4771
|
-
return;
|
|
4772
|
-
}
|
|
4773
|
-
|
|
4774
3643
|
console.log(yellow(BANNER));
|
|
4775
3644
|
console.log(dim(" " + TAGLINE + " v" + VERSION));
|
|
4776
3645
|
console.log();
|
|
@@ -4872,320 +3741,22 @@ Read the file, make the updates, and save it. Create a backup first.`;
|
|
|
4872
3741
|
cwd: home,
|
|
4873
3742
|
});
|
|
4874
3743
|
|
|
4875
|
-
const exitCode = await proc.exited;
|
|
4876
|
-
|
|
4877
|
-
if (exitCode === 0) {
|
|
4878
|
-
s.stop("AGENTS.md updated via LLM");
|
|
4879
|
-
p.log.success("Hivemind unification complete");
|
|
4880
|
-
} else {
|
|
4881
|
-
const stderr = await new Response(proc.stderr).text();
|
|
4882
|
-
s.stop("LLM update failed");
|
|
4883
|
-
p.log.error(stderr || `Exit code: ${exitCode}`);
|
|
4884
|
-
}
|
|
4885
|
-
} catch (error) {
|
|
4886
|
-
s.stop("Failed to run opencode");
|
|
4887
|
-
p.log.error(String(error));
|
|
4888
|
-
}
|
|
4889
|
-
|
|
4890
|
-
p.outro("Done");
|
|
4891
|
-
}
|
|
4892
|
-
|
|
4893
|
-
// ============================================================================
|
|
4894
|
-
// Backup Command - Rolling database backups
|
|
4895
|
-
// ============================================================================
|
|
4896
|
-
|
|
4897
|
-
const BACKUP_DIR = join(homedir(), ".config", "swarm-tools", "backups");
|
|
4898
|
-
const SWARM_DB_PATH = join(homedir(), ".config", "swarm-tools", "swarm.db");
|
|
4899
|
-
|
|
4900
|
-
interface BackupInfo {
|
|
4901
|
-
path: string;
|
|
4902
|
-
timestamp: Date;
|
|
4903
|
-
size: number;
|
|
4904
|
-
type: "hourly" | "daily" | "weekly" | "manual";
|
|
4905
|
-
}
|
|
4906
|
-
|
|
4907
|
-
/**
|
|
4908
|
-
* Get backup file path for a given type and timestamp
|
|
4909
|
-
*/
|
|
4910
|
-
function getBackupPath(type: "hourly" | "daily" | "weekly" | "manual", date: Date): string {
|
|
4911
|
-
const dateStr = date.toISOString().slice(0, 10); // YYYY-MM-DD
|
|
4912
|
-
const timeStr = date.toISOString().slice(11, 16).replace(":", ""); // HHMM
|
|
4913
|
-
return join(BACKUP_DIR, `swarm-${type}-${dateStr}-${timeStr}.db`);
|
|
4914
|
-
}
|
|
4915
|
-
|
|
4916
|
-
/**
|
|
4917
|
-
* List all existing backups
|
|
4918
|
-
*/
|
|
4919
|
-
function listBackups(): BackupInfo[] {
|
|
4920
|
-
if (!existsSync(BACKUP_DIR)) return [];
|
|
4921
|
-
|
|
4922
|
-
const files = readdirSync(BACKUP_DIR).filter(f => f.endsWith(".db"));
|
|
4923
|
-
return files.map(f => {
|
|
4924
|
-
const path = join(BACKUP_DIR, f);
|
|
4925
|
-
const stat = statSync(path);
|
|
4926
|
-
const type = f.includes("-hourly-") ? "hourly" :
|
|
4927
|
-
f.includes("-daily-") ? "daily" :
|
|
4928
|
-
f.includes("-weekly-") ? "weekly" : "manual";
|
|
4929
|
-
return {
|
|
4930
|
-
path,
|
|
4931
|
-
timestamp: stat.mtime,
|
|
4932
|
-
size: stat.size,
|
|
4933
|
-
type: type as BackupInfo["type"],
|
|
4934
|
-
};
|
|
4935
|
-
}).sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
4936
|
-
}
|
|
4937
|
-
|
|
4938
|
-
/**
|
|
4939
|
-
* Create a backup of the swarm database
|
|
4940
|
-
*/
|
|
4941
|
-
async function createBackup(type: "hourly" | "daily" | "weekly" | "manual" = "manual"): Promise<string | null> {
|
|
4942
|
-
if (!existsSync(SWARM_DB_PATH)) {
|
|
4943
|
-
console.error("No swarm.db found at", SWARM_DB_PATH);
|
|
4944
|
-
return null;
|
|
4945
|
-
}
|
|
4946
|
-
|
|
4947
|
-
// Ensure backup directory exists
|
|
4948
|
-
if (!existsSync(BACKUP_DIR)) {
|
|
4949
|
-
mkdirSync(BACKUP_DIR, { recursive: true });
|
|
4950
|
-
}
|
|
4951
|
-
|
|
4952
|
-
const now = new Date();
|
|
4953
|
-
const backupPath = getBackupPath(type, now);
|
|
4954
|
-
|
|
4955
|
-
// Copy the database (use sqlite3 backup command for consistency)
|
|
4956
|
-
try {
|
|
4957
|
-
execSync(`sqlite3 "${SWARM_DB_PATH}" ".backup '${backupPath}'"`, {
|
|
4958
|
-
encoding: "utf-8",
|
|
4959
|
-
timeout: 60000,
|
|
4960
|
-
});
|
|
4961
|
-
|
|
4962
|
-
// Verify backup
|
|
4963
|
-
const srcSize = statSync(SWARM_DB_PATH).size;
|
|
4964
|
-
const dstSize = statSync(backupPath).size;
|
|
4965
|
-
|
|
4966
|
-
if (Math.abs(srcSize - dstSize) > 1024) { // Allow 1KB difference for WAL
|
|
4967
|
-
console.error(`Backup size mismatch: source ${srcSize}, backup ${dstSize}`);
|
|
4968
|
-
unlinkSync(backupPath);
|
|
4969
|
-
return null;
|
|
4970
|
-
}
|
|
4971
|
-
|
|
4972
|
-
return backupPath;
|
|
4973
|
-
} catch (error) {
|
|
4974
|
-
console.error("Backup failed:", error);
|
|
4975
|
-
return null;
|
|
4976
|
-
}
|
|
4977
|
-
}
|
|
4978
|
-
|
|
4979
|
-
/**
|
|
4980
|
-
* Rotate old backups based on retention policy
|
|
4981
|
-
* - Hourly: keep last 24
|
|
4982
|
-
* - Daily: keep last 7
|
|
4983
|
-
* - Weekly: keep last 4
|
|
4984
|
-
* - Manual: keep last 10
|
|
4985
|
-
*/
|
|
4986
|
-
function rotateBackups(): { deleted: number; kept: number } {
|
|
4987
|
-
const limits: Record<string, number> = {
|
|
4988
|
-
hourly: 24,
|
|
4989
|
-
daily: 7,
|
|
4990
|
-
weekly: 4,
|
|
4991
|
-
manual: 10,
|
|
4992
|
-
};
|
|
4993
|
-
|
|
4994
|
-
const backups = listBackups();
|
|
4995
|
-
const byType: Record<string, BackupInfo[]> = { hourly: [], daily: [], weekly: [], manual: [] };
|
|
4996
|
-
|
|
4997
|
-
for (const b of backups) {
|
|
4998
|
-
byType[b.type].push(b);
|
|
4999
|
-
}
|
|
5000
|
-
|
|
5001
|
-
let deleted = 0;
|
|
5002
|
-
let kept = 0;
|
|
5003
|
-
|
|
5004
|
-
for (const [type, list] of Object.entries(byType)) {
|
|
5005
|
-
const limit = limits[type] || 10;
|
|
5006
|
-
const toKeep = list.slice(0, limit);
|
|
5007
|
-
const toDelete = list.slice(limit);
|
|
5008
|
-
|
|
5009
|
-
kept += toKeep.length;
|
|
5010
|
-
|
|
5011
|
-
for (const b of toDelete) {
|
|
5012
|
-
try {
|
|
5013
|
-
unlinkSync(b.path);
|
|
5014
|
-
deleted++;
|
|
5015
|
-
} catch (error) {
|
|
5016
|
-
console.error(`Failed to delete ${b.path}:`, error);
|
|
5017
|
-
}
|
|
5018
|
-
}
|
|
5019
|
-
}
|
|
5020
|
-
|
|
5021
|
-
return { deleted, kept };
|
|
5022
|
-
}
|
|
5023
|
-
|
|
5024
|
-
/**
|
|
5025
|
-
* Verify backup integrity by opening and running a simple query
|
|
5026
|
-
*/
|
|
5027
|
-
async function verifyBackup(backupPath: string): Promise<boolean> {
|
|
5028
|
-
try {
|
|
5029
|
-
const result = execSync(`sqlite3 "${backupPath}" "SELECT COUNT(*) FROM events;"`, {
|
|
5030
|
-
encoding: "utf-8",
|
|
5031
|
-
timeout: 10000,
|
|
5032
|
-
});
|
|
5033
|
-
return result.trim().length > 0;
|
|
5034
|
-
} catch {
|
|
5035
|
-
return false;
|
|
5036
|
-
}
|
|
5037
|
-
}
|
|
5038
|
-
|
|
5039
|
-
/**
|
|
5040
|
-
* Main backup command handler
|
|
5041
|
-
*/
|
|
5042
|
-
async function backup(action: string) {
|
|
5043
|
-
switch (action) {
|
|
5044
|
-
case "create": {
|
|
5045
|
-
p.intro("swarm backup v" + VERSION);
|
|
5046
|
-
|
|
5047
|
-
const s = p.spinner();
|
|
5048
|
-
s.start("Creating backup...");
|
|
5049
|
-
|
|
5050
|
-
const backupPath = await createBackup("manual");
|
|
5051
|
-
if (backupPath) {
|
|
5052
|
-
const size = statSync(backupPath).size;
|
|
5053
|
-
s.stop(`Backup created: ${backupPath} (${(size / 1024).toFixed(1)} KB)`);
|
|
5054
|
-
|
|
5055
|
-
// Verify
|
|
5056
|
-
s.start("Verifying backup...");
|
|
5057
|
-
const valid = await verifyBackup(backupPath);
|
|
5058
|
-
s.stop(valid ? "Backup verified ✓" : "Backup verification FAILED");
|
|
5059
|
-
|
|
5060
|
-
// Rotate
|
|
5061
|
-
const { deleted, kept } = rotateBackups();
|
|
5062
|
-
if (deleted > 0) {
|
|
5063
|
-
p.log.info(`Rotated: kept ${kept} backups, deleted ${deleted} old backups`);
|
|
5064
|
-
}
|
|
5065
|
-
} else {
|
|
5066
|
-
s.stop("Backup failed");
|
|
5067
|
-
}
|
|
5068
|
-
|
|
5069
|
-
p.outro("Done");
|
|
5070
|
-
break;
|
|
5071
|
-
}
|
|
5072
|
-
|
|
5073
|
-
case "list": {
|
|
5074
|
-
const backups = listBackups();
|
|
5075
|
-
if (backups.length === 0) {
|
|
5076
|
-
console.log("No backups found");
|
|
5077
|
-
return;
|
|
5078
|
-
}
|
|
5079
|
-
|
|
5080
|
-
console.log(`\n${cyan("Backups")} (${BACKUP_DIR}):\n`);
|
|
5081
|
-
|
|
5082
|
-
for (const b of backups) {
|
|
5083
|
-
const age = Math.round((Date.now() - b.timestamp.getTime()) / 1000 / 60);
|
|
5084
|
-
const ageStr = age < 60 ? `${age}m ago` : age < 1440 ? `${Math.round(age/60)}h ago` : `${Math.round(age/1440)}d ago`;
|
|
5085
|
-
const sizeStr = `${(b.size / 1024).toFixed(1)} KB`;
|
|
5086
|
-
console.log(` ${dim(b.type.padEnd(8))} ${basename(b.path)} ${dim(`(${sizeStr}, ${ageStr})`)}`);
|
|
5087
|
-
}
|
|
5088
|
-
console.log("");
|
|
5089
|
-
break;
|
|
5090
|
-
}
|
|
5091
|
-
|
|
5092
|
-
case "rotate": {
|
|
5093
|
-
const { deleted, kept } = rotateBackups();
|
|
5094
|
-
console.log(`Rotated: kept ${kept} backups, deleted ${deleted} old backups`);
|
|
5095
|
-
break;
|
|
5096
|
-
}
|
|
5097
|
-
|
|
5098
|
-
case "verify": {
|
|
5099
|
-
const backups = listBackups();
|
|
5100
|
-
if (backups.length === 0) {
|
|
5101
|
-
console.log("No backups to verify");
|
|
5102
|
-
return;
|
|
5103
|
-
}
|
|
5104
|
-
|
|
5105
|
-
console.log(`\nVerifying ${backups.length} backups...\n`);
|
|
5106
|
-
|
|
5107
|
-
let passed = 0;
|
|
5108
|
-
let failed = 0;
|
|
5109
|
-
|
|
5110
|
-
for (const b of backups) {
|
|
5111
|
-
const valid = await verifyBackup(b.path);
|
|
5112
|
-
if (valid) {
|
|
5113
|
-
console.log(` ${green("✓")} ${basename(b.path)}`);
|
|
5114
|
-
passed++;
|
|
5115
|
-
} else {
|
|
5116
|
-
console.log(` ${red("✗")} ${basename(b.path)}`);
|
|
5117
|
-
failed++;
|
|
5118
|
-
}
|
|
5119
|
-
}
|
|
5120
|
-
|
|
5121
|
-
console.log(`\n${passed} passed, ${failed} failed\n`);
|
|
5122
|
-
break;
|
|
5123
|
-
}
|
|
5124
|
-
|
|
5125
|
-
case "restore": {
|
|
5126
|
-
const backupFile = process.argv[4];
|
|
5127
|
-
if (!backupFile) {
|
|
5128
|
-
console.error("Usage: swarm backup restore <backup-file>");
|
|
5129
|
-
console.error("\nAvailable backups:");
|
|
5130
|
-
const backups = listBackups();
|
|
5131
|
-
for (const b of backups.slice(0, 5)) {
|
|
5132
|
-
console.error(` ${basename(b.path)}`);
|
|
5133
|
-
}
|
|
5134
|
-
return;
|
|
5135
|
-
}
|
|
5136
|
-
|
|
5137
|
-
const backupPath = backupFile.startsWith("/") ? backupFile : join(BACKUP_DIR, backupFile);
|
|
5138
|
-
if (!existsSync(backupPath)) {
|
|
5139
|
-
console.error(`Backup not found: ${backupPath}`);
|
|
5140
|
-
return;
|
|
5141
|
-
}
|
|
5142
|
-
|
|
5143
|
-
// Verify before restore
|
|
5144
|
-
const valid = await verifyBackup(backupPath);
|
|
5145
|
-
if (!valid) {
|
|
5146
|
-
console.error("Backup verification failed - aborting restore");
|
|
5147
|
-
return;
|
|
5148
|
-
}
|
|
5149
|
-
|
|
5150
|
-
// Create a backup of current db first
|
|
5151
|
-
const preRestoreBackup = await createBackup("manual");
|
|
5152
|
-
console.log(`Created pre-restore backup: ${preRestoreBackup}`);
|
|
5153
|
-
|
|
5154
|
-
// Restore
|
|
5155
|
-
try {
|
|
5156
|
-
copyFileSync(backupPath, SWARM_DB_PATH);
|
|
5157
|
-
console.log(`Restored from: ${backupPath}`);
|
|
5158
|
-
} catch (error) {
|
|
5159
|
-
console.error("Restore failed:", error);
|
|
5160
|
-
}
|
|
5161
|
-
break;
|
|
3744
|
+
const exitCode = await proc.exited;
|
|
3745
|
+
|
|
3746
|
+
if (exitCode === 0) {
|
|
3747
|
+
s.stop("AGENTS.md updated via LLM");
|
|
3748
|
+
p.log.success("Hivemind unification complete");
|
|
3749
|
+
} else {
|
|
3750
|
+
const stderr = await new Response(proc.stderr).text();
|
|
3751
|
+
s.stop("LLM update failed");
|
|
3752
|
+
p.log.error(stderr || `Exit code: ${exitCode}`);
|
|
5162
3753
|
}
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
console.log(`
|
|
5167
|
-
${cyan("swarm backup")} - Database backup management
|
|
5168
|
-
|
|
5169
|
-
${bold("Commands:")}
|
|
5170
|
-
create Create a new backup (default)
|
|
5171
|
-
list List all backups
|
|
5172
|
-
rotate Rotate old backups based on retention policy
|
|
5173
|
-
verify Verify all backups are valid
|
|
5174
|
-
restore Restore from a backup file
|
|
5175
|
-
|
|
5176
|
-
${bold("Retention Policy:")}
|
|
5177
|
-
Hourly: 24 backups
|
|
5178
|
-
Daily: 7 backups
|
|
5179
|
-
Weekly: 4 backups
|
|
5180
|
-
Manual: 10 backups
|
|
5181
|
-
|
|
5182
|
-
${bold("Examples:")}
|
|
5183
|
-
swarm backup # Create a manual backup
|
|
5184
|
-
swarm backup list # List all backups
|
|
5185
|
-
swarm backup restore latest.db # Restore from a backup
|
|
5186
|
-
`);
|
|
5187
|
-
break;
|
|
3754
|
+
} catch (error) {
|
|
3755
|
+
s.stop("Failed to run opencode");
|
|
3756
|
+
p.log.error(String(error));
|
|
5188
3757
|
}
|
|
3758
|
+
|
|
3759
|
+
p.outro("Done");
|
|
5189
3760
|
}
|
|
5190
3761
|
|
|
5191
3762
|
// ============================================================================
|
|
@@ -6157,178 +4728,7 @@ async function logs() {
|
|
|
6157
4728
|
*
|
|
6158
4729
|
* Helps debug which database is being used and its schema state.
|
|
6159
4730
|
*/
|
|
6160
|
-
/**
|
|
6161
|
-
* Run database repair programmatically
|
|
6162
|
-
* Returns counts of cleaned records
|
|
6163
|
-
*/
|
|
6164
|
-
async function runDbRepair(options: { dryRun: boolean }): Promise<{
|
|
6165
|
-
nullBeads: number;
|
|
6166
|
-
orphanedRecipients: number;
|
|
6167
|
-
messagesWithoutRecipients: number;
|
|
6168
|
-
expiredReservations: number;
|
|
6169
|
-
totalCleaned: number;
|
|
6170
|
-
}> {
|
|
6171
|
-
const { dryRun } = options;
|
|
6172
|
-
const globalDbPath = getGlobalDbPath();
|
|
6173
|
-
const swarmMail = await getSwarmMailLibSQL(globalDbPath);
|
|
6174
|
-
const db = await swarmMail.getDatabase();
|
|
6175
|
-
|
|
6176
|
-
// Count records before cleanup
|
|
6177
|
-
const nullBeadsResult = await db.query<{ count: number }>("SELECT COUNT(*) as count FROM beads WHERE id IS NULL");
|
|
6178
|
-
const nullBeads = Number(nullBeadsResult[0]?.count ?? 0);
|
|
6179
|
-
|
|
6180
|
-
const orphanedRecipientsResult = await db.query<{ count: number }>(
|
|
6181
|
-
"SELECT COUNT(*) as count FROM message_recipients WHERE NOT EXISTS (SELECT 1 FROM agents WHERE agents.name = message_recipients.agent_name)"
|
|
6182
|
-
);
|
|
6183
|
-
const orphanedRecipients = Number(orphanedRecipientsResult[0]?.count ?? 0);
|
|
6184
|
-
|
|
6185
|
-
const messagesWithoutRecipientsResult = await db.query<{ count: number }>(
|
|
6186
|
-
"SELECT COUNT(*) as count FROM messages WHERE NOT EXISTS (SELECT 1 FROM message_recipients WHERE message_recipients.message_id = messages.id)"
|
|
6187
|
-
);
|
|
6188
|
-
const messagesWithoutRecipients = Number(messagesWithoutRecipientsResult[0]?.count ?? 0);
|
|
6189
|
-
|
|
6190
|
-
const expiredReservationsResult = await db.query<{ count: number }>(
|
|
6191
|
-
"SELECT COUNT(*) as count FROM reservations WHERE released_at IS NULL AND expires_at < strftime('%s', 'now') * 1000"
|
|
6192
|
-
);
|
|
6193
|
-
const expiredReservations = Number(expiredReservationsResult[0]?.count ?? 0);
|
|
6194
|
-
|
|
6195
|
-
const totalCleaned = nullBeads + orphanedRecipients + messagesWithoutRecipients + expiredReservations;
|
|
6196
|
-
|
|
6197
|
-
// If dry run or nothing to clean, return early
|
|
6198
|
-
if (dryRun || totalCleaned === 0) {
|
|
6199
|
-
return {
|
|
6200
|
-
nullBeads,
|
|
6201
|
-
orphanedRecipients,
|
|
6202
|
-
messagesWithoutRecipients,
|
|
6203
|
-
expiredReservations,
|
|
6204
|
-
totalCleaned,
|
|
6205
|
-
};
|
|
6206
|
-
}
|
|
6207
|
-
|
|
6208
|
-
// Execute cleanup queries
|
|
6209
|
-
if (nullBeads > 0) {
|
|
6210
|
-
await db.query("DELETE FROM beads WHERE id IS NULL");
|
|
6211
|
-
}
|
|
6212
|
-
|
|
6213
|
-
if (orphanedRecipients > 0) {
|
|
6214
|
-
await db.query(
|
|
6215
|
-
"DELETE FROM message_recipients WHERE NOT EXISTS (SELECT 1 FROM agents WHERE agents.name = message_recipients.agent_name)"
|
|
6216
|
-
);
|
|
6217
|
-
}
|
|
6218
|
-
|
|
6219
|
-
if (messagesWithoutRecipients > 0) {
|
|
6220
|
-
await db.query(
|
|
6221
|
-
"DELETE FROM messages WHERE NOT EXISTS (SELECT 1 FROM message_recipients WHERE message_recipients.message_id = messages.id)"
|
|
6222
|
-
);
|
|
6223
|
-
}
|
|
6224
|
-
|
|
6225
|
-
if (expiredReservations > 0) {
|
|
6226
|
-
await db.query(
|
|
6227
|
-
"UPDATE reservations SET released_at = strftime('%s', 'now') * 1000 WHERE released_at IS NULL AND expires_at < strftime('%s', 'now') * 1000"
|
|
6228
|
-
);
|
|
6229
|
-
}
|
|
6230
|
-
|
|
6231
|
-
return {
|
|
6232
|
-
nullBeads,
|
|
6233
|
-
orphanedRecipients,
|
|
6234
|
-
messagesWithoutRecipients,
|
|
6235
|
-
expiredReservations,
|
|
6236
|
-
totalCleaned,
|
|
6237
|
-
};
|
|
6238
|
-
}
|
|
6239
|
-
|
|
6240
|
-
/**
|
|
6241
|
-
* Database repair command (CLI interface)
|
|
6242
|
-
* Executes cleanup SQL to remove orphaned/invalid data
|
|
6243
|
-
*/
|
|
6244
|
-
async function dbRepair() {
|
|
6245
|
-
const args = process.argv.slice(4); // Skip 'swarm', 'db', 'repair'
|
|
6246
|
-
let dryRun = false;
|
|
6247
|
-
|
|
6248
|
-
// Parse --dry-run flag
|
|
6249
|
-
for (const arg of args) {
|
|
6250
|
-
if (arg === "--dry-run") {
|
|
6251
|
-
dryRun = true;
|
|
6252
|
-
}
|
|
6253
|
-
}
|
|
6254
|
-
|
|
6255
|
-
p.intro(dryRun ? "swarm db repair (DRY RUN)" : "swarm db repair");
|
|
6256
|
-
|
|
6257
|
-
const s = p.spinner();
|
|
6258
|
-
s.start("Analyzing database...");
|
|
6259
|
-
|
|
6260
|
-
try {
|
|
6261
|
-
// Use shared helper for analysis
|
|
6262
|
-
const result = await runDbRepair({ dryRun: true });
|
|
6263
|
-
|
|
6264
|
-
s.stop("Analysis complete");
|
|
6265
|
-
|
|
6266
|
-
// Show counts
|
|
6267
|
-
p.log.step(dryRun ? "Would delete:" : "Deleting:");
|
|
6268
|
-
if (result.nullBeads > 0) {
|
|
6269
|
-
p.log.message(` - ${result.nullBeads} beads with NULL IDs`);
|
|
6270
|
-
}
|
|
6271
|
-
if (result.orphanedRecipients > 0) {
|
|
6272
|
-
p.log.message(` - ${result.orphanedRecipients} orphaned message_recipients`);
|
|
6273
|
-
}
|
|
6274
|
-
if (result.messagesWithoutRecipients > 0) {
|
|
6275
|
-
p.log.message(` - ${result.messagesWithoutRecipients} messages without recipients`);
|
|
6276
|
-
}
|
|
6277
|
-
if (result.expiredReservations > 0) {
|
|
6278
|
-
p.log.message(` - ${result.expiredReservations} expired unreleased reservations`);
|
|
6279
|
-
}
|
|
6280
|
-
|
|
6281
|
-
if (result.totalCleaned === 0) {
|
|
6282
|
-
p.outro(green("✓ Database is clean! No records to delete."));
|
|
6283
|
-
return;
|
|
6284
|
-
}
|
|
6285
|
-
|
|
6286
|
-
console.log();
|
|
6287
|
-
p.log.message(dim(`Total: ${result.totalCleaned} records ${dryRun ? "would be" : "will be"} cleaned`));
|
|
6288
|
-
console.log();
|
|
6289
|
-
|
|
6290
|
-
// If dry run, stop here
|
|
6291
|
-
if (dryRun) {
|
|
6292
|
-
p.outro(dim("Run without --dry-run to execute cleanup"));
|
|
6293
|
-
return;
|
|
6294
|
-
}
|
|
6295
|
-
|
|
6296
|
-
// Confirm before actual deletion
|
|
6297
|
-
const confirmed = await p.confirm({
|
|
6298
|
-
message: `Delete ${result.totalCleaned} records?`,
|
|
6299
|
-
initialValue: false,
|
|
6300
|
-
});
|
|
6301
|
-
|
|
6302
|
-
if (p.isCancel(confirmed) || !confirmed) {
|
|
6303
|
-
p.cancel("Cleanup cancelled");
|
|
6304
|
-
return;
|
|
6305
|
-
}
|
|
6306
|
-
|
|
6307
|
-
// Execute cleanup
|
|
6308
|
-
const cleanupSpinner = p.spinner();
|
|
6309
|
-
cleanupSpinner.start("Cleaning database...");
|
|
6310
|
-
|
|
6311
|
-
await runDbRepair({ dryRun: false });
|
|
6312
|
-
|
|
6313
|
-
cleanupSpinner.stop("Cleanup complete");
|
|
6314
|
-
|
|
6315
|
-
p.outro(green(`✓ Successfully cleaned ${result.totalCleaned} records`));
|
|
6316
|
-
} catch (error) {
|
|
6317
|
-
s.stop("Error");
|
|
6318
|
-
p.log.error(error instanceof Error ? error.message : String(error));
|
|
6319
|
-
process.exit(1);
|
|
6320
|
-
}
|
|
6321
|
-
}
|
|
6322
|
-
|
|
6323
4731
|
async function db() {
|
|
6324
|
-
const args = process.argv.slice(3);
|
|
6325
|
-
|
|
6326
|
-
// Check for 'repair' subcommand
|
|
6327
|
-
if (args[0] === "repair") {
|
|
6328
|
-
await dbRepair();
|
|
6329
|
-
return;
|
|
6330
|
-
}
|
|
6331
|
-
|
|
6332
4732
|
const projectPath = process.cwd();
|
|
6333
4733
|
const projectName = basename(projectPath);
|
|
6334
4734
|
const hash = hashLibSQLProjectPath(projectPath);
|
|
@@ -6597,8 +4997,6 @@ async function stats() {
|
|
|
6597
4997
|
let period = "7d"; // default to 7 days
|
|
6598
4998
|
let format: "text" | "json" = "text";
|
|
6599
4999
|
let showRegressions = false;
|
|
6600
|
-
let showRejections = false;
|
|
6601
|
-
let showCompactionPrompts = false;
|
|
6602
5000
|
|
|
6603
5001
|
for (let i = 0; i < args.length; i++) {
|
|
6604
5002
|
if (args[i] === "--since" || args[i] === "-s") {
|
|
@@ -6608,10 +5006,6 @@ async function stats() {
|
|
|
6608
5006
|
format = "json";
|
|
6609
5007
|
} else if (args[i] === "--regressions") {
|
|
6610
5008
|
showRegressions = true;
|
|
6611
|
-
} else if (args[i] === "--rejections") {
|
|
6612
|
-
showRejections = true;
|
|
6613
|
-
} else if (args[i] === "--compaction-prompts") {
|
|
6614
|
-
showCompactionPrompts = true;
|
|
6615
5009
|
}
|
|
6616
5010
|
}
|
|
6617
5011
|
|
|
@@ -6768,88 +5162,6 @@ async function stats() {
|
|
|
6768
5162
|
console.log("✅ No eval regressions detected (>10% threshold)\n");
|
|
6769
5163
|
}
|
|
6770
5164
|
}
|
|
6771
|
-
} else if (showRejections) {
|
|
6772
|
-
// If --rejections flag, show rejection analytics
|
|
6773
|
-
const rejectionAnalytics = await getRejectionAnalytics(swarmMail);
|
|
6774
|
-
|
|
6775
|
-
if (format === "json") {
|
|
6776
|
-
console.log(JSON.stringify(rejectionAnalytics, null, 2));
|
|
6777
|
-
} else {
|
|
6778
|
-
console.log();
|
|
6779
|
-
const boxWidth = 61;
|
|
6780
|
-
const pad = (text: string) => text + " ".repeat(Math.max(0, boxWidth - text.length));
|
|
6781
|
-
|
|
6782
|
-
console.log("┌─────────────────────────────────────────────────────────────┐");
|
|
6783
|
-
console.log("│" + pad(" REJECTION ANALYSIS (last " + period + ")") + "│");
|
|
6784
|
-
console.log("├─────────────────────────────────────────────────────────────┤");
|
|
6785
|
-
console.log("│" + pad(" Total Reviews: " + rejectionAnalytics.totalReviews) + "│");
|
|
6786
|
-
|
|
6787
|
-
const rejectionRate = rejectionAnalytics.totalReviews > 0
|
|
6788
|
-
? (100 - rejectionAnalytics.approvalRate).toFixed(0)
|
|
6789
|
-
: "0";
|
|
6790
|
-
console.log("│" + pad(" Approved: " + rejectionAnalytics.approved + " (" + rejectionAnalytics.approvalRate.toFixed(0) + "%)") + "│");
|
|
6791
|
-
console.log("│" + pad(" Rejected: " + rejectionAnalytics.rejected + " (" + rejectionRate + "%)") + "│");
|
|
6792
|
-
console.log("│" + pad("") + "│");
|
|
6793
|
-
|
|
6794
|
-
if (rejectionAnalytics.topReasons.length > 0) {
|
|
6795
|
-
console.log("│" + pad(" Top Rejection Reasons:") + "│");
|
|
6796
|
-
for (const reason of rejectionAnalytics.topReasons) {
|
|
6797
|
-
const line = " ├── " + reason.category + ": " + reason.count + " (" + reason.percentage.toFixed(0) + "%)";
|
|
6798
|
-
console.log("│" + pad(line) + "│");
|
|
6799
|
-
}
|
|
6800
|
-
} else {
|
|
6801
|
-
console.log("│" + pad(" No rejections in this period") + "│");
|
|
6802
|
-
}
|
|
6803
|
-
|
|
6804
|
-
console.log("└─────────────────────────────────────────────────────────────┘");
|
|
6805
|
-
console.log();
|
|
6806
|
-
}
|
|
6807
|
-
} else if (showCompactionPrompts) {
|
|
6808
|
-
// If --compaction-prompts flag, show compaction analytics
|
|
6809
|
-
const compactionAnalytics = await getCompactionAnalytics(swarmMail);
|
|
6810
|
-
|
|
6811
|
-
if (format === "json") {
|
|
6812
|
-
console.log(JSON.stringify(compactionAnalytics, null, 2));
|
|
6813
|
-
} else {
|
|
6814
|
-
console.log();
|
|
6815
|
-
const boxWidth = 61;
|
|
6816
|
-
const pad = (text: string) => text + " ".repeat(Math.max(0, boxWidth - text.length));
|
|
6817
|
-
|
|
6818
|
-
console.log("┌─────────────────────────────────────────────────────────────┐");
|
|
6819
|
-
console.log("│" + pad(" COMPACTION PROMPT ANALYTICS (all time)") + "│");
|
|
6820
|
-
console.log("├─────────────────────────────────────────────────────────────┤");
|
|
6821
|
-
console.log("│" + pad(" Total Events: " + compactionAnalytics.totalEvents) + "│");
|
|
6822
|
-
console.log("│" + pad(" Success Rate: " + compactionAnalytics.successRate.toFixed(1) + "%") + "│");
|
|
6823
|
-
console.log("│" + pad(" Avg Prompt Size: " + compactionAnalytics.avgPromptSize + " chars") + "│");
|
|
6824
|
-
console.log("│" + pad("") + "│");
|
|
6825
|
-
console.log("│" + pad(" By Type:") + "│");
|
|
6826
|
-
console.log("│" + pad(" ├── Prompts Generated: " + compactionAnalytics.byType.prompt_generated) + "│");
|
|
6827
|
-
console.log("│" + pad(" └── Detections Failed: " + compactionAnalytics.byType.detection_failed) + "│");
|
|
6828
|
-
console.log("│" + pad("") + "│");
|
|
6829
|
-
console.log("│" + pad(" Confidence Distribution:") + "│");
|
|
6830
|
-
console.log("│" + pad(" ├── High: " + compactionAnalytics.byConfidence.high) + "│");
|
|
6831
|
-
console.log("│" + pad(" ├── Medium: " + compactionAnalytics.byConfidence.medium) + "│");
|
|
6832
|
-
console.log("│" + pad(" └── Low: " + compactionAnalytics.byConfidence.low) + "│");
|
|
6833
|
-
|
|
6834
|
-
if (compactionAnalytics.recentPrompts.length > 0) {
|
|
6835
|
-
console.log("│" + pad("") + "│");
|
|
6836
|
-
console.log("│" + pad(" Recent Prompts:") + "│");
|
|
6837
|
-
for (const prompt of compactionAnalytics.recentPrompts.slice(0, 5)) {
|
|
6838
|
-
const timestamp = new Date(prompt.timestamp).toLocaleDateString();
|
|
6839
|
-
const conf = prompt.confidence ? ` (${prompt.confidence})` : "";
|
|
6840
|
-
const line = ` ├── ${timestamp}: ${prompt.length} chars${conf}`;
|
|
6841
|
-
console.log("│" + pad(line) + "│");
|
|
6842
|
-
|
|
6843
|
-
if (prompt.preview) {
|
|
6844
|
-
const previewLine = ` ${prompt.preview.substring(0, 50)}...`;
|
|
6845
|
-
console.log("│" + pad(previewLine) + "│");
|
|
6846
|
-
}
|
|
6847
|
-
}
|
|
6848
|
-
}
|
|
6849
|
-
|
|
6850
|
-
console.log("└─────────────────────────────────────────────────────────────┘");
|
|
6851
|
-
console.log();
|
|
6852
|
-
}
|
|
6853
5165
|
} else {
|
|
6854
5166
|
// Normal stats output
|
|
6855
5167
|
if (format === "json") {
|
|
@@ -7191,49 +5503,6 @@ async function evalRun() {
|
|
|
7191
5503
|
}
|
|
7192
5504
|
}
|
|
7193
5505
|
|
|
7194
|
-
// ============================================================================
|
|
7195
|
-
// MCP Server (Debug Only)
|
|
7196
|
-
// ============================================================================
|
|
7197
|
-
|
|
7198
|
-
/**
|
|
7199
|
-
* Start the MCP server over stdio.
|
|
7200
|
-
* When run non-interactively (piped stdin), runs silently for MCP protocol.
|
|
7201
|
-
* When run interactively (TTY), shows debug output.
|
|
7202
|
-
*/
|
|
7203
|
-
async function mcpServe() {
|
|
7204
|
-
const isInteractive = process.stdin.isTTY;
|
|
7205
|
-
|
|
7206
|
-
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || getClaudePluginRoot();
|
|
7207
|
-
const candidates = [
|
|
7208
|
-
join(pluginRoot, "bin", "swarm-mcp-server.ts"),
|
|
7209
|
-
join(PACKAGE_ROOT, "bin", "swarm-mcp-server.ts"),
|
|
7210
|
-
];
|
|
7211
|
-
const serverPath = candidates.find((path) => existsSync(path));
|
|
7212
|
-
|
|
7213
|
-
if (!serverPath) {
|
|
7214
|
-
if (isInteractive) {
|
|
7215
|
-
p.intro("swarm mcp-serve");
|
|
7216
|
-
p.log.error("MCP server entrypoint not found");
|
|
7217
|
-
p.log.message(dim(` Looked for: ${candidates.join(", ")}`));
|
|
7218
|
-
p.outro("Aborted");
|
|
7219
|
-
} else {
|
|
7220
|
-
console.error("[swarm-mcp] Server entrypoint not found");
|
|
7221
|
-
}
|
|
7222
|
-
process.exit(1);
|
|
7223
|
-
}
|
|
7224
|
-
|
|
7225
|
-
if (isInteractive) {
|
|
7226
|
-
p.intro("swarm mcp-serve");
|
|
7227
|
-
p.log.step("Starting MCP server...");
|
|
7228
|
-
p.log.message(dim(` Using: ${serverPath}`));
|
|
7229
|
-
}
|
|
7230
|
-
|
|
7231
|
-
const proc = spawn("bun", ["run", serverPath], { stdio: "inherit" });
|
|
7232
|
-
proc.on("close", (exitCode) => {
|
|
7233
|
-
process.exit(exitCode ?? 0);
|
|
7234
|
-
});
|
|
7235
|
-
}
|
|
7236
|
-
|
|
7237
5506
|
// ============================================================================
|
|
7238
5507
|
// Serve Command - Start SSE Server
|
|
7239
5508
|
// ============================================================================
|
|
@@ -7451,32 +5720,22 @@ async function capture() {
|
|
|
7451
5720
|
*/
|
|
7452
5721
|
function parseMemoryArgs(subcommand: string, args: string[]): {
|
|
7453
5722
|
json: boolean;
|
|
7454
|
-
debug: boolean;
|
|
7455
|
-
extractEntities: boolean;
|
|
7456
5723
|
info?: string;
|
|
7457
5724
|
query?: string;
|
|
7458
5725
|
id?: string;
|
|
7459
|
-
name?: string;
|
|
7460
5726
|
tags?: string;
|
|
7461
5727
|
limit?: number;
|
|
7462
5728
|
collection?: string;
|
|
7463
|
-
type?: string;
|
|
7464
|
-
direction?: string;
|
|
7465
5729
|
} {
|
|
7466
5730
|
let json = false;
|
|
7467
|
-
let debug = false;
|
|
7468
|
-
let extractEntities = false;
|
|
7469
5731
|
let info: string | undefined;
|
|
7470
5732
|
let query: string | undefined;
|
|
7471
5733
|
let id: string | undefined;
|
|
7472
|
-
let name: string | undefined;
|
|
7473
5734
|
let tags: string | undefined;
|
|
7474
5735
|
let limit: number | undefined;
|
|
7475
5736
|
let collection: string | undefined;
|
|
7476
|
-
let type: string | undefined;
|
|
7477
|
-
let direction: string | undefined;
|
|
7478
5737
|
|
|
7479
|
-
// First positional arg for store/find/get/remove/validate
|
|
5738
|
+
// First positional arg for store/find/get/remove/validate
|
|
7480
5739
|
if (args.length > 0 && !args[0].startsWith("--")) {
|
|
7481
5740
|
if (subcommand === "store") {
|
|
7482
5741
|
info = args[0];
|
|
@@ -7484,8 +5743,6 @@ function parseMemoryArgs(subcommand: string, args: string[]): {
|
|
|
7484
5743
|
query = args[0];
|
|
7485
5744
|
} else if (subcommand === "get" || subcommand === "remove" || subcommand === "validate") {
|
|
7486
5745
|
id = args[0];
|
|
7487
|
-
} else if (subcommand === "entity" || subcommand === "taxonomy") {
|
|
7488
|
-
name = args[0];
|
|
7489
5746
|
}
|
|
7490
5747
|
}
|
|
7491
5748
|
|
|
@@ -7493,10 +5750,6 @@ function parseMemoryArgs(subcommand: string, args: string[]): {
|
|
|
7493
5750
|
const arg = args[i];
|
|
7494
5751
|
if (arg === "--json") {
|
|
7495
5752
|
json = true;
|
|
7496
|
-
} else if (arg === "--debug" || arg === "-v" || arg === "--verbose") {
|
|
7497
|
-
debug = true;
|
|
7498
|
-
} else if (arg === "--extract-entities" || arg === "-e") {
|
|
7499
|
-
extractEntities = true;
|
|
7500
5753
|
} else if (arg === "--tags" && i + 1 < args.length) {
|
|
7501
5754
|
tags = args[++i];
|
|
7502
5755
|
} else if (arg === "--limit" && i + 1 < args.length) {
|
|
@@ -7504,19 +5757,15 @@ function parseMemoryArgs(subcommand: string, args: string[]): {
|
|
|
7504
5757
|
if (!isNaN(val)) limit = val;
|
|
7505
5758
|
} else if (arg === "--collection" && i + 1 < args.length) {
|
|
7506
5759
|
collection = args[++i];
|
|
7507
|
-
} else if (arg === "--type" && i + 1 < args.length) {
|
|
7508
|
-
type = args[++i];
|
|
7509
|
-
} else if (arg === "--direction" && i + 1 < args.length) {
|
|
7510
|
-
direction = args[++i];
|
|
7511
5760
|
}
|
|
7512
5761
|
}
|
|
7513
5762
|
|
|
7514
|
-
return { json,
|
|
5763
|
+
return { json, info, query, id, tags, limit, collection };
|
|
7515
5764
|
}
|
|
7516
5765
|
|
|
7517
5766
|
/**
|
|
7518
5767
|
* Memory command - unified interface to memory operations
|
|
7519
|
-
*
|
|
5768
|
+
*
|
|
7520
5769
|
* Commands:
|
|
7521
5770
|
* swarm memory store <info> [--tags <tags>]
|
|
7522
5771
|
* swarm memory find <query> [--limit <n>] [--collection <name>]
|
|
@@ -7526,9 +5775,6 @@ function parseMemoryArgs(subcommand: string, args: string[]): {
|
|
|
7526
5775
|
* swarm memory stats
|
|
7527
5776
|
* swarm memory index
|
|
7528
5777
|
* swarm memory sync
|
|
7529
|
-
* swarm memory entities [--type <type>]
|
|
7530
|
-
* swarm memory entity <name>
|
|
7531
|
-
* swarm memory taxonomy <entity> [--direction broader|narrower|related]
|
|
7532
5778
|
*/
|
|
7533
5779
|
async function memory() {
|
|
7534
5780
|
const subcommand = process.argv[3];
|
|
@@ -7542,20 +5788,21 @@ async function memory() {
|
|
|
7542
5788
|
// Get database instance using getDb from swarm-mail
|
|
7543
5789
|
// This returns a drizzle instance (SwarmDb) that memory adapter expects
|
|
7544
5790
|
const { getDb } = await import("swarm-mail");
|
|
7545
|
-
|
|
7546
|
-
//
|
|
7547
|
-
const
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
|
|
5791
|
+
|
|
5792
|
+
// Calculate DB path (same logic as libsql.convenience.ts)
|
|
5793
|
+
const tempDirName = getLibSQLProjectTempDirName(projectPath);
|
|
5794
|
+
const tempDir = join(tmpdir(), tempDirName);
|
|
5795
|
+
|
|
5796
|
+
// Ensure temp directory exists
|
|
5797
|
+
if (!existsSync(tempDir)) {
|
|
5798
|
+
mkdirSync(tempDir, { recursive: true });
|
|
7552
5799
|
}
|
|
7553
|
-
|
|
7554
|
-
const dbPath = join(
|
|
7555
|
-
|
|
5800
|
+
|
|
5801
|
+
const dbPath = join(tempDir, "streams.db");
|
|
5802
|
+
|
|
7556
5803
|
// Convert to file:// URL (required by libSQL)
|
|
7557
5804
|
const dbUrl = `file://${dbPath}`;
|
|
7558
|
-
|
|
5805
|
+
|
|
7559
5806
|
const db = await getDb(dbUrl);
|
|
7560
5807
|
|
|
7561
5808
|
// Create memory adapter with default Ollama config
|
|
@@ -7568,39 +5815,23 @@ async function memory() {
|
|
|
7568
5815
|
switch (subcommand) {
|
|
7569
5816
|
case "store": {
|
|
7570
5817
|
if (!parsed.info) {
|
|
7571
|
-
console.error("Usage: swarm memory store <information> [--tags <tags>]
|
|
5818
|
+
console.error("Usage: swarm memory store <information> [--tags <tags>]");
|
|
7572
5819
|
process.exit(1);
|
|
7573
5820
|
}
|
|
7574
5821
|
|
|
7575
|
-
if (parsed.debug) {
|
|
7576
|
-
console.log("[DEBUG] Store options:", {
|
|
7577
|
-
content: parsed.info.substring(0, 50) + "...",
|
|
7578
|
-
tags: parsed.tags,
|
|
7579
|
-
collection: parsed.collection || "default",
|
|
7580
|
-
extractEntities: parsed.extractEntities,
|
|
7581
|
-
});
|
|
7582
|
-
}
|
|
7583
|
-
|
|
7584
5822
|
const result = await adapter.store(parsed.info, {
|
|
7585
5823
|
tags: parsed.tags,
|
|
7586
5824
|
collection: parsed.collection || "default",
|
|
7587
|
-
extractEntities: parsed.extractEntities,
|
|
7588
5825
|
});
|
|
7589
5826
|
|
|
7590
5827
|
if (parsed.json) {
|
|
7591
|
-
console.log(JSON.stringify({ success: true, id: result.id
|
|
5828
|
+
console.log(JSON.stringify({ success: true, id: result.id }));
|
|
7592
5829
|
} else {
|
|
7593
5830
|
p.intro("swarm memory store");
|
|
7594
5831
|
p.log.success(`Stored memory: ${result.id}`);
|
|
7595
5832
|
if (result.autoTags) {
|
|
7596
5833
|
p.log.message(`Auto-tags: ${result.autoTags.tags.join(", ")}`);
|
|
7597
5834
|
}
|
|
7598
|
-
if (result.links && result.links.length > 0) {
|
|
7599
|
-
p.log.message(`Linked to ${result.links.length} related memories`);
|
|
7600
|
-
}
|
|
7601
|
-
if (parsed.extractEntities) {
|
|
7602
|
-
p.log.message(`Entity extraction: enabled`);
|
|
7603
|
-
}
|
|
7604
5835
|
p.outro("Done");
|
|
7605
5836
|
}
|
|
7606
5837
|
break;
|
|
@@ -7743,237 +5974,6 @@ async function memory() {
|
|
|
7743
5974
|
break;
|
|
7744
5975
|
}
|
|
7745
5976
|
|
|
7746
|
-
case "entities": {
|
|
7747
|
-
const { createClient } = await import("@libsql/client");
|
|
7748
|
-
const client = createClient({ url: dbUrl });
|
|
7749
|
-
|
|
7750
|
-
const { getEntitiesByType } = await import("swarm-mail");
|
|
7751
|
-
|
|
7752
|
-
let query = "SELECT id, name, entity_type, created_at FROM entities";
|
|
7753
|
-
const params: string[] = [];
|
|
7754
|
-
|
|
7755
|
-
if (parsed.type) {
|
|
7756
|
-
query += " WHERE entity_type = ?";
|
|
7757
|
-
params.push(parsed.type);
|
|
7758
|
-
}
|
|
7759
|
-
|
|
7760
|
-
query += " ORDER BY created_at DESC";
|
|
7761
|
-
|
|
7762
|
-
const result = await client.execute(query, params);
|
|
7763
|
-
|
|
7764
|
-
if (parsed.json) {
|
|
7765
|
-
console.log(JSON.stringify({
|
|
7766
|
-
success: true,
|
|
7767
|
-
entities: result.rows.map((row) => ({
|
|
7768
|
-
id: row.id,
|
|
7769
|
-
name: row.name,
|
|
7770
|
-
entityType: row.entity_type,
|
|
7771
|
-
createdAt: row.created_at,
|
|
7772
|
-
})),
|
|
7773
|
-
}));
|
|
7774
|
-
} else {
|
|
7775
|
-
p.intro("swarm memory entities");
|
|
7776
|
-
if (result.rows.length === 0) {
|
|
7777
|
-
p.log.warn("No entities found");
|
|
7778
|
-
} else {
|
|
7779
|
-
console.log();
|
|
7780
|
-
for (const row of result.rows) {
|
|
7781
|
-
console.log(cyan(`[${row.id}] ${row.name}`));
|
|
7782
|
-
console.log(dim(` Type: ${row.entity_type}`));
|
|
7783
|
-
console.log(dim(` Created: ${new Date(row.created_at as string).toLocaleDateString()}`));
|
|
7784
|
-
}
|
|
7785
|
-
}
|
|
7786
|
-
p.outro(`Found ${result.rows.length} entit${result.rows.length === 1 ? "y" : "ies"}`);
|
|
7787
|
-
}
|
|
7788
|
-
|
|
7789
|
-
client.close();
|
|
7790
|
-
break;
|
|
7791
|
-
}
|
|
7792
|
-
|
|
7793
|
-
case "entity": {
|
|
7794
|
-
if (!parsed.name) {
|
|
7795
|
-
console.error("Usage: swarm memory entity <name>");
|
|
7796
|
-
process.exit(1);
|
|
7797
|
-
}
|
|
7798
|
-
|
|
7799
|
-
const { createClient } = await import("@libsql/client");
|
|
7800
|
-
const client = createClient({ url: dbUrl });
|
|
7801
|
-
|
|
7802
|
-
const { getTaxonomyForEntity } = await import("swarm-mail");
|
|
7803
|
-
|
|
7804
|
-
// Lookup entity by name (case-insensitive)
|
|
7805
|
-
const entityResult = await client.execute(
|
|
7806
|
-
"SELECT id, name, entity_type, created_at FROM entities WHERE LOWER(name) = LOWER(?) LIMIT 1",
|
|
7807
|
-
[parsed.name]
|
|
7808
|
-
);
|
|
7809
|
-
|
|
7810
|
-
if (entityResult.rows.length === 0) {
|
|
7811
|
-
if (parsed.json) {
|
|
7812
|
-
console.log(JSON.stringify({ success: false, error: "Entity not found" }));
|
|
7813
|
-
} else {
|
|
7814
|
-
p.intro("swarm memory entity");
|
|
7815
|
-
p.log.error(`Entity "${parsed.name}" not found`);
|
|
7816
|
-
p.outro("Aborted");
|
|
7817
|
-
}
|
|
7818
|
-
client.close();
|
|
7819
|
-
process.exit(1);
|
|
7820
|
-
}
|
|
7821
|
-
|
|
7822
|
-
const entity = entityResult.rows[0];
|
|
7823
|
-
const entityId = entity.id as string;
|
|
7824
|
-
|
|
7825
|
-
// Get taxonomy relationships
|
|
7826
|
-
const taxonomy = await getTaxonomyForEntity(entityId, client);
|
|
7827
|
-
|
|
7828
|
-
// Fetch related entity names
|
|
7829
|
-
const relatedIds = new Set<string>();
|
|
7830
|
-
for (const rel of taxonomy) {
|
|
7831
|
-
relatedIds.add(rel.entityId);
|
|
7832
|
-
relatedIds.add(rel.relatedEntityId);
|
|
7833
|
-
}
|
|
7834
|
-
|
|
7835
|
-
const relatedEntities = new Map<string, string>();
|
|
7836
|
-
for (const id of relatedIds) {
|
|
7837
|
-
const res = await client.execute("SELECT name FROM entities WHERE id = ? LIMIT 1", [id]);
|
|
7838
|
-
if (res.rows.length > 0) {
|
|
7839
|
-
relatedEntities.set(id, res.rows[0].name as string);
|
|
7840
|
-
}
|
|
7841
|
-
}
|
|
7842
|
-
|
|
7843
|
-
if (parsed.json) {
|
|
7844
|
-
console.log(JSON.stringify({
|
|
7845
|
-
success: true,
|
|
7846
|
-
entity: {
|
|
7847
|
-
id: entity.id,
|
|
7848
|
-
name: entity.name,
|
|
7849
|
-
entityType: entity.entity_type,
|
|
7850
|
-
createdAt: entity.created_at,
|
|
7851
|
-
},
|
|
7852
|
-
taxonomy: taxonomy.map((rel) => ({
|
|
7853
|
-
id: rel.id,
|
|
7854
|
-
subjectEntity: relatedEntities.get(rel.entityId),
|
|
7855
|
-
relationshipType: rel.relationshipType,
|
|
7856
|
-
relatedEntity: relatedEntities.get(rel.relatedEntityId),
|
|
7857
|
-
})),
|
|
7858
|
-
}));
|
|
7859
|
-
} else {
|
|
7860
|
-
p.intro(`swarm memory entity: ${entity.name}`);
|
|
7861
|
-
console.log();
|
|
7862
|
-
console.log(cyan("Entity Details:"));
|
|
7863
|
-
console.log(` ID: ${entity.id}`);
|
|
7864
|
-
console.log(` Name: ${entity.name}`);
|
|
7865
|
-
console.log(` Type: ${entity.entity_type}`);
|
|
7866
|
-
console.log(` Created: ${new Date(entity.created_at as string).toLocaleDateString()}`);
|
|
7867
|
-
|
|
7868
|
-
if (taxonomy.length > 0) {
|
|
7869
|
-
console.log();
|
|
7870
|
-
console.log(cyan("Taxonomy Relationships:"));
|
|
7871
|
-
for (const rel of taxonomy) {
|
|
7872
|
-
const subject = relatedEntities.get(rel.entityId);
|
|
7873
|
-
const related = relatedEntities.get(rel.relatedEntityId);
|
|
7874
|
-
console.log(` ${subject} ${dim(`--[${rel.relationshipType}]-->`)} ${related}`);
|
|
7875
|
-
}
|
|
7876
|
-
} else {
|
|
7877
|
-
console.log();
|
|
7878
|
-
console.log(dim(" No taxonomy relationships"));
|
|
7879
|
-
}
|
|
7880
|
-
|
|
7881
|
-
p.outro("Done");
|
|
7882
|
-
}
|
|
7883
|
-
|
|
7884
|
-
client.close();
|
|
7885
|
-
break;
|
|
7886
|
-
}
|
|
7887
|
-
|
|
7888
|
-
case "taxonomy": {
|
|
7889
|
-
if (!parsed.name) {
|
|
7890
|
-
console.error("Usage: swarm memory taxonomy <entity> [--direction broader|narrower|related]");
|
|
7891
|
-
process.exit(1);
|
|
7892
|
-
}
|
|
7893
|
-
|
|
7894
|
-
const { createClient } = await import("@libsql/client");
|
|
7895
|
-
const client = createClient({ url: dbUrl });
|
|
7896
|
-
|
|
7897
|
-
const { getTaxonomyForEntity, findByTaxonomy } = await import("swarm-mail");
|
|
7898
|
-
|
|
7899
|
-
// Lookup entity by name (case-insensitive)
|
|
7900
|
-
const entityResult = await client.execute(
|
|
7901
|
-
"SELECT id, name FROM entities WHERE LOWER(name) = LOWER(?) LIMIT 1",
|
|
7902
|
-
[parsed.name]
|
|
7903
|
-
);
|
|
7904
|
-
|
|
7905
|
-
if (entityResult.rows.length === 0) {
|
|
7906
|
-
if (parsed.json) {
|
|
7907
|
-
console.log(JSON.stringify({ success: false, error: "Entity not found" }));
|
|
7908
|
-
} else {
|
|
7909
|
-
p.intro("swarm memory taxonomy");
|
|
7910
|
-
p.log.error(`Entity "${parsed.name}" not found`);
|
|
7911
|
-
p.outro("Aborted");
|
|
7912
|
-
}
|
|
7913
|
-
client.close();
|
|
7914
|
-
process.exit(1);
|
|
7915
|
-
}
|
|
7916
|
-
|
|
7917
|
-
const entity = entityResult.rows[0];
|
|
7918
|
-
const entityId = entity.id as string;
|
|
7919
|
-
|
|
7920
|
-
let relatedIds: string[];
|
|
7921
|
-
|
|
7922
|
-
if (parsed.direction === "broader" || parsed.direction === "narrower" || parsed.direction === "related") {
|
|
7923
|
-
// Filter by relationship type
|
|
7924
|
-
relatedIds = await findByTaxonomy(entityId, parsed.direction, client);
|
|
7925
|
-
} else {
|
|
7926
|
-
// Get all relationships
|
|
7927
|
-
const taxonomy = await getTaxonomyForEntity(entityId, client);
|
|
7928
|
-
relatedIds = taxonomy
|
|
7929
|
-
.filter((rel) => rel.entityId === entityId)
|
|
7930
|
-
.map((rel) => rel.relatedEntityId);
|
|
7931
|
-
}
|
|
7932
|
-
|
|
7933
|
-
// Fetch related entity details
|
|
7934
|
-
const relatedEntities: Array<{ id: string; name: string; relationshipType?: string }> = [];
|
|
7935
|
-
for (const id of relatedIds) {
|
|
7936
|
-
const res = await client.execute("SELECT name FROM entities WHERE id = ? LIMIT 1", [id]);
|
|
7937
|
-
if (res.rows.length > 0) {
|
|
7938
|
-
relatedEntities.push({
|
|
7939
|
-
id,
|
|
7940
|
-
name: res.rows[0].name as string,
|
|
7941
|
-
});
|
|
7942
|
-
}
|
|
7943
|
-
}
|
|
7944
|
-
|
|
7945
|
-
if (parsed.json) {
|
|
7946
|
-
console.log(JSON.stringify({
|
|
7947
|
-
success: true,
|
|
7948
|
-
entity: {
|
|
7949
|
-
id: entity.id,
|
|
7950
|
-
name: entity.name,
|
|
7951
|
-
},
|
|
7952
|
-
direction: parsed.direction || "all",
|
|
7953
|
-
related: relatedEntities,
|
|
7954
|
-
}));
|
|
7955
|
-
} else {
|
|
7956
|
-
const directionLabel = parsed.direction
|
|
7957
|
-
? `${parsed.direction} relationships`
|
|
7958
|
-
: "all relationships";
|
|
7959
|
-
p.intro(`swarm memory taxonomy: ${entity.name} (${directionLabel})`);
|
|
7960
|
-
|
|
7961
|
-
if (relatedEntities.length === 0) {
|
|
7962
|
-
p.log.warn("No related entities found");
|
|
7963
|
-
} else {
|
|
7964
|
-
console.log();
|
|
7965
|
-
for (const rel of relatedEntities) {
|
|
7966
|
-
console.log(` ${cyan(rel.name)} ${dim(`[${rel.id}]`)}`);
|
|
7967
|
-
}
|
|
7968
|
-
}
|
|
7969
|
-
|
|
7970
|
-
p.outro(`Found ${relatedEntities.length} related entit${relatedEntities.length === 1 ? "y" : "ies"}`);
|
|
7971
|
-
}
|
|
7972
|
-
|
|
7973
|
-
client.close();
|
|
7974
|
-
break;
|
|
7975
|
-
}
|
|
7976
|
-
|
|
7977
5977
|
case "sync": {
|
|
7978
5978
|
// Sync is a stub - actual sync happens via .hive/memories.jsonl
|
|
7979
5979
|
// which is handled by hivemind_sync tool
|
|
@@ -7994,20 +5994,17 @@ async function memory() {
|
|
|
7994
5994
|
console.error("Usage: swarm memory <subcommand> [options]");
|
|
7995
5995
|
console.error("");
|
|
7996
5996
|
console.error("Subcommands:");
|
|
7997
|
-
console.error(" store <info> [--tags <tags>]
|
|
7998
|
-
console.error(" find <query> [--limit <n>]
|
|
7999
|
-
console.error(" get <id>
|
|
8000
|
-
console.error(" remove <id>
|
|
8001
|
-
console.error(" validate <id>
|
|
8002
|
-
console.error(" stats
|
|
8003
|
-
console.error(" index
|
|
8004
|
-
console.error(" sync
|
|
8005
|
-
console.error(" entities [--type <type>] List all entities (optionally filtered by type)");
|
|
8006
|
-
console.error(" entity <name> Show entity details with taxonomy relationships");
|
|
8007
|
-
console.error(" taxonomy <entity> [--direction <dir>] Show taxonomy tree (direction: broader|narrower|related)");
|
|
5997
|
+
console.error(" store <info> [--tags <tags>] Store a memory");
|
|
5998
|
+
console.error(" find <query> [--limit <n>] Search memories");
|
|
5999
|
+
console.error(" get <id> Get memory by ID");
|
|
6000
|
+
console.error(" remove <id> Delete memory");
|
|
6001
|
+
console.error(" validate <id> Reset decay timer");
|
|
6002
|
+
console.error(" stats Show database stats");
|
|
6003
|
+
console.error(" index Index sessions (use hivemind_index)");
|
|
6004
|
+
console.error(" sync Sync to git (use hivemind_sync)");
|
|
8008
6005
|
console.error("");
|
|
8009
6006
|
console.error("Global options:");
|
|
8010
|
-
console.error(" --json
|
|
6007
|
+
console.error(" --json Output JSON");
|
|
8011
6008
|
process.exit(1);
|
|
8012
6009
|
}
|
|
8013
6010
|
}
|
|
@@ -8041,14 +6038,8 @@ switch (command) {
|
|
|
8041
6038
|
break;
|
|
8042
6039
|
}
|
|
8043
6040
|
case "doctor": {
|
|
8044
|
-
const
|
|
8045
|
-
|
|
8046
|
-
const { doctorDeep } = await import("./commands/doctor.js");
|
|
8047
|
-
await doctorDeep(process.argv.slice(3));
|
|
8048
|
-
} else {
|
|
8049
|
-
const debugFlag = process.argv.includes("--debug") || process.argv.includes("-d");
|
|
8050
|
-
await doctor(debugFlag);
|
|
8051
|
-
}
|
|
6041
|
+
const debugFlag = process.argv.includes("--debug") || process.argv.includes("-d");
|
|
6042
|
+
await doctor(debugFlag);
|
|
8052
6043
|
break;
|
|
8053
6044
|
}
|
|
8054
6045
|
case "init":
|
|
@@ -8057,12 +6048,6 @@ switch (command) {
|
|
|
8057
6048
|
case "config":
|
|
8058
6049
|
config();
|
|
8059
6050
|
break;
|
|
8060
|
-
case "claude":
|
|
8061
|
-
await claudeCommand();
|
|
8062
|
-
break;
|
|
8063
|
-
case "mcp-serve":
|
|
8064
|
-
await mcpServe();
|
|
8065
|
-
break;
|
|
8066
6051
|
case "serve":
|
|
8067
6052
|
await serve();
|
|
8068
6053
|
break;
|
|
@@ -8075,8 +6060,7 @@ switch (command) {
|
|
|
8075
6060
|
case "tool": {
|
|
8076
6061
|
const toolName = process.argv[3];
|
|
8077
6062
|
if (!toolName || toolName === "--list" || toolName === "-l") {
|
|
8078
|
-
|
|
8079
|
-
await listTools(jsonOutput);
|
|
6063
|
+
await listTools();
|
|
8080
6064
|
} else {
|
|
8081
6065
|
// Look for --json flag
|
|
8082
6066
|
const jsonFlagIndex = process.argv.indexOf("--json");
|
|
@@ -8094,11 +6078,6 @@ switch (command) {
|
|
|
8094
6078
|
case "migrate":
|
|
8095
6079
|
await migrate();
|
|
8096
6080
|
break;
|
|
8097
|
-
case "backup": {
|
|
8098
|
-
const backupAction = process.argv[3] || "create";
|
|
8099
|
-
await backup(backupAction);
|
|
8100
|
-
break;
|
|
8101
|
-
}
|
|
8102
6081
|
case "db":
|
|
8103
6082
|
await db();
|
|
8104
6083
|
break;
|
|
@@ -8107,7 +6086,7 @@ switch (command) {
|
|
|
8107
6086
|
break;
|
|
8108
6087
|
case "log":
|
|
8109
6088
|
case "logs":
|
|
8110
|
-
await
|
|
6089
|
+
await logs();
|
|
8111
6090
|
break;
|
|
8112
6091
|
case "stats":
|
|
8113
6092
|
await stats();
|
|
@@ -8133,27 +6112,12 @@ switch (command) {
|
|
|
8133
6112
|
case "dashboard":
|
|
8134
6113
|
await dashboard();
|
|
8135
6114
|
break;
|
|
8136
|
-
case "compliance":
|
|
8137
|
-
await showWorkerCompliance();
|
|
8138
|
-
break;
|
|
8139
6115
|
case "replay":
|
|
8140
6116
|
await replay();
|
|
8141
6117
|
break;
|
|
8142
6118
|
case "export":
|
|
8143
6119
|
await exportEvents();
|
|
8144
6120
|
break;
|
|
8145
|
-
case "tree":
|
|
8146
|
-
await treeCommand();
|
|
8147
|
-
break;
|
|
8148
|
-
case "session":
|
|
8149
|
-
await session();
|
|
8150
|
-
break;
|
|
8151
|
-
case "queue":
|
|
8152
|
-
await queue();
|
|
8153
|
-
break;
|
|
8154
|
-
case "status":
|
|
8155
|
-
await status(process.argv.slice(3));
|
|
8156
|
-
break;
|
|
8157
6121
|
case "version":
|
|
8158
6122
|
case "--version":
|
|
8159
6123
|
case "-v":
|
|
@@ -8165,7 +6129,7 @@ switch (command) {
|
|
|
8165
6129
|
await help();
|
|
8166
6130
|
break;
|
|
8167
6131
|
case undefined:
|
|
8168
|
-
await
|
|
6132
|
+
await setup();
|
|
8169
6133
|
break;
|
|
8170
6134
|
default:
|
|
8171
6135
|
console.error("Unknown command: " + command);
|