@gorlitzer-labs/seldon 0.1.20 → 0.1.22
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/bin/seldon.js +57 -13
- package/package.json +1 -1
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 {
|
|
@@ -324,6 +328,7 @@ const RUN_DIR = path.join(SELDON_HOME, "run");
|
|
|
324
328
|
const pidFile = (name) => path.join(RUN_DIR, name + ".pid");
|
|
325
329
|
const readPid = (name) => { try { return parseInt(fs.readFileSync(pidFile(name), "utf8").trim(), 10) || 0; } catch { return 0; } };
|
|
326
330
|
const isAlive = (pid) => { try { process.kill(pid, 0); return true; } catch { return false; } };
|
|
331
|
+
const portListening = (port) => { try { return !!execSync(`lsof -nP -iTCP:${port} -sTCP:LISTEN -t`, { stdio: ["ignore", "pipe", "ignore"] }).toString().trim(); } catch { return false; } };
|
|
327
332
|
const daemonUp = (name) => { const p = readPid(name); return p && isAlive(p) ? p : 0; };
|
|
328
333
|
|
|
329
334
|
function startDaemon(name, cmd, args, opts = {}) {
|
|
@@ -344,11 +349,24 @@ function stopDaemon(name) {
|
|
|
344
349
|
try { fs.rmSync(pidFile(name), { force: true }); } catch {}
|
|
345
350
|
return true;
|
|
346
351
|
}
|
|
347
|
-
// demerzel needs its model set
|
|
348
|
-
|
|
352
|
+
// demerzel needs its model set before it can serve. Check each model it loads by
|
|
353
|
+
// name — "any models-- dir in the HF cache" was true after a fetch died halfway,
|
|
354
|
+
// so the one it died on (the CAM++ voiceprint, the last download) was never
|
|
355
|
+
// offered again. The LLM is not listed: which one depends on the brain, and
|
|
356
|
+
// bonsai brings its own. Returns the missing labels; [] means ready.
|
|
357
|
+
function demerzelModelsMissing() {
|
|
349
358
|
const hub = path.join(process.env.HOME, ".cache", "huggingface", "hub");
|
|
350
|
-
|
|
359
|
+
const snapshotOk = (repo) => {
|
|
360
|
+
const snaps = path.join(hub, "models--" + repo.replace("/", "--"), "snapshots");
|
|
361
|
+
try { return fs.readdirSync(snaps).some((d) => fs.readdirSync(path.join(snaps, d)).length > 0); } catch { return false; }
|
|
362
|
+
};
|
|
363
|
+
const missing = [];
|
|
364
|
+
if (!snapshotOk("mlx-community/Qwen3-ASR-1.7B-8bit")) missing.push("ears (Qwen3-ASR)");
|
|
365
|
+
if (!snapshotOk("hexgrad/Kokoro-82M")) missing.push("voice (Kokoro)");
|
|
366
|
+
if (!fs.existsSync(path.join(SELDON_HOME, "demerzel", "models", "campplus_en.onnx"))) missing.push("voiceprint (CAM++)");
|
|
367
|
+
return missing;
|
|
351
368
|
}
|
|
369
|
+
const demerzelModelsReady = () => demerzelModelsMissing().length === 0;
|
|
352
370
|
const demerzelInstalled = () => fs.existsSync(path.join(SELDON_HOME, "demerzel", ".venv", "bin", "python"));
|
|
353
371
|
|
|
354
372
|
// This machine's Tailscale IPv4 (100.64.0.0/10), or null. Tries PATH then the
|
|
@@ -667,32 +685,58 @@ function moduleInstalled(m) {
|
|
|
667
685
|
|
|
668
686
|
function statusCmd() {
|
|
669
687
|
console.log(C.bold("\n seldon — stack status\n"));
|
|
688
|
+
// The dot answers "is it working?". For a CLI tool that is "installed"; for a
|
|
689
|
+
// SERVICE it is "running" — a green dot next to "voice down" read as up, and
|
|
690
|
+
// `seldon down` saying "nothing was running" then looked like a lie.
|
|
691
|
+
const up = [], starting = [], stopped = [];
|
|
692
|
+
const svcState = (label, pid, upText) => {
|
|
693
|
+
(pid ? up : stopped).push(label);
|
|
694
|
+
return pid ? " " + C.green(upText) : " " + C.gold(label + " down");
|
|
695
|
+
};
|
|
696
|
+
const brain = demerzelInstalled() ? (savedBrain() || "qwen") : null;
|
|
670
697
|
for (const m of MODULES) {
|
|
671
698
|
const inst = moduleInstalled(m);
|
|
672
|
-
const dot = inst ? C.green("●") : C.dim("○");
|
|
673
699
|
let detail;
|
|
674
700
|
if (!inst) detail = platformOk(m) ? C.dim(`not installed ${C.dim("· seldon install " + m.id)}`) : C.dim("not supported on this machine");
|
|
675
701
|
else if (m.method === "npm") detail = C.dim("v" + (npmGlobalVersion(m.pkg) || "?"));
|
|
676
702
|
else if (m.id === "bifrost") detail = C.dim("v" + (bifrostVersion() || "?"));
|
|
677
|
-
else if (m.id === "demerzel") detail = demerzelModelsReady() ? C.dim("venv + models ✓") : C.gold(
|
|
703
|
+
else if (m.id === "demerzel") detail = demerzelModelsReady() ? C.dim("venv + models ✓") : C.gold(`venv ok · missing ${demerzelModelsMissing().join(", ")} (seldon up)`);
|
|
678
704
|
else detail = C.dim("installed");
|
|
679
705
|
|
|
680
706
|
// service state for the two daemons
|
|
681
|
-
let svc = "";
|
|
682
|
-
if (m.id === "factory"
|
|
683
|
-
|
|
684
|
-
|
|
707
|
+
let svc = "", running = true;
|
|
708
|
+
if (m.id === "factory" && inst) {
|
|
709
|
+
const p = daemonUp("factory-watch"); running = !!p;
|
|
710
|
+
svc = svcState("supervisor", p, "supervisor up") + (p ? C.dim(` (pid ${p})`) : "");
|
|
711
|
+
}
|
|
712
|
+
if (m.id === "demerzel" && inst) {
|
|
713
|
+
const p = daemonUp("demerzel"), b = brain !== "bonsai" || daemonUp("bonsai") || bonsaiHealthy();
|
|
714
|
+
// A live pid is not a working voice: models load for ~30-90s before :8770
|
|
715
|
+
// listens, and "voice up" there sent you to a page that would not open.
|
|
716
|
+
const serving = p && portListening(8770);
|
|
717
|
+
running = !!serving && !!b; // the voice without its brain server cannot answer
|
|
718
|
+
if (p && !serving) { starting.push("voice"); svc = " " + C.gold("voice starting") + C.dim(" — models loading, give it a minute"); }
|
|
719
|
+
else svc = svcState("voice", p, "voice up") + (p ? " " + C.cyan("http://localhost:8770") : "");
|
|
720
|
+
}
|
|
721
|
+
const dot = !inst ? C.dim("○") : running ? C.green("●") : C.gold("●");
|
|
685
722
|
console.log(` ${dot} ${C.bold(m.id.padEnd(11))} ${detail}${svc}`);
|
|
686
723
|
}
|
|
687
|
-
if (
|
|
688
|
-
const brain = savedBrain() || "qwen";
|
|
724
|
+
if (brain) {
|
|
689
725
|
const bstate = brain === "bonsai"
|
|
690
|
-
? (daemonUp("bonsai") || bonsaiHealthy() ? C.green("server up") : C.
|
|
726
|
+
? ((daemonUp("bonsai") || bonsaiHealthy()) ? (up.push("brain"), C.green("server up")) : (stopped.push("brain"), C.gold("server down")))
|
|
691
727
|
: C.dim("in-process");
|
|
692
728
|
console.log(C.dim(`\n voice brain: `) + C.bold(brain) + " " + bstate + C.dim(" · switch: seldon up --brain=qwen|bonsai"));
|
|
693
729
|
const phone = tailscaleServeUrl();
|
|
694
730
|
if (phone) console.log(C.dim(" phone (HTTPS): ") + C.cyan(phone));
|
|
695
731
|
}
|
|
732
|
+
if (up.length || starting.length || stopped.length) {
|
|
733
|
+
const parts = [];
|
|
734
|
+
if (up.length) parts.push(C.green("● running: " + up.join(", ")));
|
|
735
|
+
if (starting.length) parts.push(C.gold("● starting: " + starting.join(", ")));
|
|
736
|
+
if (stopped.length) parts.push(C.gold("● stopped: " + stopped.join(", ")) + C.dim(" — start: ") + C.cyan("seldon up"));
|
|
737
|
+
if (!up.length && !starting.length) parts[0] = C.gold("● nothing is running") + C.dim(" — start: ") + C.cyan("seldon up");
|
|
738
|
+
console.log("\n " + parts.join(" "));
|
|
739
|
+
}
|
|
696
740
|
const dk = dockerState();
|
|
697
741
|
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
742
|
else if (dk === "up") console.log(C.dim("\n docker: ") + C.green("up"));
|