swarmkit 0.0.6 → 0.0.8

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.
Files changed (40) hide show
  1. package/dist/cli.js +2 -0
  2. package/dist/commands/configure/configure.d.ts +5 -0
  3. package/dist/commands/configure/configure.js +354 -0
  4. package/dist/commands/configure/configure.test.d.ts +1 -0
  5. package/dist/commands/configure/configure.test.js +539 -0
  6. package/dist/commands/configure/read-config.d.ts +12 -0
  7. package/dist/commands/configure/read-config.js +81 -0
  8. package/dist/commands/configure.d.ts +2 -0
  9. package/dist/commands/configure.js +14 -0
  10. package/dist/commands/init/phases/configure.js +0 -21
  11. package/dist/commands/init/phases/global-setup.d.ts +1 -1
  12. package/dist/commands/init/phases/global-setup.js +22 -44
  13. package/dist/commands/init/phases/integrations.d.ts +16 -0
  14. package/dist/commands/init/phases/integrations.js +172 -0
  15. package/dist/commands/init/phases/package-config.d.ts +10 -0
  16. package/dist/commands/init/phases/package-config.js +117 -0
  17. package/dist/commands/init/phases/phases.test.d.ts +1 -0
  18. package/dist/commands/init/phases/phases.test.js +711 -0
  19. package/dist/commands/init/phases/project.js +17 -0
  20. package/dist/commands/init/phases/review.d.ts +8 -0
  21. package/dist/commands/init/phases/review.js +79 -0
  22. package/dist/commands/init/phases/use-case.js +41 -27
  23. package/dist/commands/init/phases/wizard-flow.test.d.ts +1 -0
  24. package/dist/commands/init/phases/wizard-flow.test.js +657 -0
  25. package/dist/commands/init/phases/wizard-modes.test.d.ts +1 -0
  26. package/dist/commands/init/phases/wizard-modes.test.js +270 -0
  27. package/dist/commands/init/state.d.ts +31 -1
  28. package/dist/commands/init/state.js +4 -0
  29. package/dist/commands/init/state.test.js +7 -0
  30. package/dist/commands/init/wizard.d.ts +1 -0
  31. package/dist/commands/init/wizard.js +31 -23
  32. package/dist/commands/init.js +2 -0
  33. package/dist/index.d.ts +3 -2
  34. package/dist/index.js +2 -1
  35. package/dist/packages/registry.d.ts +66 -0
  36. package/dist/packages/registry.js +258 -0
  37. package/dist/packages/setup.d.ts +42 -0
  38. package/dist/packages/setup.js +244 -15
  39. package/dist/packages/setup.test.js +520 -13
  40. package/package.json +1 -1
@@ -1,9 +1,99 @@
1
- import { execFile } from "node:child_process";
1
+ import { execFile, spawn } from "node:child_process";
2
2
  import { promisify } from "node:util";
3
3
  import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
4
4
  import { basename, join } from "node:path";
5
5
  import { homedir } from "node:os";
6
6
  const execFileAsync = promisify(execFile);
