@gorlitzer-labs/seldon 0.1.19 → 0.1.21
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 +3 -1
- package/bin/seldon.js +51 -6
- package/package.json +1 -1
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
|
@@ -158,7 +158,11 @@ function installPython(m, dev) {
|
|
|
158
158
|
const venvArgs = ["venv", venv, "--seed"]; // --seed puts pip in the venv:
|
|
159
159
|
// kokoro/misaki/spaCy shells out to pip at runtime to load its model, and a
|
|
160
160
|
// bare uv venv has none (crashes the voice on start otherwise).
|
|
161
|
-
|
|
161
|
+
// uv fetches a standalone CPython — but only if told to. By default it PREFERS
|
|
162
|
+
// a matching system interpreter, and a python.org framework build ships with
|
|
163
|
+
// no CA bundle, so the venv's urllib failed every HTTPS download (the CAM++
|
|
164
|
+
// voiceprint model silently never arrived). only-managed makes the comment true.
|
|
165
|
+
if (m.pyVersion) venvArgs.push("--python", m.pyVersion, "--python-preference", "only-managed");
|
|
162
166
|
if (!run(uv, venvArgs)) return false;
|
|
163
167
|
ok = run(uv, ["pip", "install", "--python", path.join(venv, "bin", "python"), "-r", req]);
|
|
164
168
|
} else {
|
|
@@ -330,7 +334,9 @@ function startDaemon(name, cmd, args, opts = {}) {
|
|
|
330
334
|
if (daemonUp(name)) return { already: true, pid: readPid(name) };
|
|
331
335
|
fs.mkdirSync(RUN_DIR, { recursive: true });
|
|
332
336
|
const log = fs.openSync(path.join(RUN_DIR, name + ".log"), "a");
|
|
333
|
-
|
|
337
|
+
// cwd defaults to SELDON_HOME: a long-lived daemon must not pin whatever folder
|
|
338
|
+
// `seldon up` was typed in (it did — the supervisor held a project dir open).
|
|
339
|
+
const child = spawn(cmd, args, { detached: true, stdio: ["ignore", log, log], cwd: SELDON_HOME, ...opts });
|
|
334
340
|
fs.writeFileSync(pidFile(name), String(child.pid));
|
|
335
341
|
child.unref();
|
|
336
342
|
return { pid: child.pid };
|
|
@@ -342,11 +348,24 @@ function stopDaemon(name) {
|
|
|
342
348
|
try { fs.rmSync(pidFile(name), { force: true }); } catch {}
|
|
343
349
|
return true;
|
|
344
350
|
}
|
|
345
|
-
// demerzel needs its model set
|
|
346
|
-
|
|
351
|
+
// demerzel needs its model set before it can serve. Check each model it loads by
|
|
352
|
+
// name — "any models-- dir in the HF cache" was true after a fetch died halfway,
|
|
353
|
+
// so the one it died on (the CAM++ voiceprint, the last download) was never
|
|
354
|
+
// offered again. The LLM is not listed: which one depends on the brain, and
|
|
355
|
+
// bonsai brings its own. Returns the missing labels; [] means ready.
|
|
356
|
+
function demerzelModelsMissing() {
|
|
347
357
|
const hub = path.join(process.env.HOME, ".cache", "huggingface", "hub");
|
|
348
|
-
|
|
358
|
+
const snapshotOk = (repo) => {
|
|
359
|
+
const snaps = path.join(hub, "models--" + repo.replace("/", "--"), "snapshots");
|
|
360
|
+
try { return fs.readdirSync(snaps).some((d) => fs.readdirSync(path.join(snaps, d)).length > 0); } catch { return false; }
|
|
361
|
+
};
|
|
362
|
+
const missing = [];
|
|
363
|
+
if (!snapshotOk("mlx-community/Qwen3-ASR-1.7B-8bit")) missing.push("ears (Qwen3-ASR)");
|
|
364
|
+
if (!snapshotOk("hexgrad/Kokoro-82M")) missing.push("voice (Kokoro)");
|
|
365
|
+
if (!fs.existsSync(path.join(SELDON_HOME, "demerzel", "models", "campplus_en.onnx"))) missing.push("voiceprint (CAM++)");
|
|
366
|
+
return missing;
|
|
349
367
|
}
|
|
368
|
+
const demerzelModelsReady = () => demerzelModelsMissing().length === 0;
|
|
350
369
|
const demerzelInstalled = () => fs.existsSync(path.join(SELDON_HOME, "demerzel", ".venv", "bin", "python"));
|
|
351
370
|
|
|
352
371
|
// This machine's Tailscale IPv4 (100.64.0.0/10), or null. Tries PATH then the
|
|
@@ -593,6 +612,7 @@ async function up() {
|
|
|
593
612
|
|
|
594
613
|
// The rest are on-demand, not daemons — point at the command.
|
|
595
614
|
if (has("factory")) go.push(`${C.bold("Put agents to work")} ${C.cyan('factory new "<your idea>"')} ${C.dim("→ repo · foundation · apiary hive")}`);
|
|
615
|
+
if (has("factory")) go.push(`${C.bold("An existing repo")} ${C.cyan("seldon adopt [dir]")} ${C.dim("→ foundation (keeps your docs) · hive · supervised")}`);
|
|
596
616
|
if (has("apiary")) go.push(`${C.bold("Agent rooms")} ${C.cyan("apiary ps")} ${C.dim("(active rooms + join links)")}`);
|
|
597
617
|
if (has("bifrost")) go.push(`${C.bold("Across machines / phone")} ${C.cyan("bifrost sessions")}`);
|
|
598
618
|
|
|
@@ -671,7 +691,7 @@ function statusCmd() {
|
|
|
671
691
|
if (!inst) detail = platformOk(m) ? C.dim(`not installed ${C.dim("· seldon install " + m.id)}`) : C.dim("not supported on this machine");
|
|
672
692
|
else if (m.method === "npm") detail = C.dim("v" + (npmGlobalVersion(m.pkg) || "?"));
|
|
673
693
|
else if (m.id === "bifrost") detail = C.dim("v" + (bifrostVersion() || "?"));
|
|
674
|
-
else if (m.id === "demerzel") detail = demerzelModelsReady() ? C.dim("venv + models ✓") : C.gold(
|
|
694
|
+
else if (m.id === "demerzel") detail = demerzelModelsReady() ? C.dim("venv + models ✓") : C.gold(`venv ok · missing ${demerzelModelsMissing().join(", ")} (seldon up)`);
|
|
675
695
|
else detail = C.dim("installed");
|
|
676
696
|
|
|
677
697
|
// service state for the two daemons
|
|
@@ -690,9 +710,31 @@ function statusCmd() {
|
|
|
690
710
|
const phone = tailscaleServeUrl();
|
|
691
711
|
if (phone) console.log(C.dim(" phone (HTTPS): ") + C.cyan(phone));
|
|
692
712
|
}
|
|
713
|
+
const dk = dockerState();
|
|
714
|
+
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)"));
|
|
715
|
+
else if (dk === "up") console.log(C.dim("\n docker: ") + C.green("up"));
|
|
693
716
|
console.log(C.dim(`\n start: seldon up · stop: seldon down · logs: ${RUN_DIR}/\n`));
|
|
694
717
|
}
|
|
695
718
|
|
|
719
|
+
// "none" (no docker CLI) | "down" (CLI but no daemon) | "up". Only shown when docker exists.
|
|
720
|
+
function dockerState() {
|
|
721
|
+
if (!has("docker")) return "none";
|
|
722
|
+
try { execSync("docker info", { stdio: "ignore", timeout: 8000 }); return "up"; } catch { return "down"; }
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// ---- adopt: bring an existing repo onto the line ----------------------------
|
|
726
|
+
// factory owns the registry + hive, so this is a front door onto `factory adopt`.
|
|
727
|
+
function adopt(target) {
|
|
728
|
+
if (!has("factory")) {
|
|
729
|
+
console.log(C.red(" adopt needs factory — install it: ") + C.cyan("seldon install factory"));
|
|
730
|
+
process.exitCode = 1; return;
|
|
731
|
+
}
|
|
732
|
+
const passthru = process.argv.slice(3).filter((a) => a !== "--dev");
|
|
733
|
+
const r = spawnSync("factory", ["adopt", ...passthru], { stdio: "inherit" });
|
|
734
|
+
if (r.status !== 0) { process.exitCode = r.status || 1; return; }
|
|
735
|
+
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");
|
|
736
|
+
}
|
|
737
|
+
|
|
696
738
|
// ---- the checklist TUI ------------------------------------------------------
|
|
697
739
|
const BANNER = [
|
|
698
740
|
" ███████╗███████╗██╗ ██████╗ ██████╗ ███╗ ██╗",
|
|
@@ -776,6 +818,8 @@ ${C.bold("seldon")} — install the Seldon stack, pick what you use.
|
|
|
776
818
|
${C.bold("seldon up --tailnet")} expose the voice over HTTPS (tailscale serve) so the mic works on your phone
|
|
777
819
|
${C.bold("seldon up --brain=X")} pick the voice brain: qwen (in-process) | bonsai (local server); remembered
|
|
778
820
|
${C.bold("seldon status")} what's running · ${C.bold("seldon down")} stop it
|
|
821
|
+
${C.bold("seldon adopt")} [dir] put an existing repo on the line: foundation docs (never overwritten),
|
|
822
|
+
an apiary hive, supervised by factory — and print what's next. Safe to re-run.
|
|
779
823
|
${C.bold("seldon install")} [ids…] install everything picked, or the named modules
|
|
780
824
|
${C.bold("seldon uninstall")} ids… remove the named modules (global CLI / isolated venv)
|
|
781
825
|
${C.bold("seldon uninstall --all")} remove the whole stack (also drops ~/.seldon)
|
|
@@ -817,6 +861,7 @@ const ids = tokens.filter((id) => byId[id]); // only the valid module
|
|
|
817
861
|
if (cmd === "up") return up();
|
|
818
862
|
if (cmd === "down") return down();
|
|
819
863
|
if (cmd === "status") return statusCmd();
|
|
864
|
+
if (cmd === "adopt") return adopt(tokens[0]);
|
|
820
865
|
if (cmd && cmd !== "install") { help(); process.exitCode = 1; return; }
|
|
821
866
|
// interactive
|
|
822
867
|
if (!process.stdin.isTTY) { console.log(C.red("no TTY — use: seldon install <ids…>")); process.exitCode = 1; return; }
|