@gorlitzer-labs/seldon 0.1.18 → 0.1.20

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 CHANGED
@@ -37,7 +37,9 @@ seldon up # starts the voice (demerzel) + the supervisor (factory wat
37
37
  # then prints: Voice → http://localhost:8770, how to put agents
38
38
  # to work, rooms, phone access
39
39
  seldon up --tailnet # also expose the voice on your tailnet → reach it from your phone
40
- seldon status # what's running (pids)
40
+ seldon status # what's running (pids) — and warns if docker's daemon is down
41
+ seldon adopt [dir] # put an existing repo on the line (foundation · hive · supervised)
42
+ # and print what's next from its queue. Safe to re-run.
41
43
  seldon down # stop everything
42
44
  ```
43
45
 
package/bin/seldon.js CHANGED
@@ -317,8 +317,10 @@ async function doUninstall(ids, { yes = false, all = false } = {}) {
317
317
  // The stack's only always-on services are the voice (demerzel) and the
318
318
  // supervisor loop (factory watch). We run them as detached background daemons,
319
319
  // track them by pidfile under ~/.seldon/run, and print one map of where to go.
320
- const RUN_DIR = path.join(process.env.HOME, ".seldon", "run");
321
- const SELDON_HOME = path.join(process.env.HOME, ".seldon");
320
+ // SELDON_HOME can be overridden (tests point it at a temp dir; also a legit
321
+ // user override). Everything — run/, bonsai/, demerzel/, brain — derives from it.
322
+ const SELDON_HOME = process.env.SELDON_HOME || path.join(process.env.HOME, ".seldon");
323
+ const RUN_DIR = path.join(SELDON_HOME, "run");
322
324
  const pidFile = (name) => path.join(RUN_DIR, name + ".pid");
323
325
  const readPid = (name) => { try { return parseInt(fs.readFileSync(pidFile(name), "utf8").trim(), 10) || 0; } catch { return 0; } };
324
326
  const isAlive = (pid) => { try { process.kill(pid, 0); return true; } catch { return false; } };
@@ -328,7 +330,9 @@ function startDaemon(name, cmd, args, opts = {}) {
328
330
  if (daemonUp(name)) return { already: true, pid: readPid(name) };
329
331
  fs.mkdirSync(RUN_DIR, { recursive: true });
330
332
  const log = fs.openSync(path.join(RUN_DIR, name + ".log"), "a");
331
- const child = spawn(cmd, args, { detached: true, stdio: ["ignore", log, log], ...opts });
333
+ // cwd defaults to SELDON_HOME: a long-lived daemon must not pin whatever folder
334
+ // `seldon up` was typed in (it did — the supervisor held a project dir open).
335
+ const child = spawn(cmd, args, { detached: true, stdio: ["ignore", log, log], cwd: SELDON_HOME, ...opts });
332
336
  fs.writeFileSync(pidFile(name), String(child.pid));
333
337
  child.unref();
334
338
  return { pid: child.pid };
@@ -591,6 +595,7 @@ async function up() {
591
595
 
592
596
  // The rest are on-demand, not daemons — point at the command.
593
597
  if (has("factory")) go.push(`${C.bold("Put agents to work")} ${C.cyan('factory new "<your idea>"')} ${C.dim("→ repo · foundation · apiary hive")}`);
598
+ if (has("factory")) go.push(`${C.bold("An existing repo")} ${C.cyan("seldon adopt [dir]")} ${C.dim("→ foundation (keeps your docs) · hive · supervised")}`);
594
599
  if (has("apiary")) go.push(`${C.bold("Agent rooms")} ${C.cyan("apiary ps")} ${C.dim("(active rooms + join links)")}`);
595
600
  if (has("bifrost")) go.push(`${C.bold("Across machines / phone")} ${C.cyan("bifrost sessions")}`);
596
601
 
@@ -688,9 +693,31 @@ function statusCmd() {
688
693
  const phone = tailscaleServeUrl();
689
694
  if (phone) console.log(C.dim(" phone (HTTPS): ") + C.cyan(phone));
690
695
  }
696
+ const dk = dockerState();
697
+ if (dk === "down") console.log(C.gold("\n ⚠ docker: installed but the daemon is not running") + C.dim(" — factory box and containerised gates need it (open Docker Desktop)"));
698
+ else if (dk === "up") console.log(C.dim("\n docker: ") + C.green("up"));
691
699
  console.log(C.dim(`\n start: seldon up · stop: seldon down · logs: ${RUN_DIR}/\n`));
692
700
  }
693
701
 
702
+ // "none" (no docker CLI) | "down" (CLI but no daemon) | "up". Only shown when docker exists.
703
+ function dockerState() {
704
+ if (!has("docker")) return "none";
705
+ try { execSync("docker info", { stdio: "ignore", timeout: 8000 }); return "up"; } catch { return "down"; }
706
+ }
707
+
708
+ // ---- adopt: bring an existing repo onto the line ----------------------------
709
+ // factory owns the registry + hive, so this is a front door onto `factory adopt`.
710
+ function adopt(target) {
711
+ if (!has("factory")) {
712
+ console.log(C.red(" adopt needs factory — install it: ") + C.cyan("seldon install factory"));
713
+ process.exitCode = 1; return;
714
+ }
715
+ const passthru = process.argv.slice(3).filter((a) => a !== "--dev");
716
+ const r = spawnSync("factory", ["adopt", ...passthru], { stdio: "inherit" });
717
+ if (r.status !== 0) { process.exitCode = r.status || 1; return; }
718
+ if (!daemonUp("factory-watch")) console.log(C.gold(" the supervisor is not running — ") + C.cyan("seldon up") + C.dim(" starts it, so agents here get healed and nudged.") + "\n");
719
+ }
720
+
694
721
  // ---- the checklist TUI ------------------------------------------------------
695
722
  const BANNER = [
696
723
  " ███████╗███████╗██╗ ██████╗ ██████╗ ███╗ ██╗",
@@ -774,6 +801,8 @@ ${C.bold("seldon")} — install the Seldon stack, pick what you use.
774
801
  ${C.bold("seldon up --tailnet")} expose the voice over HTTPS (tailscale serve) so the mic works on your phone
775
802
  ${C.bold("seldon up --brain=X")} pick the voice brain: qwen (in-process) | bonsai (local server); remembered
776
803
  ${C.bold("seldon status")} what's running · ${C.bold("seldon down")} stop it
804
+ ${C.bold("seldon adopt")} [dir] put an existing repo on the line: foundation docs (never overwritten),
805
+ an apiary hive, supervised by factory — and print what's next. Safe to re-run.
777
806
  ${C.bold("seldon install")} [ids…] install everything picked, or the named modules
778
807
  ${C.bold("seldon uninstall")} ids… remove the named modules (global CLI / isolated venv)
779
808
  ${C.bold("seldon uninstall --all")} remove the whole stack (also drops ~/.seldon)
@@ -815,6 +844,7 @@ const ids = tokens.filter((id) => byId[id]); // only the valid module
815
844
  if (cmd === "up") return up();
816
845
  if (cmd === "down") return down();
817
846
  if (cmd === "status") return statusCmd();
847
+ if (cmd === "adopt") return adopt(tokens[0]);
818
848
  if (cmd && cmd !== "install") { help(); process.exitCode = 1; return; }
819
849
  // interactive
820
850
  if (!process.stdin.isTTY) { console.log(C.red("no TTY — use: seldon install <ids…>")); process.exitCode = 1; return; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gorlitzer-labs/seldon",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "Install the Seldon stack — pick your tools from a checklist; each installs via its native method.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,5 +27,8 @@
27
27
  "license": "MIT",
28
28
  "publishConfig": {
29
29
  "access": "public"
30
+ },
31
+ "scripts": {
32
+ "test": "node --test test/*.test.mjs"
30
33
  }
31
34
  }