7
+ /**
8
+ * Run a package's own setup wizard.
9
+ *
10
+ * In interactive mode (default): uses `stdio: 'inherit'` so the package's
11
+ * prompts take over the terminal.
12
+ *
13
+ * In non-interactive mode: uses `stdio: 'pipe'` with `nonInteractiveArgs`
14
+ * (falling back to `args`), suitable for --quick mode, CI, and testing.
15
+ */
16
+ export async function runPackageWizard(pkg, wizardConfig, state, opts) {
17
+ // Backwards compat: opts used to be just cwd string
18
+ const options = typeof opts === "string"
19
+ ? { cwd: opts }
20
+ : opts ?? {};
21
+ const interactive = options.interactive ?? true;
22
+ const env = {
23
+ ...cleanEnv(),
24
+ HOME: homedir(),
25
+ };
26
+ // Pass swarmkit context via env vars
27
+ if (state.embeddingProvider) {
28
+ env.SWARMKIT_EMBEDDING_PROVIDER = state.embeddingProvider;
29
+ }
30
+ if (state.usePrefix !== undefined) {
31
+ env.SWARMKIT_USE_PREFIX = String(state.usePrefix);
32
+ }
33
+ if (state.apiKeys.openai) {
34
+ env.OPENAI_API_KEY = state.apiKeys.openai;
35
+ }
36
+ if (state.apiKeys.gemini) {
37
+ env.GEMINI_API_KEY = state.apiKeys.gemini;
38
+ }
39
+ // Merge package-specific env overrides
40
+ if (wizardConfig.env) {
41
+ Object.assign(env, wizardConfig.env);
42
+ }
43
+ const timeout = wizardConfig.timeout ?? 120_000;
44
+ const args = (!interactive && wizardConfig.nonInteractiveArgs)
45
+ ? wizardConfig.nonInteractiveArgs
46
+ : wizardConfig.args;
47
+ const stdio = interactive ? "inherit" : "pipe";
48
+ return new Promise((resolve) => {
49
+ const child = spawn(wizardConfig.command, args, {
50
+ stdio,
51
+ env,
52
+ cwd: options.cwd,
53
+ timeout,
54
+ });
55
+ child.on("close", (code) => {
56
+ if (code === 0) {
57
+ resolve({ package: pkg, success: true });
58
+ }
59
+ else {
60
+ resolve({
61
+ package: pkg,
62
+ success: false,
63
+ message: `exited with code ${code}`,
64
+ });
65
+ }
66
+ });
67
+ child.on("error", (err) => {
68
+ resolve({
69
+ package: pkg,
70
+ success: false,
71
+ message: formatError(err),
72
+ });
73
+ });
74
+ });
75
+ }
76
+ /**
77
+ * Env var overrides that tell each package's CLI to create config in the
78
+ * .swarm/<pkg>/ prefix layout instead of the default flat location.
79
+ * Used by both initProjectPackage (via shellInit) and runPackageWizard.
80
+ */
81
+ export const PREFIX_ENV_VARS = {
82
+ opentasks: "OPENTASKS_PROJECT_DIR",
83
+ "cognitive-core": "COGNITIVE_CORE_HOME",
84
+ "self-driving-repo": "SDR_CONFIG_DIR",
85
+ openteams: "OPENTEAMS_PROJECT_DIR",
86
+ };
87
+ /**
88
+ * Flat dir name → prefixed dir name for relocation after CLI wizard runs.
89
+ * Packages that create at .<name>/ need relocation to .swarm/<name>/.
90
+ */
91
+ export const RELOCATE_MAP = {
92
+ opentasks: [".opentasks", "opentasks"],
93
+ "cognitive-core": [".cognitive-core", "cognitive-core"],
94
+ "self-driving-repo": [".self-driving", "self-driving"],
95
+ openteams: [".openteams", "openteams"],
96
+ };
7
97
  // ─── Project-level setup ─────────────────────────────────────────────────────
8
98
  /** Root directory for all swarmkit project-level config */
9
99
  export const PROJECT_ROOT = ".swarm";
