swarmkit 0.0.2 → 0.0.4
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 +67 -131
- package/dist/commands/add.js +45 -2
- package/dist/commands/init/phases/global-setup.js +1 -1
- package/dist/commands/init/phases/project.js +5 -3
- package/dist/commands/init/state.d.ts +2 -0
- package/dist/commands/init/state.js +1 -0
- package/dist/commands/init/state.test.js +1 -0
- package/dist/commands/init/wizard.d.ts +5 -1
- package/dist/commands/init/wizard.js +78 -8
- package/dist/commands/init.js +7 -2
- package/dist/commands/remove.js +6 -0
- package/dist/config/global.d.ts +12 -0
- package/dist/config/global.js +34 -1
- package/dist/config/global.test.js +48 -15
- package/dist/doctor/checks.d.ts +1 -1
- package/dist/doctor/checks.js +26 -10
- package/dist/doctor/checks.test.js +52 -27
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/packages/installer.d.ts +9 -0
- package/dist/packages/installer.js +43 -12
- package/dist/packages/installer.test.js +84 -1
- package/dist/packages/plugin.d.ts +13 -0
- package/dist/packages/plugin.js +33 -0
- package/dist/packages/plugin.test.d.ts +1 -0
- package/dist/packages/plugin.test.js +99 -0
- package/dist/packages/registry.d.ts +1 -1
- package/dist/packages/registry.js +25 -50
- package/dist/packages/registry.test.js +38 -49
- package/dist/packages/setup.d.ts +11 -4
- package/dist/packages/setup.js +208 -103
- package/dist/packages/setup.test.js +337 -246
- package/package.json +1 -1
|
@@ -12,7 +12,7 @@ vi.mock("node:os", async () => {
|
|
|
12
12
|
};
|
|
13
13
|
});
|
|
14
14
|
// Import after mock is set up
|
|
15
|
-
const { getConfigDir, getConfigPath, ensureConfigDir, isFirstRun, readConfig, writeConfig, addInstalledPackages, removeInstalledPackage, } = await import("./global.js");
|
|
15
|
+
const { getConfigDir, getConfigPath, ensureConfigDir, isFirstRun, readConfig, writeConfig, addInstalledPackages, removeInstalledPackage, getSwarmkitVersion, isConfigOutdated, } = await import("./global.js");
|
|
16
16
|
describe("config/global", () => {
|
|
17
17
|
beforeEach(() => {
|
|
18
18
|
tempHome = mkdtempSync(join(tmpdir(), "swarmkit-test-"));
|
|
@@ -67,12 +67,12 @@ describe("config/global", () => {
|
|
|
67
67
|
});
|
|
68
68
|
it("reads written config", () => {
|
|
69
69
|
writeConfig({
|
|
70
|
-
installedPackages: ["
|
|
70
|
+
installedPackages: ["opentasks"],
|
|
71
71
|
embeddingProvider: "openai",
|
|
72
72
|
embeddingModel: "text-embedding-3-small",
|
|
73
73
|
});
|
|
74
74
|
const config = readConfig();
|
|
75
|
-
expect(config.installedPackages).toEqual(["
|
|
75
|
+
expect(config.installedPackages).toEqual(["opentasks"]);
|
|
76
76
|
expect(config.embeddingProvider).toBe("openai");
|
|
77
77
|
expect(config.embeddingModel).toBe("text-embedding-3-small");
|
|
78
78
|
});
|
|
@@ -112,23 +112,23 @@ describe("config/global", () => {
|
|
|
112
112
|
describe("addInstalledPackages", () => {
|
|
113
113
|
it("adds packages to an empty list", () => {
|
|
114
114
|
writeConfig({ installedPackages: [] });
|
|
115
|
-
addInstalledPackages(["
|
|
115
|
+
addInstalledPackages(["opentasks", "minimem"]);
|
|
116
116
|
const config = readConfig();
|
|
117
|
-
expect(config.installedPackages).toEqual(["
|
|
117
|
+
expect(config.installedPackages).toEqual(["minimem", "opentasks"]);
|
|
118
118
|
});
|
|
119
119
|
it("deduplicates packages", () => {
|
|
120
|
-
writeConfig({ installedPackages: ["
|
|
121
|
-
addInstalledPackages(["
|
|
120
|
+
writeConfig({ installedPackages: ["opentasks"] });
|
|
121
|
+
addInstalledPackages(["opentasks", "minimem"]);
|
|
122
122
|
const config = readConfig();
|
|
123
|
-
expect(config.installedPackages).toEqual(["
|
|
123
|
+
expect(config.installedPackages).toEqual(["minimem", "opentasks"]);
|
|
124
124
|
});
|
|
125
125
|
it("sorts packages alphabetically", () => {
|
|
126
|
-
addInstalledPackages(["minimem", "
|
|
126
|
+
addInstalledPackages(["minimem", "cognitive-core", "opentasks"]);
|
|
127
127
|
const config = readConfig();
|
|
128
128
|
expect(config.installedPackages).toEqual([
|
|
129
|
-
"
|
|
130
|
-
"macro-agent",
|
|
129
|
+
"cognitive-core",
|
|
131
130
|
"minimem",
|
|
131
|
+
"opentasks",
|
|
132
132
|
]);
|
|
133
133
|
});
|
|
134
134
|
it("preserves other config fields", () => {
|
|
@@ -143,16 +143,16 @@ describe("config/global", () => {
|
|
|
143
143
|
});
|
|
144
144
|
describe("removeInstalledPackage", () => {
|
|
145
145
|
it("removes a package from the list", () => {
|
|
146
|
-
writeConfig({ installedPackages: ["
|
|
146
|
+
writeConfig({ installedPackages: ["opentasks", "minimem"] });
|
|
147
147
|
removeInstalledPackage("minimem");
|
|
148
148
|
const config = readConfig();
|
|
149
|
-
expect(config.installedPackages).toEqual(["
|
|
149
|
+
expect(config.installedPackages).toEqual(["opentasks"]);
|
|
150
150
|
});
|
|
151
151
|
it("does nothing if package is not in list", () => {
|
|
152
|
-
writeConfig({ installedPackages: ["
|
|
152
|
+
writeConfig({ installedPackages: ["opentasks"] });
|
|
153
153
|
removeInstalledPackage("minimem");
|
|
154
154
|
const config = readConfig();
|
|
155
|
-
expect(config.installedPackages).toEqual(["
|
|
155
|
+
expect(config.installedPackages).toEqual(["opentasks"]);
|
|
156
156
|
});
|
|
157
157
|
it("preserves other config fields", () => {
|
|
158
158
|
writeConfig({
|
|
@@ -164,4 +164,37 @@ describe("config/global", () => {
|
|
|
164
164
|
expect(config.embeddingProvider).toBe("gemini");
|
|
165
165
|
});
|
|
166
166
|
});
|
|
167
|
+
describe("getSwarmkitVersion", () => {
|
|
168
|
+
it("returns a semver string", () => {
|
|
169
|
+
const version = getSwarmkitVersion();
|
|
170
|
+
expect(version).toMatch(/^\d+\.\d+\.\d+/);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
describe("isConfigOutdated", () => {
|
|
174
|
+
it("returns true when configVersion is missing", () => {
|
|
175
|
+
const config = { installedPackages: [] };
|
|
176
|
+
expect(isConfigOutdated(config, "1.0.0")).toBe(true);
|
|
177
|
+
});
|
|
178
|
+
it("returns false when versions match exactly", () => {
|
|
179
|
+
const config = { installedPackages: [], configVersion: "1.2.3" };
|
|
180
|
+
expect(isConfigOutdated(config, "1.2.3")).toBe(false);
|
|
181
|
+
});
|
|
182
|
+
it("returns false for patch-only difference", () => {
|
|
183
|
+
const config = { installedPackages: [], configVersion: "1.2.0" };
|
|
184
|
+
expect(isConfigOutdated(config, "1.2.5")).toBe(false);
|
|
185
|
+
});
|
|
186
|
+
it("returns true when minor version differs", () => {
|
|
187
|
+
const config = { installedPackages: [], configVersion: "1.0.0" };
|
|
188
|
+
expect(isConfigOutdated(config, "1.1.0")).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
it("returns true when major version differs", () => {
|
|
191
|
+
const config = { installedPackages: [], configVersion: "0.1.0" };
|
|
192
|
+
expect(isConfigOutdated(config, "1.0.0")).toBe(true);
|
|
193
|
+
});
|
|
194
|
+
it("uses getSwarmkitVersion when currentVersion is not provided", () => {
|
|
195
|
+
const version = getSwarmkitVersion();
|
|
196
|
+
const config = { installedPackages: [], configVersion: version };
|
|
197
|
+
expect(isConfigOutdated(config)).toBe(false);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
167
200
|
});
|
package/dist/doctor/checks.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export declare function checkPackages(ctx: CheckContext): Promise<CheckResult[]>
|
|
|
9
9
|
export declare function checkCredentials(ctx: CheckContext): CheckResult[];
|
|
10
10
|
/**
|
|
11
11
|
* Check that project-level config directories exist for installed packages.
|
|
12
|
-
* Only runs when cwd is a project directory.
|
|
12
|
+
* Only runs when cwd is a project directory. Checks both .swarm/ and flat layouts.
|
|
13
13
|
*/
|
|
14
14
|
export declare function checkProjectConfigs(ctx: CheckContext): CheckResult[];
|
|
15
15
|
/**
|
package/dist/doctor/checks.js
CHANGED
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { PACKAGES, getActiveIntegrations } from "../packages/registry.js";
|
|
3
|
-
/** Config directories
|
|
4
|
-
const
|
|
5
|
-
|
|
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 = {
|
|
6
16
|
opentasks: ".opentasks",
|
|
7
17
|
minimem: ".minimem",
|
|
8
18
|
"cognitive-core": ".cognitive-core",
|
|
9
19
|
"skill-tree": ".skilltree",
|
|
10
20
|
"self-driving-repo": ".self-driving",
|
|
21
|
+
openteams: ".openteams",
|
|
22
|
+
sessionlog: ".sessionlog",
|
|
23
|
+
"claude-code-swarm": ".claude-swarm",
|
|
11
24
|
};
|
|
12
25
|
/** Embedding provider → required key name */
|
|
13
26
|
const EMBEDDING_KEY_MAP = {
|
|
@@ -113,31 +126,34 @@ export function checkCredentials(ctx) {
|
|
|
113
126
|
// ── Project config checks ───────────────────────────────────────
|
|
114
127
|
/**
|
|
115
128
|
* Check that project-level config directories exist for installed packages.
|
|
116
|
-
* Only runs when cwd is a project directory.
|
|
129
|
+
* Only runs when cwd is a project directory. Checks both .swarm/ and flat layouts.
|
|
117
130
|
*/
|
|
118
131
|
export function checkProjectConfigs(ctx) {
|
|
119
132
|
if (!ctx.isProject)
|
|
120
133
|
return [];
|
|
121
134
|
const results = [];
|
|
122
135
|
for (const pkg of ctx.installedPackages) {
|
|
123
|
-
const
|
|
124
|
-
|
|
136
|
+
const prefixedDir = PREFIXED_CONFIG_DIRS[pkg];
|
|
137
|
+
const flatDir = FLAT_CONFIG_DIRS[pkg];
|
|
138
|
+
if (!prefixedDir)
|
|
125
139
|
continue; // Package has no project-level config
|
|
126
140
|
if (PACKAGES[pkg]?.globalOnly)
|
|
127
141
|
continue;
|
|
128
|
-
const
|
|
129
|
-
|
|
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;
|
|
130
146
|
results.push({
|
|
131
147
|
name: `${pkg}-config`,
|
|
132
148
|
status: "pass",
|
|
133
|
-
message: `${
|
|
149
|
+
message: `${found}/`,
|
|
134
150
|
});
|
|
135
151
|
}
|
|
136
152
|
else {
|
|
137
153
|
results.push({
|
|
138
154
|
name: `${pkg}-config`,
|
|
139
155
|
status: "warn",
|
|
140
|
-
message: `${
|
|
156
|
+
message: `${prefixedDir}/ not found`,
|
|
141
157
|
fix: `swarmkit init (or ${pkg} init)`,
|
|
142
158
|
});
|
|
143
159
|
}
|
|
@@ -16,10 +16,10 @@ function createContext(overrides = {}) {
|
|
|
16
16
|
describe("checkPackages", () => {
|
|
17
17
|
it("passes when all packages are installed", async () => {
|
|
18
18
|
const ctx = createContext({
|
|
19
|
-
installedPackages: ["
|
|
19
|
+
installedPackages: ["opentasks", "minimem"],
|
|
20
20
|
getInstalledVersion: async (pkg) => {
|
|
21
21
|
const versions = {
|
|
22
|
-
|
|
22
|
+
opentasks: "0.1.0",
|
|
23
23
|
minimem: "0.2.0",
|
|
24
24
|
};
|
|
25
25
|
return versions[pkg] ?? null;
|
|
@@ -28,14 +28,14 @@ describe("checkPackages", () => {
|
|
|
28
28
|
const results = await checkPackages(ctx);
|
|
29
29
|
expect(results).toHaveLength(2);
|
|
30
30
|
expect(results[0].status).toBe("pass");
|
|
31
|
-
expect(results[0].message).toContain("
|
|
31
|
+
expect(results[0].message).toContain("opentasks");
|
|
32
32
|
expect(results[0].message).toContain("0.1.0");
|
|
33
33
|
expect(results[1].status).toBe("pass");
|
|
34
34
|
});
|
|
35
35
|
it("fails when a package is not found", async () => {
|
|
36
36
|
const ctx = createContext({
|
|
37
|
-
installedPackages: ["
|
|
38
|
-
getInstalledVersion: async (pkg) => pkg === "
|
|
37
|
+
installedPackages: ["opentasks", "minimem"],
|
|
38
|
+
getInstalledVersion: async (pkg) => pkg === "opentasks" ? "0.1.0" : null,
|
|
39
39
|
});
|
|
40
40
|
const results = await checkPackages(ctx);
|
|
41
41
|
expect(results[0].status).toBe("pass");
|
|
@@ -124,7 +124,7 @@ describe("checkCredentials", () => {
|
|
|
124
124
|
});
|
|
125
125
|
it("skips embedding check when no embedding consumers installed", () => {
|
|
126
126
|
const ctx = createContext({
|
|
127
|
-
installedPackages: ["
|
|
127
|
+
installedPackages: ["opentasks", "self-driving-repo"],
|
|
128
128
|
embeddingProvider: undefined,
|
|
129
129
|
});
|
|
130
130
|
const results = checkCredentials(ctx);
|
|
@@ -146,21 +146,32 @@ describe("checkProjectConfigs", () => {
|
|
|
146
146
|
it("returns empty when not in a project", () => {
|
|
147
147
|
const ctx = createContext({
|
|
148
148
|
isProject: false,
|
|
149
|
-
installedPackages: ["
|
|
149
|
+
installedPackages: ["opentasks"],
|
|
150
150
|
});
|
|
151
151
|
const results = checkProjectConfigs(ctx);
|
|
152
152
|
expect(results).toEqual([]);
|
|
153
153
|
});
|
|
154
|
-
it("passes when config directory exists", () => {
|
|
154
|
+
it("passes when prefixed config directory exists", () => {
|
|
155
155
|
const ctx = createContext({
|
|
156
156
|
isProject: true,
|
|
157
|
-
installedPackages: ["
|
|
158
|
-
exists: (path) => path.endsWith(".
|
|
157
|
+
installedPackages: ["opentasks"],
|
|
158
|
+
exists: (path) => path.endsWith(".swarm/opentasks"),
|
|
159
159
|
});
|
|
160
160
|
const results = checkProjectConfigs(ctx);
|
|
161
161
|
expect(results).toHaveLength(1);
|
|
162
162
|
expect(results[0].status).toBe("pass");
|
|
163
|
-
expect(results[0].message).toContain(".
|
|
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");
|
|
164
175
|
});
|
|
165
176
|
it("warns when config directory is missing", () => {
|
|
166
177
|
const ctx = createContext({
|
|
@@ -171,54 +182,68 @@ describe("checkProjectConfigs", () => {
|
|
|
171
182
|
const results = checkProjectConfigs(ctx);
|
|
172
183
|
expect(results).toHaveLength(1);
|
|
173
184
|
expect(results[0].status).toBe("warn");
|
|
174
|
-
expect(results[0].message).toContain(".opentasks");
|
|
185
|
+
expect(results[0].message).toContain(".swarm/opentasks");
|
|
175
186
|
expect(results[0].fix).toContain("opentasks init");
|
|
176
187
|
});
|
|
177
|
-
it("
|
|
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", () => {
|
|
178
200
|
const ctx = createContext({
|
|
179
201
|
isProject: true,
|
|
180
|
-
installedPackages: ["agent-
|
|
202
|
+
installedPackages: ["multi-agent-protocol"],
|
|
181
203
|
exists: () => false,
|
|
182
204
|
});
|
|
183
205
|
const results = checkProjectConfigs(ctx);
|
|
184
206
|
expect(results).toEqual([]);
|
|
185
207
|
});
|
|
186
|
-
it("checks multiple packages", () => {
|
|
187
|
-
const existingDirs = new Set([
|
|
208
|
+
it("checks multiple packages (mixed layouts)", () => {
|
|
209
|
+
const existingDirs = new Set([
|
|
210
|
+
"/tmp/test-project/.swarm/opentasks",
|
|
211
|
+
"/tmp/test-project/.minimem",
|
|
212
|
+
]);
|
|
188
213
|
const ctx = createContext({
|
|
189
214
|
isProject: true,
|
|
190
215
|
cwd: "/tmp/test-project",
|
|
191
|
-
installedPackages: ["
|
|
216
|
+
installedPackages: ["opentasks", "minimem", "cognitive-core"],
|
|
192
217
|
exists: (path) => existingDirs.has(path),
|
|
193
218
|
});
|
|
194
219
|
const results = checkProjectConfigs(ctx);
|
|
195
220
|
expect(results).toHaveLength(3);
|
|
196
|
-
expect(results[0].status).toBe("pass"); //
|
|
197
|
-
expect(results[1].status).toBe("
|
|
198
|
-
expect(results[2].status).toBe("warn"); //
|
|
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)
|
|
199
224
|
});
|
|
200
225
|
});
|
|
201
226
|
describe("checkIntegrations", () => {
|
|
202
227
|
it("passes when both packages in an integration are installed", async () => {
|
|
203
228
|
const ctx = createContext({
|
|
204
|
-
installedPackages: ["
|
|
229
|
+
installedPackages: ["cognitive-core", "minimem"],
|
|
205
230
|
getInstalledVersion: async () => "0.1.0",
|
|
206
231
|
});
|
|
207
232
|
const results = await checkIntegrations(ctx);
|
|
208
233
|
expect(results).toHaveLength(1);
|
|
209
234
|
expect(results[0].status).toBe("pass");
|
|
210
|
-
expect(results[0].message).toContain("
|
|
211
|
-
expect(results[0].message).toContain("
|
|
235
|
+
expect(results[0].message).toContain("cognitive-core");
|
|
236
|
+
expect(results[0].message).toContain("minimem");
|
|
212
237
|
});
|
|
213
238
|
it("fails when one package in an integration is missing from PATH", async () => {
|
|
214
239
|
const ctx = createContext({
|
|
215
|
-
installedPackages: ["
|
|
216
|
-
getInstalledVersion: async (pkg) => pkg === "
|
|
240
|
+
installedPackages: ["cognitive-core", "minimem"],
|
|
241
|
+
getInstalledVersion: async (pkg) => pkg === "cognitive-core" ? "0.1.0" : null,
|
|
217
242
|
});
|
|
218
243
|
const results = await checkIntegrations(ctx);
|
|
219
244
|
expect(results).toHaveLength(1);
|
|
220
245
|
expect(results[0].status).toBe("fail");
|
|
221
|
-
expect(results[0].fix).toContain("
|
|
246
|
+
expect(results[0].fix).toContain("minimem");
|
|
222
247
|
});
|
|
223
248
|
it("returns empty when no integrations are active", async () => {
|
|
224
249
|
const ctx = createContext({
|
|
@@ -251,7 +276,7 @@ describe("checkGlobalConfig", () => {
|
|
|
251
276
|
describe("runAllChecks", () => {
|
|
252
277
|
it("returns a complete report", async () => {
|
|
253
278
|
const ctx = createContext({
|
|
254
|
-
installedPackages: ["
|
|
279
|
+
installedPackages: ["opentasks", "minimem"],
|
|
255
280
|
embeddingProvider: "openai",
|
|
256
281
|
storedKeys: ["anthropic", "openai"],
|
|
257
282
|
isProject: true,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
export { PACKAGES, BUNDLES, INTEGRATIONS, getBundlePackages, getActiveIntegrations, getNewIntegrations, getLostIntegrations, getNpmName, isKnownPackage, getAllPackageNames, } from "./packages/registry.js";
|
|
2
2
|
export type { PackageDefinition, BundleDefinition, Integration, } from "./packages/registry.js";
|
|
3
|
-
export { installPackages, uninstallPackage, getInstalledVersion, getLatestVersion, updatePackages, } from "./packages/installer.js";
|
|
3
|
+
export { installPackages, uninstallPackage, getInstalledVersion, getLatestVersion, updatePackages, isClaudeCliAvailable, getGlobalPackagePath, } from "./packages/installer.js";
|
|
4
4
|
export type { InstallResult, UpdateResult } from "./packages/installer.js";
|
|
5
|
+
export { isInstalledPlugin, registerPlugin, } from "./packages/plugin.js";
|
|
5
6
|
export { PROJECT_CONFIG_DIRS, PROJECT_INIT_ORDER, GLOBAL_CONFIG_DIRS, isProjectInit, isGlobalInit, initProjectPackage, initGlobalPackage, } from "./packages/setup.js";
|
|
6
7
|
export type { InitContext, GlobalContext, OpenhiveOptions, SetupResult, } from "./packages/setup.js";
|
|
7
|
-
export { readConfig, writeConfig, isFirstRun, getConfigDir, addInstalledPackages, removeInstalledPackage, } from "./config/global.js";
|
|
8
|
+
export { readConfig, writeConfig, isFirstRun, getConfigDir, addInstalledPackages, removeInstalledPackage, getSwarmkitVersion, isConfigOutdated, } from "./config/global.js";
|
|
8
9
|
export type { GlobalConfig } from "./config/global.js";
|
|
9
10
|
export { readKey, writeKey, deleteKey, listKeys, hasKey, } from "./config/keys.js";
|
|
10
11
|
export { runAllChecks } from "./doctor/checks.js";
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// Public API — re-export modules for programmatic use
|
|
2
2
|
export { PACKAGES, BUNDLES, INTEGRATIONS, getBundlePackages, getActiveIntegrations, getNewIntegrations, getLostIntegrations, getNpmName, isKnownPackage, getAllPackageNames, } from "./packages/registry.js";
|
|
3
|
-
export { installPackages, uninstallPackage, getInstalledVersion, getLatestVersion, updatePackages, } from "./packages/installer.js";
|
|
3
|
+
export { installPackages, uninstallPackage, getInstalledVersion, getLatestVersion, updatePackages, isClaudeCliAvailable, getGlobalPackagePath, } from "./packages/installer.js";
|
|
4
|
+
export { isInstalledPlugin, registerPlugin, } from "./packages/plugin.js";
|
|
4
5
|
export { PROJECT_CONFIG_DIRS, PROJECT_INIT_ORDER, GLOBAL_CONFIG_DIRS, isProjectInit, isGlobalInit, initProjectPackage, initGlobalPackage, } from "./packages/setup.js";
|
|
5
|
-
export { readConfig, writeConfig, isFirstRun, getConfigDir, addInstalledPackages, removeInstalledPackage, } from "./config/global.js";
|
|
6
|
+
export { readConfig, writeConfig, isFirstRun, getConfigDir, addInstalledPackages, removeInstalledPackage, getSwarmkitVersion, isConfigOutdated, } from "./config/global.js";
|
|
6
7
|
export { readKey, writeKey, deleteKey, listKeys, hasKey, } from "./config/keys.js";
|
|
7
8
|
export { runAllChecks } from "./doctor/checks.js";
|
|
8
9
|
export { readCredentials, writeCredentials, deleteCredentials, isLoggedIn, } from "./hub/credentials.js";
|
|
@@ -31,3 +31,12 @@ export declare function getLatestVersion(packageName: string): Promise<string |
|
|
|
31
31
|
* Update installed packages to their latest versions.
|
|
32
32
|
*/
|
|
33
33
|
export declare function updatePackages(packages: string[]): Promise<UpdateResult[]>;
|
|
34
|
+
/**
|
|
35
|
+
* Check if the Claude Code CLI is available.
|
|
36
|
+
*/
|
|
37
|
+
export declare function isClaudeCliAvailable(): Promise<boolean>;
|
|
38
|
+
/**
|
|
39
|
+
* Resolve the global installation path of an npm package.
|
|
40
|
+
* Uses `npm list -g --parseable` to get the filesystem path.
|
|
41
|
+
*/
|
|
42
|
+
export declare function getGlobalPackagePath(npmName: string): Promise<string | null>;
|
|
@@ -8,21 +8,24 @@ const execFileAsync = promisify(execFile);
|
|
|
8
8
|
export async function installPackages(packages) {
|
|
9
9
|
const results = [];
|
|
10
10
|
for (const pkg of packages) {
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
await execFileAsync("npm", ["install", "-g", npmName], {
|
|
14
|
-
timeout: 120_000,
|
|
15
|
-
});
|
|
16
|
-
const version = await getInstalledVersion(pkg);
|
|
17
|
-
results.push({ package: pkg, success: true, version: version ?? undefined });
|
|
18
|
-
}
|
|
19
|
-
catch (err) {
|
|
20
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
21
|
-
results.push({ package: pkg, success: false, error: formatNpmError(message) });
|
|
22
|
-
}
|
|
11
|
+
results.push(await installNpmPackage(pkg));
|
|
23
12
|
}
|
|
24
13
|
return results;
|
|
25
14
|
}
|
|
15
|
+
async function installNpmPackage(pkg) {
|
|
16
|
+
const npmName = getNpmName(pkg);
|
|
17
|
+
try {
|
|
18
|
+
await execFileAsync("npm", ["install", "-g", npmName], {
|
|
19
|
+
timeout: 120_000,
|
|
20
|
+
});
|
|
21
|
+
const version = await getInstalledVersion(pkg);
|
|
22
|
+
return { package: pkg, success: true, version: version ?? undefined };
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
26
|
+
return { package: pkg, success: false, error: formatNpmError(message) };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
26
29
|
/**
|
|
27
30
|
* Uninstall a package globally via npm.
|
|
28
31
|
*/
|
|
@@ -112,6 +115,34 @@ export async function updatePackages(packages) {
|
|
|
112
115
|
}
|
|
113
116
|
return results;
|
|
114
117
|
}
|
|
118
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
119
|
+
/**
|
|
120
|
+
* Check if the Claude Code CLI is available.
|
|
121
|
+
*/
|
|
122
|
+
export async function isClaudeCliAvailable() {
|
|
123
|
+
try {
|
|
124
|
+
await execFileAsync("claude", ["--version"], { timeout: 10_000 });
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Resolve the global installation path of an npm package.
|
|
133
|
+
* Uses `npm list -g --parseable` to get the filesystem path.
|
|
134
|
+
*/
|
|
135
|
+
export async function getGlobalPackagePath(npmName) {
|
|
136
|
+
try {
|
|
137
|
+
const { stdout } = await execFileAsync("npm", ["list", "-g", npmName, "--parseable", "--depth=0"], { timeout: 15_000 });
|
|
138
|
+
// --parseable returns: prefix-root\npackage-path
|
|
139
|
+
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
140
|
+
return lines.length > 1 ? lines[lines.length - 1] : null;
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
115
146
|
function formatNpmError(message) {
|
|
116
147
|
if (message.includes("EACCES") || message.includes("permission")) {
|
|
117
148
|
return "Permission denied — try running with sudo or configure npm prefix";
|
|
@@ -12,7 +12,7 @@ vi.mock("node:util", async () => {
|
|
|
12
12
|
promisify: () => mockExecFile,
|
|
13
13
|
};
|
|
14
14
|
});
|
|
15
|
-
const { installPackages, uninstallPackage, getInstalledVersion, getLatestVersion, updatePackages, } = await import("./installer.js");
|
|
15
|
+
const { installPackages, uninstallPackage, getInstalledVersion, getLatestVersion, updatePackages, isClaudeCliAvailable, getGlobalPackagePath, } = await import("./installer.js");
|
|
16
16
|
describe("installer", () => {
|
|
17
17
|
beforeEach(() => {
|
|
18
18
|
mockExecFile.mockReset();
|
|
@@ -197,4 +197,87 @@ describe("installer", () => {
|
|
|
197
197
|
expect(results[0].error).toContain("Permission denied");
|
|
198
198
|
});
|
|
199
199
|
});
|
|
200
|
+
describe("isClaudeCliAvailable", () => {
|
|
201
|
+
it("returns true when claude --version succeeds", async () => {
|
|
202
|
+
mockExecFile.mockResolvedValueOnce({ stdout: "1.0.0\n" });
|
|
203
|
+
const available = await isClaudeCliAvailable();
|
|
204
|
+
expect(available).toBe(true);
|
|
205
|
+
expect(mockExecFile).toHaveBeenCalledWith("claude", ["--version"], { timeout: 10_000 });
|
|
206
|
+
});
|
|
207
|
+
it("returns false when claude is not found", async () => {
|
|
208
|
+
mockExecFile.mockRejectedValueOnce(new Error("ENOENT"));
|
|
209
|
+
const available = await isClaudeCliAvailable();
|
|
210
|
+
expect(available).toBe(false);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
describe("getGlobalPackagePath", () => {
|
|
214
|
+
it("returns path from npm list --parseable", async () => {
|
|
215
|
+
mockExecFile.mockResolvedValueOnce({
|
|
216
|
+
stdout: "/usr/local/lib/node_modules\n/usr/local/lib/node_modules/claude-code-swarm\n",
|
|
217
|
+
});
|
|
218
|
+
const result = await getGlobalPackagePath("claude-code-swarm");
|
|
219
|
+
expect(result).toBe("/usr/local/lib/node_modules/claude-code-swarm");
|
|
220
|
+
});
|
|
221
|
+
it("returns null when package is not installed", async () => {
|
|
222
|
+
mockExecFile.mockRejectedValueOnce(new Error("not found"));
|
|
223
|
+
const result = await getGlobalPackagePath("not-installed");
|
|
224
|
+
expect(result).toBeNull();
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
describe("installPackages — claude-code-swarm as normal npm", () => {
|
|
228
|
+
it("installs claude-code-swarm via npm without any claude CLI calls", async () => {
|
|
229
|
+
// npm install -g
|
|
230
|
+
mockExecFile.mockResolvedValueOnce({ stdout: "" });
|
|
231
|
+
// getInstalledVersion
|
|
232
|
+
mockExecFile.mockResolvedValueOnce({
|
|
233
|
+
stdout: JSON.stringify({
|
|
234
|
+
dependencies: { "claude-code-swarm": { version: "0.3.1" } },
|
|
235
|
+
}),
|
|
236
|
+
});
|
|
237
|
+
const results = await installPackages(["claude-code-swarm"]);
|
|
238
|
+
expect(results).toHaveLength(1);
|
|
239
|
+
expect(results[0].success).toBe(true);
|
|
240
|
+
expect(results[0].version).toBe("0.3.1");
|
|
241
|
+
// Verify no calls to claude CLI
|
|
242
|
+
for (const call of mockExecFile.mock.calls) {
|
|
243
|
+
expect(call[0]).not.toBe("claude");
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
describe("uninstallPackage — claude-code-swarm as normal npm", () => {
|
|
248
|
+
it("just calls npm uninstall (no plugin deregistration)", async () => {
|
|
249
|
+
mockExecFile.mockResolvedValueOnce({ stdout: "" });
|
|
250
|
+
await uninstallPackage("claude-code-swarm");
|
|
251
|
+
expect(mockExecFile).toHaveBeenCalledTimes(1);
|
|
252
|
+
expect(mockExecFile).toHaveBeenCalledWith("npm", ["uninstall", "-g", "claude-code-swarm"], { timeout: 60_000 });
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
describe("updatePackages — claude-code-swarm as normal npm", () => {
|
|
256
|
+
it("updates via npm without plugin re-registration", async () => {
|
|
257
|
+
// getInstalledVersion (before)
|
|
258
|
+
mockExecFile.mockResolvedValueOnce({
|
|
259
|
+
stdout: JSON.stringify({
|
|
260
|
+
dependencies: { "claude-code-swarm": { version: "0.3.0" } },
|
|
261
|
+
}),
|
|
262
|
+
});
|
|
263
|
+
// getLatestVersion
|
|
264
|
+
mockExecFile.mockResolvedValueOnce({ stdout: "0.4.0\n" });
|
|
265
|
+
// npm install -g claude-code-swarm@latest
|
|
266
|
+
mockExecFile.mockResolvedValueOnce({ stdout: "" });
|
|
267
|
+
// getInstalledVersion (after)
|
|
268
|
+
mockExecFile.mockResolvedValueOnce({
|
|
269
|
+
stdout: JSON.stringify({
|
|
270
|
+
dependencies: { "claude-code-swarm": { version: "0.4.0" } },
|
|
271
|
+
}),
|
|
272
|
+
});
|
|
273
|
+
const results = await updatePackages(["claude-code-swarm"]);
|
|
274
|
+
expect(results).toHaveLength(1);
|
|
275
|
+
expect(results[0].updated).toBe(true);
|
|
276
|
+
expect(results[0].newVersion).toBe("0.4.0");
|
|
277
|
+
// Verify no calls to claude CLI
|
|
278
|
+
for (const call of mockExecFile.mock.calls) {
|
|
279
|
+
expect(call[0]).not.toBe("claude");
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
});
|
|
200
283
|
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if a globally installed npm package is a Claude Code plugin.
|
|
3
|
+
* A package is a Claude plugin if it contains `.claude-plugin/plugin.json`.
|
|
4
|
+
*/
|
|
5
|
+
export declare function isInstalledPlugin(packageName: string): Promise<boolean>;
|
|
6
|
+
/**
|
|
7
|
+
* Register a package as a Claude Code plugin using `claude plugin add`.
|
|
8
|
+
* Scope controls where the plugin is activated:
|
|
9
|
+
* - "user": all projects (default)
|
|
10
|
+
* - "project": this repository only (shared with collaborators)
|
|
11
|
+
* - "local": this repository only (not shared)
|
|
12
|
+
*/
|
|
13
|
+
export declare function registerPlugin(packageName: string, scope?: "user" | "project" | "local"): Promise<void>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { getNpmName } from "./registry.js";
|
|
6
|
+
import { getGlobalPackagePath } from "./installer.js";
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
/**
|
|
9
|
+
* Check if a globally installed npm package is a Claude Code plugin.
|
|
10
|
+
* A package is a Claude plugin if it contains `.claude-plugin/plugin.json`.
|
|
11
|
+
*/
|
|
12
|
+
export async function isInstalledPlugin(packageName) {
|
|
13
|
+
const npmName = getNpmName(packageName);
|
|
14
|
+
const pkgPath = await getGlobalPackagePath(npmName);
|
|
15
|
+
if (!pkgPath)
|
|
16
|
+
return false;
|
|
17
|
+
return existsSync(join(pkgPath, ".claude-plugin", "plugin.json"));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Register a package as a Claude Code plugin using `claude plugin add`.
|
|
21
|
+
* Scope controls where the plugin is activated:
|
|
22
|
+
* - "user": all projects (default)
|
|
23
|
+
* - "project": this repository only (shared with collaborators)
|
|
24
|
+
* - "local": this repository only (not shared)
|
|
25
|
+
*/
|
|
26
|
+
export async function registerPlugin(packageName, scope = "user") {
|
|
27
|
+
const npmName = getNpmName(packageName);
|
|
28
|
+
const pkgPath = await getGlobalPackagePath(npmName);
|
|
29
|
+
if (!pkgPath) {
|
|
30
|
+
throw new Error(`Could not resolve global install path for ${packageName}`);
|
|
31
|
+
}
|
|
32
|
+
await execFileAsync("claude", ["plugin", "add", pkgPath, "--scope", scope], { timeout: 30_000 });
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|