@gorlitzer-labs/seldon 0.1.13 → 0.1.15
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 +12 -5
- package/bin/seldon.js +43 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,13 +33,20 @@ seldon list # everything available
|
|
|
33
33
|
One command brings up the always-on services and prints where to go:
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
seldon up
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
seldon
|
|
40
|
-
seldon
|
|
36
|
+
seldon up # starts the voice (demerzel) + the supervisor (factory watch),
|
|
37
|
+
# then prints: Voice → http://localhost:8770, how to put agents
|
|
38
|
+
# to work, rooms, phone access
|
|
39
|
+
seldon up --tailnet # also expose the voice on your tailnet → reach it from your phone
|
|
40
|
+
seldon status # what's running (pids)
|
|
41
|
+
seldon down # stop everything
|
|
41
42
|
```
|
|
42
43
|
|
|
44
|
+
**Phone access:** `seldon up --tailnet` binds the voice to this machine's Tailscale
|
|
45
|
+
IP and prints `http://<tailnet-ip>:8770` — open it on any device on your tailnet.
|
|
46
|
+
The default `seldon up` stays loopback-only. Since Demerzel can act on the host,
|
|
47
|
+
exposing it means anyone on your tailnet can talk to it — run with
|
|
48
|
+
`DEMERZEL_READONLY=1` when sharing.
|
|
49
|
+
|
|
43
50
|
`seldon up` only starts what's installed. demerzel serves the voice UI on
|
|
44
51
|
`http://localhost:8770`. Its model set (~25 GB) is a one-time download —
|
|
45
52
|
`seldon install demerzel` **offers to fetch it right there** ([y/N]), and `seldon up`
|
package/bin/seldon.js
CHANGED
|
@@ -172,8 +172,14 @@ function installPython(m, dev) {
|
|
|
172
172
|
run(py, ["-m", "venv", venv]);
|
|
173
173
|
ok = run(path.join(venv, "bin", "pip"), ["install", "-r", req]);
|
|
174
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
|
+
}
|
|
175
181
|
// demerzel has no standalone binary — it runs as a module from its venv.
|
|
176
|
-
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)`));
|
|
177
183
|
else if (ok) console.log(C.dim(` run it: ${path.join(dest, m.bin)} (isolated venv at ${venv})`));
|
|
178
184
|
return ok;
|
|
179
185
|
}
|
|
@@ -320,6 +326,30 @@ function demerzelModelsReady() {
|
|
|
320
326
|
}
|
|
321
327
|
const demerzelInstalled = () => fs.existsSync(path.join(SELDON_HOME, "demerzel", ".venv", "bin", "python"));
|
|
322
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
|
+
// CLI missing/moved? The tailnet address is still on a utun interface —
|
|
339
|
+
// read it straight from the OS (Tailscale's CGNAT range is 100.64.0.0/10).
|
|
340
|
+
try {
|
|
341
|
+
const out = execSync("ifconfig", { stdio: ["ignore", "pipe", "ignore"] }).toString();
|
|
342
|
+
const m = out.match(/inet (100\.(?:6[4-9]|[7-9]\d|1[01]\d|12[0-7])\.\d+\.\d+)/);
|
|
343
|
+
if (m) return m[1];
|
|
344
|
+
} catch {}
|
|
345
|
+
try { // linux `ip addr`
|
|
346
|
+
const out = execSync("ip -4 addr", { stdio: ["ignore", "pipe", "ignore"] }).toString();
|
|
347
|
+
const m = out.match(/inet (100\.(?:6[4-9]|[7-9]\d|1[01]\d|12[0-7])\.\d+\.\d+)/);
|
|
348
|
+
if (m) return m[1];
|
|
349
|
+
} catch {}
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
|
|
323
353
|
// Fetch demerzel's model set (~25 GB) — offered, never silent. Respects the
|
|
324
354
|
// chosen LLM (DEMERZEL_LLM) so `seldon install demerzel` / `seldon up` pull the
|
|
325
355
|
// model you'll actually run. --models forces yes, --no-models forces skip.
|
|
@@ -352,8 +382,17 @@ async function up() {
|
|
|
352
382
|
else {
|
|
353
383
|
if (!demerzelModelsReady()) await ensureDemerzelModels({ ask: true }); // offer the fetch inline
|
|
354
384
|
if (demerzelModelsReady()) {
|
|
355
|
-
|
|
356
|
-
|
|
385
|
+
// --tailnet exposes the voice on this machine's tailnet IP (reachable
|
|
386
|
+
// from your phone). Default stays loopback (this machine only).
|
|
387
|
+
const wantTailnet = process.argv.includes("--tailnet") || process.argv.includes("--phone");
|
|
388
|
+
let host = null;
|
|
389
|
+
if (wantTailnet) { host = tailnetIp(); if (!host) later.push("--tailnet: no Tailscale IP found (is Tailscale running?) — started the voice on localhost instead."); }
|
|
390
|
+
const env = { ...process.env };
|
|
391
|
+
if (host) { env.DEMERZEL_HOST = host; stopDaemon("demerzel"); } // rebind: restart if it was on loopback
|
|
392
|
+
const r = startDaemon("demerzel", path.join(home, ".venv/bin/python"), ["-m", "demerzel.server"], { cwd: home, env });
|
|
393
|
+
const url = host ? `http://${host}:8770` : "http://localhost:8770";
|
|
394
|
+
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)"))}`);
|
|
395
|
+
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."));
|
|
357
396
|
} else {
|
|
358
397
|
later.push("Voice (demerzel): models not fetched yet — run `seldon up` again when you're ready to pull them.");
|
|
359
398
|
}
|
|
@@ -510,6 +549,7 @@ ${C.bold("seldon")} — install the Seldon stack, pick what you use.
|
|
|
510
549
|
|
|
511
550
|
${C.bold("seldon")} open the checklist (space to pick, enter to install)
|
|
512
551
|
${C.bold("seldon up")} start the stack (voice + supervisor) and print where to go
|
|
552
|
+
${C.bold("seldon up --tailnet")} also expose the voice on your tailnet (reach it from your phone)
|
|
513
553
|
${C.bold("seldon status")} what's running · ${C.bold("seldon down")} stop it
|
|
514
554
|
${C.bold("seldon install")} [ids…] install everything picked, or the named modules
|
|
515
555
|
${C.bold("seldon uninstall")} ids… remove the named modules (global CLI / isolated venv)
|