@@ -33,6 +123,23 @@ export const FLAT_PROJECT_CONFIG_DIRS = {
33
123
  export function projectConfigDirs(usePrefix) {
34
124
  return usePrefix ? PROJECT_CONFIG_DIRS : FLAT_PROJECT_CONFIG_DIRS;
35
125
  }
126
+ /**
127
+ * After a CLI wizard runs for a project-level package, relocate its output
128
+ * from the flat location to the .swarm/ prefix location if needed.
129
+ */
130
+ export function relocateAfterWizard(cwd, pkg, usePrefix) {
131
+ if (!usePrefix)
132
+ return;
133
+ const entry = RELOCATE_MAP[pkg];
134
+ if (!entry)
135
+ return;
136
+ const root = join(cwd, PROJECT_ROOT);
137
+ if (!existsSync(root)) {
138
+ mkdirSync(root, { recursive: true });
139
+ }
140
+ const [legacyName, targetName] = entry;
141
+ relocate(cwd, legacyName, targetName);
142
+ }
36
143
  /**
37
144
  * Project-level packages in correct init order.
38
145
  * Order matters: minimem before cognitive-core (runtime detection).
@@ -137,6 +244,7 @@ export async function initProjectPackage(pkg, ctx) {
137
244
  export const GLOBAL_CONFIG_DIRS = {
138
245
  "skill-tree": ".skill-tree",
139
246
  openhive: ".openhive",
247
+ "claude-code-swarm": ".claude-swarm",
140
248
  };
141
249
  /** Check whether a global package is already configured */
142
250
  export function isGlobalInit(pkg) {
@@ -163,7 +271,7 @@ export async function initGlobalPackage(pkg, ctx, openhiveOpts) {
163
271
  case "sessionlog":
164
272
  return { package: "sessionlog", success: true, message: "no setup required" };
165
273
  case "claude-code-swarm":
166
- return { package: "claude-code-swarm", success: true, message: "no setup required" };
274
+ return initClaudeSwarmGlobal(ctx);
167
275
  default:
168
276
  return { package: pkg, success: false, message: "Unknown package" };
169
277
  }
@@ -203,20 +311,26 @@ async function initMinimem(ctx) {
203
311
  if (!existsSync(join(absTarget, "config.json"))) {
204
312
  initMinimemInline(absTarget);
205
313
  }
206
- // Patch embedding provider if the wizard chose one
207
- const provider = ctx.embeddingProvider && ctx.embeddingProvider !== "local"
208
- ? ctx.embeddingProvider
209
- : null;
210
- if (provider) {
211
- const configPath = join(absTarget, "config.json");
212
- try {
213
- const config = JSON.parse(readFileSync(configPath, "utf-8"));
314
+ // Patch config with wizard choices
315
+ const configPath = join(absTarget, "config.json");
316
+ try {
317
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
318
+ // Patch embedding provider if the wizard chose one
319
+ const provider = ctx.embeddingProvider && ctx.embeddingProvider !== "local"
320
+ ? ctx.embeddingProvider
321
+ : null;
322
+ if (provider) {
214
323
  config.embedding = { ...config.embedding, provider };
215
- writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
216
324
  }
217
- catch {
218
- // Non-fatal
325
+ // Apply per-package config overrides from the interactive config phase
326
+ const overrides = ctx.packageConfigs?.minimem?.values;
327
+ if (overrides) {
328
+ applyNestedOverrides(config, overrides);
219
329
  }
330
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
331
+ }
332
+ catch {
333
+ // Non-fatal
220
334
  }
221
335
  return { package: "minimem", success: true };
222
336
  }
@@ -242,7 +356,10 @@ function initMinimemInline(targetDir) {
242
356
  }
243
357
  }
244
358
  async function initSdr(ctx) {
245
- const result = await shellInit("sdr", ["init", "-t", "triage-only"], ctx.cwd, ctx.usePrefix
359
+ // Use template from interactive config if available, otherwise default
360
+ const template = ctx.packageConfigs?.["self-driving-repo"]?.values?.template ??
361
+ "triage-only";
362
+ const result = await shellInit("sdr", ["init", "-t", template], ctx.cwd, ctx.usePrefix
246
363
  ? { SDR_CONFIG_DIR: join(PROJECT_ROOT, "self-driving") }
247
364
  : undefined);
248
365
  // relocate handles packages that don't yet respect the env var;
@@ -311,7 +428,15 @@ async function initSessionlogProject(ctx) {
311
428
  ? join(ctx.cwd, PROJECT_ROOT, "sessionlog")
312
429
  : join(ctx.cwd, ".sessionlog");
313
430
  mkdirSync(targetDir, { recursive: true });
314
- // Write default settings.json
431
+ // Apply per-package config overrides
432
+ const overrides = ctx.packageConfigs?.sessionlog?.values;
433
+ // Extract session repo config from dot-notation overrides.
434
+ // sessionRepo.remote and sessionRepo.directory are committable (go to settings.json).
435
+ // sessionRepo.localPath is machine-specific (goes to settings.local.json).
436
+ const repoRemote = overrides?.["sessionRepo.remote"];
437
+ const repoDirectory = overrides?.["sessionRepo.directory"];
438
+ const repoLocalPath = overrides?.["sessionRepo.localPath"];
439
+ // Write settings.json with defaults (committed, shared across clones)
315
440
  const settingsPath = join(targetDir, "settings.json");
316
441
  if (!existsSync(settingsPath)) {
317
442
  const defaultSettings = {
@@ -321,8 +446,48 @@ async function initSessionlogProject(ctx) {
321
446
  telemetryEnabled: false,
322
447
  summarizationEnabled: false,
323
448
  };
449
+ // Apply overrides, stripping session repo dot-notation keys
450
+ if (overrides) {
451
+ const sessionRepoKeys = ["sessionRepo.remote", "sessionRepo.directory", "sessionRepo.localPath", "sessionRepo.autoPush"];
452
+ for (const [key, value] of Object.entries(overrides)) {
453
+ if (!sessionRepoKeys.includes(key)) {
454
+ defaultSettings[key] = value;
455
+ }
456
+ }
457
+ }
458
+ // Build sessionRepo object for committable fields (remote + directory)
459
+ if (repoRemote) {
460
+ const sessionRepo = { remote: repoRemote };
461
+ if (repoDirectory) {
462
+ sessionRepo.directory = repoDirectory;
463
+ }
464
+ defaultSettings.sessionRepo = sessionRepo;
465
+ }
324
466
  writeFileSync(settingsPath, JSON.stringify(defaultSettings, null, 2) + "\n");
325
467
  }
468
+ // Write machine-specific session repo fields to settings.local.json
469
+ if (repoLocalPath) {
470
+ const localSettingsPath = join(targetDir, "settings.local.json");
471
+ const localSettings = {};
472
+ if (existsSync(localSettingsPath)) {
473
+ try {
474
+ Object.assign(localSettings, JSON.parse(readFileSync(localSettingsPath, "utf-8")));
475
+ }
476
+ catch {
477
+ // Start fresh
478
+ }
479
+ }
480
+ localSettings.sessionRepo = {
481
+ ...(localSettings.sessionRepo ?? {}),
482
+ localPath: repoLocalPath,
483
+ };
484
+ writeFileSync(localSettingsPath, JSON.stringify(localSettings, null, 2) + "\n");
485
+ }
486
+ // Ensure settings.local.json is gitignored
487
+ const gitignorePath = join(targetDir, ".gitignore");
488
+ if (!existsSync(gitignorePath)) {
489
+ writeFileSync(gitignorePath, "# Sessionlog local files (not committed)\nsettings.local.json\ntmp/\nlogs/\n");
490
+ }
326
491
  return { package: "sessionlog", success: true };
327
492
  }
328
493
  catch (err) {
@@ -386,6 +551,40 @@ async function initOpenhive(opts) {
386
551
  };
387
552
  }
388
553
  }
554
+ async function initClaudeSwarmGlobal(ctx) {
555
+ try {
556
+ const targetDir = join(homedir(), ".claude-swarm");
557
+ mkdirSync(targetDir, { recursive: true });
558
+ // Write default global config.json (user preferences that apply across projects)
559
+ const configPath = join(targetDir, "config.json");
560
+ if (!existsSync(configPath)) {
561
+ const defaultConfig = {
562
+ map: {
563
+ server: "",
564
+ sidecar: "session",
565
+ },
566
+ sessionlog: {
567
+ enabled: false,
568
+ sync: "off",
569
+ },
570
+ };
571
+ // Apply per-package config overrides
572
+ const overrides = ctx.packageConfigs?.["claude-code-swarm"]?.values;
573
+ if (overrides) {
574
+ applyNestedOverrides(defaultConfig, overrides);
575
+ }
576
+ writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2) + "\n", { mode: 0o600 });
577
+ }
578
+ return { package: "claude-code-swarm", success: true };
579
+ }
580
+ catch (err) {
581
+ return {
582
+ package: "claude-code-swarm",
583
+ success: false,
584
+ message: formatError(err),
585
+ };
586
+ }
587
+ }
389
588
  async function initClaudeSwarmProject(ctx) {
390
589
  try {
391
590
  const targetDir = ctx.usePrefix
@@ -409,6 +608,11 @@ async function initClaudeSwarmProject(ctx) {
409
608
  sync: "off",
410
609
  },
411
610
  };
611
+ // Apply per-package config overrides
612
+ const overrides = ctx.packageConfigs?.["claude-code-swarm"]?.values;
613
+ if (overrides) {
614
+ applyNestedOverrides(defaultConfig, overrides);
615
+ }
412
616
  writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2) + "\n");
413
617
  }
414
618
  // Write .gitignore for tmp/
@@ -427,6 +631,31 @@ async function initClaudeSwarmProject(ctx) {
427
631
  }
428
632
  }
429
633
  // ─── Helpers ─────────────────────────────────────────────────────────────────
634
+ /**
635
+ * Apply dotted-key overrides to a nested config object.
636
+ * e.g., { "hybrid.vectorWeight": 0.8 } sets config.hybrid.vectorWeight = 0.8
637
+ */
638
+ function applyNestedOverrides(config, overrides) {
639
+ for (const [key, value] of Object.entries(overrides)) {
640
+ const parts = key.split(".");
641
+ let target = config;
642
+ for (let i = 0; i < parts.length - 1; i++) {
643
+ if (typeof target[parts[i]] !== "object" ||
644
+ target[parts[i]] === null) {
645
+ target[parts[i]] = {};
646
+ }
647
+ target = target[parts[i]];
648
+ }
649
+ const finalKey = parts[parts.length - 1];
650
+ // Coerce numeric strings to numbers for config values
651
+ if (typeof value === "string" && /^\d+(\.\d+)?$/.test(value)) {
652
+ target[finalKey] = parseFloat(value);
653
+ }
654
+ else {
655
+ target[finalKey] = value;
656
+ }
657
+ }
658
+ }
430
659
  /** Check if a directory exists and contains at least one file (recursively) */
431
660
  function hasContent(dir) {
432
661
  if (!existsSync(dir))