opencode-swarm-plugin 0.55.0 → 0.56.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/README.md +23 -0
- package/bin/swarm.ts +558 -1
- package/claude-plugin/.claude-plugin/plugin.json +14 -0
- package/claude-plugin/.lsp.json +1 -0
- package/claude-plugin/.mcp.json +10 -0
- package/claude-plugin/agents/background-worker.md +36 -0
- package/claude-plugin/agents/coordinator.md +39 -0
- package/claude-plugin/agents/worker.md +42 -0
- package/claude-plugin/bin/swarm-mcp-server.ts +103 -0
- package/claude-plugin/commands/handoff.md +17 -0
- package/claude-plugin/commands/hive.md +19 -0
- package/claude-plugin/commands/inbox.md +15 -0
- package/claude-plugin/commands/status.md +15 -0
- package/claude-plugin/commands/swarm.md +19 -0
- package/claude-plugin/hooks/hooks.json +48 -0
- package/claude-plugin/skills/swarm-coordination/SKILL.md +76 -0
- package/dist/bin/swarm.js +418 -10
- package/dist/index.d.ts +18 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/plugin.js +1 -0
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -25,6 +25,29 @@ npm install -g opencode-swarm-plugin@latest
|
|
|
25
25
|
swarm setup
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
### Claude Code Plugin (Marketplace)
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
/plugin
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Choose **Marketplace → opencode-swarm-plugin → Install**.
|
|
35
|
+
|
|
36
|
+
**Global install (npm):**
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# After `npm install -g opencode-swarm-plugin`
|
|
40
|
+
swarm claude install
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Project-local config (standalone):**
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
swarm claude init
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**MCP auto-launch:** Claude Code starts MCP servers declared in the plugin `mcpServers` config automatically. You only need `swarm mcp-serve` when debugging outside Claude Code.
|
|
50
|
+
|
|
28
51
|
### 2. Initialize in Your Project
|
|
29
52
|
|
|
30
53
|
```bash
|
package/bin/swarm.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
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)
|
|
11
13
|
* swarm version - Show version info
|
|
12
14
|
* swarm - Interactive mode (same as setup)
|
|
13
15
|
*/
|
|
@@ -17,17 +19,20 @@ import {
|
|
|
17
19
|
chmodSync,
|
|
18
20
|
copyFileSync,
|
|
19
21
|
existsSync,
|
|
22
|
+
lstatSync,
|
|
20
23
|
mkdirSync,
|
|
21
24
|
readFileSync,
|
|
25
|
+
readlinkSync,
|
|
22
26
|
readdirSync,
|
|
23
27
|
renameSync,
|
|
24
28
|
rmdirSync,
|
|
25
29
|
rmSync,
|
|
26
30
|
statSync,
|
|
31
|
+
symlinkSync,
|
|
27
32
|
writeFileSync,
|
|
28
33
|
} from "fs";
|
|
29
34
|
import { homedir } from "os";
|
|
30
|
-
import { basename, dirname, join } from "path";
|
|
35
|
+
import { basename, dirname, join, resolve } from "path";
|
|
31
36
|
import { fileURLToPath } from "url";
|
|
32
37
|
import {
|
|
33
38
|
checkBeadsMigrationNeeded,
|
|
@@ -117,6 +122,8 @@ const pkgPath = existsSync(join(__dirname, "..", "package.json"))
|
|
|
117
122
|
: join(__dirname, "..", "..", "package.json");
|
|
118
123
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
119
124
|
const VERSION: string = pkg.version;
|
|
125
|
+
const PACKAGE_ROOT = dirname(pkgPath);
|
|
126
|
+
const CLAUDE_PLUGIN_NAME = "swarm";
|
|
120
127
|
|
|
121
128
|
// ============================================================================
|
|
122
129
|
// ASCII Art & Branding
|
|
@@ -493,6 +500,15 @@ const DEPENDENCIES: Dependency[] = [
|
|
|
493
500
|
installType: "brew",
|
|
494
501
|
description: "AI coding assistant (plugin host)",
|
|
495
502
|
},
|
|
503
|
+
{
|
|
504
|
+
name: "Claude Code",
|
|
505
|
+
command: "claude",
|
|
506
|
+
checkArgs: ["--version"],
|
|
507
|
+
required: false,
|
|
508
|
+
install: "https://docs.anthropic.com/claude-code",
|
|
509
|
+
installType: "manual",
|
|
510
|
+
description: "Claude Code CLI (optional host)",
|
|
511
|
+
},
|
|
496
512
|
// Note: Beads CLI (bd) is NO LONGER required - we use HiveAdapter from swarm-mail
|
|
497
513
|
// which provides the same functionality programmatically without external dependencies
|
|
498
514
|
{
|
|
@@ -584,6 +600,134 @@ async function checkAllDependencies(): Promise<CheckResult[]> {
|
|
|
584
600
|
return results;
|
|
585
601
|
}
|
|
586
602
|
|
|
603
|
+
// ============================================================================
|
|
604
|
+
// Claude Code Helpers
|
|
605
|
+
// ============================================================================
|
|
606
|
+
|
|
607
|
+
interface ClaudeHookInput {
|
|
608
|
+
project?: { path?: string };
|
|
609
|
+
cwd?: string;
|
|
610
|
+
session?: { id?: string };
|
|
611
|
+
metadata?: { cwd?: string };
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Resolve the Claude Code plugin root bundled with the package.
|
|
616
|
+
*/
|
|
617
|
+
function getClaudePluginRoot(): string {
|
|
618
|
+
return join(PACKAGE_ROOT, "claude-plugin");
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Resolve the Claude Code config directory.
|
|
623
|
+
*/
|
|
624
|
+
function getClaudeConfigDir(): string {
|
|
625
|
+
return join(homedir(), ".claude");
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Read JSON input from stdin for Claude Code hooks.
|
|
630
|
+
*/
|
|
631
|
+
async function readHookInput<T>(): Promise<T | null> {
|
|
632
|
+
if (process.stdin.isTTY) return null;
|
|
633
|
+
const chunks: string[] = [];
|
|
634
|
+
for await (const chunk of process.stdin) {
|
|
635
|
+
chunks.push(chunk.toString());
|
|
636
|
+
}
|
|
637
|
+
const raw = chunks.join("").trim();
|
|
638
|
+
if (!raw) return null;
|
|
639
|
+
try {
|
|
640
|
+
return JSON.parse(raw) as T;
|
|
641
|
+
} catch {
|
|
642
|
+
return null;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Resolve the project path for Claude Code hook executions.
|
|
648
|
+
*/
|
|
649
|
+
function resolveClaudeProjectPath(input: ClaudeHookInput | null): string {
|
|
650
|
+
return (
|
|
651
|
+
input?.project?.path ||
|
|
652
|
+
input?.cwd ||
|
|
653
|
+
input?.metadata?.cwd ||
|
|
654
|
+
process.env.CLAUDE_PROJECT_DIR ||
|
|
655
|
+
process.env.PWD ||
|
|
656
|
+
process.cwd()
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Format hook output for Claude Code context injection.
|
|
662
|
+
*/
|
|
663
|
+
function writeClaudeHookContext(additionalContext: string): void {
|
|
664
|
+
if (!additionalContext.trim()) return;
|
|
665
|
+
process.stdout.write(
|
|
666
|
+
`${JSON.stringify({
|
|
667
|
+
additionalContext,
|
|
668
|
+
})}\n`,
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
interface ClaudeInstallStatus {
|
|
673
|
+
pluginRoot: string;
|
|
674
|
+
globalPluginPath: string;
|
|
675
|
+
globalPluginTarget?: string;
|
|
676
|
+
globalPluginExists: boolean;
|
|
677
|
+
globalPluginLinked: boolean;
|
|
678
|
+
projectClaudeDir: string;
|
|
679
|
+
projectConfigExists: boolean;
|
|
680
|
+
projectConfigPaths: string[];
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Inspect Claude Code install state for global and project scopes.
|
|
685
|
+
*/
|
|
686
|
+
function getClaudeInstallStatus(projectPath: string): ClaudeInstallStatus {
|
|
687
|
+
const pluginRoot = getClaudePluginRoot();
|
|
688
|
+
const claudeConfigDir = getClaudeConfigDir();
|
|
689
|
+
const globalPluginPath = join(claudeConfigDir, "plugins", CLAUDE_PLUGIN_NAME);
|
|
690
|
+
|
|
691
|
+
let globalPluginExists = false;
|
|
692
|
+
let globalPluginLinked = false;
|
|
693
|
+
let globalPluginTarget: string | undefined;
|
|
694
|
+
|
|
695
|
+
if (existsSync(globalPluginPath)) {
|
|
696
|
+
globalPluginExists = true;
|
|
697
|
+
try {
|
|
698
|
+
const stat = lstatSync(globalPluginPath);
|
|
699
|
+
if (stat.isSymbolicLink()) {
|
|
700
|
+
globalPluginLinked = true;
|
|
701
|
+
const target = readlinkSync(globalPluginPath);
|
|
702
|
+
globalPluginTarget = resolve(dirname(globalPluginPath), target);
|
|
703
|
+
}
|
|
704
|
+
} catch {
|
|
705
|
+
// Ignore errors
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const projectClaudeDir = join(projectPath, ".claude");
|
|
710
|
+
const projectConfigPaths = [
|
|
711
|
+
join(projectClaudeDir, "commands"),
|
|
712
|
+
join(projectClaudeDir, "agents"),
|
|
713
|
+
join(projectClaudeDir, "skills"),
|
|
714
|
+
join(projectClaudeDir, "hooks"),
|
|
715
|
+
join(projectClaudeDir, ".mcp.json"),
|
|
716
|
+
];
|
|
717
|
+
const projectConfigExists = projectConfigPaths.some((path) => existsSync(path));
|
|
718
|
+
|
|
719
|
+
return {
|
|
720
|
+
pluginRoot,
|
|
721
|
+
globalPluginPath,
|
|
722
|
+
globalPluginTarget,
|
|
723
|
+
globalPluginExists,
|
|
724
|
+
globalPluginLinked,
|
|
725
|
+
projectClaudeDir,
|
|
726
|
+
projectConfigExists,
|
|
727
|
+
projectConfigPaths,
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
|
|
587
731
|
// ============================================================================
|
|
588
732
|
// Skills Sync Utilities
|
|
589
733
|
// ============================================================================
|
|
@@ -1594,6 +1738,8 @@ function getFixCommand(dep: Dependency): string | null {
|
|
|
1594
1738
|
switch (dep.name) {
|
|
1595
1739
|
case "OpenCode":
|
|
1596
1740
|
return "brew install sst/tap/opencode";
|
|
1741
|
+
case "Claude Code":
|
|
1742
|
+
return "See: https://docs.anthropic.com/claude-code";
|
|
1597
1743
|
case "Ollama":
|
|
1598
1744
|
return "brew install ollama && ollama pull mxbai-embed-large";
|
|
1599
1745
|
case "Redis":
|
|
@@ -1729,6 +1875,42 @@ async function doctor(debug = false) {
|
|
|
1729
1875
|
}
|
|
1730
1876
|
}
|
|
1731
1877
|
|
|
1878
|
+
// Claude Code checks
|
|
1879
|
+
p.log.step("Claude Code:");
|
|
1880
|
+
const claudeResult = results.find((result) => result.dep.name === "Claude Code");
|
|
1881
|
+
const claudeStatus = getClaudeInstallStatus(process.cwd());
|
|
1882
|
+
|
|
1883
|
+
if (!claudeResult?.available) {
|
|
1884
|
+
p.log.warn("Claude Code CLI not detected (optional)");
|
|
1885
|
+
p.log.message(dim(" Install: https://docs.anthropic.com/claude-code"));
|
|
1886
|
+
} else {
|
|
1887
|
+
p.log.success(`Claude Code CLI detected${claudeResult.version ? ` v${claudeResult.version}` : ""}`);
|
|
1888
|
+
|
|
1889
|
+
if (existsSync(claudeStatus.pluginRoot)) {
|
|
1890
|
+
p.log.message(dim(` Plugin bundle: ${claudeStatus.pluginRoot}`));
|
|
1891
|
+
} else {
|
|
1892
|
+
p.log.warn(`Claude plugin bundle missing: ${claudeStatus.pluginRoot}`);
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
if (claudeStatus.globalPluginExists) {
|
|
1896
|
+
if (claudeStatus.globalPluginLinked) {
|
|
1897
|
+
p.log.success(`Global plugin symlink: ${claudeStatus.globalPluginPath}`);
|
|
1898
|
+
} else {
|
|
1899
|
+
p.log.warn(`Global plugin exists but is not a symlink: ${claudeStatus.globalPluginPath}`);
|
|
1900
|
+
}
|
|
1901
|
+
} else {
|
|
1902
|
+
p.log.warn("Global Claude plugin not installed");
|
|
1903
|
+
p.log.message(dim(" Run: swarm claude install"));
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
if (claudeStatus.projectConfigExists) {
|
|
1907
|
+
p.log.success(`Project Claude config: ${claudeStatus.projectClaudeDir}`);
|
|
1908
|
+
} else {
|
|
1909
|
+
p.log.warn("Project Claude config not found");
|
|
1910
|
+
p.log.message(dim(" Run: swarm claude init"));
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1732
1914
|
if (requiredMissing.length > 0) {
|
|
1733
1915
|
p.outro(
|
|
1734
1916
|
"Missing " +
|
|
@@ -2545,6 +2727,36 @@ async function setup(forceReinstall = false, nonInteractive = false) {
|
|
|
2545
2727
|
}
|
|
2546
2728
|
}
|
|
2547
2729
|
|
|
2730
|
+
// Claude Code checks
|
|
2731
|
+
p.log.step("Claude Code integration (optional)...");
|
|
2732
|
+
const claudeResult = results.find((result) => result.dep.name === "Claude Code");
|
|
2733
|
+
const claudeStatus = getClaudeInstallStatus(cwd);
|
|
2734
|
+
|
|
2735
|
+
if (!claudeResult?.available) {
|
|
2736
|
+
p.log.warn("Claude Code not detected (optional)");
|
|
2737
|
+
p.log.message(dim(" Install: https://docs.anthropic.com/claude-code"));
|
|
2738
|
+
} else {
|
|
2739
|
+
const versionInfo = claudeResult.version ? ` v${claudeResult.version}` : "";
|
|
2740
|
+
p.log.success(`Claude Code detected${versionInfo}`);
|
|
2741
|
+
p.log.message(dim(` Plugin bundle: ${claudeStatus.pluginRoot}`));
|
|
2742
|
+
|
|
2743
|
+
if (claudeStatus.globalPluginExists) {
|
|
2744
|
+
if (claudeStatus.globalPluginLinked) {
|
|
2745
|
+
p.log.success(`Claude plugin linked: ${claudeStatus.globalPluginPath}`);
|
|
2746
|
+
} else {
|
|
2747
|
+
p.log.warn(`Claude plugin exists but is not a symlink: ${claudeStatus.globalPluginPath}`);
|
|
2748
|
+
}
|
|
2749
|
+
} else {
|
|
2750
|
+
p.log.message(dim(" Run 'swarm claude install' for a dev symlink"));
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
if (claudeStatus.projectConfigExists) {
|
|
2754
|
+
p.log.success(`Project Claude config: ${claudeStatus.projectClaudeDir}`);
|
|
2755
|
+
} else {
|
|
2756
|
+
p.log.message(dim(" Run 'swarm claude init' to create .claude/ config"));
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2548
2760
|
// Show setup summary
|
|
2549
2761
|
const totalFiles = stats.created + stats.updated + stats.unchanged;
|
|
2550
2762
|
const summaryParts: string[] = [];
|
|
@@ -2563,6 +2775,299 @@ async function setup(forceReinstall = false, nonInteractive = false) {
|
|
|
2563
2775
|
p.outro("Run 'swarm doctor' to verify installation.");
|
|
2564
2776
|
}
|
|
2565
2777
|
|
|
2778
|
+
// ============================================================================
|
|
2779
|
+
// Claude Code Commands
|
|
2780
|
+
// ============================================================================
|
|
2781
|
+
|
|
2782
|
+
/**
|
|
2783
|
+
* Handle Claude Code subcommands.
|
|
2784
|
+
*/
|
|
2785
|
+
async function claudeCommand() {
|
|
2786
|
+
const args = process.argv.slice(3);
|
|
2787
|
+
const subcommand = args[0];
|
|
2788
|
+
|
|
2789
|
+
if (!subcommand || ["help", "--help", "-h"].includes(subcommand)) {
|
|
2790
|
+
showClaudeHelp();
|
|
2791
|
+
return;
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
switch (subcommand) {
|
|
2795
|
+
case "path":
|
|
2796
|
+
claudePath();
|
|
2797
|
+
break;
|
|
2798
|
+
case "install":
|
|
2799
|
+
await claudeInstall();
|
|
2800
|
+
break;
|
|
2801
|
+
case "uninstall":
|
|
2802
|
+
await claudeUninstall();
|
|
2803
|
+
break;
|
|
2804
|
+
case "init":
|
|
2805
|
+
await claudeInit();
|
|
2806
|
+
break;
|
|
2807
|
+
case "session-start":
|
|
2808
|
+
await claudeSessionStart();
|
|
2809
|
+
break;
|
|
2810
|
+
case "user-prompt":
|
|
2811
|
+
await claudeUserPrompt();
|
|
2812
|
+
break;
|
|
2813
|
+
case "pre-compact":
|
|
2814
|
+
await claudePreCompact();
|
|
2815
|
+
break;
|
|
2816
|
+
case "session-end":
|
|
2817
|
+
await claudeSessionEnd();
|
|
2818
|
+
break;
|
|
2819
|
+
default:
|
|
2820
|
+
console.error(`Unknown subcommand: ${subcommand}`);
|
|
2821
|
+
showClaudeHelp();
|
|
2822
|
+
process.exit(1);
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
function showClaudeHelp() {
|
|
2827
|
+
console.log(`
|
|
2828
|
+
Usage: swarm claude <command>
|
|
2829
|
+
|
|
2830
|
+
Commands:
|
|
2831
|
+
path Print Claude plugin path (for --plugin-dir)
|
|
2832
|
+
install Symlink plugin into ~/.claude/plugins/${CLAUDE_PLUGIN_NAME}
|
|
2833
|
+
uninstall Remove Claude plugin symlink
|
|
2834
|
+
init Create project-local .claude/ config
|
|
2835
|
+
session-start Hook: session start context (JSON output)
|
|
2836
|
+
user-prompt Hook: prompt submit context (JSON output)
|
|
2837
|
+
pre-compact Hook: pre-compaction handler
|
|
2838
|
+
session-end Hook: session cleanup
|
|
2839
|
+
`);
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
/**
|
|
2843
|
+
* Print the bundled Claude plugin path.
|
|
2844
|
+
*/
|
|
2845
|
+
function claudePath() {
|
|
2846
|
+
const pluginRoot = getClaudePluginRoot();
|
|
2847
|
+
if (!existsSync(pluginRoot)) {
|
|
2848
|
+
console.error(`Claude plugin not found at ${pluginRoot}`);
|
|
2849
|
+
process.exit(1);
|
|
2850
|
+
}
|
|
2851
|
+
console.log(pluginRoot);
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2854
|
+
/**
|
|
2855
|
+
* Install the Claude plugin symlink for local development.
|
|
2856
|
+
*/
|
|
2857
|
+
async function claudeInstall() {
|
|
2858
|
+
p.intro("swarm claude install");
|
|
2859
|
+
const pluginRoot = getClaudePluginRoot();
|
|
2860
|
+
if (!existsSync(pluginRoot)) {
|
|
2861
|
+
p.log.error(`Claude plugin not found at ${pluginRoot}`);
|
|
2862
|
+
p.outro("Aborted");
|
|
2863
|
+
process.exit(1);
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
const claudeConfigDir = getClaudeConfigDir();
|
|
2867
|
+
const pluginsDir = join(claudeConfigDir, "plugins");
|
|
2868
|
+
const pluginPath = join(pluginsDir, CLAUDE_PLUGIN_NAME);
|
|
2869
|
+
|
|
2870
|
+
mkdirWithStatus(pluginsDir);
|
|
2871
|
+
|
|
2872
|
+
if (existsSync(pluginPath)) {
|
|
2873
|
+
const stat = lstatSync(pluginPath);
|
|
2874
|
+
if (!stat.isSymbolicLink()) {
|
|
2875
|
+
p.log.error(`Existing path is not a symlink: ${pluginPath}`);
|
|
2876
|
+
p.outro("Aborted");
|
|
2877
|
+
process.exit(1);
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
const target = readlinkSync(pluginPath);
|
|
2881
|
+
const resolved = resolve(dirname(pluginPath), target);
|
|
2882
|
+
if (resolved === pluginRoot) {
|
|
2883
|
+
p.log.success("Claude plugin already linked");
|
|
2884
|
+
p.outro("Done");
|
|
2885
|
+
return;
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
rmSync(pluginPath, { force: true });
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2891
|
+
symlinkSync(pluginRoot, pluginPath);
|
|
2892
|
+
p.log.success(`Linked ${CLAUDE_PLUGIN_NAME} → ${pluginRoot}`);
|
|
2893
|
+
p.log.message(dim(" Claude Code will auto-launch MCP from .mcp.json"));
|
|
2894
|
+
p.outro("Claude plugin installed");
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
/**
|
|
2898
|
+
* Remove the Claude plugin symlink.
|
|
2899
|
+
*/
|
|
2900
|
+
async function claudeUninstall() {
|
|
2901
|
+
p.intro("swarm claude uninstall");
|
|
2902
|
+
const pluginPath = join(getClaudeConfigDir(), "plugins", CLAUDE_PLUGIN_NAME);
|
|
2903
|
+
|
|
2904
|
+
if (!existsSync(pluginPath)) {
|
|
2905
|
+
p.log.warn("Claude plugin symlink not found");
|
|
2906
|
+
p.outro("Nothing to remove");
|
|
2907
|
+
return;
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
rmSync(pluginPath, { recursive: true, force: true });
|
|
2911
|
+
p.log.success(`Removed ${pluginPath}`);
|
|
2912
|
+
p.outro("Claude plugin uninstalled");
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
/**
|
|
2916
|
+
* Create project-local Claude Code config from bundled plugin assets.
|
|
2917
|
+
*/
|
|
2918
|
+
async function claudeInit() {
|
|
2919
|
+
p.intro("swarm claude init");
|
|
2920
|
+
const projectPath = process.cwd();
|
|
2921
|
+
const pluginRoot = getClaudePluginRoot();
|
|
2922
|
+
|
|
2923
|
+
if (!existsSync(pluginRoot)) {
|
|
2924
|
+
p.log.error(`Claude plugin not found at ${pluginRoot}`);
|
|
2925
|
+
p.outro("Aborted");
|
|
2926
|
+
process.exit(1);
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
const projectClaudeDir = join(projectPath, ".claude");
|
|
2930
|
+
const commandDir = join(projectClaudeDir, "commands");
|
|
2931
|
+
const agentDir = join(projectClaudeDir, "agents");
|
|
2932
|
+
const skillsDir = join(projectClaudeDir, "skills");
|
|
2933
|
+
const hooksDir = join(projectClaudeDir, "hooks");
|
|
2934
|
+
|
|
2935
|
+
for (const dir of [projectClaudeDir, commandDir, agentDir, skillsDir, hooksDir]) {
|
|
2936
|
+
mkdirWithStatus(dir);
|
|
2937
|
+
}
|
|
2938
|
+
|
|
2939
|
+
const copyMap = [
|
|
2940
|
+
{ src: join(pluginRoot, "commands"), dest: commandDir, label: "Commands" },
|
|
2941
|
+
{ src: join(pluginRoot, "agents"), dest: agentDir, label: "Agents" },
|
|
2942
|
+
{ src: join(pluginRoot, "skills"), dest: skillsDir, label: "Skills" },
|
|
2943
|
+
{ src: join(pluginRoot, "hooks"), dest: hooksDir, label: "Hooks" },
|
|
2944
|
+
];
|
|
2945
|
+
|
|
2946
|
+
for (const { src, dest, label } of copyMap) {
|
|
2947
|
+
if (existsSync(src)) {
|
|
2948
|
+
copyDirRecursiveSync(src, dest);
|
|
2949
|
+
p.log.success(`${label}: ${dest}`);
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2953
|
+
const mcpSourcePath = join(pluginRoot, ".mcp.json");
|
|
2954
|
+
if (existsSync(mcpSourcePath)) {
|
|
2955
|
+
const mcpDestPath = join(projectClaudeDir, ".mcp.json");
|
|
2956
|
+
const content = readFileSync(mcpSourcePath, "utf-8");
|
|
2957
|
+
writeFileWithStatus(mcpDestPath, content, "MCP config");
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
const lspSourcePath = join(pluginRoot, ".lsp.json");
|
|
2961
|
+
if (existsSync(lspSourcePath)) {
|
|
2962
|
+
const lspDestPath = join(projectClaudeDir, ".lsp.json");
|
|
2963
|
+
const content = readFileSync(lspSourcePath, "utf-8");
|
|
2964
|
+
writeFileWithStatus(lspDestPath, content, "LSP config");
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2967
|
+
p.log.message(dim(" Uses ${CLAUDE_PLUGIN_ROOT} in MCP config"));
|
|
2968
|
+
p.outro("Claude project config ready");
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2971
|
+
/**
|
|
2972
|
+
* Claude hook: start a session and emit context for Claude Code.
|
|
2973
|
+
*/
|
|
2974
|
+
async function claudeSessionStart() {
|
|
2975
|
+
try {
|
|
2976
|
+
const input = await readHookInput<ClaudeHookInput>();
|
|
2977
|
+
const projectPath = resolveClaudeProjectPath(input);
|
|
2978
|
+
const swarmMail = await getSwarmMailLibSQL(projectPath);
|
|
2979
|
+
const db = await swarmMail.getDatabase(projectPath);
|
|
2980
|
+
const adapter = createHiveAdapter(db, projectPath);
|
|
2981
|
+
|
|
2982
|
+
await adapter.runMigrations();
|
|
2983
|
+
|
|
2984
|
+
const session = await adapter.startSession(projectPath, {});
|
|
2985
|
+
const contextLines = [
|
|
2986
|
+
`Swarm session started: ${session.id}`,
|
|
2987
|
+
];
|
|
2988
|
+
|
|
2989
|
+
if (session.active_cell_id) {
|
|
2990
|
+
contextLines.push(`Active cell: ${session.active_cell_id}`);
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
if (session.previous_handoff_notes) {
|
|
2994
|
+
contextLines.push("Previous handoff notes:");
|
|
2995
|
+
contextLines.push(session.previous_handoff_notes);
|
|
2996
|
+
}
|
|
2997
|
+
|
|
2998
|
+
writeClaudeHookContext(contextLines.join("\n"));
|
|
2999
|
+
} catch (error) {
|
|
3000
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
|
|
3004
|
+
/**
|
|
3005
|
+
* Claude hook: provide active session context on prompt submission.
|
|
3006
|
+
*/
|
|
3007
|
+
async function claudeUserPrompt() {
|
|
3008
|
+
try {
|
|
3009
|
+
const input = await readHookInput<ClaudeHookInput>();
|
|
3010
|
+
const projectPath = resolveClaudeProjectPath(input);
|
|
3011
|
+
const swarmMail = await getSwarmMailLibSQL(projectPath);
|
|
3012
|
+
const db = await swarmMail.getDatabase(projectPath);
|
|
3013
|
+
const adapter = createHiveAdapter(db, projectPath);
|
|
3014
|
+
|
|
3015
|
+
await adapter.runMigrations();
|
|
3016
|
+
|
|
3017
|
+
const session = await adapter.getCurrentSession(projectPath);
|
|
3018
|
+
if (!session) return;
|
|
3019
|
+
|
|
3020
|
+
const contextLines = [`Active swarm session: ${session.id}`];
|
|
3021
|
+
if (session.active_cell_id) {
|
|
3022
|
+
contextLines.push(`Active cell: ${session.active_cell_id}`);
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
writeClaudeHookContext(contextLines.join("\n"));
|
|
3026
|
+
} catch (error) {
|
|
3027
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
/**
|
|
3032
|
+
* Claude hook: prepare for compaction (best-effort).
|
|
3033
|
+
*/
|
|
3034
|
+
async function claudePreCompact() {
|
|
3035
|
+
try {
|
|
3036
|
+
const input = await readHookInput<ClaudeHookInput>();
|
|
3037
|
+
const projectPath = resolveClaudeProjectPath(input);
|
|
3038
|
+
const swarmMail = await getSwarmMailLibSQL(projectPath);
|
|
3039
|
+
const db = await swarmMail.getDatabase(projectPath);
|
|
3040
|
+
const adapter = createHiveAdapter(db, projectPath);
|
|
3041
|
+
|
|
3042
|
+
await adapter.runMigrations();
|
|
3043
|
+
await adapter.getCurrentSession(projectPath);
|
|
3044
|
+
} catch (error) {
|
|
3045
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
|
|
3049
|
+
/**
|
|
3050
|
+
* Claude hook: end the active session.
|
|
3051
|
+
*/
|
|
3052
|
+
async function claudeSessionEnd() {
|
|
3053
|
+
try {
|
|
3054
|
+
const input = await readHookInput<ClaudeHookInput>();
|
|
3055
|
+
const projectPath = resolveClaudeProjectPath(input);
|
|
3056
|
+
const swarmMail = await getSwarmMailLibSQL(projectPath);
|
|
3057
|
+
const db = await swarmMail.getDatabase(projectPath);
|
|
3058
|
+
const adapter = createHiveAdapter(db, projectPath);
|
|
3059
|
+
|
|
3060
|
+
await adapter.runMigrations();
|
|
3061
|
+
|
|
3062
|
+
const currentSession = await adapter.getCurrentSession(projectPath);
|
|
3063
|
+
if (!currentSession) return;
|
|
3064
|
+
|
|
3065
|
+
await adapter.endSession(projectPath, currentSession.id, {});
|
|
3066
|
+
} catch (error) {
|
|
3067
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
|
|
2566
3071
|
async function init() {
|
|
2567
3072
|
p.intro("swarm init v" + VERSION);
|
|
2568
3073
|
|
|
@@ -3292,10 +3797,12 @@ ${cyan("Commands:")}
|
|
|
3292
3797
|
swarm doctor Health check - shows status of all dependencies
|
|
3293
3798
|
swarm init Initialize beads in current project
|
|
3294
3799
|
swarm config Show paths to generated config files
|
|
3800
|
+
swarm claude Claude Code integration (path/install/uninstall/init/hooks)
|
|
3295
3801
|
swarm agents Update AGENTS.md with skill awareness
|
|
3296
3802
|
swarm migrate Migrate PGlite database to libSQL
|
|
3297
3803
|
swarm serve Start SSE server for real-time event streaming (port 4483 - HIVE)
|
|
3298
3804
|
--port <n> Port to listen on (default: 4483)
|
|
3805
|
+
swarm mcp-serve Debug-only MCP server (Claude auto-launches via .mcp.json)
|
|
3299
3806
|
swarm viz Alias for 'swarm serve' (deprecated, use serve)
|
|
3300
3807
|
--port <n> Port to listen on (default: 4483)
|
|
3301
3808
|
swarm cells List or get cells from database (replaces 'swarm tool hive_query')
|
|
@@ -3410,6 +3917,16 @@ ${cyan("Usage in OpenCode:")}
|
|
|
3410
3917
|
@swarm-worker "Execute this specific subtask"
|
|
3411
3918
|
@swarm-researcher "Research Next.js caching APIs"
|
|
3412
3919
|
|
|
3920
|
+
${cyan("Claude Code:")}
|
|
3921
|
+
swarm claude path Show bundled Claude plugin path
|
|
3922
|
+
swarm claude install Symlink plugin into ~/.claude/plugins
|
|
3923
|
+
swarm claude uninstall Remove Claude plugin symlink
|
|
3924
|
+
swarm claude init Create project-local .claude config
|
|
3925
|
+
swarm claude session-start Hook: session start context
|
|
3926
|
+
swarm claude user-prompt Hook: prompt submit context
|
|
3927
|
+
swarm claude pre-compact Hook: pre-compaction handler
|
|
3928
|
+
swarm claude session-end Hook: session cleanup
|
|
3929
|
+
|
|
3413
3930
|
${cyan("Customization:")}
|
|
3414
3931
|
Edit the generated files to customize behavior:
|
|
3415
3932
|
${dim("~/.config/opencode/command/swarm.md")} - /swarm command prompt
|
|
@@ -5663,6 +6180,40 @@ async function evalRun() {
|
|
|
5663
6180
|
}
|
|
5664
6181
|
}
|
|
5665
6182
|
|
|
6183
|
+
// ============================================================================
|
|
6184
|
+
// MCP Server (Debug Only)
|
|
6185
|
+
// ============================================================================
|
|
6186
|
+
|
|
6187
|
+
/**
|
|
6188
|
+
* Start the MCP server over stdio (debug only).
|
|
6189
|
+
*/
|
|
6190
|
+
async function mcpServe() {
|
|
6191
|
+
p.intro("swarm mcp-serve");
|
|
6192
|
+
|
|
6193
|
+
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || getClaudePluginRoot();
|
|
6194
|
+
const candidates = [
|
|
6195
|
+
join(pluginRoot, "bin", "swarm-mcp-server.ts"),
|
|
6196
|
+
join(PACKAGE_ROOT, "bin", "swarm-mcp-server.ts"),
|
|
6197
|
+
];
|
|
6198
|
+
const serverPath = candidates.find((path) => existsSync(path));
|
|
6199
|
+
|
|
6200
|
+
if (!serverPath) {
|
|
6201
|
+
p.log.error("MCP server entrypoint not found");
|
|
6202
|
+
p.log.message(dim(` Looked for: ${candidates.join(", ")}`));
|
|
6203
|
+
p.log.message(dim(" Claude Code auto-launches MCP via .mcp.json; use for debugging only"));
|
|
6204
|
+
p.outro("Aborted");
|
|
6205
|
+
process.exit(1);
|
|
6206
|
+
}
|
|
6207
|
+
|
|
6208
|
+
p.log.step("Starting MCP server...");
|
|
6209
|
+
p.log.message(dim(` Using: ${serverPath}`));
|
|
6210
|
+
|
|
6211
|
+
const proc = spawn("bun", ["run", serverPath], { stdio: "inherit" });
|
|
6212
|
+
proc.on("close", (exitCode) => {
|
|
6213
|
+
process.exit(exitCode ?? 0);
|
|
6214
|
+
});
|
|
6215
|
+
}
|
|
6216
|
+
|
|
5666
6217
|
// ============================================================================
|
|
5667
6218
|
// Serve Command - Start SSE Server
|
|
5668
6219
|
// ============================================================================
|
|
@@ -6208,6 +6759,12 @@ switch (command) {
|
|
|
6208
6759
|
case "config":
|
|
6209
6760
|
config();
|
|
6210
6761
|
break;
|
|
6762
|
+
case "claude":
|
|
6763
|
+
await claudeCommand();
|
|
6764
|
+
break;
|
|
6765
|
+
case "mcp-serve":
|
|
6766
|
+
await mcpServe();
|
|
6767
|
+
break;
|
|
6211
6768
|
case "serve":
|
|
6212
6769
|
await serve();
|
|
6213
6770
|
break;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "swarm",
|
|
3
|
+
"description": "Multi-agent task decomposition and coordination for Claude Code",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Joel Hooks"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/joelhooks/opencode-swarm-plugin"
|
|
11
|
+
},
|
|
12
|
+
"keywords": ["multi-agent", "coordination", "tasks", "parallel"],
|
|
13
|
+
"license": "MIT"
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|