@synap-core/eve 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +251 -0
  2. package/dist/add-R6RGUYER.js +22 -0
  3. package/dist/add-R6RGUYER.js.map +1 -0
  4. package/dist/auth-7FFZNARZ-4OIRGZ7R.js +31 -0
  5. package/dist/auth-7FFZNARZ-4OIRGZ7R.js.map +1 -0
  6. package/dist/chunk-22RIHU4J.js +975 -0
  7. package/dist/chunk-22RIHU4J.js.map +1 -0
  8. package/dist/chunk-23XQDB7K.js +1773 -0
  9. package/dist/chunk-23XQDB7K.js.map +1 -0
  10. package/dist/chunk-5TBV2VHZ.js +6067 -0
  11. package/dist/chunk-5TBV2VHZ.js.map +1 -0
  12. package/dist/chunk-D7P3IYYR.js +44 -0
  13. package/dist/chunk-D7P3IYYR.js.map +1 -0
  14. package/dist/chunk-EHPC7CKE.js +757 -0
  15. package/dist/chunk-EHPC7CKE.js.map +1 -0
  16. package/dist/chunk-FAE5UZFE.js +17 -0
  17. package/dist/chunk-FAE5UZFE.js.map +1 -0
  18. package/dist/chunk-HRF6TNXX.js +53 -0
  19. package/dist/chunk-HRF6TNXX.js.map +1 -0
  20. package/dist/chunk-HVJEW7TU.js +495 -0
  21. package/dist/chunk-HVJEW7TU.js.map +1 -0
  22. package/dist/chunk-JO5H5G6U.js +1529 -0
  23. package/dist/chunk-JO5H5G6U.js.map +1 -0
  24. package/dist/chunk-MUEQNBE6.js +1140 -0
  25. package/dist/chunk-MUEQNBE6.js.map +1 -0
  26. package/dist/chunk-O5WTCVMZ.js +3391 -0
  27. package/dist/chunk-O5WTCVMZ.js.map +1 -0
  28. package/dist/chunk-ROKIQIEX.js +65 -0
  29. package/dist/chunk-ROKIQIEX.js.map +1 -0
  30. package/dist/chunk-T7GCAJK5.js +4895 -0
  31. package/dist/chunk-T7GCAJK5.js.map +1 -0
  32. package/dist/chunk-UOQWJ3S4.js +138 -0
  33. package/dist/chunk-UOQWJ3S4.js.map +1 -0
  34. package/dist/chunk-YQWV5W4U.js +3479 -0
  35. package/dist/chunk-YQWV5W4U.js.map +1 -0
  36. package/dist/chunk-ZPDNZRVW.js +272 -0
  37. package/dist/chunk-ZPDNZRVW.js.map +1 -0
  38. package/dist/config-store-KTOWIKAS-XVZHNPKW.js +11 -0
  39. package/dist/config-store-KTOWIKAS-XVZHNPKW.js.map +1 -0
  40. package/dist/dist-2VRFSXMO.js +40 -0
  41. package/dist/dist-2VRFSXMO.js.map +1 -0
  42. package/dist/dist-C2QB4IKT.js +340 -0
  43. package/dist/dist-C2QB4IKT.js.map +1 -0
  44. package/dist/dist-DEIUVZ57.js +299 -0
  45. package/dist/dist-DEIUVZ57.js.map +1 -0
  46. package/dist/dist-DSKHGECB.js +36 -0
  47. package/dist/dist-DSKHGECB.js.map +1 -0
  48. package/dist/dist-GN2DN7CI.js +95 -0
  49. package/dist/dist-GN2DN7CI.js.map +1 -0
  50. package/dist/dist-TZL4XW2R.js +58 -0
  51. package/dist/dist-TZL4XW2R.js.map +1 -0
  52. package/dist/dist-ZP2APOYV.js +53 -0
  53. package/dist/dist-ZP2APOYV.js.map +1 -0
  54. package/dist/index.js +15083 -0
  55. package/dist/index.js.map +1 -0
  56. package/dist/operational-5RT6AZJI-ZEMPD32Q.js +12 -0
  57. package/dist/operational-5RT6AZJI-ZEMPD32Q.js.map +1 -0
  58. package/dist/reconcile-A4I3Z7VX-GQMEN4GC.js +12 -0
  59. package/dist/reconcile-A4I3Z7VX-GQMEN4GC.js.map +1 -0
  60. package/dist/rsshub-GOJBTNOD-UNNVKBFL.js +18 -0
  61. package/dist/rsshub-GOJBTNOD-UNNVKBFL.js.map +1 -0
  62. package/dist/synap-image-install-X36REYQ5-UGO4MAOP.js +17 -0
  63. package/dist/synap-image-install-X36REYQ5-UGO4MAOP.js.map +1 -0
  64. package/package.json +44 -0
