swarmkit 0.0.1 → 0.0.3

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 (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +130 -1
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +33 -0
  5. package/dist/commands/add.d.ts +2 -0
  6. package/dist/commands/add.js +98 -0
  7. package/dist/commands/doctor.d.ts +2 -0
  8. package/dist/commands/doctor.js +100 -0
  9. package/dist/commands/hive.d.ts +2 -0
  10. package/dist/commands/hive.js +248 -0
  11. package/dist/commands/init/phases/configure.d.ts +2 -0
  12. package/dist/commands/init/phases/configure.js +85 -0
  13. package/dist/commands/init/phases/global-setup.d.ts +2 -0
  14. package/dist/commands/init/phases/global-setup.js +81 -0
  15. package/dist/commands/init/phases/packages.d.ts +2 -0
  16. package/dist/commands/init/phases/packages.js +30 -0
  17. package/dist/commands/init/phases/project.d.ts +2 -0
  18. package/dist/commands/init/phases/project.js +56 -0
  19. package/dist/commands/init/phases/use-case.d.ts +2 -0
  20. package/dist/commands/init/phases/use-case.js +41 -0
  21. package/dist/commands/init/state.d.ts +13 -0
  22. package/dist/commands/init/state.js +9 -0
  23. package/dist/commands/init/state.test.d.ts +1 -0
  24. package/dist/commands/init/state.test.js +21 -0
  25. package/dist/commands/init/wizard.d.ts +4 -0
  26. package/dist/commands/init/wizard.js +108 -0
  27. package/dist/commands/init.d.ts +2 -0
  28. package/dist/commands/init.js +11 -0
  29. package/dist/commands/login.d.ts +2 -0
  30. package/dist/commands/login.js +91 -0
  31. package/dist/commands/logout.d.ts +2 -0
  32. package/dist/commands/logout.js +19 -0
  33. package/dist/commands/remove.d.ts +2 -0
  34. package/dist/commands/remove.js +55 -0
  35. package/dist/commands/status.d.ts +2 -0
  36. package/dist/commands/status.js +87 -0
  37. package/dist/commands/update.d.ts +2 -0
  38. package/dist/commands/update.js +54 -0
  39. package/dist/commands/whoami.d.ts +2 -0
  40. package/dist/commands/whoami.js +40 -0
  41. package/dist/config/global.d.ts +26 -0
  42. package/dist/config/global.js +71 -0
  43. package/dist/config/global.test.d.ts +1 -0
  44. package/dist/config/global.test.js +167 -0
  45. package/dist/config/keys.d.ts +10 -0
  46. package/dist/config/keys.js +47 -0
  47. package/dist/config/keys.test.d.ts +1 -0
  48. package/dist/config/keys.test.js +87 -0
  49. package/dist/doctor/checks.d.ts +31 -0
  50. package/dist/doctor/checks.js +226 -0
  51. package/dist/doctor/checks.test.d.ts +1 -0
  52. package/dist/doctor/checks.test.js +301 -0
  53. package/dist/doctor/types.d.ts +29 -0
  54. package/dist/doctor/types.js +1 -0
  55. package/dist/hub/auth-flow.d.ts +16 -0
  56. package/dist/hub/auth-flow.js +118 -0
  57. package/dist/hub/auth-flow.test.d.ts +1 -0
  58. package/dist/hub/auth-flow.test.js +98 -0
  59. package/dist/hub/client.d.ts +51 -0
  60. package/dist/hub/client.js +107 -0
  61. package/dist/hub/client.test.d.ts +1 -0
  62. package/dist/hub/client.test.js +177 -0
  63. package/dist/hub/credentials.d.ts +14 -0
  64. package/dist/hub/credentials.js +41 -0
  65. package/dist/hub/credentials.test.d.ts +1 -0
  66. package/dist/hub/credentials.test.js +102 -0
  67. package/dist/index.d.ts +17 -1
  68. package/dist/index.js +10 -2
  69. package/dist/packages/installer.d.ts +42 -0
  70. package/dist/packages/installer.js +158 -0
  71. package/dist/packages/installer.test.d.ts +1 -0
  72. package/dist/packages/installer.test.js +283 -0
  73. package/dist/packages/plugin.d.ts +13 -0
  74. package/dist/packages/plugin.js +33 -0
  75. package/dist/packages/plugin.test.d.ts +1 -0
  76. package/dist/packages/plugin.test.js +99 -0
  77. package/dist/packages/registry.d.ts +37 -0
  78. package/dist/packages/registry.js +154 -0
  79. package/dist/packages/registry.test.d.ts +1 -0
  80. package/dist/packages/registry.test.js +188 -0
  81. package/dist/packages/setup.d.ts +55 -0
  82. package/dist/packages/setup.js +414 -0
  83. package/dist/packages/setup.test.d.ts +1 -0
  84. package/dist/packages/setup.test.js +808 -0
  85. package/dist/utils/ui.d.ts +10 -0
  86. package/dist/utils/ui.js +47 -0
  87. package/dist/utils/ui.test.d.ts +1 -0
  88. package/dist/utils/ui.test.js +102 -0
  89. package/package.json +29 -6
@@ -0,0 +1,31 @@
1
+ import type { CheckResult, CheckContext } from "./types.js";
2
+ /**
3
+ * Verify each registered package is actually installed and in PATH.
4
+ */
5
+ export declare function checkPackages(ctx: CheckContext): Promise<CheckResult[]>;
6
+ /**
7
+ * Check that required API keys are available (stored or in env).
8
+ */
9
+ export declare function checkCredentials(ctx: CheckContext): CheckResult[];
10
+ /**
11
+ * Check that project-level config directories exist for installed packages.
12
+ * Only runs when cwd is a project directory. Checks both .swarm/ and flat layouts.
13
+ */
14
+ export declare function checkProjectConfigs(ctx: CheckContext): CheckResult[];
15
+ /**
16
+ * Check that active integrations have both packages installed.
17
+ * Reports on the state of known integration pairs.
18
+ */
19
+ export declare function checkIntegrations(ctx: CheckContext): Promise<CheckResult[]>;
20
+ /**
21
+ * Check that the global swarmkit config exists.
22
+ */
23
+ export declare function checkGlobalConfig(ctx: CheckContext): CheckResult;
24
+ export interface DoctorReport {
25
+ packages: CheckResult[];
26
+ credentials: CheckResult[];
27
+ projectConfigs: CheckResult[];
28
+ integrations: CheckResult[];
29
+ globalConfig: CheckResult;
30
+ }
31
+ export declare function runAllChecks(ctx: CheckContext): Promise<DoctorReport>;
@@ -0,0 +1,226 @@
1
+ import { join } from "node:path";
2
+ import { PACKAGES, getActiveIntegrations } from "../packages/registry.js";
3
+ /** Config directories — prefixed layout (.swarm/) */
4
+ const PREFIXED_CONFIG_DIRS = {
5
+ opentasks: ".swarm/opentasks",
6
+ minimem: ".swarm/minimem",
7
+ "cognitive-core": ".swarm/cognitive-core",
8
+ "skill-tree": ".swarm/skilltree",
9
+ "self-driving-repo": ".swarm/self-driving",
10
+ openteams: ".swarm/openteams",
11
+ sessionlog: ".swarm/sessionlog",
12
+ "claude-code-swarm": ".swarm/claude-swarm",
13
+ };
14
+ /** Config directories — flat layout (legacy / --no-prefix) */
15
+ const FLAT_CONFIG_DIRS = {
16
+ opentasks: ".opentasks",
17
+ minimem: ".minimem",
18
+ "cognitive-core": ".cognitive-core",
19
+ "skill-tree": ".skilltree",
20
+ "self-driving-repo": ".self-driving",
21
+ openteams: ".openteams",
22
+ sessionlog: ".sessionlog",
23
+ "claude-code-swarm": ".claude-swarm",
24
+ };
25
+ /** Embedding provider → required key name */
26
+ const EMBEDDING_KEY_MAP = {
27
+ openai: "openai",
28
+ gemini: "gemini",
29
+ };
30
+ /** Packages that need an embedding provider to work fully */
31
+ const EMBEDDING_CONSUMERS = ["minimem", "cognitive-core", "skill-tree"];
32
+ // ── Package checks ──────────────────────────────────────────────
33
+ /**
34
+ * Verify each registered package is actually installed and in PATH.
35
+ */
36
+ export async function checkPackages(ctx) {
37
+ const results = [];
38
+ for (const pkg of ctx.installedPackages) {
39
+ const version = await ctx.getInstalledVersion(pkg);
40
+ if (version) {
41
+ results.push({
42
+ name: pkg,
43
+ status: "pass",
44
+ message: `${pkg} ${version}`,
45
+ });
46
+ }
47
+ else {
48
+ results.push({
49
+ name: pkg,
50
+ status: "fail",
51
+ message: `${pkg} not found in PATH`,
52
+ fix: `swarmkit add ${pkg}`,
53
+ });
54
+ }
55
+ }
56
+ return results;
57
+ }
58
+ // ── Credential checks ───────────────────────────────────────────
59
+ /**
60
+ * Check that required API keys are available (stored or in env).
61
+ */
62
+ export function checkCredentials(ctx) {
63
+ const results = [];
64
+ // Anthropic key — needed for running agents
65
+ const hasAnthropicStored = ctx.storedKeys.includes("anthropic");
66
+ const hasAnthropicEnv = !!ctx.env["ANTHROPIC_API_KEY"];
67
+ if (hasAnthropicStored || hasAnthropicEnv) {
68
+ const source = hasAnthropicEnv ? "environment" : "stored";
69
+ results.push({
70
+ name: "anthropic-key",
71
+ status: "pass",
72
+ message: `Anthropic API key (${source})`,
73
+ });
74
+ }
75
+ else {
76
+ results.push({
77
+ name: "anthropic-key",
78
+ status: "warn",
79
+ message: "Anthropic API key not found — agents will not be able to run",
80
+ fix: "swarmkit init (or set ANTHROPIC_API_KEY)",
81
+ });
82
+ }
83
+ // Embedding key — needed if embedding provider is configured
84
+ const needsEmbeddings = ctx.installedPackages.some((p) => EMBEDDING_CONSUMERS.includes(p));
85
+ if (needsEmbeddings && ctx.embeddingProvider) {
86
+ const requiredKey = EMBEDDING_KEY_MAP[ctx.embeddingProvider];
87
+ if (ctx.embeddingProvider === "local") {
88
+ results.push({
89
+ name: "embedding-key",
90
+ status: "pass",
91
+ message: "Embeddings using local models (no key needed)",
92
+ });
93
+ }
94
+ else if (requiredKey) {
95
+ const hasKeyStored = ctx.storedKeys.includes(requiredKey);
96
+ const envVarName = requiredKey === "openai" ? "OPENAI_API_KEY" : "GEMINI_API_KEY";
97
+ const hasKeyEnv = !!ctx.env[envVarName];
98
+ if (hasKeyStored || hasKeyEnv) {
99
+ const source = hasKeyEnv ? "environment" : "stored";
100
+ results.push({
101
+ name: "embedding-key",
102
+ status: "pass",
103
+ message: `${ctx.embeddingProvider} embedding key (${source})`,
104
+ });
105
+ }
106
+ else {
107
+ results.push({
108
+ name: "embedding-key",
109
+ status: "warn",
110
+ message: `${ctx.embeddingProvider} API key not found — embeddings will fall back to BM25-only`,
111
+ fix: "swarmkit init (or set " + envVarName + ")",
112
+ });
113
+ }
114
+ }
115
+ }
116
+ else if (needsEmbeddings && !ctx.embeddingProvider) {
117
+ results.push({
118
+ name: "embedding-provider",
119
+ status: "warn",
120
+ message: "No embedding provider configured — semantic search disabled",
121
+ fix: "swarmkit init",
122
+ });
123
+ }
124
+ return results;
125
+ }
126
+ // ── Project config checks ───────────────────────────────────────
127
+ /**
128
+ * Check that project-level config directories exist for installed packages.
129
+ * Only runs when cwd is a project directory. Checks both .swarm/ and flat layouts.
130
+ */
131
+ export function checkProjectConfigs(ctx) {
132
+ if (!ctx.isProject)
133
+ return [];
134
+ const results = [];
135
+ for (const pkg of ctx.installedPackages) {
136
+ const prefixedDir = PREFIXED_CONFIG_DIRS[pkg];
137
+ const flatDir = FLAT_CONFIG_DIRS[pkg];
138
+ if (!prefixedDir)
139
+ continue; // Package has no project-level config
140
+ if (PACKAGES[pkg]?.globalOnly)
141
+ continue;
142
+ const hasPrefixed = ctx.exists(join(ctx.cwd, prefixedDir));
143
+ const hasFlat = flatDir ? ctx.exists(join(ctx.cwd, flatDir)) : false;
144
+ if (hasPrefixed || hasFlat) {
145
+ const found = hasPrefixed ? prefixedDir : flatDir;
146
+ results.push({
147
+ name: `${pkg}-config`,
148
+ status: "pass",
149
+ message: `${found}/`,
150
+ });
151
+ }
152
+ else {
153
+ results.push({
154
+ name: `${pkg}-config`,
155
+ status: "warn",
156
+ message: `${prefixedDir}/ not found`,
157
+ fix: `swarmkit init (or ${pkg} init)`,
158
+ });
159
+ }
160
+ }
161
+ return results;
162
+ }
163
+ // ── Integration checks ──────────────────────────────────────────
164
+ /**
165
+ * Check that active integrations have both packages installed.
166
+ * Reports on the state of known integration pairs.
167
+ */
168
+ export async function checkIntegrations(ctx) {
169
+ const integrations = getActiveIntegrations(ctx.installedPackages);
170
+ const results = [];
171
+ for (const integration of integrations) {
172
+ const [a, b] = integration.packages;
173
+ const versionA = await ctx.getInstalledVersion(a);
174
+ const versionB = await ctx.getInstalledVersion(b);
175
+ if (versionA && versionB) {
176
+ results.push({
177
+ name: `${a}+${b}`,
178
+ status: "pass",
179
+ message: `${a} ↔ ${b}`,
180
+ });
181
+ }
182
+ else {
183
+ const missing = !versionA ? a : b;
184
+ results.push({
185
+ name: `${a}+${b}`,
186
+ status: "fail",
187
+ message: `${a} ↔ ${b} — ${missing} not found`,
188
+ fix: `swarmkit add ${missing}`,
189
+ });
190
+ }
191
+ }
192
+ return results;
193
+ }
194
+ // ── Global config check ─────────────────────────────────────────
195
+ /**
196
+ * Check that the global swarmkit config exists.
197
+ */
198
+ export function checkGlobalConfig(ctx) {
199
+ const configPath = join(ctx.env["HOME"] ?? "", ".swarmkit", "config.json");
200
+ if (ctx.exists(configPath)) {
201
+ return {
202
+ name: "global-config",
203
+ status: "pass",
204
+ message: "~/.swarmkit/config.json",
205
+ };
206
+ }
207
+ return {
208
+ name: "global-config",
209
+ status: "fail",
210
+ message: "~/.swarmkit/config.json not found",
211
+ fix: "swarmkit init",
212
+ };
213
+ }
214
+ export async function runAllChecks(ctx) {
215
+ const [packages, integrations] = await Promise.all([
216
+ checkPackages(ctx),
217
+ checkIntegrations(ctx),
218
+ ]);
219
+ return {
220
+ packages,
221
+ credentials: checkCredentials(ctx),
222
+ projectConfigs: checkProjectConfigs(ctx),
223
+ integrations,
224
+ globalConfig: checkGlobalConfig(ctx),
225
+ };
226
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,301 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { checkPackages, checkCredentials, checkProjectConfigs, checkIntegrations, checkGlobalConfig, runAllChecks, } from "./checks.js";
3
+ function createContext(overrides = {}) {
4
+ return {
5
+ installedPackages: [],
6
+ embeddingProvider: undefined,
7
+ storedKeys: [],
8
+ cwd: "/tmp/test-project",
9
+ isProject: false,
10
+ getInstalledVersion: async () => null,
11
+ exists: () => false,
12
+ env: {},
13
+ ...overrides,
14
+ };
15
+ }
16
+ describe("checkPackages", () => {
17
+ it("passes when all packages are installed", async () => {
18
+ const ctx = createContext({
19
+ installedPackages: ["opentasks", "minimem"],
20
+ getInstalledVersion: async (pkg) => {
21
+ const versions = {
22
+ opentasks: "0.1.0",
23
+ minimem: "0.2.0",
24
+ };
25
+ return versions[pkg] ?? null;
26
+ },
27
+ });
28
+ const results = await checkPackages(ctx);
29
+ expect(results).toHaveLength(2);
30
+ expect(results[0].status).toBe("pass");
31
+ expect(results[0].message).toContain("opentasks");
32
+ expect(results[0].message).toContain("0.1.0");
33
+ expect(results[1].status).toBe("pass");
34
+ });
35
+ it("fails when a package is not found", async () => {
36
+ const ctx = createContext({
37
+ installedPackages: ["opentasks", "minimem"],
38
+ getInstalledVersion: async (pkg) => pkg === "opentasks" ? "0.1.0" : null,
39
+ });
40
+ const results = await checkPackages(ctx);
41
+ expect(results[0].status).toBe("pass");
42
+ expect(results[1].status).toBe("fail");
43
+ expect(results[1].message).toContain("not found");
44
+ expect(results[1].fix).toBe("swarmkit add minimem");
45
+ });
46
+ it("returns empty for no packages", async () => {
47
+ const ctx = createContext();
48
+ const results = await checkPackages(ctx);
49
+ expect(results).toEqual([]);
50
+ });
51
+ });
52
+ describe("checkCredentials", () => {
53
+ it("passes when anthropic key is stored", () => {
54
+ const ctx = createContext({ storedKeys: ["anthropic"] });
55
+ const results = checkCredentials(ctx);
56
+ const anthropic = results.find((r) => r.name === "anthropic-key");
57
+ expect(anthropic?.status).toBe("pass");
58
+ expect(anthropic?.message).toContain("stored");
59
+ });
60
+ it("passes when anthropic key is in env", () => {
61
+ const ctx = createContext({ env: { ANTHROPIC_API_KEY: "sk-ant-test" } });
62
+ const results = checkCredentials(ctx);
63
+ const anthropic = results.find((r) => r.name === "anthropic-key");
64
+ expect(anthropic?.status).toBe("pass");
65
+ expect(anthropic?.message).toContain("environment");
66
+ });
67
+ it("warns when anthropic key is missing", () => {
68
+ const ctx = createContext();
69
+ const results = checkCredentials(ctx);
70
+ const anthropic = results.find((r) => r.name === "anthropic-key");
71
+ expect(anthropic?.status).toBe("warn");
72
+ expect(anthropic?.fix).toBeTruthy();
73
+ });
74
+ it("checks embedding key when provider is openai and consumers installed", () => {
75
+ const ctx = createContext({
76
+ installedPackages: ["minimem"],
77
+ embeddingProvider: "openai",
78
+ storedKeys: ["openai"],
79
+ });
80
+ const results = checkCredentials(ctx);
81
+ const embedding = results.find((r) => r.name === "embedding-key");
82
+ expect(embedding?.status).toBe("pass");
83
+ });
84
+ it("warns when embedding key is missing for configured provider", () => {
85
+ const ctx = createContext({
86
+ installedPackages: ["minimem"],
87
+ embeddingProvider: "openai",
88
+ storedKeys: [],
89
+ });
90
+ const results = checkCredentials(ctx);
91
+ const embedding = results.find((r) => r.name === "embedding-key");
92
+ expect(embedding?.status).toBe("warn");
93
+ expect(embedding?.message).toContain("BM25-only");
94
+ });
95
+ it("passes embedding key from env", () => {
96
+ const ctx = createContext({
97
+ installedPackages: ["minimem"],
98
+ embeddingProvider: "openai",
99
+ env: { OPENAI_API_KEY: "sk-test" },
100
+ });
101
+ const results = checkCredentials(ctx);
102
+ const embedding = results.find((r) => r.name === "embedding-key");
103
+ expect(embedding?.status).toBe("pass");
104
+ expect(embedding?.message).toContain("environment");
105
+ });
106
+ it("passes for local embedding provider (no key needed)", () => {
107
+ const ctx = createContext({
108
+ installedPackages: ["minimem"],
109
+ embeddingProvider: "local",
110
+ });
111
+ const results = checkCredentials(ctx);
112
+ const embedding = results.find((r) => r.name === "embedding-key");
113
+ expect(embedding?.status).toBe("pass");
114
+ expect(embedding?.message).toContain("local");
115
+ });
116
+ it("warns when embedding consumers installed but no provider configured", () => {
117
+ const ctx = createContext({
118
+ installedPackages: ["cognitive-core"],
119
+ embeddingProvider: undefined,
120
+ });
121
+ const results = checkCredentials(ctx);
122
+ const embedding = results.find((r) => r.name === "embedding-provider");
123
+ expect(embedding?.status).toBe("warn");
124
+ });
125
+ it("skips embedding check when no embedding consumers installed", () => {
126
+ const ctx = createContext({
127
+ installedPackages: ["opentasks", "self-driving-repo"],
128
+ embeddingProvider: undefined,
129
+ });
130
+ const results = checkCredentials(ctx);
131
+ const embedding = results.find((r) => r.name === "embedding-key" || r.name === "embedding-provider");
132
+ expect(embedding).toBeUndefined();
133
+ });
134
+ it("checks gemini key via GEMINI_API_KEY env var", () => {
135
+ const ctx = createContext({
136
+ installedPackages: ["minimem"],
137
+ embeddingProvider: "gemini",
138
+ env: { GEMINI_API_KEY: "test-key" },
139
+ });
140
+ const results = checkCredentials(ctx);
141
+ const embedding = results.find((r) => r.name === "embedding-key");
142
+ expect(embedding?.status).toBe("pass");
143
+ });
144
+ });
145
+ describe("checkProjectConfigs", () => {
146
+ it("returns empty when not in a project", () => {
147
+ const ctx = createContext({
148
+ isProject: false,
149
+ installedPackages: ["opentasks"],
150
+ });
151
+ const results = checkProjectConfigs(ctx);
152
+ expect(results).toEqual([]);
153
+ });
154
+ it("passes when prefixed config directory exists", () => {
155
+ const ctx = createContext({
156
+ isProject: true,
157
+ installedPackages: ["opentasks"],
158
+ exists: (path) => path.endsWith(".swarm/opentasks"),
159
+ });
160
+ const results = checkProjectConfigs(ctx);
161
+ expect(results).toHaveLength(1);
162
+ expect(results[0].status).toBe("pass");
163
+ expect(results[0].message).toContain(".swarm/opentasks");
164
+ });
165
+ it("passes when flat config directory exists", () => {
166
+ const ctx = createContext({
167
+ isProject: true,
168
+ installedPackages: ["opentasks"],
169
+ exists: (path) => path.endsWith(".opentasks"),
170
+ });
171
+ const results = checkProjectConfigs(ctx);
172
+ expect(results).toHaveLength(1);
173
+ expect(results[0].status).toBe("pass");
174
+ expect(results[0].message).toContain(".opentasks");
175
+ });
176
+ it("warns when config directory is missing", () => {
177
+ const ctx = createContext({
178
+ isProject: true,
179
+ installedPackages: ["opentasks"],
180
+ exists: () => false,
181
+ });
182
+ const results = checkProjectConfigs(ctx);
183
+ expect(results).toHaveLength(1);
184
+ expect(results[0].status).toBe("warn");
185
+ expect(results[0].message).toContain(".swarm/opentasks");
186
+ expect(results[0].fix).toContain("opentasks init");
187
+ });
188
+ it("passes for claude-code-swarm with prefixed config", () => {
189
+ const ctx = createContext({
190
+ isProject: true,
191
+ installedPackages: ["claude-code-swarm"],
192
+ exists: (path) => path.endsWith(".swarm/claude-swarm"),
193
+ });
194
+ const results = checkProjectConfigs(ctx);
195
+ expect(results).toHaveLength(1);
196
+ expect(results[0].status).toBe("pass");
197
+ expect(results[0].message).toContain(".swarm/claude-swarm");
198
+ });
199
+ it("skips packages without project config dirs", () => {
200
+ const ctx = createContext({
201
+ isProject: true,
202
+ installedPackages: ["multi-agent-protocol"],
203
+ exists: () => false,
204
+ });
205
+ const results = checkProjectConfigs(ctx);
206
+ expect(results).toEqual([]);
207
+ });
208
+ it("checks multiple packages (mixed layouts)", () => {
209
+ const existingDirs = new Set([
210
+ "/tmp/test-project/.swarm/opentasks",
211
+ "/tmp/test-project/.minimem",
212
+ ]);
213
+ const ctx = createContext({
214
+ isProject: true,
215
+ cwd: "/tmp/test-project",
216
+ installedPackages: ["opentasks", "minimem", "cognitive-core"],
217
+ exists: (path) => existingDirs.has(path),
218
+ });
219
+ const results = checkProjectConfigs(ctx);
220
+ expect(results).toHaveLength(3);
221
+ expect(results[0].status).toBe("pass"); // opentasks (prefixed)
222
+ expect(results[1].status).toBe("pass"); // minimem (flat)
223
+ expect(results[2].status).toBe("warn"); // cognitive-core (missing)
224
+ });
225
+ });
226
+ describe("checkIntegrations", () => {
227
+ it("passes when both packages in an integration are installed", async () => {
228
+ const ctx = createContext({
229
+ installedPackages: ["cognitive-core", "minimem"],
230
+ getInstalledVersion: async () => "0.1.0",
231
+ });
232
+ const results = await checkIntegrations(ctx);
233
+ expect(results).toHaveLength(1);
234
+ expect(results[0].status).toBe("pass");
235
+ expect(results[0].message).toContain("cognitive-core");
236
+ expect(results[0].message).toContain("minimem");
237
+ });
238
+ it("fails when one package in an integration is missing from PATH", async () => {
239
+ const ctx = createContext({
240
+ installedPackages: ["cognitive-core", "minimem"],
241
+ getInstalledVersion: async (pkg) => pkg === "cognitive-core" ? "0.1.0" : null,
242
+ });
243
+ const results = await checkIntegrations(ctx);
244
+ expect(results).toHaveLength(1);
245
+ expect(results[0].status).toBe("fail");
246
+ expect(results[0].fix).toContain("minimem");
247
+ });
248
+ it("returns empty when no integrations are active", async () => {
249
+ const ctx = createContext({
250
+ installedPackages: ["minimem"],
251
+ getInstalledVersion: async () => "0.1.0",
252
+ });
253
+ const results = await checkIntegrations(ctx);
254
+ expect(results).toEqual([]);
255
+ });
256
+ });
257
+ describe("checkGlobalConfig", () => {
258
+ it("passes when config file exists", () => {
259
+ const ctx = createContext({
260
+ env: { HOME: "/home/testuser" },
261
+ exists: (path) => path === "/home/testuser/.swarmkit/config.json",
262
+ });
263
+ const result = checkGlobalConfig(ctx);
264
+ expect(result.status).toBe("pass");
265
+ });
266
+ it("fails when config file is missing", () => {
267
+ const ctx = createContext({
268
+ env: { HOME: "/home/testuser" },
269
+ exists: () => false,
270
+ });
271
+ const result = checkGlobalConfig(ctx);
272
+ expect(result.status).toBe("fail");
273
+ expect(result.fix).toBe("swarmkit init");
274
+ });
275
+ });
276
+ describe("runAllChecks", () => {
277
+ it("returns a complete report", async () => {
278
+ const ctx = createContext({
279
+ installedPackages: ["opentasks", "minimem"],
280
+ embeddingProvider: "openai",
281
+ storedKeys: ["anthropic", "openai"],
282
+ isProject: true,
283
+ env: { HOME: "/home/testuser" },
284
+ getInstalledVersion: async () => "0.1.0",
285
+ exists: () => true,
286
+ });
287
+ const report = await runAllChecks(ctx);
288
+ expect(report.packages).toHaveLength(2);
289
+ expect(report.credentials.length).toBeGreaterThan(0);
290
+ expect(report.projectConfigs.length).toBeGreaterThan(0);
291
+ expect(report.globalConfig).toBeDefined();
292
+ expect(report.globalConfig.status).toBe("pass");
293
+ });
294
+ it("returns empty sections when no packages installed", async () => {
295
+ const ctx = createContext({ env: { HOME: "/tmp" } });
296
+ const report = await runAllChecks(ctx);
297
+ expect(report.packages).toEqual([]);
298
+ expect(report.integrations).toEqual([]);
299
+ expect(report.projectConfigs).toEqual([]);
300
+ });
301
+ });
@@ -0,0 +1,29 @@
1
+ export type CheckStatus = "pass" | "fail" | "warn";
2
+ export interface CheckResult {
3
+ /** What was checked */
4
+ name: string;
5
+ /** Pass, fail, or warning */
6
+ status: CheckStatus;
7
+ /** Human-readable description of the result */
8
+ message: string;
9
+ /** Suggested fix command, if applicable */
10
+ fix?: string;
11
+ }
12
+ export interface CheckContext {
13
+ /** Packages registered in swarmkit config */
14
+ installedPackages: string[];
15
+ /** Embedding provider from config */
16
+ embeddingProvider?: string;
17
+ /** API key providers that have stored keys */
18
+ storedKeys: string[];
19
+ /** Current working directory */
20
+ cwd: string;
21
+ /** Whether cwd looks like a project (has .git or package.json) */
22
+ isProject: boolean;
23
+ /** Resolve installed version — injectable for testing */
24
+ getInstalledVersion: (pkg: string) => Promise<string | null>;
25
+ /** Check if a file/directory exists — injectable for testing */
26
+ exists: (path: string) => boolean;
27
+ /** Environment variables */
28
+ env: Record<string, string | undefined>;
29
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ export interface LoginResult {
2
+ token: string;
3
+ apiUrl: string;
4
+ }
5
+ /**
6
+ * Run the login flow on a given port:
7
+ * 1. Start a local HTTP server to receive the OAuth callback
8
+ * 2. Wait for the auth code callback
9
+ * 3. Exchange the code for a JWT
10
+ * 4. Store credentials locally
11
+ */
12
+ export declare function runLoginFlow(port: number): Promise<LoginResult>;
13
+ /** Build the URL the user should open in their browser */
14
+ export declare function getLoginUrl(callbackPort: number): string;
15
+ /** Find an available port in our range */
16
+ export declare function findAvailablePort(): Promise<number>;