conductor-board 1.2.0 → 1.3.0
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/cli.js +86 -0
- package/cli/setup.js +3 -0
- package/dist/assets/index-Dauby4vm.js +34 -0
- package/dist/assets/index-nvl4ljRP.css +1 -0
- package/dist/index.html +2 -2
- package/package.json +3 -3
- package/dist/assets/index-C0erDplC.css +0 -1
- package/dist/assets/index-D02IYg9C.js +0 -34
package/bin/cli.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import readline from "node:readline";
|
|
5
6
|
import { startServer } from "../server/server.js";
|
|
6
7
|
|
|
7
8
|
const argv = process.argv.slice(2);
|
|
@@ -117,6 +118,91 @@ const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
|
117
118
|
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
118
119
|
const iris = (s) => `\x1b[38;5;141m${s}\x1b[0m`;
|
|
119
120
|
const mint = (s) => `\x1b[38;5;78m${s}\x1b[0m`;
|
|
121
|
+
const amber = (s) => `\x1b[38;5;214m${s}\x1b[0m`;
|
|
122
|
+
|
|
123
|
+
function ago(iso) {
|
|
124
|
+
const t = new Date(iso).getTime();
|
|
125
|
+
if (Number.isNaN(t)) return "";
|
|
126
|
+
const s = Math.max(0, Math.floor((Date.now() - t) / 1000));
|
|
127
|
+
if (s < 60) return `${s}s ago`;
|
|
128
|
+
const m = Math.floor(s / 60);
|
|
129
|
+
if (m < 60) return `${m}m ago`;
|
|
130
|
+
const h = Math.floor(m / 60);
|
|
131
|
+
if (h < 24) return `${h}h ago`;
|
|
132
|
+
return `${Math.floor(h / 24)}d ago`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function ask(question) {
|
|
136
|
+
return new Promise((resolve) => {
|
|
137
|
+
if (!process.stdin.isTTY) return resolve(false); // non-interactive — never block
|
|
138
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
139
|
+
rl.question(question, (answer) => {
|
|
140
|
+
rl.close();
|
|
141
|
+
resolve(/^y(es)?$/i.test(answer.trim()));
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// A pid is "alive" if signalling it doesn't throw ESRCH (EPERM = alive, not ours).
|
|
147
|
+
function pidAlive(pid) {
|
|
148
|
+
try {
|
|
149
|
+
process.kill(pid, 0);
|
|
150
|
+
return true;
|
|
151
|
+
} catch (e) {
|
|
152
|
+
return e.code === "EPERM";
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Before binding a port, look for a board that's already running here. A live
|
|
158
|
+
* one we offer to reclaim; a dead pid leaves a stale server.json we just clear.
|
|
159
|
+
*/
|
|
160
|
+
async function preflightStaleBoard(serverJsonPath) {
|
|
161
|
+
let info;
|
|
162
|
+
try {
|
|
163
|
+
info = JSON.parse(fs.readFileSync(serverJsonPath, "utf8"));
|
|
164
|
+
} catch {
|
|
165
|
+
return; // no (readable) server.json — nothing to do
|
|
166
|
+
}
|
|
167
|
+
const pid = info && info.pid;
|
|
168
|
+
const clear = () => {
|
|
169
|
+
try {
|
|
170
|
+
fs.unlinkSync(serverJsonPath);
|
|
171
|
+
} catch {
|
|
172
|
+
/* already gone */
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
if (!pid || !pidAlive(pid)) {
|
|
177
|
+
if (pid) {
|
|
178
|
+
clear();
|
|
179
|
+
console.log(dim(`\n cleared a stale server.json (pid ${pid} is no longer running).`));
|
|
180
|
+
}
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const port = info.port ?? "?";
|
|
185
|
+
const when = info.started_at ? `, started ${ago(info.started_at)}` : "";
|
|
186
|
+
console.log("");
|
|
187
|
+
console.log(` ${amber("⚠")} A board is already running on port ${bold(port)} (pid ${pid}${when}).`);
|
|
188
|
+
const yes = await ask(` Kill it and start fresh? ${dim("[y/N]")} `);
|
|
189
|
+
if (yes) {
|
|
190
|
+
try {
|
|
191
|
+
process.kill(pid, "SIGTERM");
|
|
192
|
+
} catch {
|
|
193
|
+
/* it may have just exited */
|
|
194
|
+
}
|
|
195
|
+
await new Promise((r) => setTimeout(r, 600)); // let it release the port
|
|
196
|
+
clear();
|
|
197
|
+
console.log(dim(` stopped pid ${pid}.`));
|
|
198
|
+
} else {
|
|
199
|
+
console.log(dim(` leaving it running — starting on the next free port.`));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
await preflightStaleBoard(
|
|
204
|
+
path.join(path.dirname(path.resolve(process.cwd(), statusPath)), "server.json"),
|
|
205
|
+
);
|
|
120
206
|
|
|
121
207
|
const { conductorPath: resolvedConductor, absStatus, server, serverJsonPath } =
|
|
122
208
|
await listenWithFallback(wantedPort);
|
package/cli/setup.js
CHANGED
|
@@ -82,6 +82,9 @@ steps:
|
|
|
82
82
|
At least once per minute, append a heartbeat {at, note} to the current
|
|
83
83
|
step's heartbeat array (read prior entries first; orient against the gate
|
|
84
84
|
AND the goal; use [text](url) links for any PRs or pages you produce).
|
|
85
|
+
Before marking each step done, append a finalBeat — {at, note, finalBeat:
|
|
86
|
+
true, handoff: {to, context, produced}} — summarizing the step and handing
|
|
87
|
+
off to the next; read the previous step's finalBeat before you start one.
|
|
85
88
|
For loop steps, update "completed" and the "iterations" object as EACH
|
|
86
89
|
iteration finishes — don't wait until the loop ends.
|
|
87
90
|
Tag heartbeats with an "insight" object when you spot a way to improve the
|