kaizen-agent 1.6.7 → 1.6.9
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/cli/web.ts +33 -10
- package/package.json +1 -1
package/cli/web.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// `kaizen web`: the board in a browser. A loopback HTTP server over state.ts and
|
|
2
2
|
// one HTML page; the page never sees the filesystem, only JSON.
|
|
3
|
-
import { existsSync, readFileSync, readdirSync, statSync, watch, type FSWatcher } from "node:fs";
|
|
3
|
+
import { existsSync, readFileSync, readdirSync, statSync, watch, appendFileSync, type FSWatcher } from "node:fs";
|
|
4
4
|
import { join, dirname } from "node:path";
|
|
5
5
|
import {
|
|
6
6
|
home, type Item, boardCards, knownProjects, remember, locate, label, tilde,
|
|
@@ -14,6 +14,17 @@ const DEFAULT_PORT = 7420;
|
|
|
14
14
|
type Opts = { port?: number; open?: boolean };
|
|
15
15
|
|
|
16
16
|
export async function web(repoDir: string, opts: Opts = {}) {
|
|
17
|
+
// A board started from the shortcut has no terminal to show a crash in, so any
|
|
18
|
+
// crash is written where it can be read afterwards.
|
|
19
|
+
const log = join(home, ".kaizen", "web.log");
|
|
20
|
+
for (const ev of ["uncaughtException", "unhandledRejection"] as const) {
|
|
21
|
+
process.on(ev, (err: unknown) => {
|
|
22
|
+
const line = `${new Date().toISOString()} ${ev}: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`;
|
|
23
|
+
try { appendFileSync(log, line); } catch { /* nowhere to write */ }
|
|
24
|
+
console.error(line);
|
|
25
|
+
if (ev === "uncaughtException") process.exit(1);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
17
28
|
const state = locate();
|
|
18
29
|
if (state && dirname(state) !== home) remember(dirname(state));
|
|
19
30
|
const project = state ? label(state) : null;
|
|
@@ -56,7 +67,9 @@ export async function web(repoDir: string, opts: Opts = {}) {
|
|
|
56
67
|
for (const w of watchers) { try { w.close(); } catch { /* gone */ } }
|
|
57
68
|
watchers.length = 0;
|
|
58
69
|
for (const dir of states(true)) {
|
|
59
|
-
|
|
70
|
+
// An unhandled 'error' on a watcher (Windows: a watched dir renamed, EPERM on
|
|
71
|
+
// a locked file) would end the process; the poll covers what the watch misses.
|
|
72
|
+
try { watchers.push(watch(dir, { recursive: true }, changed).on("error", () => {})); } catch { /* poll covers it */ }
|
|
60
73
|
}
|
|
61
74
|
};
|
|
62
75
|
watchAll();
|
|
@@ -83,14 +96,24 @@ export async function web(repoDir: string, opts: Opts = {}) {
|
|
|
83
96
|
const BOOT = Date.now().toString(36);
|
|
84
97
|
const ping = setInterval(() => { for (const c of clients) { try { c.enqueue(": ping\n\n"); } catch { clients.delete(c); } } }, 8000);
|
|
85
98
|
// A launcher clicked while a board is already up should show that board, not die.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
99
|
+
// A bind can fail because a board is already there, or because the OS will not
|
|
100
|
+
// hand out the port at all (Windows reserves whole ranges for Hyper-V and WSL,
|
|
101
|
+
// and reports either as "in use"). Only a board that answers is "already
|
|
102
|
+
// running"; otherwise walk up to the next port that binds.
|
|
103
|
+
let port = opts.port ?? DEFAULT_PORT;
|
|
104
|
+
let server: ReturnType<typeof serve> | null = null;
|
|
105
|
+
for (let tries = 0; tries < 10 && !server; tries++, port++) {
|
|
106
|
+
try { server = serve(port); } catch (e) {
|
|
107
|
+
if (!["EADDRINUSE", "EACCES"].includes((e as { code?: string }).code ?? "")) throw e;
|
|
108
|
+
const url = `http://127.0.0.1:${port}/`;
|
|
109
|
+
const up = await fetch(url, { signal: AbortSignal.timeout(1500) }).then((r) => r.ok, () => false);
|
|
110
|
+
if (up) { console.log(`\n kaizen web already at ${url}\n`); if (opts.open !== false) await openApp(url); return; }
|
|
111
|
+
console.log(` port ${port} refused by the OS and nothing answers there; trying ${port + 1}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (!server) { console.log(`\n kaizen web could not bind any port from ${opts.port ?? DEFAULT_PORT}\n`); return; }
|
|
115
|
+
|
|
116
|
+
function serve(port: number) { return Bun.serve({
|
|
94
117
|
hostname: "127.0.0.1",
|
|
95
118
|
port,
|
|
96
119
|
async fetch(req) {
|
package/package.json
CHANGED