@telorun/cli 0.26.1 → 0.28.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.
@@ -0,0 +1,72 @@
1
+ import type { AppEndpoint } from "@telorun/debug-ui";
2
+ import { LruBlobStore } from "./blob-store.js";
3
+ export interface DebugServerOptions {
4
+ /** Preferred bind host. Default `127.0.0.1` (loopback). */
5
+ host?: string;
6
+ /** Preferred port; falls back to an ephemeral port if taken. Default 9230. */
7
+ port?: number;
8
+ /** Path to the JSONL file, served at `/events.jsonl` for download. */
9
+ jsonlPath?: string;
10
+ /** Absolute path to the single-file debug UI (`resolveUiBundle`). When absent,
11
+ * the endpoint runs headless and `/` returns a "UI not available" notice. */
12
+ uiHtmlPath?: string;
13
+ /** Replay ring-buffer size. Default 5000. */
14
+ bufferSize?: number;
15
+ }
16
+ /**
17
+ * Localhost-only HTTP server that serves the debug-watcher UI and streams events
18
+ * to it over SSE. Bound to `127.0.0.1` because events can carry secrets. The
19
+ * caller pushes already-serialized wire lines via {@link push}; the server keeps
20
+ * a bounded replay buffer so a browser opened (or reconnected) mid-run sees
21
+ * history, then live events.
22
+ *
23
+ * Producer-side and Node-specific by design — a Rust/Go kernel reimplements this;
24
+ * the cross-runtime contract is the wire format and these endpoints, not the code.
25
+ */
26
+ export declare class DebugServer {
27
+ private readonly options;
28
+ private readonly server;
29
+ private readonly clients;
30
+ private readonly heartbeats;
31
+ private readonly buffer;
32
+ private readonly bufferSize;
33
+ private readonly host;
34
+ private _url;
35
+ private _endpoints;
36
+ /** Binary payloads are offloaded here and served at `/blobs/:id`; the serializer
37
+ * emits pointers into the event log. */
38
+ readonly blobStore: LruBlobStore;
39
+ constructor(options?: DebugServerOptions);
40
+ get url(): string;
41
+ /** Advertise where the running app is reachable; the UI fetches these from the
42
+ * `/json/version` handshake and renders them as links. Hosts are left blank —
43
+ * the producer can't know which hostname the viewer used, so the UI fills them
44
+ * from its own origin. Updatable across watch reloads. */
45
+ setEndpoints(endpoints: AppEndpoint[]): void;
46
+ /** Start listening on the configured host (loopback by default). Resolves once
47
+ * the URL is known. */
48
+ start(): Promise<void>;
49
+ private listen;
50
+ /** Fan one serialized wire line to the replay buffer and every live client. */
51
+ push(line: string): void;
52
+ /**
53
+ * Tear down so nothing keeps the process alive. `unref` alone is unreliable
54
+ * across runtimes (bun ignores `socket.unref()`), so we *actively* clear every
55
+ * heartbeat timer and destroy every live SSE socket, then close the listener.
56
+ * Synchronous and idempotent — safe to call from a signal handler.
57
+ */
58
+ stop(): void;
59
+ private handle;
60
+ /** Discovery handshake: protocol identity + version + endpoint paths, so a
61
+ * consumer can confirm it speaks this server's wire format before connecting. */
62
+ private handleVersion;
63
+ private handleSse;
64
+ private handleBlob;
65
+ private handleJsonl;
66
+ /** Serve the single-file UI for every non-API path. The bundle is fully
67
+ * self-contained (JS + CSS inlined), so there are no asset routes to resolve
68
+ * and no path-traversal surface. Absent bundle → a 503 notice; the endpoint
69
+ * itself (SSE / JSONL / blobs / version) keeps working headless. */
70
+ private handleUi;
71
+ }
72
+ //# sourceMappingURL=debug-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debug-server.d.ts","sourceRoot":"","sources":["../src/debug-server.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAO/C,MAAM,WAAW,kBAAkB;IACjC,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;kFAC8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,qBAAa,WAAW;IAcV,OAAO,CAAC,QAAQ,CAAC,OAAO;IAbpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;IAC1D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6C;IACxE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,UAAU,CAAqB;IAEvC;6CACyC;IACzC,QAAQ,CAAC,SAAS,eAAsB;gBAEX,OAAO,GAAE,kBAAuB;IAM7D,IAAI,GAAG,IAAI,MAAM,CAEhB;IAED;;;+DAG2D;IAC3D,YAAY,CAAC,SAAS,EAAE,WAAW,EAAE,GAAG,IAAI;IAI5C;4BACwB;IAClB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAW5B,OAAO,CAAC,MAAM;IAqBd,+EAA+E;IAC/E,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IASxB;;;;;OAKG;IACH,IAAI,IAAI,IAAI;IAuBZ,OAAO,CAAC,MAAM;IAWd;sFACkF;IAClF,OAAO,CAAC,aAAa;IAoBrB,OAAO,CAAC,SAAS;IA6BjB,OAAO,CAAC,UAAU;YAgBJ,WAAW;IAazB;;;yEAGqE;YACvD,QAAQ;CAiBvB"}
@@ -0,0 +1,230 @@
1
+ import * as http from "http";
2
+ import * as fsp from "fs/promises";
3
+ import { LruBlobStore } from "./blob-store.js";
4
+ /** The debug wire protocol this server speaks, advertised at `/json/version` so a
5
+ * consumer can refuse a mismatch. The wire format (`wire-schema.json`) is v1. */
6
+ const PROTOCOL = "telo-debug";
7
+ const PROTOCOL_VERSION = 1;
8
+ /**
9
+ * Localhost-only HTTP server that serves the debug-watcher UI and streams events
10
+ * to it over SSE. Bound to `127.0.0.1` because events can carry secrets. The
11
+ * caller pushes already-serialized wire lines via {@link push}; the server keeps
12
+ * a bounded replay buffer so a browser opened (or reconnected) mid-run sees
13
+ * history, then live events.
14
+ *
15
+ * Producer-side and Node-specific by design — a Rust/Go kernel reimplements this;
16
+ * the cross-runtime contract is the wire format and these endpoints, not the code.
17
+ */
18
+ export class DebugServer {
19
+ options;
20
+ server;
21
+ clients = new Set();
22
+ heartbeats = new Set();
23
+ buffer = [];
24
+ bufferSize;
25
+ host;
26
+ _url = "";
27
+ _endpoints = [];
28
+ /** Binary payloads are offloaded here and served at `/blobs/:id`; the serializer
29
+ * emits pointers into the event log. */
30
+ blobStore = new LruBlobStore();
31
+ constructor(options = {}) {
32
+ this.options = options;
33
+ this.bufferSize = options.bufferSize ?? 5000;
34
+ this.host = options.host ?? "127.0.0.1";
35
+ this.server = http.createServer((req, res) => this.handle(req, res));
36
+ }
37
+ get url() {
38
+ return this._url;
39
+ }
40
+ /** Advertise where the running app is reachable; the UI fetches these from the
41
+ * `/json/version` handshake and renders them as links. Hosts are left blank —
42
+ * the producer can't know which hostname the viewer used, so the UI fills them
43
+ * from its own origin. Updatable across watch reloads. */
44
+ setEndpoints(endpoints) {
45
+ this._endpoints = endpoints;
46
+ }
47
+ /** Start listening on the configured host (loopback by default). Resolves once
48
+ * the URL is known. */
49
+ async start() {
50
+ const preferred = this.options.port ?? 9230;
51
+ const port = await this.listen(preferred).catch(() => this.listen(0));
52
+ // A non-loopback bind is reachable as-is; loopback is friendlier as localhost.
53
+ const displayHost = this.host === "127.0.0.1" || this.host === "::1" || this.host === "0.0.0.0"
54
+ ? "localhost"
55
+ : this.host;
56
+ this._url = `http://${displayHost}:${port}`;
57
+ }
58
+ listen(port) {
59
+ return new Promise((resolve, reject) => {
60
+ const onError = (err) => {
61
+ this.server.removeListener("listening", onListening);
62
+ reject(err);
63
+ };
64
+ const onListening = () => {
65
+ this.server.removeListener("error", onError);
66
+ const addr = this.server.address();
67
+ resolve(typeof addr === "object" && addr ? addr.port : port);
68
+ };
69
+ this.server.once("error", onError);
70
+ this.server.once("listening", onListening);
71
+ this.server.listen(port, this.host);
72
+ // Never keep the process alive on the debug server's account: a one-shot
73
+ // `telo run --debug` still exits when its work is done; the UI is live only
74
+ // while the app itself keeps running (the watch-the-events use case).
75
+ this.server.unref();
76
+ });
77
+ }
78
+ /** Fan one serialized wire line to the replay buffer and every live client. */
79
+ push(line) {
80
+ this.buffer.push(line);
81
+ if (this.buffer.length > this.bufferSize) {
82
+ this.buffer.splice(0, this.buffer.length - this.bufferSize);
83
+ }
84
+ const frame = `data: ${line}\n\n`;
85
+ for (const res of this.clients)
86
+ res.write(frame);
87
+ }
88
+ /**
89
+ * Tear down so nothing keeps the process alive. `unref` alone is unreliable
90
+ * across runtimes (bun ignores `socket.unref()`), so we *actively* clear every
91
+ * heartbeat timer and destroy every live SSE socket, then close the listener.
92
+ * Synchronous and idempotent — safe to call from a signal handler.
93
+ */
94
+ stop() {
95
+ for (const hb of this.heartbeats)
96
+ clearInterval(hb);
97
+ this.heartbeats.clear();
98
+ for (const res of this.clients) {
99
+ try {
100
+ res.destroy();
101
+ }
102
+ catch {
103
+ /* already gone */
104
+ }
105
+ try {
106
+ res.socket?.destroy();
107
+ }
108
+ catch {
109
+ /* already gone */
110
+ }
111
+ }
112
+ this.clients.clear();
113
+ try {
114
+ this.server.close();
115
+ }
116
+ catch {
117
+ /* not listening */
118
+ }
119
+ }
120
+ handle(req, res) {
121
+ const url = new URL(req.url ?? "/", "http://localhost");
122
+ if (url.pathname === "/events")
123
+ return this.handleSse(res);
124
+ if (url.pathname === "/events.jsonl")
125
+ return void this.handleJsonl(res);
126
+ if (url.pathname.startsWith("/blobs/")) {
127
+ return this.handleBlob(decodeURIComponent(url.pathname.slice("/blobs/".length)), res);
128
+ }
129
+ if (url.pathname === "/json/version")
130
+ return this.handleVersion(res);
131
+ return void this.handleUi(res);
132
+ }
133
+ /** Discovery handshake: protocol identity + version + endpoint paths, so a
134
+ * consumer can confirm it speaks this server's wire format before connecting. */
135
+ handleVersion(res) {
136
+ res
137
+ .writeHead(200, {
138
+ "Content-Type": "application/json; charset=utf-8",
139
+ "Cache-Control": "no-store",
140
+ "Access-Control-Allow-Origin": "*",
141
+ })
142
+ .end(JSON.stringify({
143
+ protocol: PROTOCOL,
144
+ protocolVersion: PROTOCOL_VERSION,
145
+ url: this._url,
146
+ events: "/events",
147
+ eventsLog: "/events.jsonl",
148
+ blobs: "/blobs/",
149
+ appEndpoints: this._endpoints,
150
+ }));
151
+ }
152
+ handleSse(res) {
153
+ res.writeHead(200, {
154
+ "Content-Type": "text/event-stream",
155
+ "Cache-Control": "no-cache",
156
+ Connection: "keep-alive",
157
+ // The server is loopback-bound (the real boundary); CORS only governs
158
+ // which browser origins may read it. `*` lets an embedding webview (the
159
+ // editor's debug panel) consume the stream cross-origin.
160
+ "Access-Control-Allow-Origin": "*",
161
+ });
162
+ // Replay history, then stream live.
163
+ for (const line of this.buffer)
164
+ res.write(`data: ${line}\n\n`);
165
+ this.clients.add(res);
166
+ // The debug server must never keep the CLI alive. The listening socket is
167
+ // unref'd in listen(), but an established SSE connection — and its heartbeat
168
+ // timer — are separately ref'd; without unref'ing them, Ctrl+C hangs while a
169
+ // browser is watching. Unref'd handles still work while the app keeps the
170
+ // loop alive; they just stop being a reason *not* to exit.
171
+ res.socket?.unref();
172
+ const heartbeat = setInterval(() => res.write(": ping\n\n"), 25_000);
173
+ heartbeat.unref?.();
174
+ this.heartbeats.add(heartbeat);
175
+ res.on("close", () => {
176
+ clearInterval(heartbeat);
177
+ this.heartbeats.delete(heartbeat);
178
+ this.clients.delete(res);
179
+ });
180
+ }
181
+ handleBlob(id, res) {
182
+ const blob = this.blobStore.get(id);
183
+ if (!blob) {
184
+ res.writeHead(404).end("blob not found");
185
+ return;
186
+ }
187
+ res
188
+ .writeHead(200, {
189
+ "Content-Type": blob.mediaType,
190
+ "Cache-Control": "no-store",
191
+ "Content-Length": String(blob.bytes.byteLength),
192
+ "Access-Control-Allow-Origin": "*",
193
+ })
194
+ .end(blob.bytes);
195
+ }
196
+ async handleJsonl(res) {
197
+ if (!this.options.jsonlPath) {
198
+ res.writeHead(404).end("no event log");
199
+ return;
200
+ }
201
+ try {
202
+ const body = await fsp.readFile(this.options.jsonlPath);
203
+ res.writeHead(200, { "Content-Type": "application/x-ndjson; charset=utf-8" }).end(body);
204
+ }
205
+ catch {
206
+ res.writeHead(404).end("event log not found");
207
+ }
208
+ }
209
+ /** Serve the single-file UI for every non-API path. The bundle is fully
210
+ * self-contained (JS + CSS inlined), so there are no asset routes to resolve
211
+ * and no path-traversal surface. Absent bundle → a 503 notice; the endpoint
212
+ * itself (SSE / JSONL / blobs / version) keeps working headless. */
213
+ async handleUi(res) {
214
+ if (!this.options.uiHtmlPath) {
215
+ res
216
+ .writeHead(503, { "Content-Type": "text/html; charset=utf-8" })
217
+ .end("<h1>Debug UI not available</h1><p>The endpoint is live at <code>/events</code>; " +
218
+ "the UI bundle could not be resolved or fetched.</p>");
219
+ return;
220
+ }
221
+ try {
222
+ const body = await fsp.readFile(this.options.uiHtmlPath);
223
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }).end(body);
224
+ }
225
+ catch {
226
+ res.writeHead(404).end("not found");
227
+ }
228
+ }
229
+ }
230
+ //# sourceMappingURL=debug-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debug-server.js","sourceRoot":"","sources":["../src/debug-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Best-effort open of a URL in the user's browser. Never throws — a failure to
3
+ * launch is non-fatal (the URL is always logged alongside).
4
+ *
5
+ * `$BROWSER` wins when set: VSCode remote / devcontainers / Codespaces point it
6
+ * at a helper that forwards the URL to the user's *local* browser through the
7
+ * tunnel, so this works even when the host itself is headless. Otherwise fall
8
+ * back to the OS opener.
9
+ */
10
+ export declare function openBrowser(url: string): void;
11
+ /**
12
+ * Whether auto-opening makes sense here. `$BROWSER` (VSCode remote, devcontainers)
13
+ * forwards to the user's local browser, so opening works even on a headless host —
14
+ * only skip when there's no opener path at all: CI, or a bare headless Linux box
15
+ * with no display and no `$BROWSER`.
16
+ */
17
+ export declare function canOpenBrowser(): boolean;
18
+ //# sourceMappingURL=open-browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open-browser.d.ts","sourceRoot":"","sources":["../src/open-browser.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAgB7C;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAWxC"}
@@ -0,0 +1,47 @@
1
+ import { spawn } from "child_process";
2
+ /**
3
+ * Best-effort open of a URL in the user's browser. Never throws — a failure to
4
+ * launch is non-fatal (the URL is always logged alongside).
5
+ *
6
+ * `$BROWSER` wins when set: VSCode remote / devcontainers / Codespaces point it
7
+ * at a helper that forwards the URL to the user's *local* browser through the
8
+ * tunnel, so this works even when the host itself is headless. Otherwise fall
9
+ * back to the OS opener.
10
+ */
11
+ export function openBrowser(url) {
12
+ const browser = process.env.BROWSER;
13
+ const [cmd, args] = browser
14
+ ? [browser, [url]]
15
+ : process.platform === "darwin"
16
+ ? ["open", [url]]
17
+ : process.platform === "win32"
18
+ ? ["cmd", ["/c", "start", "", url]]
19
+ : ["xdg-open", [url]];
20
+ try {
21
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true });
22
+ child.on("error", () => { });
23
+ child.unref();
24
+ }
25
+ catch {
26
+ // ignored — the URL is logged regardless
27
+ }
28
+ }
29
+ /**
30
+ * Whether auto-opening makes sense here. `$BROWSER` (VSCode remote, devcontainers)
31
+ * forwards to the user's local browser, so opening works even on a headless host —
32
+ * only skip when there's no opener path at all: CI, or a bare headless Linux box
33
+ * with no display and no `$BROWSER`.
34
+ */
35
+ export function canOpenBrowser() {
36
+ if (process.env.CI)
37
+ return false;
38
+ if (process.env.BROWSER)
39
+ return true;
40
+ if (process.platform === "linux" &&
41
+ !process.env.DISPLAY &&
42
+ !process.env.WAYLAND_DISPLAY) {
43
+ return false;
44
+ }
45
+ return true;
46
+ }
47
+ //# sourceMappingURL=open-browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open-browser.js","sourceRoot":"","sources":["../src/open-browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAEtC;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;IACpC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,GAAuB,OAAO;QAC7C,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;QAClB,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,QAAQ;YAC7B,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC;YACjB,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO;gBAC5B,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;gBACnC,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5B,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QACpE,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC5B,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,yCAAyC;IAC3C,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc;IAC5B,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE;QAAE,OAAO,KAAK,CAAC;IACjC,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IACrC,IACE,OAAO,CAAC,QAAQ,KAAK,OAAO;QAC5B,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO;QACpB,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,EAC5B,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,12 @@
1
+ export type StdioLineHandler = (stream: "stdout" | "stderr", line: string) => void;
2
+ /**
3
+ * Tee `process.stdout` / `process.stderr`: every write still reaches the real
4
+ * stream (the terminal is untouched), and each *completed* line is also handed to
5
+ * `onLine`. Output is line-buffered — a partial write is held until its newline —
6
+ * and a trailing partial line is flushed on restore so nothing is dropped.
7
+ *
8
+ * Entirely contained in this process: the returned function reinstates the
9
+ * original `write`s, leaving no trace of the wrapping behind.
10
+ */
11
+ export declare function teeStdio(onLine: StdioLineHandler): () => void;
12
+ //# sourceMappingURL=stdio-tee.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio-tee.d.ts","sourceRoot":"","sources":["../src/stdio-tee.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AAEnF;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,IAAI,CA+C7D"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Tee `process.stdout` / `process.stderr`: every write still reaches the real
3
+ * stream (the terminal is untouched), and each *completed* line is also handed to
4
+ * `onLine`. Output is line-buffered — a partial write is held until its newline —
5
+ * and a trailing partial line is flushed on restore so nothing is dropped.
6
+ *
7
+ * Entirely contained in this process: the returned function reinstates the
8
+ * original `write`s, leaving no trace of the wrapping behind.
9
+ */
10
+ export function teeStdio(onLine) {
11
+ const restores = [];
12
+ const wrap = (stream, name) => {
13
+ const original = stream.write.bind(stream);
14
+ // One decoder per stream so a multi-byte char split across writes is rejoined.
15
+ const decoder = new TextDecoder();
16
+ let pending = "";
17
+ const emit = (text) => {
18
+ pending += text;
19
+ let idx;
20
+ while ((idx = pending.indexOf("\n")) >= 0) {
21
+ onLine(name, pending.slice(0, idx).replace(/\r$/, ""));
22
+ pending = pending.slice(idx + 1);
23
+ }
24
+ };
25
+ // Pass every arg through verbatim so all write() overloads keep working
26
+ // (string+encoding, buffer+callback, …); only observe the chunk's text.
27
+ const tee = ((chunk, encoding, cb) => {
28
+ const text = typeof chunk === "string"
29
+ ? chunk
30
+ : chunk instanceof Uint8Array
31
+ ? decoder.decode(chunk, { stream: true })
32
+ : "";
33
+ if (text)
34
+ emit(text);
35
+ return original(chunk, encoding, cb);
36
+ });
37
+ stream.write = tee;
38
+ restores.push(() => {
39
+ stream.write = original;
40
+ if (pending.length > 0) {
41
+ onLine(name, pending);
42
+ pending = "";
43
+ }
44
+ });
45
+ };
46
+ wrap(process.stdout, "stdout");
47
+ wrap(process.stderr, "stderr");
48
+ return () => {
49
+ for (const restore of restores)
50
+ restore();
51
+ };
52
+ }
53
+ //# sourceMappingURL=stdio-tee.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio-tee.js","sourceRoot":"","sources":["../src/stdio-tee.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,UAAU,QAAQ,CAAC,MAAwB;IAC/C,MAAM,QAAQ,GAAsB,EAAE,CAAC;IAEvC,MAAM,IAAI,GAAG,CAAC,MAA0B,EAAE,IAAyB,EAAQ,EAAE;QAC3E,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAwB,CAAC;QAClE,+EAA+E;QAC/E,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;QAClC,IAAI,OAAO,GAAG,EAAE,CAAC;QAEjB,MAAM,IAAI,GAAG,CAAC,IAAY,EAAQ,EAAE;YAClC,OAAO,IAAI,IAAI,CAAC;YAChB,IAAI,GAAW,CAAC;YAChB,OAAO,CAAC,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC1C,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC;gBACvD,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;YACnC,CAAC;QACH,CAAC,CAAC;QAEF,wEAAwE;QACxE,wEAAwE;QACxE,MAAM,GAAG,GAAG,CAAC,CAAC,KAAc,EAAE,QAAkB,EAAE,EAAY,EAAW,EAAE;YACzE,MAAM,IAAI,GACR,OAAO,KAAK,KAAK,QAAQ;gBACvB,CAAC,CAAC,KAAK;gBACP,CAAC,CAAC,KAAK,YAAY,UAAU;oBAC3B,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;oBACzC,CAAC,CAAC,EAAE,CAAC;YACX,IAAI,IAAI;gBAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YACrB,OAAQ,QAA4C,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC5E,CAAC,CAAwB,CAAC;QAE1B,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC;QACnB,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE;YACjB,MAAM,CAAC,KAAK,GAAG,QAAQ,CAAC;YACxB,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;gBACtB,OAAO,GAAG,EAAE,CAAC;YACf,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC/B,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAE/B,OAAO,GAAG,EAAE;QACV,KAAK,MAAM,OAAO,IAAI,QAAQ;YAAE,OAAO,EAAE,CAAC;IAC5C,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,14 @@
1
+ /** Resolve the on-demand single-file debug UI to an absolute path, first hit
2
+ * wins so local development never touches the network:
3
+ *
4
+ * 1. `TELO_DEBUG_UI_PATH` — explicit override (any local build).
5
+ * 2. devDep on disk — present in the monorepo, absent in a
6
+ * production install.
7
+ * 3. `<cacheRoot>/debug-ui/<ver>` — a previous fetch.
8
+ * 4. jsDelivr — fetch the pinned version, cache, serve.
9
+ *
10
+ * Returns `null` when nothing resolves (offline + uncached): the inspect
11
+ * endpoint still works headless, only the served UI is absent.
12
+ */
13
+ export declare function resolveUiBundle(cacheRoot: string | null): Promise<string | null>;
14
+ //# sourceMappingURL=ui-fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-fetch.d.ts","sourceRoot":"","sources":["../src/ui-fetch.ts"],"names":[],"mappings":"AA6CA;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA6BtF"}
@@ -0,0 +1,89 @@
1
+ import * as fs from "fs";
2
+ import * as fsp from "fs/promises";
3
+ import { createRequire } from "module";
4
+ import * as path from "path";
5
+ import { fileURLToPath } from "url";
6
+ const require = createRequire(import.meta.url);
7
+ /** jsDelivr base for the on-demand single-file debug UI. Overridable via env so
8
+ * an air-gapped mirror / staging bundle can be pointed at without a rebuild. */
9
+ const DEFAULT_CDN_BASE = "https://cdn.jsdelivr.net/npm";
10
+ const UI_PACKAGE = "@telorun/debug-ui";
11
+ const UI_ASSET = "app-single/index.html";
12
+ /** Walk up from this module to the CLI's own `package.json`. Works from both the
13
+ * compiled `dist/**` layout and the bun-run `src/**` layout. */
14
+ function readOwnPackageJson() {
15
+ let dir = path.dirname(fileURLToPath(import.meta.url));
16
+ for (;;) {
17
+ const candidate = path.join(dir, "package.json");
18
+ if (fs.existsSync(candidate)) {
19
+ try {
20
+ return JSON.parse(fs.readFileSync(candidate, "utf8"));
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ }
26
+ const parent = path.dirname(dir);
27
+ if (parent === dir)
28
+ return null;
29
+ dir = parent;
30
+ }
31
+ }
32
+ /** The `@telorun/debug-ui` version this CLI was published against. In the
33
+ * monorepo the pin is `workspace:*` (no concrete version) — but there the devDep
34
+ * resolves on disk, so this is only consulted for the cache/fetch paths an end
35
+ * user hits, where `pnpm publish` has rewritten it to an exact version. */
36
+ function pinnedUiVersion() {
37
+ const pkg = readOwnPackageJson();
38
+ const raw = pkg?.devDependencies?.[UI_PACKAGE] ?? pkg?.dependencies?.[UI_PACKAGE];
39
+ if (typeof raw !== "string")
40
+ return null;
41
+ const version = raw.replace(/^[\^~>=<\s]+/, "").trim();
42
+ return /^\d/.test(version) ? version : null;
43
+ }
44
+ /** Resolve the on-demand single-file debug UI to an absolute path, first hit
45
+ * wins so local development never touches the network:
46
+ *
47
+ * 1. `TELO_DEBUG_UI_PATH` — explicit override (any local build).
48
+ * 2. devDep on disk — present in the monorepo, absent in a
49
+ * production install.
50
+ * 3. `<cacheRoot>/debug-ui/<ver>` — a previous fetch.
51
+ * 4. jsDelivr — fetch the pinned version, cache, serve.
52
+ *
53
+ * Returns `null` when nothing resolves (offline + uncached): the inspect
54
+ * endpoint still works headless, only the served UI is absent.
55
+ */
56
+ export async function resolveUiBundle(cacheRoot) {
57
+ const override = process.env.TELO_DEBUG_UI_PATH;
58
+ if (override && fs.existsSync(override))
59
+ return override;
60
+ try {
61
+ const resolved = require.resolve(`${UI_PACKAGE}/${UI_ASSET}`);
62
+ if (fs.existsSync(resolved))
63
+ return resolved;
64
+ }
65
+ catch {
66
+ // Not installed (production CLI strips the devDep) — fall through to fetch.
67
+ }
68
+ const version = pinnedUiVersion();
69
+ if (!version || !cacheRoot)
70
+ return null;
71
+ const cached = path.join(cacheRoot, "debug-ui", version, "index.html");
72
+ if (fs.existsSync(cached))
73
+ return cached;
74
+ const base = process.env.TELO_DEBUG_UI_URL ?? DEFAULT_CDN_BASE;
75
+ const url = `${base}/${UI_PACKAGE}@${version}/${UI_ASSET}`;
76
+ try {
77
+ const res = await fetch(url);
78
+ if (!res.ok)
79
+ return null;
80
+ const body = Buffer.from(await res.arrayBuffer());
81
+ await fsp.mkdir(path.dirname(cached), { recursive: true });
82
+ await fsp.writeFile(cached, body);
83
+ return cached;
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ }
89
+ //# sourceMappingURL=ui-fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-fetch.js","sourceRoot":"","sources":["../src/ui-fetch.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/cli",
3
- "version": "0.26.1",
3
+ "version": "0.28.0",
4
4
  "description": "Telo CLI - Command-line interface for the Telo runtime.",
5
5
  "keywords": [
6
6
  "telo",
@@ -34,9 +34,9 @@
34
34
  "dist"
35
35
  ],
36
36
  "dependencies": {
37
- "@telorun/analyzer": "0.23.1",
38
- "@telorun/ide-support": "0.4.24",
39
- "@telorun/kernel": "0.26.1",
37
+ "@telorun/analyzer": "0.23.2",
38
+ "@telorun/ide-support": "0.4.25",
39
+ "@telorun/kernel": "0.28.0",
40
40
  "@telorun/sdk": "0.26.0",
41
41
  "@telorun/templating": "0.8.0",
42
42
  "dotenv": "^17.4.0",
@@ -52,7 +52,8 @@
52
52
  "@types/yargs": "^17.0.33",
53
53
  "nock": "^14.0.15",
54
54
  "typescript": "^5.0.0",
55
- "vitest": "^2.1.8"
55
+ "vitest": "^2.1.8",
56
+ "@telorun/debug-ui": "0.2.0"
56
57
  },
57
58
  "overrides": {
58
59
  "@telorun/analyzer": "$@telorun/analyzer",