@gorlitzer-labs/seldon 0.1.12 → 0.1.14
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 +34 -4
- package/package.json +1 -1
package/bin/seldon.js
CHANGED
|
@@ -155,7 +155,9 @@ function installPython(m, dev) {
|
|
|
155
155
|
let ok;
|
|
156
156
|
const uv = ensureUv();
|
|
157
157
|
if (uv) {
|
|
158
|
-
const venvArgs = ["venv", venv];
|
|
158
|
+
const venvArgs = ["venv", venv, "--seed"]; // --seed puts pip in the venv:
|
|
159
|
+
// kokoro/misaki/spaCy shells out to pip at runtime to load its model, and a
|
|
160
|
+
// bare uv venv has none (crashes the voice on start otherwise).
|
|
159
161
|
if (m.pyVersion) venvArgs.push("--python", m.pyVersion); // uv fetches a standalone CPython
|
|
160
162
|
if (!run(uv, venvArgs)) return false;
|
|
161
163
|
ok = run(uv, ["pip", "install", "--python", path.join(venv, "bin", "python"), "-r", req]);
|
|
@@ -170,8 +172,14 @@ function installPython(m, dev) {
|
|
|
170
172
|
run(py, ["-m", "venv", venv]);
|
|
171
173
|
ok = run(path.join(venv, "bin", "pip"), ["install", "-r", req]);
|
|
172
174
|
}
|
|
175
|
+
// demerzel needs spaCy's en_core_web_sm (kokoro G2P) in the venv — install it
|
|
176
|
+
// with the venv (a bare fetch-models skip would leave the voice unable to start).
|
|
177
|
+
if (ok && m.id === "demerzel") {
|
|
178
|
+
console.log(C.dim(" installing spaCy model en_core_web_sm (voice text processing)…"));
|
|
179
|
+
run(path.join(venv, "bin", "python"), ["-m", "spacy", "download", "en_core_web_sm"]);
|
|
180
|
+
}
|
|
173
181
|
// demerzel has no standalone binary — it runs as a module from its venv.
|
|
174
|
-
if (ok && m.id === "demerzel") console.log(C.dim(` run it: seldon up (
|
|
182
|
+
if (ok && m.id === "demerzel") console.log(C.dim(` run it: seldon up (voice on http://localhost:8770; add --tailnet for your phone)`));
|
|
175
183
|
else if (ok) console.log(C.dim(` run it: ${path.join(dest, m.bin)} (isolated venv at ${venv})`));
|
|
176
184
|
return ok;
|
|
177
185
|
}
|
|
@@ -318,6 +326,18 @@ function demerzelModelsReady() {
|
|
|
318
326
|
}
|
|
319
327
|
const demerzelInstalled = () => fs.existsSync(path.join(SELDON_HOME, "demerzel", ".venv", "bin", "python"));
|
|
320
328
|
|
|
329
|
+
// This machine's Tailscale IPv4 (100.64.0.0/10), or null. Tries PATH then the
|
|
330
|
+
// macOS app location. Used to expose the voice to your phone over the tailnet.
|
|
331
|
+
function tailnetIp() {
|
|
332
|
+
for (const bin of ["tailscale", "/Applications/Tailscale.app/Contents/MacOS/tailscale"]) {
|
|
333
|
+
try {
|
|
334
|
+
const ip = execSync(`${bin} ip -4`, { stdio: ["ignore", "pipe", "ignore"] }).toString().trim().split(/\s+/)[0];
|
|
335
|
+
if (/^100\./.test(ip)) return ip;
|
|
336
|
+
} catch {}
|
|
337
|
+
}
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
|
|
321
341
|
// Fetch demerzel's model set (~25 GB) — offered, never silent. Respects the
|
|
322
342
|
// chosen LLM (DEMERZEL_LLM) so `seldon install demerzel` / `seldon up` pull the
|
|
323
343
|
// model you'll actually run. --models forces yes, --no-models forces skip.
|
|
@@ -350,8 +370,17 @@ async function up() {
|
|
|
350
370
|
else {
|
|
351
371
|
if (!demerzelModelsReady()) await ensureDemerzelModels({ ask: true }); // offer the fetch inline
|
|
352
372
|
if (demerzelModelsReady()) {
|
|
353
|
-
|
|
354
|
-
|
|
373
|
+
// --tailnet exposes the voice on this machine's tailnet IP (reachable
|
|
374
|
+
// from your phone). Default stays loopback (this machine only).
|
|
375
|
+
const wantTailnet = process.argv.includes("--tailnet") || process.argv.includes("--phone");
|
|
376
|
+
let host = null;
|
|
377
|
+
if (wantTailnet) { host = tailnetIp(); if (!host) later.push("--tailnet: no Tailscale IP found (is Tailscale running?) — started the voice on localhost instead."); }
|
|
378
|
+
const env = { ...process.env };
|
|
379
|
+
if (host) { env.DEMERZEL_HOST = host; stopDaemon("demerzel"); } // rebind: restart if it was on loopback
|
|
380
|
+
const r = startDaemon("demerzel", path.join(home, ".venv/bin/python"), ["-m", "demerzel.server"], { cwd: home, env });
|
|
381
|
+
const url = host ? `http://${host}:8770` : "http://localhost:8770";
|
|
382
|
+
go.push(`${C.bold("Voice")} ${C.cyan(url)} ${C.dim(host ? "(open it on your phone — on your tailnet)" : (r.already ? "(already up)" : "(starting — models load, ~30s)"))}`);
|
|
383
|
+
if (host) later.push(C.dim("Voice is on your tailnet — anyone on it can talk to Demerzel (and it can act on this Mac). Run with DEMERZEL_READONLY=1 to share it safely."));
|
|
355
384
|
} else {
|
|
356
385
|
later.push("Voice (demerzel): models not fetched yet — run `seldon up` again when you're ready to pull them.");
|
|
357
386
|
}
|
|
@@ -508,6 +537,7 @@ ${C.bold("seldon")} — install the Seldon stack, pick what you use.
|
|
|
508
537
|
|
|
509
538
|
${C.bold("seldon")} open the checklist (space to pick, enter to install)
|
|
510
539
|
${C.bold("seldon up")} start the stack (voice + supervisor) and print where to go
|
|
540
|
+
${C.bold("seldon up --tailnet")} also expose the voice on your tailnet (reach it from your phone)
|
|
511
541
|
${C.bold("seldon status")} what's running · ${C.bold("seldon down")} stop it
|
|
512
542
|
${C.bold("seldon install")} [ids…] install everything picked, or the named modules
|
|
513
543
|
${C.bold("seldon uninstall")} ids… remove the named modules (global CLI / isolated venv)
|