@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.
- package/dist/blob-store.d.ts +28 -0
- package/dist/blob-store.d.ts.map +1 -0
- package/dist/blob-store.js +53 -0
- package/dist/blob-store.js.map +1 -0
- package/dist/commands/run.d.ts +6 -0
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +137 -9
- package/dist/commands/run.js.map +1 -1
- package/dist/debug-event-subscriber.d.ts +16 -0
- package/dist/debug-event-subscriber.d.ts.map +1 -0
- package/dist/debug-event-subscriber.js +32 -0
- package/dist/debug-event-subscriber.js.map +1 -0
- package/dist/debug-serialize.d.ts +14 -0
- package/dist/debug-serialize.d.ts.map +1 -0
- package/dist/debug-serialize.js +137 -0
- package/dist/debug-serialize.js.map +1 -0
- package/dist/debug-server.d.ts +72 -0
- package/dist/debug-server.d.ts.map +1 -0
- package/dist/debug-server.js +230 -0
- package/dist/debug-server.js.map +1 -0
- package/dist/open-browser.d.ts +18 -0
- package/dist/open-browser.d.ts.map +1 -0
- package/dist/open-browser.js +47 -0
- package/dist/open-browser.js.map +1 -0
- package/dist/stdio-tee.d.ts +12 -0
- package/dist/stdio-tee.d.ts.map +1 -0
- package/dist/stdio-tee.js +53 -0
- package/dist/stdio-tee.js.map +1 -0
- package/dist/ui-fetch.d.ts +14 -0
- package/dist/ui-fetch.d.ts.map +1 -0
- package/dist/ui-fetch.js +89 -0
- package/dist/ui-fetch.js.map +1 -0
- package/package.json +6 -5
|
@@ -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"}
|
package/dist/ui-fetch.js
ADDED
|
@@ -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.
|
|
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.
|
|
38
|
-
"@telorun/ide-support": "0.4.
|
|
39
|
-
"@telorun/kernel": "0.
|
|
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",
|