demobite 1.18.3 → 1.18.5
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/launcher/index.mjs +21 -15
- package/package.json +1 -1
package/launcher/index.mjs
CHANGED
|
@@ -25,6 +25,7 @@ import os from "node:os";
|
|
|
25
25
|
import path from "node:path";
|
|
26
26
|
import { fileURLToPath } from "node:url";
|
|
27
27
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
28
|
+
import { createHash } from "node:crypto";
|
|
28
29
|
|
|
29
30
|
const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
30
31
|
const pkg = JSON.parse(fs.readFileSync(path.join(pkgRoot, "package.json"), "utf8"));
|
|
@@ -61,7 +62,12 @@ const LOCK = path.join(HOME, "listen.lock");
|
|
|
61
62
|
const LOG = path.join(HOME, "listen.log");
|
|
62
63
|
const STATE = path.join(HOME, "state.json");
|
|
63
64
|
const CHILDREN = path.join(HOME, "children.json");
|
|
64
|
-
|
|
65
|
+
// A second home (DEMOBITE_HOME, used by tests and rigs) gets its own service name, so `off` on a rig can
|
|
66
|
+
// never unload the real one (2026-10-16: a scratch rig's `off` removed the founder's service).
|
|
67
|
+
const HOME_TAG = process.env.DEMOBITE_HOME ? "." + createHash("sha1").update(HOME).digest("hex").slice(0, 8) : "";
|
|
68
|
+
const SERVICE_LABEL = `com.demobites.listen${HOME_TAG}`;
|
|
69
|
+
const SERVICE_UNIT = `demobite${HOME_TAG}.service`;
|
|
70
|
+
const RUN_NAME = `DemoBites${HOME_TAG}`;
|
|
65
71
|
const SERVICE = process.argv.includes("--service");
|
|
66
72
|
const launcherPath = fileURLToPath(import.meta.url);
|
|
67
73
|
const readJson = (f) => { try { return JSON.parse(fs.readFileSync(f, "utf8")); } catch { return null; } };
|
|
@@ -70,11 +76,11 @@ const pidAlive = (pid) => { if (!pid) return false; try { process.kill(pid, 0);
|
|
|
70
76
|
const ago = (iso) => { const s = Math.max(0, Math.round((Date.now() - new Date(iso).getTime()) / 1000)); return s < 60 ? `${s} s ago` : s < 3600 ? `${Math.round(s / 60)} min ago` : `${Math.round(s / 3600)} h ago`; };
|
|
71
77
|
const serviceDef = () => {
|
|
72
78
|
if (process.platform === "darwin") return { file: path.join(os.homedir(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`) };
|
|
73
|
-
if (process.platform === "linux") return { file: path.join(os.homedir(), ".config", "systemd", "user",
|
|
79
|
+
if (process.platform === "linux") return { file: path.join(os.homedir(), ".config", "systemd", "user", SERVICE_UNIT) };
|
|
74
80
|
return { file: path.join(HOME, "service.vbs") };
|
|
75
81
|
};
|
|
76
82
|
const serviceInstalled = () => {
|
|
77
|
-
if (process.platform === "win32") { const r = spawnSync("reg", ["query", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", "/v",
|
|
83
|
+
if (process.platform === "win32") { const r = spawnSync("reg", ["query", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", "/v", RUN_NAME], { encoding: "utf8" }); return r.status === 0; }
|
|
78
84
|
return fs.existsSync(serviceDef().file);
|
|
79
85
|
};
|
|
80
86
|
const killListener = () => {
|
|
@@ -89,11 +95,11 @@ const printStatus = () => {
|
|
|
89
95
|
const cfgThere = lock?.cwd ? readJson(path.join(lock.cwd, ".recorder", "config.json")) : null;
|
|
90
96
|
const st = { base: cfgThere?.base ?? null, workspace: cfgThere?.workspace?.name ?? null, ...(readJson(STATE) ?? {}) };
|
|
91
97
|
if (live) {
|
|
92
|
-
console.log(` ●
|
|
98
|
+
console.log(` ● Connected as "${st?.name ?? os.hostname().replace(/\.local$/, "")}" since ${new Date(lock.since).toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" })} · for ${st?.workspace ?? "your workspace"} on ${(st?.base ?? "").replace(/^https?:\/\//, "") || "app.demobites.com"} (pid ${lock.pid}${lock.service ? ", login service" : ", a Terminal window"})`);
|
|
93
99
|
if (st?.at) console.log(` last check-in ${ago(st.at)} · ${st.busy ? `recording: ${st.job ?? ""}` : "idle"}${st.last_error ? ` · last error: ${st.last_error}` : ""}`);
|
|
94
100
|
console.log(` working in ${lock.cwd.replace(os.homedir(), "~")}${lock.repo ? ` · code: ${lock.repo}` : ""}`);
|
|
95
101
|
} else {
|
|
96
|
-
console.log(` ○ Not
|
|
102
|
+
console.log(` ○ Not connected.${st?.at ? ` Last check-in ${ago(st.at)}.` : ""}`);
|
|
97
103
|
}
|
|
98
104
|
console.log(` login service: ${serviceInstalled() ? "installed (starts at login, restarts if it dies)" : "not installed"} · run: demobite ${live ? "off" : "on"}`);
|
|
99
105
|
console.log(` log: ${LOG.replace(os.homedir(), "~")}`);
|
|
@@ -107,8 +113,8 @@ if (arg === "logs") {
|
|
|
107
113
|
if (arg === "off") {
|
|
108
114
|
const def = serviceDef();
|
|
109
115
|
if (process.platform === "darwin") spawnSync("launchctl", ["bootout", `gui/${process.getuid()}/${SERVICE_LABEL}`], { stdio: "ignore" });
|
|
110
|
-
else if (process.platform === "linux") spawnSync("systemctl", ["--user", "disable", "--now",
|
|
111
|
-
else spawnSync("reg", ["delete", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", "/v",
|
|
116
|
+
else if (process.platform === "linux") spawnSync("systemctl", ["--user", "disable", "--now", SERVICE_UNIT], { stdio: "ignore" });
|
|
117
|
+
else spawnSync("reg", ["delete", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", "/v", RUN_NAME, "/f"], { stdio: "ignore" });
|
|
112
118
|
fs.rmSync(def.file, { force: true });
|
|
113
119
|
const pid = killListener();
|
|
114
120
|
// the browsers of an interrupted take go with it
|
|
@@ -322,14 +328,14 @@ if (arg === "on") {
|
|
|
322
328
|
fs.writeFileSync(def.file, unit);
|
|
323
329
|
killListener();
|
|
324
330
|
spawnSync("systemctl", ["--user", "daemon-reload"], { stdio: "ignore" });
|
|
325
|
-
const r = spawnSync("systemctl", ["--user", "enable", "--now",
|
|
331
|
+
const r = spawnSync("systemctl", ["--user", "enable", "--now", SERVICE_UNIT], { encoding: "utf8" });
|
|
326
332
|
if (r.status !== 0) { warn(`systemd refused the service: ${(r.stderr || "").trim()}`); process.exit(1); }
|
|
327
333
|
} else {
|
|
328
334
|
// Windows: a Run entry that starts the listener hidden at logon (no console window), via a tiny script.
|
|
329
335
|
const q = (t) => `""${t}""`;
|
|
330
336
|
const vbs = `Set sh = CreateObject("WScript.Shell")\nsh.CurrentDirectory = "${cwd}"\nsh.Run "${args.map((a) => q(a)).join(" ")} >> ${q(LOG)} 2>&1", 0, False\n`;
|
|
331
337
|
fs.writeFileSync(def.file, vbs);
|
|
332
|
-
spawnSync("reg", ["add", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", "/v",
|
|
338
|
+
spawnSync("reg", ["add", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", "/v", RUN_NAME, "/t", "REG_SZ", "/d", `wscript.exe "${def.file}"`, "/f"], { stdio: "ignore" });
|
|
333
339
|
killListener();
|
|
334
340
|
spawnSync("wscript.exe", [def.file], { stdio: "ignore" });
|
|
335
341
|
}
|
|
@@ -340,7 +346,7 @@ if (arg === "on") {
|
|
|
340
346
|
console.log(`
|
|
341
347
|
┌──────────────────────────────────────────────────────────┐
|
|
342
348
|
│ │
|
|
343
|
-
│ ✓ Done. DemoBites is
|
|
349
|
+
│ ✓ Done. DemoBites is connected on this ${process.platform === "darwin" ? "Mac" : "machine"}.${process.platform === "darwin" ? " " : " "}│
|
|
344
350
|
│ │
|
|
345
351
|
│ You can close this window. The recorder keeps │
|
|
346
352
|
│ running quietly in the background, and starts │
|
|
@@ -349,7 +355,7 @@ if (arg === "on") {
|
|
|
349
355
|
│ Back in the app, the Recorder box says Connected. │
|
|
350
356
|
│ │
|
|
351
357
|
│ Any time, in a Terminal: │
|
|
352
|
-
│ demobite status is it
|
|
358
|
+
│ demobite status is it connected │
|
|
353
359
|
│ demobite off stop it and remove it │
|
|
354
360
|
│ demobite on bring it back │
|
|
355
361
|
│ │
|
|
@@ -385,7 +391,7 @@ if (arg === "listen") {
|
|
|
385
391
|
{
|
|
386
392
|
const cur = readJson(LOCK);
|
|
387
393
|
if (cur?.pid && cur.pid !== process.pid && pidAlive(cur.pid)) {
|
|
388
|
-
console.log(` ● Already
|
|
394
|
+
console.log(` ● Already connected (pid ${cur.pid}, since ${new Date(cur.since).toLocaleTimeString()}${cur.service ? ", the login service" : ""}). Nothing to do.\n demobite status · demobite off`);
|
|
389
395
|
process.exit(0);
|
|
390
396
|
}
|
|
391
397
|
// the browsers of a listener that died hard
|
|
@@ -457,11 +463,11 @@ if (arg === "listen") {
|
|
|
457
463
|
const event = (job_id, kind, payload) => api({ action: "event", job_id, kind, payload });
|
|
458
464
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
459
465
|
|
|
460
|
-
console.log(`\n
|
|
466
|
+
console.log(`\n Connected as "${name}" for ${cfg.workspace?.name ?? "your workspace"} on ${base.replace(/^https?:\/\//, "")}.`);
|
|
461
467
|
console.log(` Ask for a demo in the app (Create → Agentic Capture). ${engine === "claude" ? (agentBin ? `Claude Code${agentInfo.agent_version ? " " + agentInfo.agent_version : ""} plans the demos, on your own plan; they film on ${JOB_MODEL} unless you pick another model in the app.` : "! --engine=claude, but Claude Code was not found on this machine.") : `Demos are planned by the DemoBites agent on ${JOB_MODEL} (pick another model in the app)${process.env.DEMOBITES_NO_CODE ? "; code sharing is off" : repo ? "; the files it reads from this project are logged in each take" : ""}.`}`);
|
|
462
468
|
// The one thing the person must know, boxed (founder 2026-10-15): this
|
|
463
469
|
// window is the machine's side of every recording. The login service has no window: one line instead.
|
|
464
|
-
if (SERVICE) console.log(` ${new Date().toISOString()}
|
|
470
|
+
if (SERVICE) console.log(` ${new Date().toISOString()} connected (login service, pid ${process.pid})`);
|
|
465
471
|
else console.log(`
|
|
466
472
|
┌──────────────────────────────────────────────────────────┐
|
|
467
473
|
│ │
|
|
@@ -482,7 +488,7 @@ if (arg === "listen") {
|
|
|
482
488
|
const s = Math.floor((Date.now() - startedAt) / 1000);
|
|
483
489
|
const clock = `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
|
|
484
490
|
const dot = s % 2 ? "●" : "○";
|
|
485
|
-
process.stdout.write(`\r\x1b[2K ${dot}
|
|
491
|
+
process.stdout.write(`\r\x1b[2K ${dot} Connected · ${clock} · waiting for a demo from the app`);
|
|
486
492
|
};
|
|
487
493
|
const waitOn = () => { if (tty && !ticker) { tick(); ticker = setInterval(tick, 1000); } };
|
|
488
494
|
const waitOff = () => { if (ticker) { clearInterval(ticker); ticker = null; process.stdout.write("\r\x1b[2K"); } };
|
package/package.json
CHANGED