swarmkit 0.0.1 → 0.0.2
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/LICENSE +21 -0
- package/README.md +194 -1
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +33 -0
- package/dist/commands/add.d.ts +2 -0
- package/dist/commands/add.js +55 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +100 -0
- package/dist/commands/hive.d.ts +2 -0
- package/dist/commands/hive.js +248 -0
- package/dist/commands/init/phases/configure.d.ts +2 -0
- package/dist/commands/init/phases/configure.js +85 -0
- package/dist/commands/init/phases/global-setup.d.ts +2 -0
- package/dist/commands/init/phases/global-setup.js +81 -0
- package/dist/commands/init/phases/packages.d.ts +2 -0
- package/dist/commands/init/phases/packages.js +30 -0
- package/dist/commands/init/phases/project.d.ts +2 -0
- package/dist/commands/init/phases/project.js +54 -0
- package/dist/commands/init/phases/use-case.d.ts +2 -0
- package/dist/commands/init/phases/use-case.js +41 -0
- package/dist/commands/init/state.d.ts +11 -0
- package/dist/commands/init/state.js +8 -0
- package/dist/commands/init/state.test.d.ts +1 -0
- package/dist/commands/init/state.test.js +20 -0
- package/dist/commands/init/wizard.d.ts +1 -0
- package/dist/commands/init/wizard.js +56 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +10 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +91 -0
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.js +19 -0
- package/dist/commands/remove.d.ts +2 -0
- package/dist/commands/remove.js +49 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +87 -0
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.js +54 -0
- package/dist/commands/whoami.d.ts +2 -0
- package/dist/commands/whoami.js +40 -0
- package/dist/config/global.d.ts +24 -0
- package/dist/config/global.js +71 -0
- package/dist/config/global.test.d.ts +1 -0
- package/dist/config/global.test.js +167 -0
- package/dist/config/keys.d.ts +10 -0
- package/dist/config/keys.js +47 -0
- package/dist/config/keys.test.d.ts +1 -0
- package/dist/config/keys.test.js +87 -0
- package/dist/doctor/checks.d.ts +31 -0
- package/dist/doctor/checks.js +210 -0
- package/dist/doctor/checks.test.d.ts +1 -0
- package/dist/doctor/checks.test.js +276 -0
- package/dist/doctor/types.d.ts +29 -0
- package/dist/doctor/types.js +1 -0
- package/dist/hub/auth-flow.d.ts +16 -0
- package/dist/hub/auth-flow.js +118 -0
- package/dist/hub/auth-flow.test.d.ts +1 -0
- package/dist/hub/auth-flow.test.js +98 -0
- package/dist/hub/client.d.ts +51 -0
- package/dist/hub/client.js +107 -0
- package/dist/hub/client.test.d.ts +1 -0
- package/dist/hub/client.test.js +177 -0
- package/dist/hub/credentials.d.ts +14 -0
- package/dist/hub/credentials.js +41 -0
- package/dist/hub/credentials.test.d.ts +1 -0
- package/dist/hub/credentials.test.js +102 -0
- package/dist/index.d.ts +16 -1
- package/dist/index.js +9 -2
- package/dist/packages/installer.d.ts +33 -0
- package/dist/packages/installer.js +127 -0
- package/dist/packages/installer.test.d.ts +1 -0
- package/dist/packages/installer.test.js +200 -0
- package/dist/packages/registry.d.ts +37 -0
- package/dist/packages/registry.js +179 -0
- package/dist/packages/registry.test.d.ts +1 -0
- package/dist/packages/registry.test.js +199 -0
- package/dist/packages/setup.d.ts +48 -0
- package/dist/packages/setup.js +309 -0
- package/dist/packages/setup.test.d.ts +1 -0
- package/dist/packages/setup.test.js +717 -0
- package/dist/utils/ui.d.ts +10 -0
- package/dist/utils/ui.js +47 -0
- package/dist/utils/ui.test.d.ts +1 -0
- package/dist/utils/ui.test.js +102 -0
- package/package.json +29 -6
|
@@ -0,0 +1,717 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { mkdirSync, rmSync, existsSync, readFileSync, writeFileSync, statSync, realpathSync, } from "node:fs";
|
|
3
|
+
import { join, basename } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import { execFile, execSync } from "node:child_process";
|
|
7
|
+
import { promisify } from "node:util";
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
// ─── Minimal mocking ────────────────────────────────────────────────────────
|
|
10
|
+
//
|
|
11
|
+
// Only ONE thing is mocked: homedir — so global-package tests don't pollute
|
|
12
|
+
// the real home directory. Everything else (filesystem, CLI shell-outs) is real.
|
|
13
|
+
//
|
|
14
|
+
// The shell-out tests call real published CLIs (opentasks, minimem, etc.).
|
|
15
|
+
// If a CLI is not installed, the test is skipped via a check helper.
|
|
16
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
let testHome;
|
|
18
|
+
vi.mock("node:os", async () => {
|
|
19
|
+
const actual = await import("node:os");
|
|
20
|
+
return { ...actual, homedir: () => testHome };
|
|
21
|
+
});
|
|
22
|
+
const { PROJECT_CONFIG_DIRS, PROJECT_INIT_ORDER, GLOBAL_CONFIG_DIRS, isProjectInit, isGlobalInit, initProjectPackage, initGlobalPackage, } = await import("./setup.js");
|
|
23
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
24
|
+
let testDir;
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
// Use realpathSync to resolve macOS /var → /private/var symlink.
|
|
27
|
+
// Git resolves symlinks in rev-parse --show-toplevel, so paths must match.
|
|
28
|
+
const realTmp = realpathSync(tmpdir());
|
|
29
|
+
testDir = join(realTmp, `swarmkit-setup-test-${randomUUID()}`);
|
|
30
|
+
testHome = join(realTmp, `swarmkit-setup-home-${randomUUID()}`);
|
|
31
|
+
mkdirSync(testDir, { recursive: true });
|
|
32
|
+
mkdirSync(testHome, { recursive: true });
|
|
33
|
+
});
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
36
|
+
rmSync(testHome, { recursive: true, force: true });
|
|
37
|
+
});
|
|
38
|
+
/** Check if a CLI binary is available on PATH */
|
|
39
|
+
async function hasCliInstalled(command) {
|
|
40
|
+
try {
|
|
41
|
+
await execFileAsync("which", [command]);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Create a realistic project directory with real git repo and package.json */
|
|
49
|
+
function createProject(name, dir = testDir) {
|
|
50
|
+
execSync("git init -q", { cwd: dir });
|
|
51
|
+
writeFileSync(join(dir, "package.json"), JSON.stringify({ name, version: "1.0.0" }));
|
|
52
|
+
return dir;
|
|
53
|
+
}
|
|
54
|
+
/** Shared init context builder */
|
|
55
|
+
function projectCtx(overrides = {}) {
|
|
56
|
+
return {
|
|
57
|
+
cwd: overrides.cwd ?? testDir,
|
|
58
|
+
packages: overrides.packages ?? [],
|
|
59
|
+
embeddingProvider: overrides.embeddingProvider ?? null,
|
|
60
|
+
apiKeys: overrides.apiKeys ?? {},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function globalCtx(overrides = {}) {
|
|
64
|
+
return {
|
|
65
|
+
packages: overrides.packages ?? [],
|
|
66
|
+
embeddingProvider: overrides.embeddingProvider ?? null,
|
|
67
|
+
apiKeys: overrides.apiKeys ?? {},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
71
|
+
describe("constants", () => {
|
|
72
|
+
it("PROJECT_INIT_ORDER is opentasks → minimem → cognitive-core → macro-agent → sdr", () => {
|
|
73
|
+
expect(PROJECT_INIT_ORDER).toEqual([
|
|
74
|
+
"opentasks",
|
|
75
|
+
"minimem",
|
|
76
|
+
"cognitive-core",
|
|
77
|
+
"macro-agent",
|
|
78
|
+
"self-driving-repo",
|
|
79
|
+
]);
|
|
80
|
+
});
|
|
81
|
+
it("every ordered package has a config dir mapping", () => {
|
|
82
|
+
for (const pkg of PROJECT_INIT_ORDER) {
|
|
83
|
+
expect(PROJECT_CONFIG_DIRS[pkg]).toBeDefined();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
it("GLOBAL_CONFIG_DIRS maps all global packages", () => {
|
|
87
|
+
expect(Object.keys(GLOBAL_CONFIG_DIRS).sort()).toEqual(["agent-iam", "openhive", "openswarm", "skill-tree"]);
|
|
88
|
+
});
|
|
89
|
+
it("no overlap between project and global config dirs", () => {
|
|
90
|
+
const projectDirs = new Set(Object.values(PROJECT_CONFIG_DIRS));
|
|
91
|
+
const globalDirs = new Set(Object.values(GLOBAL_CONFIG_DIRS));
|
|
92
|
+
for (const d of projectDirs) {
|
|
93
|
+
expect(globalDirs.has(d)).toBe(false);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
// ─── Detection: isProjectInit ────────────────────────────────────────────────
|
|
98
|
+
describe("isProjectInit", () => {
|
|
99
|
+
it("returns false for every package in an empty directory", () => {
|
|
100
|
+
for (const pkg of PROJECT_INIT_ORDER) {
|
|
101
|
+
expect(isProjectInit(testDir, pkg)).toBe(false);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
it.each(Object.entries(PROJECT_CONFIG_DIRS))("returns true for %s when %s/ exists", (pkg, configDir) => {
|
|
105
|
+
mkdirSync(join(testDir, configDir), { recursive: true });
|
|
106
|
+
expect(isProjectInit(testDir, pkg)).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
it("returns false for unknown package", () => {
|
|
109
|
+
expect(isProjectInit(testDir, "nonexistent")).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
it("returns false for nonexistent base dir", () => {
|
|
112
|
+
expect(isProjectInit("/does/not/exist", "opentasks")).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
// ─── Detection: isGlobalInit ─────────────────────────────────────────────────
|
|
116
|
+
describe("isGlobalInit", () => {
|
|
117
|
+
it("returns false for every global package in a fresh home", () => {
|
|
118
|
+
for (const pkg of Object.keys(GLOBAL_CONFIG_DIRS)) {
|
|
119
|
+
expect(isGlobalInit(pkg)).toBe(false);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
it.each(Object.entries(GLOBAL_CONFIG_DIRS))("returns true for %s when ~/%s/ exists", (pkg, configDir) => {
|
|
123
|
+
mkdirSync(join(testHome, configDir), { recursive: true });
|
|
124
|
+
expect(isGlobalInit(pkg)).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
it("returns false for unknown package", () => {
|
|
127
|
+
expect(isGlobalInit("nonexistent")).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
// ─── Project: opentasks (real CLI) ───────────────────────────────────────────
|
|
131
|
+
describe("initProjectPackage — opentasks (real CLI)", async () => {
|
|
132
|
+
const installed = await hasCliInstalled("opentasks");
|
|
133
|
+
it.skipIf(!installed)("runs opentasks init and creates .opentasks/", async () => {
|
|
134
|
+
createProject("test-opentasks");
|
|
135
|
+
const result = await initProjectPackage("opentasks", projectCtx({ cwd: testDir, packages: ["opentasks"] }));
|
|
136
|
+
expect(result.success).toBe(true);
|
|
137
|
+
expect(result.package).toBe("opentasks");
|
|
138
|
+
// Verify real filesystem artifacts
|
|
139
|
+
expect(existsSync(join(testDir, ".opentasks"))).toBe(true);
|
|
140
|
+
expect(existsSync(join(testDir, ".opentasks", "config.json"))).toBe(true);
|
|
141
|
+
expect(existsSync(join(testDir, ".opentasks", "graph.jsonl"))).toBe(true);
|
|
142
|
+
expect(existsSync(join(testDir, ".opentasks", ".gitignore"))).toBe(true);
|
|
143
|
+
// Verify config content
|
|
144
|
+
const config = JSON.parse(readFileSync(join(testDir, ".opentasks", "config.json"), "utf-8"));
|
|
145
|
+
expect(config.version).toBe("1.0");
|
|
146
|
+
expect(config.location.name).toBe("test-opentasks");
|
|
147
|
+
expect(config.location.hash).toBeDefined();
|
|
148
|
+
expect(config.location.uuid).toBeDefined();
|
|
149
|
+
});
|
|
150
|
+
it.skipIf(!installed)("uses project name from package.json", async () => {
|
|
151
|
+
createProject("my-custom-name");
|
|
152
|
+
await initProjectPackage("opentasks", projectCtx({ cwd: testDir, packages: ["opentasks"] }));
|
|
153
|
+
const config = JSON.parse(readFileSync(join(testDir, ".opentasks", "config.json"), "utf-8"));
|
|
154
|
+
expect(config.location.name).toBe("my-custom-name");
|
|
155
|
+
});
|
|
156
|
+
it.skipIf(!installed)("falls back to directory name without package.json", async () => {
|
|
157
|
+
// No package.json, just a real git repo
|
|
158
|
+
execSync("git init -q", { cwd: testDir });
|
|
159
|
+
await initProjectPackage("opentasks", projectCtx({ cwd: testDir, packages: ["opentasks"] }));
|
|
160
|
+
const config = JSON.parse(readFileSync(join(testDir, ".opentasks", "config.json"), "utf-8"));
|
|
161
|
+
expect(config.location.name).toBe(basename(testDir));
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
// ─── Project: minimem (real CLI) + embedding patching ────────────────────────
|
|
165
|
+
describe("initProjectPackage — minimem (real CLI)", async () => {
|
|
166
|
+
const installed = await hasCliInstalled("minimem");
|
|
167
|
+
it.skipIf(!installed)("runs minimem init and creates .minimem/", async () => {
|
|
168
|
+
createProject("test-minimem");
|
|
169
|
+
const result = await initProjectPackage("minimem", projectCtx({ cwd: testDir, packages: ["minimem"] }));
|
|
170
|
+
expect(result.success).toBe(true);
|
|
171
|
+
// Verify real filesystem artifacts
|
|
172
|
+
expect(existsSync(join(testDir, ".minimem"))).toBe(true);
|
|
173
|
+
expect(existsSync(join(testDir, ".minimem", "config.json"))).toBe(true);
|
|
174
|
+
expect(existsSync(join(testDir, ".minimem", ".gitignore"))).toBe(true);
|
|
175
|
+
expect(existsSync(join(testDir, "MEMORY.md"))).toBe(true);
|
|
176
|
+
// Verify default config
|
|
177
|
+
const config = JSON.parse(readFileSync(join(testDir, ".minimem", "config.json"), "utf-8"));
|
|
178
|
+
expect(config.embedding.provider).toBe("auto");
|
|
179
|
+
expect(config.hybrid.enabled).toBe(true);
|
|
180
|
+
});
|
|
181
|
+
it.skipIf(!installed)("patches embedding.provider to openai", async () => {
|
|
182
|
+
createProject("test-embed");
|
|
183
|
+
await initProjectPackage("minimem", projectCtx({
|
|
184
|
+
cwd: testDir,
|
|
185
|
+
packages: ["minimem"],
|
|
186
|
+
embeddingProvider: "openai",
|
|
187
|
+
}));
|
|
188
|
+
const config = JSON.parse(readFileSync(join(testDir, ".minimem", "config.json"), "utf-8"));
|
|
189
|
+
expect(config.embedding.provider).toBe("openai");
|
|
190
|
+
// Other fields preserved
|
|
191
|
+
expect(config.hybrid.enabled).toBe(true);
|
|
192
|
+
expect(config.query.maxResults).toBe(10);
|
|
193
|
+
});
|
|
194
|
+
it.skipIf(!installed)("patches embedding.provider to gemini", async () => {
|
|
195
|
+
createProject("test-embed-gemini");
|
|
196
|
+
await initProjectPackage("minimem", projectCtx({
|
|
197
|
+
cwd: testDir,
|
|
198
|
+
packages: ["minimem"],
|
|
199
|
+
embeddingProvider: "gemini",
|
|
200
|
+
}));
|
|
201
|
+
const config = JSON.parse(readFileSync(join(testDir, ".minimem", "config.json"), "utf-8"));
|
|
202
|
+
expect(config.embedding.provider).toBe("gemini");
|
|
203
|
+
});
|
|
204
|
+
it.skipIf(!installed)("does NOT patch when provider is local", async () => {
|
|
205
|
+
createProject("test-local");
|
|
206
|
+
await initProjectPackage("minimem", projectCtx({
|
|
207
|
+
cwd: testDir,
|
|
208
|
+
packages: ["minimem"],
|
|
209
|
+
embeddingProvider: "local",
|
|
210
|
+
}));
|
|
211
|
+
const config = JSON.parse(readFileSync(join(testDir, ".minimem", "config.json"), "utf-8"));
|
|
212
|
+
expect(config.embedding.provider).toBe("auto");
|
|
213
|
+
});
|
|
214
|
+
it.skipIf(!installed)("does NOT patch when provider is null", async () => {
|
|
215
|
+
createProject("test-null");
|
|
216
|
+
await initProjectPackage("minimem", projectCtx({
|
|
217
|
+
cwd: testDir,
|
|
218
|
+
packages: ["minimem"],
|
|
219
|
+
embeddingProvider: null,
|
|
220
|
+
}));
|
|
221
|
+
const config = JSON.parse(readFileSync(join(testDir, ".minimem", "config.json"), "utf-8"));
|
|
222
|
+
expect(config.embedding.provider).toBe("auto");
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
// ─── Project: cognitive-core (real CLI) ──────────────────────────────────────
|
|
226
|
+
describe("initProjectPackage — cognitive-core (real CLI)", async () => {
|
|
227
|
+
const installed = await hasCliInstalled("cognitive-core");
|
|
228
|
+
it.skipIf(!installed)("runs cognitive-core init and creates .cognitive-core/", async () => {
|
|
229
|
+
createProject("test-cc");
|
|
230
|
+
const result = await initProjectPackage("cognitive-core", projectCtx({ cwd: testDir, packages: ["cognitive-core"] }));
|
|
231
|
+
expect(result.success).toBe(true);
|
|
232
|
+
// Verify directory structure
|
|
233
|
+
expect(existsSync(join(testDir, ".cognitive-core"))).toBe(true);
|
|
234
|
+
expect(existsSync(join(testDir, ".cognitive-core", "experiences"))).toBe(true);
|
|
235
|
+
expect(existsSync(join(testDir, ".cognitive-core", "playbooks"))).toBe(true);
|
|
236
|
+
expect(existsSync(join(testDir, ".cognitive-core", "meta-strategies"))).toBe(true);
|
|
237
|
+
expect(existsSync(join(testDir, ".cognitive-core", "meta-observations"))).toBe(true);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
// ─── Project: macro-agent (direct write — no CLI) ───────────────────────────
|
|
241
|
+
describe("initProjectPackage — macro-agent (direct config write)", () => {
|
|
242
|
+
it("creates .multiagent/config.json", async () => {
|
|
243
|
+
const result = await initProjectPackage("macro-agent", projectCtx({ cwd: testDir, packages: ["macro-agent"] }));
|
|
244
|
+
expect(result.success).toBe(true);
|
|
245
|
+
expect(existsSync(join(testDir, ".multiagent", "config.json"))).toBe(true);
|
|
246
|
+
});
|
|
247
|
+
it("config has correct defaults", async () => {
|
|
248
|
+
await initProjectPackage("macro-agent", projectCtx({ cwd: testDir, packages: ["macro-agent"] }));
|
|
249
|
+
const config = JSON.parse(readFileSync(join(testDir, ".multiagent", "config.json"), "utf-8"));
|
|
250
|
+
expect(config).toEqual({
|
|
251
|
+
team: "default",
|
|
252
|
+
port: 3001,
|
|
253
|
+
host: "localhost",
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
it("wires opentasks as task backend when opentasks is selected", async () => {
|
|
257
|
+
await initProjectPackage("macro-agent", projectCtx({ cwd: testDir, packages: ["macro-agent", "opentasks"] }));
|
|
258
|
+
const config = JSON.parse(readFileSync(join(testDir, ".multiagent", "config.json"), "utf-8"));
|
|
259
|
+
expect(config.task).toEqual({
|
|
260
|
+
backend: "opentasks",
|
|
261
|
+
opentasks: { auto_start: true },
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
it("omits task backend when opentasks not selected", async () => {
|
|
265
|
+
await initProjectPackage("macro-agent", projectCtx({ cwd: testDir, packages: ["macro-agent"] }));
|
|
266
|
+
const config = JSON.parse(readFileSync(join(testDir, ".multiagent", "config.json"), "utf-8"));
|
|
267
|
+
expect(config.task).toBeUndefined();
|
|
268
|
+
});
|
|
269
|
+
it("is idempotent — second run overwrites cleanly", async () => {
|
|
270
|
+
await initProjectPackage("macro-agent", projectCtx({ cwd: testDir, packages: ["macro-agent", "opentasks"] }));
|
|
271
|
+
await initProjectPackage("macro-agent", projectCtx({ cwd: testDir, packages: ["macro-agent"] }));
|
|
272
|
+
const config = JSON.parse(readFileSync(join(testDir, ".multiagent", "config.json"), "utf-8"));
|
|
273
|
+
expect(config.task).toBeUndefined();
|
|
274
|
+
});
|
|
275
|
+
it("produces valid JSON with trailing newline", async () => {
|
|
276
|
+
await initProjectPackage("macro-agent", projectCtx({ cwd: testDir, packages: ["macro-agent", "opentasks"] }));
|
|
277
|
+
const raw = readFileSync(join(testDir, ".multiagent", "config.json"), "utf-8");
|
|
278
|
+
expect(() => JSON.parse(raw)).not.toThrow();
|
|
279
|
+
expect(raw.endsWith("\n")).toBe(true);
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
// ─── Project: self-driving-repo ──────────────────────────────────────────────
|
|
283
|
+
// sdr is not published with a bin — skip real CLI test
|
|
284
|
+
describe("initProjectPackage — self-driving-repo", async () => {
|
|
285
|
+
const installed = await hasCliInstalled("sdr");
|
|
286
|
+
it.skipIf(!installed)("runs sdr init and creates .self-driving/", async () => {
|
|
287
|
+
createProject("test-sdr");
|
|
288
|
+
const result = await initProjectPackage("self-driving-repo", projectCtx({ cwd: testDir, packages: ["self-driving-repo"] }));
|
|
289
|
+
expect(result.success).toBe(true);
|
|
290
|
+
expect(existsSync(join(testDir, ".self-driving"))).toBe(true);
|
|
291
|
+
});
|
|
292
|
+
it.skipIf(installed)("returns failure when sdr CLI is not installed", async () => {
|
|
293
|
+
const result = await initProjectPackage("self-driving-repo", projectCtx({ cwd: testDir, packages: ["self-driving-repo"] }));
|
|
294
|
+
expect(result.success).toBe(false);
|
|
295
|
+
expect(result.package).toBe("self-driving-repo");
|
|
296
|
+
expect(result.message).toBeDefined();
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
// ─── Project: unknown ────────────────────────────────────────────────────────
|
|
300
|
+
describe("initProjectPackage — unknown", () => {
|
|
301
|
+
it("returns failure with descriptive message", async () => {
|
|
302
|
+
const result = await initProjectPackage("not-a-package", projectCtx({ cwd: testDir }));
|
|
303
|
+
expect(result).toEqual({
|
|
304
|
+
package: "not-a-package",
|
|
305
|
+
success: false,
|
|
306
|
+
message: "Unknown package",
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
// ─── Global: agent-iam (real CLI) ────────────────────────────────────────────
|
|
311
|
+
describe("initGlobalPackage — agent-iam (real CLI)", async () => {
|
|
312
|
+
const installed = await hasCliInstalled("agent-iam");
|
|
313
|
+
it.skipIf(!installed)("creates ~/.agent-credentials/ with 0700 permissions", async () => {
|
|
314
|
+
const result = await initGlobalPackage("agent-iam", globalCtx({ packages: ["agent-iam"] }));
|
|
315
|
+
expect(result.success).toBe(true);
|
|
316
|
+
expect(existsSync(join(testHome, ".agent-credentials"))).toBe(true);
|
|
317
|
+
const stats = statSync(join(testHome, ".agent-credentials"));
|
|
318
|
+
expect(stats.mode & 0o777).toBe(0o700);
|
|
319
|
+
});
|
|
320
|
+
it.skipIf(!installed)("registers openai API key via real CLI", async () => {
|
|
321
|
+
const result = await initGlobalPackage("agent-iam", globalCtx({
|
|
322
|
+
packages: ["agent-iam"],
|
|
323
|
+
apiKeys: { openai: "sk-test-integration-key" },
|
|
324
|
+
}));
|
|
325
|
+
expect(result.success).toBe(true);
|
|
326
|
+
// Verify the key was actually stored by agent-iam
|
|
327
|
+
const config = JSON.parse(readFileSync(join(testHome, ".agent-credentials", "config.json"), "utf-8"));
|
|
328
|
+
expect(config.providers.apikeys.openai.apiKey).toBe("sk-test-integration-key");
|
|
329
|
+
expect(config.providers.apikeys.openai.providerName).toBe("openai");
|
|
330
|
+
});
|
|
331
|
+
it.skipIf(!installed)("registers multiple API keys", async () => {
|
|
332
|
+
await initGlobalPackage("agent-iam", globalCtx({
|
|
333
|
+
packages: ["agent-iam"],
|
|
334
|
+
apiKeys: {
|
|
335
|
+
openai: "sk-o",
|
|
336
|
+
anthropic: "sk-a",
|
|
337
|
+
gemini: "g-1",
|
|
338
|
+
},
|
|
339
|
+
}));
|
|
340
|
+
const config = JSON.parse(readFileSync(join(testHome, ".agent-credentials", "config.json"), "utf-8"));
|
|
341
|
+
expect(config.providers.apikeys.openai).toBeDefined();
|
|
342
|
+
expect(config.providers.apikeys.anthropic).toBeDefined();
|
|
343
|
+
expect(config.providers.apikeys.gemini).toBeDefined();
|
|
344
|
+
});
|
|
345
|
+
it.skipIf(!installed)("ignores non-standard provider keys", async () => {
|
|
346
|
+
await initGlobalPackage("agent-iam", globalCtx({
|
|
347
|
+
packages: ["agent-iam"],
|
|
348
|
+
apiKeys: { custom: "xxx" },
|
|
349
|
+
}));
|
|
350
|
+
// Only credentials dir exists, no apikeys config
|
|
351
|
+
const configPath = join(testHome, ".agent-credentials", "config.json");
|
|
352
|
+
if (existsSync(configPath)) {
|
|
353
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
354
|
+
expect(config.providers?.apikeys?.custom).toBeUndefined();
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
it.skipIf(!installed)("does not recreate dir if it already exists", async () => {
|
|
358
|
+
mkdirSync(join(testHome, ".agent-credentials"), { mode: 0o700 });
|
|
359
|
+
writeFileSync(join(testHome, ".agent-credentials", "token_secret"), "existing-secret");
|
|
360
|
+
await initGlobalPackage("agent-iam", globalCtx({ packages: ["agent-iam"] }));
|
|
361
|
+
// Existing file preserved
|
|
362
|
+
expect(readFileSync(join(testHome, ".agent-credentials", "token_secret"), "utf-8")).toBe("existing-secret");
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
// ─── Global: skill-tree (real CLI) ───────────────────────────────────────────
|
|
366
|
+
describe("initGlobalPackage — skill-tree (real CLI)", async () => {
|
|
367
|
+
const installed = await hasCliInstalled("skill-tree");
|
|
368
|
+
it.skipIf(!installed)("runs skill-tree config init and creates config.yaml", async () => {
|
|
369
|
+
const result = await initGlobalPackage("skill-tree", globalCtx({ packages: ["skill-tree"] }));
|
|
370
|
+
expect(result.success).toBe(true);
|
|
371
|
+
const configPath = join(testHome, ".skill-tree", "config.yaml");
|
|
372
|
+
expect(existsSync(configPath)).toBe(true);
|
|
373
|
+
const yaml = readFileSync(configPath, "utf-8");
|
|
374
|
+
expect(yaml).toContain("storage:");
|
|
375
|
+
expect(yaml).toContain("sqlite");
|
|
376
|
+
expect(yaml).toContain("indexer:");
|
|
377
|
+
});
|
|
378
|
+
it.skipIf(!installed)("skips when config.yaml already exists", async () => {
|
|
379
|
+
mkdirSync(join(testHome, ".skill-tree"), { recursive: true });
|
|
380
|
+
writeFileSync(join(testHome, ".skill-tree", "config.yaml"), "custom: true\n");
|
|
381
|
+
const result = await initGlobalPackage("skill-tree", globalCtx({ packages: ["skill-tree"] }));
|
|
382
|
+
expect(result.success).toBe(true);
|
|
383
|
+
expect(result.message).toBe("already configured");
|
|
384
|
+
// Original content preserved
|
|
385
|
+
const yaml = readFileSync(join(testHome, ".skill-tree", "config.yaml"), "utf-8");
|
|
386
|
+
expect(yaml).toBe("custom: true\n");
|
|
387
|
+
});
|
|
388
|
+
it.skipIf(!installed)("does NOT skip when directory exists but config.yaml is missing", async () => {
|
|
389
|
+
mkdirSync(join(testHome, ".skill-tree"), { recursive: true });
|
|
390
|
+
const result = await initGlobalPackage("skill-tree", globalCtx({ packages: ["skill-tree"] }));
|
|
391
|
+
expect(result.success).toBe(true);
|
|
392
|
+
expect(result.message).toBeUndefined();
|
|
393
|
+
expect(existsSync(join(testHome, ".skill-tree", "config.yaml"))).toBe(true);
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
// ─── Global: openswarm (direct config write — no CLI) ────────────────────────
|
|
397
|
+
describe("initGlobalPackage — openswarm (direct config write)", () => {
|
|
398
|
+
it("creates ~/.openswarm/server.json", async () => {
|
|
399
|
+
const result = await initGlobalPackage("openswarm", globalCtx({ packages: ["openswarm"] }));
|
|
400
|
+
expect(result.success).toBe(true);
|
|
401
|
+
expect(existsSync(join(testHome, ".openswarm", "server.json"))).toBe(true);
|
|
402
|
+
});
|
|
403
|
+
it("config defaults are correct", async () => {
|
|
404
|
+
await initGlobalPackage("openswarm", globalCtx({ packages: ["openswarm"] }));
|
|
405
|
+
const config = JSON.parse(readFileSync(join(testHome, ".openswarm", "server.json"), "utf-8"));
|
|
406
|
+
expect(config).toEqual({
|
|
407
|
+
host: "localhost",
|
|
408
|
+
port: 3000,
|
|
409
|
+
auth: { mode: "none" },
|
|
410
|
+
storage: { type: "memory" },
|
|
411
|
+
logging: { level: "info" },
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
it("wires macro-agent adapter when macro-agent is selected", async () => {
|
|
415
|
+
await initGlobalPackage("openswarm", globalCtx({ packages: ["openswarm", "macro-agent"] }));
|
|
416
|
+
const config = JSON.parse(readFileSync(join(testHome, ".openswarm", "server.json"), "utf-8"));
|
|
417
|
+
expect(config.adapter).toEqual({ id: "macro-agent" });
|
|
418
|
+
});
|
|
419
|
+
it("omits adapter when macro-agent not selected", async () => {
|
|
420
|
+
await initGlobalPackage("openswarm", globalCtx({ packages: ["openswarm"] }));
|
|
421
|
+
const config = JSON.parse(readFileSync(join(testHome, ".openswarm", "server.json"), "utf-8"));
|
|
422
|
+
expect(config.adapter).toBeUndefined();
|
|
423
|
+
});
|
|
424
|
+
it("skips when server.json already exists (preserves existing)", async () => {
|
|
425
|
+
mkdirSync(join(testHome, ".openswarm"), { recursive: true });
|
|
426
|
+
writeFileSync(join(testHome, ".openswarm", "server.json"), JSON.stringify({ custom: true }));
|
|
427
|
+
const result = await initGlobalPackage("openswarm", globalCtx({ packages: ["openswarm", "macro-agent"] }));
|
|
428
|
+
expect(result.success).toBe(true);
|
|
429
|
+
expect(result.message).toBe("already configured");
|
|
430
|
+
// Original config NOT overwritten
|
|
431
|
+
const config = JSON.parse(readFileSync(join(testHome, ".openswarm", "server.json"), "utf-8"));
|
|
432
|
+
expect(config.custom).toBe(true);
|
|
433
|
+
expect(config.adapter).toBeUndefined();
|
|
434
|
+
});
|
|
435
|
+
it("produces valid JSON with trailing newline", async () => {
|
|
436
|
+
await initGlobalPackage("openswarm", globalCtx({ packages: ["openswarm"] }));
|
|
437
|
+
const raw = readFileSync(join(testHome, ".openswarm", "server.json"), "utf-8");
|
|
438
|
+
expect(() => JSON.parse(raw)).not.toThrow();
|
|
439
|
+
expect(raw.endsWith("\n")).toBe(true);
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
// ─── Global: openhive ────────────────────────────────────────────────────────
|
|
443
|
+
// openhive 0.0.1 is a stub publish without a bin entry
|
|
444
|
+
describe("initGlobalPackage — openhive", async () => {
|
|
445
|
+
const installed = await hasCliInstalled("openhive");
|
|
446
|
+
it.skipIf(!installed)("calls openhive init with non-interactive flags", async () => {
|
|
447
|
+
const result = await initGlobalPackage("openhive", globalCtx({ packages: ["openhive"] }), {
|
|
448
|
+
name: "TestHive",
|
|
449
|
+
port: 4000,
|
|
450
|
+
authMode: "token",
|
|
451
|
+
verification: "invite",
|
|
452
|
+
});
|
|
453
|
+
expect(result.success).toBe(true);
|
|
454
|
+
});
|
|
455
|
+
it("returns failure when openhive options are omitted", async () => {
|
|
456
|
+
const result = await initGlobalPackage("openhive", globalCtx({ packages: ["openhive"] }));
|
|
457
|
+
expect(result.success).toBe(false);
|
|
458
|
+
expect(result.message).toContain("No openhive options");
|
|
459
|
+
});
|
|
460
|
+
it.skipIf(installed)("returns failure when openhive CLI is not installed", async () => {
|
|
461
|
+
const result = await initGlobalPackage("openhive", globalCtx({ packages: ["openhive"] }), {
|
|
462
|
+
name: "H",
|
|
463
|
+
port: 3000,
|
|
464
|
+
authMode: "local",
|
|
465
|
+
verification: "open",
|
|
466
|
+
});
|
|
467
|
+
expect(result.success).toBe(false);
|
|
468
|
+
expect(result.message).toBeDefined();
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
// ─── Global: openteams (install-only — no config) ────────────────────────────
|
|
472
|
+
describe("initGlobalPackage — openteams", () => {
|
|
473
|
+
it("returns success with no-setup message", async () => {
|
|
474
|
+
const result = await initGlobalPackage("openteams", globalCtx({ packages: ["openteams"] }));
|
|
475
|
+
expect(result).toEqual({
|
|
476
|
+
package: "openteams",
|
|
477
|
+
success: true,
|
|
478
|
+
message: "no setup required",
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
// ─── Global: sessionlog (install-only — no config) ───────────────────────────
|
|
483
|
+
describe("initGlobalPackage — sessionlog", () => {
|
|
484
|
+
it("returns success with no-setup message", async () => {
|
|
485
|
+
const result = await initGlobalPackage("sessionlog", globalCtx({ packages: ["sessionlog"] }));
|
|
486
|
+
expect(result).toEqual({
|
|
487
|
+
package: "sessionlog",
|
|
488
|
+
success: true,
|
|
489
|
+
message: "no setup required",
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
// ─── Global: unknown ─────────────────────────────────────────────────────────
|
|
494
|
+
describe("initGlobalPackage — unknown", () => {
|
|
495
|
+
it("returns failure", async () => {
|
|
496
|
+
const result = await initGlobalPackage("nonexistent", globalCtx());
|
|
497
|
+
expect(result).toEqual({
|
|
498
|
+
package: "nonexistent",
|
|
499
|
+
success: false,
|
|
500
|
+
message: "Unknown package",
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
// ─── E2E: full project init flow (solo bundle) ──────────────────────────────
|
|
505
|
+
describe("e2e: project init — solo bundle", async () => {
|
|
506
|
+
const opentasksOk = await hasCliInstalled("opentasks");
|
|
507
|
+
const minimemOk = await hasCliInstalled("minimem");
|
|
508
|
+
const allOk = opentasksOk && minimemOk;
|
|
509
|
+
it.skipIf(!allOk)("initializes opentasks → minimem → macro-agent with cross-wiring", async () => {
|
|
510
|
+
createProject("solo-e2e");
|
|
511
|
+
const ctx = projectCtx({
|
|
512
|
+
cwd: testDir,
|
|
513
|
+
packages: ["opentasks", "minimem", "macro-agent"],
|
|
514
|
+
embeddingProvider: "openai",
|
|
515
|
+
});
|
|
516
|
+
const results = [];
|
|
517
|
+
for (const pkg of PROJECT_INIT_ORDER) {
|
|
518
|
+
if (!ctx.packages.includes(pkg))
|
|
519
|
+
continue;
|
|
520
|
+
if (isProjectInit(ctx.cwd, pkg))
|
|
521
|
+
continue;
|
|
522
|
+
results.push(await initProjectPackage(pkg, ctx));
|
|
523
|
+
}
|
|
524
|
+
// All succeeded
|
|
525
|
+
expect(results.every((r) => r.success)).toBe(true);
|
|
526
|
+
expect(results.map((r) => r.package)).toEqual([
|
|
527
|
+
"opentasks",
|
|
528
|
+
"minimem",
|
|
529
|
+
"macro-agent",
|
|
530
|
+
]);
|
|
531
|
+
// opentasks: real config with project name
|
|
532
|
+
const otConfig = JSON.parse(readFileSync(join(testDir, ".opentasks", "config.json"), "utf-8"));
|
|
533
|
+
expect(otConfig.location.name).toBe("solo-e2e");
|
|
534
|
+
// minimem: real config patched with openai
|
|
535
|
+
const mmConfig = JSON.parse(readFileSync(join(testDir, ".minimem", "config.json"), "utf-8"));
|
|
536
|
+
expect(mmConfig.embedding.provider).toBe("openai");
|
|
537
|
+
expect(mmConfig.hybrid.enabled).toBe(true);
|
|
538
|
+
// macro-agent: config wires opentasks
|
|
539
|
+
const maConfig = JSON.parse(readFileSync(join(testDir, ".multiagent", "config.json"), "utf-8"));
|
|
540
|
+
expect(maConfig.task.backend).toBe("opentasks");
|
|
541
|
+
expect(maConfig.task.opentasks.auto_start).toBe(true);
|
|
542
|
+
});
|
|
543
|
+
it.skipIf(!allOk)("skips already-initialized packages", async () => {
|
|
544
|
+
createProject("skip-e2e");
|
|
545
|
+
// Pre-initialize opentasks
|
|
546
|
+
const preCtx = projectCtx({
|
|
547
|
+
cwd: testDir,
|
|
548
|
+
packages: ["opentasks"],
|
|
549
|
+
});
|
|
550
|
+
await initProjectPackage("opentasks", preCtx);
|
|
551
|
+
expect(isProjectInit(testDir, "opentasks")).toBe(true);
|
|
552
|
+
// Now run full init — opentasks should be skipped
|
|
553
|
+
const ctx = projectCtx({
|
|
554
|
+
cwd: testDir,
|
|
555
|
+
packages: ["opentasks", "minimem", "macro-agent"],
|
|
556
|
+
});
|
|
557
|
+
const results = [];
|
|
558
|
+
for (const pkg of PROJECT_INIT_ORDER) {
|
|
559
|
+
if (!ctx.packages.includes(pkg))
|
|
560
|
+
continue;
|
|
561
|
+
if (isProjectInit(ctx.cwd, pkg))
|
|
562
|
+
continue;
|
|
563
|
+
results.push(await initProjectPackage(pkg, ctx));
|
|
564
|
+
}
|
|
565
|
+
// opentasks was skipped
|
|
566
|
+
expect(results.map((r) => r.package)).toEqual([
|
|
567
|
+
"minimem",
|
|
568
|
+
"macro-agent",
|
|
569
|
+
]);
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
// ─── E2E: full project init flow (team bundle — init order) ─────────────────
|
|
573
|
+
describe("e2e: project init — team bundle (init order)", async () => {
|
|
574
|
+
const opentasksOk = await hasCliInstalled("opentasks");
|
|
575
|
+
const minimemOk = await hasCliInstalled("minimem");
|
|
576
|
+
const ccOk = await hasCliInstalled("cognitive-core");
|
|
577
|
+
const allOk = opentasksOk && minimemOk && ccOk;
|
|
578
|
+
it.skipIf(!allOk)("cognitive-core runs after minimem (can detect .minimem/)", async () => {
|
|
579
|
+
createProject("team-e2e");
|
|
580
|
+
const ctx = projectCtx({
|
|
581
|
+
cwd: testDir,
|
|
582
|
+
packages: [
|
|
583
|
+
"opentasks",
|
|
584
|
+
"minimem",
|
|
585
|
+
"cognitive-core",
|
|
586
|
+
"macro-agent",
|
|
587
|
+
],
|
|
588
|
+
embeddingProvider: "gemini",
|
|
589
|
+
});
|
|
590
|
+
const results = [];
|
|
591
|
+
for (const pkg of PROJECT_INIT_ORDER) {
|
|
592
|
+
if (!ctx.packages.includes(pkg))
|
|
593
|
+
continue;
|
|
594
|
+
if (isProjectInit(ctx.cwd, pkg))
|
|
595
|
+
continue;
|
|
596
|
+
results.push(await initProjectPackage(pkg, ctx));
|
|
597
|
+
}
|
|
598
|
+
expect(results.every((r) => r.success)).toBe(true);
|
|
599
|
+
expect(results.map((r) => r.package)).toEqual([
|
|
600
|
+
"opentasks",
|
|
601
|
+
"minimem",
|
|
602
|
+
"cognitive-core",
|
|
603
|
+
"macro-agent",
|
|
604
|
+
]);
|
|
605
|
+
// All directories exist
|
|
606
|
+
expect(existsSync(join(testDir, ".opentasks"))).toBe(true);
|
|
607
|
+
expect(existsSync(join(testDir, ".minimem"))).toBe(true);
|
|
608
|
+
expect(existsSync(join(testDir, ".cognitive-core"))).toBe(true);
|
|
609
|
+
expect(existsSync(join(testDir, ".multiagent"))).toBe(true);
|
|
610
|
+
// minimem was patched to gemini
|
|
611
|
+
const mmConfig = JSON.parse(readFileSync(join(testDir, ".minimem", "config.json"), "utf-8"));
|
|
612
|
+
expect(mmConfig.embedding.provider).toBe("gemini");
|
|
613
|
+
});
|
|
614
|
+
});
|
|
615
|
+
// ─── E2E: full global init flow (platform bundle) ───────────────────────────
|
|
616
|
+
describe("e2e: global init — platform bundle", async () => {
|
|
617
|
+
const iamOk = await hasCliInstalled("agent-iam");
|
|
618
|
+
const stOk = await hasCliInstalled("skill-tree");
|
|
619
|
+
const allOk = iamOk && stOk;
|
|
620
|
+
it.skipIf(!allOk)("initializes agent-iam, skill-tree, and openswarm", async () => {
|
|
621
|
+
const ctx = globalCtx({
|
|
622
|
+
packages: [
|
|
623
|
+
"agent-iam",
|
|
624
|
+
"skill-tree",
|
|
625
|
+
"openswarm",
|
|
626
|
+
"macro-agent",
|
|
627
|
+
],
|
|
628
|
+
apiKeys: { anthropic: "sk-ant-e2e" },
|
|
629
|
+
});
|
|
630
|
+
const globalOrder = ["agent-iam", "skill-tree", "openswarm"];
|
|
631
|
+
const results = [];
|
|
632
|
+
for (const pkg of globalOrder) {
|
|
633
|
+
if (!ctx.packages.includes(pkg))
|
|
634
|
+
continue;
|
|
635
|
+
if (isGlobalInit(pkg))
|
|
636
|
+
continue;
|
|
637
|
+
results.push(await initGlobalPackage(pkg, ctx));
|
|
638
|
+
}
|
|
639
|
+
expect(results.every((r) => r.success)).toBe(true);
|
|
640
|
+
expect(results.map((r) => r.package)).toEqual([
|
|
641
|
+
"agent-iam",
|
|
642
|
+
"skill-tree",
|
|
643
|
+
"openswarm",
|
|
644
|
+
]);
|
|
645
|
+
// agent-iam: credentials dir exists, anthropic key registered
|
|
646
|
+
expect(existsSync(join(testHome, ".agent-credentials"))).toBe(true);
|
|
647
|
+
const iamConfig = JSON.parse(readFileSync(join(testHome, ".agent-credentials", "config.json"), "utf-8"));
|
|
648
|
+
expect(iamConfig.providers.apikeys.anthropic.apiKey).toBe("sk-ant-e2e");
|
|
649
|
+
// skill-tree: config.yaml exists
|
|
650
|
+
expect(existsSync(join(testHome, ".skill-tree", "config.yaml"))).toBe(true);
|
|
651
|
+
// openswarm: server.json wires macro-agent adapter
|
|
652
|
+
const swarmConfig = JSON.parse(readFileSync(join(testHome, ".openswarm", "server.json"), "utf-8"));
|
|
653
|
+
expect(swarmConfig.adapter).toEqual({ id: "macro-agent" });
|
|
654
|
+
});
|
|
655
|
+
it.skipIf(!allOk)("skips already-configured global packages", async () => {
|
|
656
|
+
// Pre-create skill-tree config
|
|
657
|
+
mkdirSync(join(testHome, ".skill-tree"), { recursive: true });
|
|
658
|
+
writeFileSync(join(testHome, ".skill-tree", "config.yaml"), "existing: true\n");
|
|
659
|
+
// Pre-create openswarm config
|
|
660
|
+
mkdirSync(join(testHome, ".openswarm"), { recursive: true });
|
|
661
|
+
writeFileSync(join(testHome, ".openswarm", "server.json"), JSON.stringify({ existing: true }));
|
|
662
|
+
const ctx = globalCtx({
|
|
663
|
+
packages: ["agent-iam", "skill-tree", "openswarm"],
|
|
664
|
+
});
|
|
665
|
+
const globalOrder = ["agent-iam", "skill-tree", "openswarm"];
|
|
666
|
+
const results = [];
|
|
667
|
+
for (const pkg of globalOrder) {
|
|
668
|
+
if (!ctx.packages.includes(pkg))
|
|
669
|
+
continue;
|
|
670
|
+
if (isGlobalInit(pkg))
|
|
671
|
+
continue;
|
|
672
|
+
results.push(await initGlobalPackage(pkg, ctx));
|
|
673
|
+
}
|
|
674
|
+
// Only agent-iam was initialized
|
|
675
|
+
expect(results.map((r) => r.package)).toEqual(["agent-iam"]);
|
|
676
|
+
// Existing configs preserved
|
|
677
|
+
expect(readFileSync(join(testHome, ".skill-tree", "config.yaml"), "utf-8")).toBe("existing: true\n");
|
|
678
|
+
const swarmConfig = JSON.parse(readFileSync(join(testHome, ".openswarm", "server.json"), "utf-8"));
|
|
679
|
+
expect(swarmConfig.existing).toBe(true);
|
|
680
|
+
});
|
|
681
|
+
});
|
|
682
|
+
// ─── E2E: embedding provider propagation ─────────────────────────────────────
|
|
683
|
+
describe("e2e: embedding provider propagation", async () => {
|
|
684
|
+
const minimemOk = await hasCliInstalled("minimem");
|
|
685
|
+
it.skipIf(!minimemOk)("openai embedding flows through to real minimem config", async () => {
|
|
686
|
+
createProject("embed-e2e");
|
|
687
|
+
await initProjectPackage("minimem", projectCtx({
|
|
688
|
+
cwd: testDir,
|
|
689
|
+
packages: ["minimem"],
|
|
690
|
+
embeddingProvider: "openai",
|
|
691
|
+
apiKeys: { openai: "sk-embed" },
|
|
692
|
+
}));
|
|
693
|
+
const config = JSON.parse(readFileSync(join(testDir, ".minimem", "config.json"), "utf-8"));
|
|
694
|
+
expect(config.embedding.provider).toBe("openai");
|
|
695
|
+
// Real minimem config has these fields too
|
|
696
|
+
expect(config.hybrid).toBeDefined();
|
|
697
|
+
expect(config.query).toBeDefined();
|
|
698
|
+
});
|
|
699
|
+
});
|
|
700
|
+
// ─── E2E: API key propagation to agent-iam ───────────────────────────────────
|
|
701
|
+
describe("e2e: API key propagation to agent-iam", async () => {
|
|
702
|
+
const iamOk = await hasCliInstalled("agent-iam");
|
|
703
|
+
it.skipIf(!iamOk)("all three standard keys end up in agent-iam config", async () => {
|
|
704
|
+
await initGlobalPackage("agent-iam", globalCtx({
|
|
705
|
+
packages: ["agent-iam"],
|
|
706
|
+
apiKeys: {
|
|
707
|
+
openai: "sk-openai-e2e",
|
|
708
|
+
anthropic: "sk-anthropic-e2e",
|
|
709
|
+
gemini: "gemini-e2e",
|
|
710
|
+
},
|
|
711
|
+
}));
|
|
712
|
+
const config = JSON.parse(readFileSync(join(testHome, ".agent-credentials", "config.json"), "utf-8"));
|
|
713
|
+
expect(config.providers.apikeys.openai.apiKey).toBe("sk-openai-e2e");
|
|
714
|
+
expect(config.providers.apikeys.anthropic.apiKey).toBe("sk-anthropic-e2e");
|
|
715
|
+
expect(config.providers.apikeys.gemini.apiKey).toBe("gemini-e2e");
|
|
716
|
+
});
|
|
717
|
+
});
|