Lines 3-43javascript
4// wicked-interactive serve [--root <docs-dir>] [--port N] [--daemon] [--restart]
5// Multi-document mode (ADR-0015). Hosts every workspace under <docs-dir>; new docs are
6// created from the UI. The control plane is wicked-bus (ADR-0019).
8// ONE SHARED INSTANCE by default (ADR-0022 amended). With no --root, every session uses the
9// canonical root ~/wicked-interactive/docs — so `serve --daemon` from any session REUSES the one
10// running bridge instead of spawning another on a different port (the "why is it on 5 ports"
11// confusion). Pass --root only when you deliberately want a SEPARATE, isolated instance.
13// Ports, identity & the bridge (ADR-0022). The port is DYNAMIC (no --port → first free from
14// 4400 up; --port N is a preference that falls forward if taken). Each root records its live
15// bridge in <root>/.wi-serve.json. Reuse is IDENTITY-aware: we hit the recorded port's
16// /api/health (retried while the recorded pid is alive, so a busy daemon is reused, not
17// duplicated) and only reuse if it reports THIS root. Distinct roots never collide.
19// --daemon self-detaches: spawns the server in the background (survives the launching shell/agent
20// call — no nohup/disown), waits until the bridge answers, prints the URL, exits 0. `--restart`
21// stops an existing daemon for the root first (clean upgrade); plain re-run reuses the live one.
23import { readFileSync, openSync, mkdirSync } from "node:fs";
24import { spawn } from "node:child_process";
25import { resolve, join } from "node:path";
HighChild Process
Package source references child process execution.
bin/wicked-interactive.jsView on unpkg · L23 26import { homedir } from "node:os";
27import { fileURLToPath } from "node:url";
28import { createMultiServer } from "../src/service/server.js";
30 readLock, writeLock, removeLock, pidAlive, pickPort, bridgeIdentity, stopDaemon,
31} from "../src/service/serve-bridge.mjs";
32import { registerInstance, deregisterInstance } from "../src/service/instances.mjs";
34const HERE = fileURLToPath(new URL(".", import.meta.url));
35const SELF = fileURLToPath(import.meta.url);
36const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
38function parseArgs(argv) {
39 const args = { _: [] };
40 for (let i = 0; i < argv.length; i++) {
42 if (!a.startsWith("--")) { args._.push(a); continue; }
43 const key = a.slice(2);