@@ -0,0 +1,975 @@
1
+ import { createRequire as __eveCreateRequire } from 'module'; const require = __eveCreateRequire(import.meta.url);
2
+ import {
3
+ InferenceGateway
4
+ } from "./chunk-23XQDB7K.js";
5
+ import {
6
+ resolveSynapDelegate,
7
+ toPodFqdn
8
+ } from "./chunk-HVJEW7TU.js";
9
+ import {
10
+ EntityStateManager,
11
+ describePodProviderResult,
12
+ entityStateManager,
13
+ getServerIp,
14
+ setPodProviderEnabled,
15
+ upsertPodProvider
16
+ } from "./chunk-5TBV2VHZ.js";
17
+ import {
18
+ resolveSynapUrl
19
+ } from "./chunk-O5WTCVMZ.js";
20
+ import {
21
+ readAgentKeyOrLegacy,
22
+ readEveSecrets
23
+ } from "./chunk-T7GCAJK5.js";
24
+
25
+ // ../@eve/brain/dist/index.js
26
+ import { spawn } from "child_process";
27
+ import { execSync } from "child_process";
28
+ import { execSync as execSync2, spawnSync } from "child_process";
29
+ import Table from "cli-table3";
30
+ function execa(command, args, options) {
31
+ return new Promise((resolve, reject) => {
32
+ const child = spawn(command, args, {
33
+ stdio: options?.stdio === "inherit" ? "inherit" : "pipe",
34
+ cwd: options?.cwd,
35
+ env: options?.env
36
+ });
37
+ let stdout = "";
38
+ let stderr = "";
39
+ if (child.stdout) {
40
+ child.stdout.on("data", (data) => {
41
+ stdout += data.toString();
42
+ });
43
+ }
44
+ if (child.stderr) {
45
+ child.stderr.on("data", (data) => {
46
+ stderr += data.toString();
47
+ });
48
+ }
49
+ child.on("close", (code) => {
50
+ if (code === 0) {
51
+ resolve({ stdout: stdout.trim(), stderr: stderr.trim() });
52
+ } else {
53
+ reject(new Error(`Command failed with code ${code}: ${stderr || stdout}`));
54
+ }
55
+ });
56
+ child.on("error", (error) => {
57
+ reject(error);
58
+ });
59
+ });
60
+ }
61
+ async function ensureNetwork() {
62
+ try {
63
+ const { stdout } = await execa("docker", ["network", "ls", "--format", "{{.Name}}"]);
64
+ if (!stdout.includes("eve-network")) {
65
+ console.log("Creating eve-network...");
66
+ await execa("docker", ["network", "create", "eve-network"]);
67
+ }
68
+ } catch (error) {
69
+ console.warn("Could not ensure Docker network:", error);
70
+ }
71
+ }
72
+ var OllamaService = class {
73
+ containerName = "eve-brain-ollama";
74
+ image = "ollama/ollama:latest";
75
+ async install() {
76
+ console.log("Installing Ollama...");
77
+ await execa("docker", ["pull", this.image], { stdio: "inherit" });
78
+ console.log("Ollama image pulled successfully");
79
+ }
80
+ /**
81
+ * @param publishToHost - When false, Ollama is only on `eve-network` (use with Traefik gateway on Full stack).
82
+ */
83
+ async start(options) {
84
+ const publishToHost = options?.publishToHost !== false;
85
+ const running = await this.isRunning();
86
+ if (running) {
87
+ console.log("Ollama is already running");
88
+ return;
89
+ }
90
+ const exists = await this.containerExists();
91
+ if (exists) {
92
+ await execa("docker", ["start", this.containerName], { stdio: "inherit" });
93
+ } else {
94
+ const args = [
95
+ "run",
96
+ "-d",
97
+ "--name",
98
+ this.containerName,
99
+ "--network",
100
+ "eve-network",
101
+ "-v",
102
+ "ollama-models:/root/.ollama",
103
+ "--restart",
104
+ "unless-stopped"
105
+ ];
106
+ if (publishToHost) {
107
+ args.push("-p", "127.0.0.1:11434:11434");
108
+ }
109
+ args.push(this.image);
110
+ await execa("docker", args, { stdio: "inherit" });
111
+ }
112
+ console.log(
113
+ publishToHost ? "Ollama started on http://127.0.0.1:11434" : "Ollama started (no host port; reachable on eve-network as eve-brain-ollama:11434)"
114
+ );
115
+ }
116
+ async pullModel(model, startOpts) {
117
+ console.log(`Pulling model: ${model}...`);
118
+ await this.start(startOpts);
119
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
120
+ await execa("docker", [
121
+ "exec",
122
+ this.containerName,
123
+ "ollama",
124
+ "pull",
125
+ model
126
+ ], { stdio: "inherit" });
127
+ console.log(`Model ${model} pulled successfully`);
128
+ }
129
+ async isRunning() {
130
+ try {
131
+ const { stdout } = await execa("docker", [
132
+ "ps",
133
+ "--filter",
134
+ `name=${this.containerName}`,
135
+ "--filter",
136
+ "status=running",
137
+ "--format",
138
+ "{{.Names}}"
139
+ ]);
140
+ return stdout.trim() === this.containerName;
141
+ } catch {
142
+ return false;
143
+ }
144
+ }
145
+ async getStatus() {
146
+ const running = await this.isRunning();
147
+ const models = await this.listModels();
148
+ return {
149
+ running,
150
+ modelsInstalled: models,
151
+ currentModel: models.length > 0 ? models[0] : void 0
152
+ };
153
+ }
154
+ async listModels() {
155
+ try {
156
+ const { stdout } = await execa("docker", [
157
+ "exec",
158
+ this.containerName,
159
+ "ollama",
160
+ "list"
161
+ ]);
162
+ const lines = stdout.trim().split("\n").slice(1);
163
+ return lines.map((line) => line.split(/\s+/)[0]).filter(Boolean);
164
+ } catch {
165
+ return [];
166
+ }
167
+ }
168
+ async containerExists() {
169
+ try {
170
+ const { stdout } = await execa("docker", [
171
+ "ps",
172
+ "-a",
173
+ "--filter",
174
+ `name=${this.containerName}`,
175
+ "--format",
176
+ "{{.Names}}"
177
+ ]);
178
+ return stdout.trim() === this.containerName;
179
+ } catch {
180
+ return false;
181
+ }
182
+ }
183
+ };
184
+ async function cleanupKnownStaleState(deployDir) {
185
+ console.log("Cleaning known stale Synap artifacts...");
186
+ try {
187
+ await execa("bash", ["-lc", `rm -f "${deployDir}/patch_migration.js"`], { stdio: "inherit" });
188
+ await execa(
189
+ "bash",
190
+ [
191
+ "-lc",
192
+ `if [ -f "${deployDir}/docker-compose.override.yml" ] && grep -q "patch_migration.js" "${deployDir}/docker-compose.override.yml"; then rm -f "${deployDir}/docker-compose.override.yml"; fi`
193
+ ],
194
+ { stdio: "inherit" }
195
+ );
196
+ } catch {
197
+ }
198
+ }
199
+ async function runBrainInit(options) {
200
+ const repo = options.synapRepo?.trim() || process.env.SYNAP_REPO_ROOT?.trim() || void 0;
201
+ if (repo) {
202
+ process.env.SYNAP_REPO_ROOT = repo;
203
+ }
204
+ const domain = options.domain?.trim() || "localhost";
205
+ const email = options.email?.trim() || process.env.LETSENCRYPT_EMAIL?.trim() || process.env.SYNAP_LETSENCRYPT_EMAIL?.trim();
206
+ const adminEmail = options.adminEmail?.trim() || process.env.ADMIN_EMAIL?.trim();
207
+ const adminPassword = options.adminPassword?.trim() || process.env.ADMIN_PASSWORD?.trim();
208
+ const adminBootstrapMode = options.adminBootstrapMode ?? "token";
209
+ if (options.fromSource && options.fromImage) {
210
+ throw new Error("--from-image and --from-source are mutually exclusive.");
211
+ }
212
+ const explicitRepo = options.synapRepo?.trim() || process.env.SYNAP_REPO_ROOT?.trim();
213
+ const existingDelegate = explicitRepo ? resolveSynapDelegate() : null;
214
+ if (existingDelegate) {
215
+ await cleanupKnownStaleState(existingDelegate.deployDir);
216
+ }
217
+ if (options.fromSource === true) {
218
+ console.log("Installing Synap Data Pod from source (synap CLI delegate)...\n");
219
+ } else if (options.fromSource === false) {
220
+ console.log("Installing Synap Data Pod from Docker image (synap CLI delegate)...\n");
221
+ } else {
222
+ console.log("Installing Synap Data Pod (synap CLI delegate, auto-detect mode)...\n");
223
+ }
224
+ const { installSynapFromImage: installSynapFromImage2 } = await import("./synap-image-install-X36REYQ5-UGO4MAOP.js");
225
+ const result = await installSynapFromImage2({
226
+ deployDir: explicitRepo,
227
+ domain,
228
+ email,
229
+ adminEmail,
230
+ adminPassword,
231
+ adminBootstrapMode,
232
+ fromSource: options.fromSource,
233
+ withOpenclaw: options.withOpenclaw,
234
+ withRsshub: options.withRsshub
235
+ });
236
+ if (options.withAi) {
237
+ console.log("\n\u{1F916} Local Ollama (optional; not part of default Synap compose)\n");
238
+ await ensureNetwork();
239
+ const ollama = new OllamaService();
240
+ await ollama.install();
241
+ await ollama.start();
242
+ await ollama.pullModel(options.model ?? "llama3.1:8b");
243
+ }
244
+ const stateManager = new EntityStateManager();
245
+ await stateManager.updateOrgan("brain", "ready");
246
+ await entityStateManager.updateComponentEntry("synap", {
247
+ organ: "brain",
248
+ state: "ready",
249
+ version: options.fromSource ? "local" : "main",
250
+ managedBy: "eve",
251
+ config: { domain, withRsshub: options.withRsshub, repoRoot: result.deployDir }
252
+ });
253
+ console.log("\n\u2705 Synap Data Pod installed.");
254
+ if (result.bootstrapToken) {
255
+ const secrets = await readEveSecrets(process.cwd()).catch(() => null);
256
+ const configuredDomain = domain !== "localhost" ? domain : secrets?.domain?.primary;
257
+ const ssl = secrets?.domain?.ssl ?? false;
258
+ const serverIp = getServerIp();
259
+ console.log(`
260
+ Admin bootstrap token (save this \u2014 one-time use):`);
261
+ console.log(` ${result.bootstrapToken}`);
262
+ console.log(`
263
+ Complete setup at:`);
264
+ if (configuredDomain) {
265
+ const proto = ssl ? "https" : "http";
266
+ console.log(` ${proto}://${toPodFqdn(configuredDomain)}/admin/bootstrap`);
267
+ }
268
+ if (serverIp) {
269
+ console.log(` http://${serverIp}:4000/admin/bootstrap`);
270
+ }
271
+ console.log(` http://localhost:4000/admin/bootstrap`);
272
+ }
273
+ if (options.withRsshub) {
274
+ console.log(" RSSHub: http://localhost:1200 (default compose port)");
275
+ }
276
+ }
277
+ function initCommand(program) {
278
+ program.command("init").description(
279
+ "Install the Synap Data Pod. Uses pre-built Docker image by default; pass --synap-repo to use a local checkout."
280
+ ).option("--with-ai", "Include local Ollama sidecar (optional)").option("--model <model>", "AI model to use", "llama3.1:8b").option(
281
+ "--synap-repo <path>",
282
+ "Path to synap-backend checkout (optional \u2014 auto-detected or uses Docker image)"
283
+ ).option("--domain <host>", "Domain for the data pod", "localhost").option("--email <email>", "SSL contact email (required if domain is not localhost)").option("--with-openclaw", "With --synap-repo: pass --with-openclaw to synap install").option("--with-rsshub", "With --synap-repo: pass --with-rsshub to synap install").option("--from-image", "Force from-image install even if synap-repo is found").option("--from-source", "With --synap-repo: build from source instead of pulling image").option("--admin-email <email>", "Admin email for bootstrap").option("--admin-password <secret>", "Admin password (preseed bootstrap mode)").option("--admin-bootstrap-mode <mode>", "preseed | token (default: token)").action(
284
+ async (options) => {
285
+ try {
286
+ await runBrainInit({
287
+ withAi: options.withAi,
288
+ model: options.model,
289
+ synapRepo: options.synapRepo,
290
+ domain: options.domain,
291
+ email: options.email,
292
+ withOpenclaw: options.withOpenclaw,
293
+ withRsshub: options.withRsshub,
294
+ fromImage: options.fromImage,
295
+ fromSource: options.fromSource,
296
+ adminEmail: options.adminEmail,
297
+ adminPassword: options.adminPassword,
298
+ adminBootstrapMode: options.adminBootstrapMode
299
+ });
300
+ } catch (error) {
301
+ console.error("Failed to initialize brain:", error);
302
+ process.exit(1);
303
+ }
304
+ }
305
+ );
306
+ }
307
+ function checkSynapViaDocker() {
308
+ try {
309
+ const out = execSync(
310
+ 'docker ps -a --filter "label=com.docker.compose.project=synap-backend" --format "{{.Names}}|{{.Status}}"',
311
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
312
+ ).trim();
313
+ const lines = out.split("\n").filter(Boolean);
314
+ if (lines.length === 0) return { found: false, healthy: false, containers: [] };
315
+ const containers = [];
316
+ let healthy = false;
317
+ for (const line of lines) {
318
+ const [name, status] = line.split("|");
319
+ containers.push(name);
320
+ if (status?.includes("healthy")) healthy = true;
321
+ else if (status?.includes("running")) healthy = false;
322
+ }
323
+ return { found: true, healthy, containers };
324
+ } catch {
325
+ return { found: false, healthy: false, containers: [] };
326
+ }
327
+ }
328
+ function statusCommand(program) {
329
+ program.command("status").description("Show brain health status").action(async () => {
330
+ try {
331
+ console.log("Checking brain health...\n");
332
+ const delegate = resolveSynapDelegate();
333
+ if (delegate) {
334
+ try {
335
+ await execa("bash", [delegate.synapScript, "health"], {
336
+ cwd: delegate.repoRoot,
337
+ env: { ...process.env, SYNAP_DEPLOY_DIR: delegate.deployDir },
338
+ stdio: "inherit"
339
+ });
340
+ } catch {
341
+ }
342
+ }
343
+ const synap = checkSynapViaDocker();
344
+ if (!synap.found) {
345
+ console.log("\u26A0\uFE0F Synap Data Pod: not found");
346
+ if (!delegate) {
347
+ console.log(" Set SYNAP_REPO_ROOT or run `eve brain init --synap-repo <path>`");
348
+ }
349
+ } else {
350
+ const mark = synap.healthy ? "\u2713" : "\u26A0";
351
+ const label = synap.healthy ? "Healthy" : "Running (not yet healthy)";
352
+ console.log(`${mark} Synap Data Pod: ${label}`);
353
+ for (const c of synap.containers) {
354
+ console.log(` - ${c}`);
355
+ }
356
+ }
357
+ try {
358
+ const hermesStatus = execSync(
359
+ 'docker inspect eve-builder-hermes --format "{{.State.Status}}" 2>/dev/null',
360
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
361
+ ).trim();
362
+ const mark = hermesStatus === "running" ? "\u2713" : "\u26A0";
363
+ console.log(`${mark} Hermes (agent): ${hermesStatus || "not found"}`);
364
+ } catch {
365
+ console.log("\u26A0\uFE0F Hermes (agent): not found");
366
+ }
367
+ const ollama = new OllamaService();
368
+ const ollamaStatus = await ollama.getStatus();
369
+ if (ollamaStatus.running) {
370
+ console.log("AI Models");
371
+ if (ollamaStatus.modelsInstalled.length > 0) {
372
+ for (const model of ollamaStatus.modelsInstalled) {
373
+ const current = model === ollamaStatus.currentModel ? " (current)" : "";
374
+ console.log(` \u2022 ${model}${current}`);
375
+ }
376
+ } else {
377
+ console.log(" No models installed");
378
+ console.log(" Run: eve brain init --with-ai --model <model>");
379
+ }
380
+ }
381
+ } catch (error) {
382
+ console.error("Failed to check brain status:", error);
383
+ process.exit(1);
384
+ }
385
+ });
386
+ }
387
+ function reconnectSynapToEveNetwork() {
388
+ try {
389
+ const name = execSync2(
390
+ `docker ps --filter "label=com.docker.compose.project=synap-backend" --filter "label=com.docker.compose.service=backend" --format "{{.Names}}"`,
391
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
392
+ ).trim().split("\n")[0]?.trim();
393
+ if (!name) return;
394
+ const inspect = execSync2(
395
+ `docker inspect --format "{{json .NetworkSettings.Networks}}" ${name}`,
396
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
397
+ ).trim();
398
+ const networks = JSON.parse(inspect);
399
+ const eve = networks["eve-network"];
400
+ if (eve && (eve.Aliases ?? []).includes("eve-brain-synap")) return;
401
+ if (eve) {
402
+ try {
403
+ execSync2(`docker network disconnect eve-network ${name}`, { stdio: ["pipe", "pipe", "ignore"] });
404
+ } catch {
405
+ }
406
+ }
407
+ execSync2(`docker network connect --alias eve-brain-synap eve-network ${name}`, { stdio: ["pipe", "pipe", "ignore"] });
408
+ console.log(` Connected ${name} \u2192 eve-network (alias: eve-brain-synap)`);
409
+ } catch {
410
+ }
411
+ }
412
+ function getSynapContainerIds(runningOnly = false) {
413
+ try {
414
+ const flag = runningOnly ? "" : "-a";
415
+ const out = execSync2(
416
+ `docker ps ${flag} --filter "label=com.docker.compose.project=synap-backend" --format "{{.Names}}"`,
417
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
418
+ ).trim();
419
+ return out.split("\n").filter(Boolean);
420
+ } catch {
421
+ return [];
422
+ }
423
+ }
424
+ var SynapService = class {
425
+ delegate() {
426
+ return resolveSynapDelegate();
427
+ }
428
+ requireDelegate() {
429
+ const d = this.delegate();
430
+ if (!d) {
431
+ throw new Error(
432
+ "Synap repo not found. Pass --synap-repo <path> or set SYNAP_REPO_ROOT to a synap-backend checkout.\nTried: SYNAP_REPO_ROOT env, saved state, /opt/synap, /opt/synap-backend."
433
+ );
434
+ }
435
+ return d;
436
+ }
437
+ async install() {
438
+ this.requireDelegate();
439
+ console.log(
440
+ "Synap install is managed via the official synap CLI. Use `eve brain init --synap-repo <path>` or `eve setup --profile data_pod`."
441
+ );
442
+ }
443
+ async start() {
444
+ const d = this.delegate();
445
+ if (d) {
446
+ console.log("Starting Synap stack via synap CLI...");
447
+ await execa("bash", [d.synapScript, "start"], {
448
+ cwd: d.repoRoot,
449
+ env: { ...process.env, SYNAP_DEPLOY_DIR: d.deployDir },
450
+ stdio: "inherit"
451
+ });
452
+ reconnectSynapToEveNetwork();
453
+ return;
454
+ }
455
+ const containers = getSynapContainerIds();
456
+ if (containers.length === 0) {
457
+ console.log("Synap Data Pod not found \u2014 installing from Docker image...\n");
458
+ const { installSynapFromImage: installSynapFromImage2 } = await import("./synap-image-install-X36REYQ5-UGO4MAOP.js");
459
+ const result = await installSynapFromImage2();
460
+ if (result.bootstrapToken) {
461
+ console.log(`
462
+ Admin bootstrap token: ${result.bootstrapToken}`);
463
+ console.log(` Use at: http://localhost:4000/admin/bootstrap`);
464
+ }
465
+ reconnectSynapToEveNetwork();
466
+ return;
467
+ }
468
+ console.log(`Starting ${containers.length} synap-backend container(s) directly...`);
469
+ for (const name of containers) {
470
+ const result = spawnSync("docker", ["start", name], { stdio: "inherit" });
471
+ if (result.status !== 0) {
472
+ throw new Error(`Failed to start container: ${name}`);
473
+ }
474
+ }
475
+ reconnectSynapToEveNetwork();
476
+ }
477
+ async stop() {
478
+ const d = this.delegate();
479
+ if (d) {
480
+ console.log("Stopping Synap stack via synap CLI...");
481
+ await execa("bash", [d.synapScript, "stop"], {
482
+ cwd: d.repoRoot,
483
+ env: { ...process.env, SYNAP_DEPLOY_DIR: d.deployDir },
484
+ stdio: "inherit"
485
+ });
486
+ return;
487
+ }
488
+ const containers = getSynapContainerIds(true);
489
+ if (containers.length === 0) {
490
+ console.log("No running synap-backend containers found.");
491
+ return;
492
+ }
493
+ console.log(`Stopping ${containers.length} synap-backend container(s) directly...`);
494
+ for (const name of containers) {
495
+ spawnSync("docker", ["stop", name], { stdio: "inherit" });
496
+ }
497
+ }
498
+ async isHealthy() {
499
+ this.requireDelegate();
500
+ try {
501
+ const res = await fetch("http://127.0.0.1:4000/health", { signal: AbortSignal.timeout(3e3) });
502
+ return res.ok;
503
+ } catch {
504
+ return false;
505
+ }
506
+ }
507
+ async getVersion() {
508
+ this.requireDelegate();
509
+ return "synap-compose";
510
+ }
511
+ };
512
+ function startCommand(program) {
513
+ program.command("start").description("Start Synap backend container").action(async () => {
514
+ const synap = new SynapService();
515
+ await synap.start();
516
+ });
517
+ }
518
+ function stopCommand(program) {
519
+ program.command("stop").description("Stop Synap backend container").action(async () => {
520
+ const synap = new SynapService();
521
+ await synap.stop();
522
+ });
523
+ }
524
+ async function hubGet(podUrl, apiKey, path) {
525
+ const res = await fetch(`${podUrl.replace(/\/$/, "")}/api/hub/${path}`, {
526
+ headers: { Authorization: `Bearer ${apiKey}` },
527
+ signal: AbortSignal.timeout(15e3)
528
+ });
529
+ if (!res.ok) {
530
+ const text = await res.text().catch(() => "");
531
+ throw new Error(`Pod responded ${res.status}: ${text}`);
532
+ }
533
+ return res.json();
534
+ }
535
+ async function hubPost(podUrl, apiKey, path, body) {
536
+ const res = await fetch(`${podUrl.replace(/\/$/, "")}/api/hub/${path}`, {
537
+ method: "POST",
538
+ headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
539
+ body: body !== void 0 ? JSON.stringify(body) : void 0,
540
+ signal: AbortSignal.timeout(15e3)
541
+ });
542
+ if (!res.ok) {
543
+ const text = await res.text().catch(() => "");
544
+ throw new Error(`Pod responded ${res.status}: ${text}`);
545
+ }
546
+ return res.json();
547
+ }
548
+ async function hubDelete(podUrl, apiKey, path) {
549
+ const res = await fetch(`${podUrl.replace(/\/$/, "")}/api/hub/${path}`, {
550
+ method: "DELETE",
551
+ headers: { Authorization: `Bearer ${apiKey}` },
552
+ signal: AbortSignal.timeout(15e3)
553
+ });
554
+ if (!res.ok) {
555
+ const text = await res.text().catch(() => "");
556
+ throw new Error(`Pod responded ${res.status}: ${text}`);
557
+ }
558
+ return res.json();
559
+ }
560
+ async function resolveAuth(cwd) {
561
+ const secrets = await readEveSecrets(cwd);
562
+ const podUrl = resolveSynapUrl(secrets) ?? process.env.SYNAP_POD_URL;
563
+ if (!podUrl) throw new Error("Pod URL not configured. Run `eve setup` first.");
564
+ const apiKey = await readAgentKeyOrLegacy("eve", cwd) || process.env.SYNAP_HUB_API_KEY || "";
565
+ if (!apiKey) throw new Error("Eve agent API key not configured. Run `eve setup` or `eve auth` first, or set SYNAP_HUB_API_KEY.");
566
+ return { podUrl, apiKey };
567
+ }
568
+ function providersCommand(brain) {
569
+ const providers = brain.command("providers").description("Manage AI provider configs on the pod (synced to IS on change)");
570
+ providers.command("list").description("List all providers registered on the pod").option("--json", "Output raw JSON").action(async (opts) => {
571
+ try {
572
+ const { podUrl, apiKey } = await resolveAuth(process.env.EVE_HOME ?? process.cwd());
573
+ const { providers: rows } = await hubGet(podUrl, apiKey, "ai-providers");
574
+ if (opts.json) {
575
+ console.log(JSON.stringify(rows, null, 2));
576
+ return;
577
+ }
578
+ if (rows.length === 0) {
579
+ console.log("No providers configured. Use `eve brain providers add` to register one.");
580
+ return;
581
+ }
582
+ const table = new Table({
583
+ head: ["ID", "Name", "Base URL", "Priority", "Enabled", "Key", "Models"],
584
+ style: { head: ["cyan"] }
585
+ });
586
+ for (const p of rows.sort((a, b) => a.priority - b.priority)) {
587
+ table.push([
588
+ p.providerId,
589
+ p.name,
590
+ p.baseUrl.replace(/^https?:\/\//, ""),
591
+ String(p.priority),
592
+ p.enabled ? "\u2713" : "\u2014",
593
+ p.hasApiKey ? "\u25CF" : "\u25CB",
594
+ p.models.length > 0 ? p.models.slice(0, 3).map((m) => m.id).join(", ") + (p.models.length > 3 ? "\u2026" : "") : "\u2014"
595
+ ]);
596
+ }
597
+ console.log(table.toString());
598
+ console.log(`
599
+ \u25CF = API key stored \u25CB = no key (uses env var ${rows[0]?.apiKeyEnvVar ?? ""})`);
600
+ } catch (err) {
601
+ console.error("Error:", String(err));
602
+ process.exit(1);
603
+ }
604
+ });
605
+ const KNOWN_PROVIDERS = {
606
+ openrouter: {
607
+ name: "OpenRouter",
608
+ url: "https://openrouter.ai/api/v1",
609
+ envVar: "OPENROUTER_API_KEY",
610
+ priority: 10,
611
+ models: [
612
+ { id: "deepseek/deepseek-v4-flash:free", tier: "balanced", contextWindow: 1e6 },
613
+ { id: "deepseek/deepseek-v4-flash:free", tier: "free", contextWindow: 1e6 },
614
+ { id: "nvidia/nemotron-3-super-120b-a12b:free", tier: "advanced", contextWindow: 1e6 },
615
+ { id: "openai/gpt-oss-20b:free", tier: "complex", contextWindow: 131072 }
616
+ ]
617
+ },
618
+ groq: {
619
+ name: "Groq",
620
+ url: "https://api.groq.com/openai/v1",
621
+ envVar: "GROQ_API_KEY",
622
+ priority: 20,
623
+ models: [{ id: "llama-3.3-70b-versatile", tier: "balanced" }]
624
+ },
625
+ cerebras: {
626
+ name: "Cerebras",
627
+ url: "https://api.cerebras.ai/v1",
628
+ envVar: "CEREBRAS_API_KEY",
629
+ priority: 30,
630
+ models: [{ id: "llama3.1-70b", tier: "balanced" }]
631
+ },
632
+ anthropic: {
633
+ name: "Anthropic",
634
+ url: "https://api.anthropic.com/v1",
635
+ envVar: "ANTHROPIC_API_KEY",
636
+ priority: 40,
637
+ models: [
638
+ { id: "claude-sonnet-4-6", tier: "balanced" },
639
+ { id: "claude-opus-4-8", tier: "advanced" }
640
+ ]
641
+ }
642
+ };
643
+ providers.command("add [providerId]").description("Add or update a provider on the pod. Known providers (openrouter, groq, cerebras, anthropic) have sensible defaults.").option("--id <providerId>", "Provider ID (overrides positional arg)").option("--name <name>", "Display name (defaults filled for known providers)").option("--url <baseUrl>", "OpenAI-compatible base URL (defaults filled for known providers)").option("--env-var <apiKeyEnvVar>", "Env var name for the API key on the IS").option("--key <apiKey>", "API key (alias for --api-key)").option("--api-key <apiKey>", "API key (encrypted and stored on pod, sent inline to IS)").option("--priority <n>", "Routing priority (lower = higher)").option("--disable", "Add in disabled state").action(async (positionalId, opts) => {
644
+ try {
645
+ const id = opts.id ?? positionalId;
646
+ if (!id) {
647
+ console.error("Error: provider ID required \u2014 pass as positional arg or --id");
648
+ console.error("Example: eve brain providers add openrouter --key <api-key>");
649
+ process.exit(1);
650
+ }
651
+ const defaults = KNOWN_PROVIDERS[id];
652
+ const name = opts.name ?? defaults?.name ?? id;
653
+ const url = opts.url ?? defaults?.url;
654
+ if (!url) {
655
+ console.error(`Error: --url required for unknown provider "${id}"`);
656
+ process.exit(1);
657
+ }
658
+ const { podUrl, apiKey } = await resolveAuth(process.env.EVE_HOME ?? process.cwd());
659
+ const result = await upsertPodProvider(podUrl, apiKey, {
660
+ providerId: id,
661
+ name,
662
+ baseUrl: url,
663
+ apiKeyEnvVar: opts.envVar ?? defaults?.envVar ?? "PROVIDER_API_KEY",
664
+ ...opts.key || opts.apiKey ? { apiKey: opts.key ?? opts.apiKey } : {},
665
+ priority: opts.priority ? parseInt(opts.priority, 10) : defaults?.priority ?? 10,
666
+ enabled: !opts.disable,
667
+ ...defaults?.models ? { models: defaults.models } : {}
668
+ });
669
+ console.log(describePodProviderResult(result));
670
+ } catch (err) {
671
+ console.error("Error:", String(err));
672
+ process.exit(1);
673
+ }
674
+ });
675
+ providers.command("enable <providerId>").description("Enable a provider and sync to IS").action(async (providerId) => {
676
+ try {
677
+ const { podUrl, apiKey } = await resolveAuth(process.env.EVE_HOME ?? process.cwd());
678
+ console.log(
679
+ describePodProviderResult(
680
+ await setPodProviderEnabled(podUrl, apiKey, providerId, true)
681
+ )
682
+ );
683
+ } catch (err) {
684
+ console.error("Error:", String(err));
685
+ process.exit(1);
686
+ }
687
+ });
688
+ providers.command("disable <providerId>").description("Disable a provider and sync to IS").action(async (providerId) => {
689
+ try {
690
+ const { podUrl, apiKey } = await resolveAuth(process.env.EVE_HOME ?? process.cwd());
691
+ console.log(
692
+ describePodProviderResult(
693
+ await setPodProviderEnabled(podUrl, apiKey, providerId, false)
694
+ )
695
+ );
696
+ } catch (err) {
697
+ console.error("Error:", String(err));
698
+ process.exit(1);
699
+ }
700
+ });
701
+ providers.command("remove <providerId>").description("Remove a provider from the pod and sync to IS").option("-y, --yes", "Skip confirmation").action(async (providerId, opts) => {
702
+ try {
703
+ if (!opts.yes) {
704
+ const readline = await import("readline");
705
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
706
+ const confirmed = await new Promise((resolve) => {
707
+ rl.question(`Remove provider "${providerId}"? (y/N) `, (answer) => {
708
+ rl.close();
709
+ resolve(answer.toLowerCase() === "y");
710
+ });
711
+ });
712
+ if (!confirmed) {
713
+ console.log("Aborted.");
714
+ return;
715
+ }
716
+ }
717
+ const { podUrl, apiKey } = await resolveAuth(process.env.EVE_HOME ?? process.cwd());
718
+ await hubDelete(podUrl, apiKey, `ai-providers/${providerId}`);
719
+ console.log(`\u2713 Provider "${providerId}" removed.`);
720
+ } catch (err) {
721
+ console.error("Error:", String(err));
722
+ process.exit(1);
723
+ }
724
+ });
725
+ providers.command("test <providerId>").description("Probe a provider via the IS (live connectivity test)").action(async (providerId) => {
726
+ try {
727
+ const { podUrl, apiKey } = await resolveAuth(process.env.EVE_HOME ?? process.cwd());
728
+ console.log(`Probing "${providerId}" via IS\u2026`);
729
+ const result = await hubPost(podUrl, apiKey, `ai-providers/${providerId}/probe`);
730
+ if (result.ok) {
731
+ console.log(`\u2713 Reachable (${result.latencyMs}ms) \u2014 ${result.models.length} models`);
732
+ if (result.models.length > 0) {
733
+ console.log(" " + result.models.slice(0, 10).join("\n "));
734
+ if (result.models.length > 10) console.log(` \u2026 and ${result.models.length - 10} more`);
735
+ }
736
+ } else {
737
+ console.error(`\u2717 Probe failed: ${result.error ?? "unknown error"}`);
738
+ process.exit(1);
739
+ }
740
+ } catch (err) {
741
+ console.error("Error:", String(err));
742
+ process.exit(1);
743
+ }
744
+ });
745
+ providers.command("sync").description("Re-push all providers from the pod to the IS (safe after IS redeploy)").action(async () => {
746
+ try {
747
+ const { podUrl, apiKey } = await resolveAuth(process.env.EVE_HOME ?? process.cwd());
748
+ const result = await hubPost(podUrl, apiKey, "ai-providers/sync");
749
+ console.log(`\u2713 Synced ${result.count} provider(s) to IS.`);
750
+ } catch (err) {
751
+ console.error("Error:", String(err));
752
+ process.exit(1);
753
+ }
754
+ });
755
+ }
756
+ var ModelService = class {
757
+ ollamaContainer = "eve-brain-ollama";
758
+ /** ── Ollama ────────────────────────────────────────────── */
759
+ /** Check whether the Ollama container is running. */
760
+ async isOllamaRunning() {
761
+ try {
762
+ const { stdout } = await execa("docker", [
763
+ "ps",
764
+ "--filter",
765
+ `name=${this.ollamaContainer}`,
766
+ "--filter",
767
+ "status=running",
768
+ "--format",
769
+ "{{.Names}}"
770
+ ]);
771
+ return stdout.trim() === this.ollamaContainer;
772
+ } catch {
773
+ return false;
774
+ }
775
+ }
776
+ /** List Ollama model names via `docker exec ollama list`. */
777
+ async listOllamaModels() {
778
+ try {
779
+ const httpModels = await this.listOllamaModelsHttp();
780
+ if (httpModels.length > 0) return httpModels;
781
+ } catch {
782
+ }
783
+ try {
784
+ const { stdout } = await execa("docker", [
785
+ "exec",
786
+ this.ollamaContainer,
787
+ "ollama",
788
+ "list"
789
+ ]);
790
+ const lines = stdout.trim().split("\n").slice(1);
791
+ return lines.map((l) => l.split(/\s+/)[0]).filter(Boolean);
792
+ } catch {
793
+ return [];
794
+ }
795
+ }
796
+ async listOllamaModelsHttp() {
797
+ try {
798
+ const res = await fetch("http://127.0.0.1:11434/api/tags", { signal: AbortSignal.timeout(3e3) });
799
+ if (!res.ok) return [];
800
+ const data = await res.json();
801
+ return (data.models ?? []).map((m) => m.name).filter(Boolean);
802
+ } catch {
803
+ return [];
804
+ }
805
+ }
806
+ /** ── Hermes gateway ────────────────────────────────────── */
807
+ /** Check whether the Hermes container is running. */
808
+ async isHermesRunning() {
809
+ try {
810
+ const { stdout } = await execa("docker", [
811
+ "ps",
812
+ "--filter",
813
+ "name=eve-builder-hermes",
814
+ "--filter",
815
+ "status=running",
816
+ "--format",
817
+ "{{.Names}}"
818
+ ]);
819
+ return stdout.trim() === "eve-builder-hermes";
820
+ } catch {
821
+ return false;
822
+ }
823
+ }
824
+ /**
825
+ * Query Hermes gateway's /v1/models endpoint.
826
+ * @param apiKey — optional Bearer token (API_SERVER_KEY from secrets)
827
+ */
828
+ async listHermesModels(apiKey) {
829
+ if (!await this.isHermesRunning()) return [];
830
+ try {
831
+ const headers = {};
832
+ if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
833
+ const res = await fetch("http://localhost:8642/v1/models", {
834
+ headers,
835
+ signal: AbortSignal.timeout(5e3)
836
+ });
837
+ if (!res.ok) return [];
838
+ const data = await res.json();
839
+ const items = Array.isArray(data.data) ? data.data : [];
840
+ return items.filter((m) => m?.id).map((m) => m.id);
841
+ } catch {
842
+ return [];
843
+ }
844
+ }
845
+ /** ── Cloud providers (OpenAI-compatible /v1/models) ───── */
846
+ /**
847
+ * Query a provider's /v1/models endpoint using its apiKey + baseUrl.
848
+ * Returns model IDs, or empty array if unreachable / no key.
849
+ */
850
+ async listCloudModels(options) {
851
+ if (!options.apiKey) return [];
852
+ const url = `${options.baseUrl.replace(/\/+$/, "")}/v1/models`;
853
+ try {
854
+ const res = await fetch(url, {
855
+ headers: { Authorization: `Bearer ${options.apiKey}` },
856
+ signal: AbortSignal.timeout(5e3)
857
+ });
858
+ if (!res.ok) return [];
859
+ const data = await res.json();
860
+ const items = Array.isArray(data.data) ? data.data : [];
861
+ return items.filter((m) => m?.id).map((m) => m.id);
862
+ } catch {
863
+ return [];
864
+ }
865
+ }
866
+ /** ── Unified discovery ─────────────────────────────────── */
867
+ /**
868
+ * Discover models from all sources and return a grouped list.
869
+ *
870
+ * @param cloudProviders — optional list of { id, name, baseUrl, apiKey, defaultModel, models }
871
+ * @param hermesApiKey — optional API_SERVER_KEY for authenticated Hermes model discovery
872
+ * @returns ProviderModels sorted: Ollama first, then Hermes, then cloud providers
873
+ */
874
+ async discoverAll(cloudProviders, hermesApiKey) {
875
+ const results = [];
876
+ const ollamaRunning = await this.isOllamaRunning();
877
+ const ollamaModels = ollamaRunning ? await this.listOllamaModels() : [];
878
+ if (ollamaModels.length > 0 || ollamaRunning) {
879
+ results.push({
880
+ providerId: "ollama",
881
+ displayName: "Ollama (local)",
882
+ models: ollamaModels.map((name) => ({ name, isDefault: ollamaModels[0] === name })),
883
+ available: ollamaRunning
884
+ });
885
+ }
886
+ const hermesRunning = await this.isHermesRunning();
887
+ const hermesModels = hermesRunning ? await this.listHermesModels(hermesApiKey) : [];
888
+ if (hermesModels.length > 0 || hermesRunning) {
889
+ results.push({
890
+ providerId: "hermes",
891
+ displayName: "Hermes Gateway",
892
+ models: hermesModels.map((name) => ({ name, isDefault: hermesModels[0] === name })),
893
+ available: hermesRunning
894
+ });
895
+ }
896
+ if (cloudProviders) {
897
+ for (const cp of cloudProviders) {
898
+ const canDiscover = !!(cp.baseUrl && cp.apiKey);
899
+ const hasCachedModels = cp.models && cp.models.length > 0;
900
+ if (!canDiscover && !hasCachedModels) continue;
901
+ let models = [];
902
+ let available = false;
903
+ if (canDiscover) {
904
+ models = await this.listCloudModels({
905
+ baseUrl: cp.baseUrl ?? "",
906
+ apiKey: cp.apiKey,
907
+ providerId: cp.id
908
+ });
909
+ available = true;
910
+ }
911
+ if (models.length === 0 && hasCachedModels) {
912
+ models = cp.models ?? [];
913
+ available = false;
914
+ }
915
+ results.push({
916
+ providerId: cp.id,
917
+ displayName: cp.name ?? cp.id,
918
+ models: models.map((name) => ({ name, isDefault: name === cp.defaultModel })),
919
+ available
920
+ });
921
+ }
922
+ }
923
+ return results;
924
+ }
925
+ };
926
+ async function runInferenceInit(options = {}) {
927
+ const withGateway = options.withGateway !== false;
928
+ const internalOnly = Boolean(options.internalOllamaOnly);
929
+ await ensureNetwork();
930
+ const ollama = new OllamaService();
931
+ await ollama.install();
932
+ await ollama.start({ publishToHost: !internalOnly });
933
+ const model = options.model ?? "llama3.1:8b";
934
+ await ollama.pullModel(model, { publishToHost: !internalOnly });
935
+ if (withGateway) {
936
+ const gw = new InferenceGateway();
937
+ const result = await gw.ensure();
938
+ console.log("\nInference gateway (Traefik)");
939
+ console.log(` URL: ${result.publicUrl}`);
940
+ console.log(` User: ${result.username}`);
941
+ console.log(` Password: ${result.password}`);
942
+ console.log(` Secrets: ${result.secretsFile}`);
943
+ console.log(` Test: curl -u '${result.username}:${result.password}' ${result.publicUrl}/api/tags`);
944
+ const stateManager2 = new EntityStateManager();
945
+ await stateManager2.updateOrgan("legs", "ready");
946
+ }
947
+ const stateManager = new EntityStateManager();
948
+ await stateManager.setAIModel("ollama");
949
+ await stateManager.updateOrgan("brain", "ready");
950
+ console.log("\nInference profile ready.");
951
+ }
952
+ function registerBrainCommands(brain) {
953
+ initCommand(brain);
954
+ statusCommand(brain);
955
+ startCommand(brain);
956
+ stopCommand(brain);
957
+ providersCommand(brain);
958
+ }
959
+
960
+ export {
961
+ execa,
962
+ ensureNetwork,
963
+ OllamaService,
964
+ runBrainInit,
965
+ initCommand,
966
+ statusCommand,
967
+ SynapService,
968
+ startCommand,
969
+ stopCommand,
970
+ providersCommand,
971
+ ModelService,
972
+ runInferenceInit,
973
+ registerBrainCommands
974
+ };
975
+ //# sourceMappingURL=chunk-22RIHU4J.js.map