@unbound-app/debugger 0.4.0 → 0.7.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.
Files changed (2) hide show
  1. package/dist/index.mjs +46 -37
  2. package/package.json +2 -2
package/dist/index.mjs CHANGED
@@ -8,6 +8,7 @@ import { cli } from "cleye";
8
8
  const HISTORY_PATH = join(__dirname, "..", "..", ".history.json");
9
9
  const HISTORY_FILE = Bun.file(HISTORY_PATH);
10
10
  let history = [];
11
+ /** @description Loads the persisted REPL history from disk, if present. */
11
12
  async function loadHistory() {
12
13
  try {
13
14
  if (await HISTORY_FILE.exists()) history = await HISTORY_FILE.json();
@@ -15,10 +16,12 @@ async function loadHistory() {
15
16
  terminal.red.bold(`Failed to load history.\n`);
16
17
  }
17
18
  }
19
+ /** @description Appends an entry to the REPL history and persists it. */
18
20
  function addHistoryItem(item) {
19
21
  history.push(item);
20
22
  persistHistory();
21
23
  }
24
+ /** @description Writes the REPL history to disk. */
22
25
  function persistHistory() {
23
26
  HISTORY_FILE.write(JSON.stringify(history, null, 2));
24
27
  }
@@ -37,6 +40,7 @@ const resultListeners = /* @__PURE__ */ new Set();
37
40
  const controllerListeners = /* @__PURE__ */ new Set();
38
41
  /** How long to wait for a device to answer an eval before failing it. */
39
42
  const EVAL_TIMEOUT_MS = 1e4;
43
+ const PLUGIN_PUSH_TIMEOUT_MS = 6e4;
40
44
  /**
41
45
  * @description Registers the connected device, rejecting a second one so eval routing stays
42
46
  * unambiguous.
@@ -123,7 +127,7 @@ function evaluateForController(socket, id, code) {
123
127
  /**
124
128
  * @description Forwards a controller's {@link PluginPushRequest} to the device and delivers the
125
129
  * id-correlated {@link PluginPushResult} back over that same controller's socket. Replies with an
126
- * error frame instead if no device is connected or it doesn't answer within {@link EVAL_TIMEOUT_MS}.
130
+ * error frame instead if no device is connected or it doesn't answer within {@link PLUGIN_PUSH_TIMEOUT_MS}.
127
131
  * @param socket The controller that issued the push.
128
132
  * @param request The push to forward verbatim; its `id` correlates the result.
129
133
  */
@@ -144,9 +148,9 @@ function pushPluginForController(socket, request) {
144
148
  type: "plugin-push-result",
145
149
  id: request.id,
146
150
  ok: false,
147
- error: `Plugin push timed out after ${EVAL_TIMEOUT_MS}ms.`
151
+ error: `Plugin push timed out after ${PLUGIN_PUSH_TIMEOUT_MS}ms.`
148
152
  });
149
- }, EVAL_TIMEOUT_MS);
153
+ }, PLUGIN_PUSH_TIMEOUT_MS);
150
154
  pendingPushes.set(request.id, {
151
155
  deliver,
152
156
  timer,
@@ -217,29 +221,41 @@ function handleDeviceMessage$1(message) {
217
221
  entry.deliver(message);
218
222
  return;
219
223
  }
220
- if (message.type === "log") {
221
- for (const socket of state$2.controllers) socket.send(JSON.stringify(message));
222
- for (const listener of logListeners) listener(message);
223
- }
224
+ if (message.type !== "log") return;
225
+ for (const socket of state$2.controllers) socket.send(JSON.stringify(message));
226
+ for (const listener of logListeners) listener(message);
224
227
  }
225
- /** @description Subscribes to device log lines; returns an unsubscribe function. */
228
+ /**
229
+ * @description Subscribes to device log lines.
230
+ * @param listener Called with each log line.
231
+ * @returns An unsubscribe function.
232
+ */
226
233
  function onLog(listener) {
227
234
  logListeners.add(listener);
228
235
  return () => void logListeners.delete(listener);
229
236
  }
230
- /** @description Subscribes to code a controller (e.g. the MCP) sends to evaluate; returns an unsubscribe function. */
237
+ /**
238
+ * @description Subscribes to code a controller (e.g. the MCP) sends to evaluate.
239
+ * @param listener Called with each controller's code.
240
+ * @returns An unsubscribe function.
241
+ */
231
242
  function onControllerEval(listener) {
232
243
  evalListeners.add(listener);
233
244
  return () => void evalListeners.delete(listener);
234
245
  }
235
- /** @description Subscribes to the results of controller evals; returns an unsubscribe function. */
246
+ /**
247
+ * @description Subscribes to the results of controller evals.
248
+ * @param listener Called with each controller eval's result.
249
+ * @returns An unsubscribe function.
250
+ */
236
251
  function onControllerResult(listener) {
237
252
  resultListeners.add(listener);
238
253
  return () => void resultListeners.delete(listener);
239
254
  }
240
255
  /**
241
- * @description Subscribes to controller (MCP) connect/disconnect transitions, with the resulting
242
- * controller count; returns an unsubscribe function.
256
+ * @description Subscribes to controller (MCP) connect/disconnect transitions.
257
+ * @param listener Called with the transition and the resulting controller count.
258
+ * @returns An unsubscribe function.
243
259
  */
244
260
  function onController(listener) {
245
261
  controllerListeners.add(listener);
@@ -269,12 +285,14 @@ const state$1 = {
269
285
  repl: null,
270
286
  currentInput: ""
271
287
  };
288
+ /** @description Exits the debugger on Ctrl+C. */
272
289
  function setupReplListeners() {
273
- terminal.on("key", (name, ...t) => {
290
+ terminal.on("key", (name) => {
274
291
  if (name !== "CTRL_C") return;
275
292
  process.exit(0);
276
293
  });
277
294
  }
295
+ /** @description Draws a fresh prompt and evaluates the submitted line on the device. */
278
296
  async function takeReplInput() {
279
297
  abortCurrentReplInput();
280
298
  terminal.bold.cyan("» ");
@@ -296,27 +314,17 @@ async function takeReplInput() {
296
314
  });
297
315
  await state$1.repl.promise;
298
316
  }
317
+ /** @description Cancels the active prompt, optionally erasing it and the pending input. */
299
318
  function abortCurrentReplInput({ deletePrompt = false, deleteCurrentInput = false } = {}) {
300
- if (state$1.repl) {
301
- state$1.repl.abort();
302
- state$1.repl = null;
303
- if (deletePrompt) terminal.deleteLine(-1);
304
- if (deleteCurrentInput) state$1.currentInput = "";
305
- }
319
+ if (!state$1.repl) return;
320
+ state$1.repl.abort();
321
+ state$1.repl = null;
322
+ if (deletePrompt) terminal.deleteLine(-1);
323
+ if (deleteCurrentInput) state$1.currentInput = "";
306
324
  }
307
325
  //#endregion
308
326
  //#region src/lib/format-log.ts
309
327
  /**
310
- * Reformats a log line as Hermes hands it to `nativeLoggingHook`. Hermes inspects each `console.*`
311
- * argument and joins them with `, ` — quoting string args (`'a', 'b', { x: 1 }`). That reads badly
312
- * in the debugger terminal, so we split on the top-level commas Hermes inserted, unquote the plain
313
- * string segments, and rejoin with spaces — leaving inspected objects/arrays untouched.
314
- *
315
- * This is best-effort: it only rewrites what parses cleanly as Hermes's join format. A segment it
316
- * can't confidently classify is left exactly as received, so worst case a line is unchanged, never
317
- * mangled.
318
- */
319
- /**
320
328
  * @description Cleans a Hermes-joined console line into space-separated, unquoted output.
321
329
  * @param message The raw `nativeLoggingHook` message string.
322
330
  * @returns The reformatted line, or the original if it doesn't look like a Hermes join.
@@ -379,9 +387,8 @@ function unquoteSegment(segment) {
379
387
  //#endregion
380
388
  //#region src/lib/banner.ts
381
389
  /**
382
- * Renders the debugger's startup banner: a rounded box, tinted in the brand pink, that adapts to the
383
- * terminal's width. The box shrinks to fit narrow terminals and caps at a comfortable reading width
384
- * on wide ones; when the terminal is too narrow for a box, it falls back to plain starred lines.
390
+ * Renders the debugger's startup banner: a pink rounded box sized to the terminal, capped on wide
391
+ * ones, falling back to plain starred lines when the terminal is too narrow for a box.
385
392
  */
386
393
  /** Brand pink used for the banner frame and title. */
387
394
  const PINK = "#ff5fbf";
@@ -449,7 +456,7 @@ function printFallback(title, lines) {
449
456
  /** The usable terminal width: terminal-kit's when it's a real TTY, else stdout's, else a default. */
450
457
  function terminalWidth() {
451
458
  const fromKit = terminal.width;
452
- if (typeof fromKit === "number" && Number.isFinite(fromKit)) return fromKit;
459
+ if (Number.isFinite(fromKit)) return fromKit;
453
460
  const fromStdout = process.stdout.columns;
454
461
  if (typeof fromStdout === "number" && fromStdout > 0) return fromStdout;
455
462
  return 80;
@@ -493,9 +500,7 @@ const SYSTEM_COLORS = {
493
500
  gray: "#9ca3af"
494
501
  };
495
502
  /**
496
- * @description Prints a bold, glyph-prefixed banner line for a bridge lifecycle event, tinted with
497
- * the given hex colour. Uses terminal-kit's runtime `colorRgbHex`, which `@types/terminal-kit`
498
- * doesn't declare, so the chain is reached through a narrow cast.
503
+ * @description Prints a bold, glyph-prefixed banner line for a bridge lifecycle event.
499
504
  * @param color The banner colour, keyed into {@link SYSTEM_COLORS}.
500
505
  * @param text The line to print (the glyph is prepended and a trailing newline added).
501
506
  */
@@ -594,6 +599,10 @@ function handleControllerMessage(ws, raw) {
594
599
  }
595
600
  if (message?.type === "plugin-push") pushPluginForController(ws, message);
596
601
  }
602
+ /**
603
+ * @description Finds the machine's first external IPv4 address, for devices on the LAN to dial.
604
+ * @returns The address, or `undefined` if the machine has none.
605
+ */
597
606
  function getLocalAddress() {
598
607
  const interfaces = os.networkInterfaces();
599
608
  for (const ifaces of Object.values(interfaces)) {
@@ -604,6 +613,6 @@ function getLocalAddress() {
604
613
  //#endregion
605
614
  //#region src/index.ts
606
615
  await loadHistory();
607
- await setupWebSocket();
616
+ setupWebSocket();
608
617
  //#endregion
609
618
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbound-app/debugger",
3
- "version": "0.4.0",
3
+ "version": "0.7.0",
4
4
  "description": "A REPL debugger for @unbound-app/client runtime instances.",
5
5
  "author": {
6
6
  "name": "Mario P.",
@@ -24,7 +24,7 @@
24
24
  "build": "tsdown"
25
25
  },
26
26
  "dependencies": {
27
- "@unbound-app/debugger-protocol": "0.4.0",
27
+ "@unbound-app/debugger-protocol": "0.7.0",
28
28
  "cleye": "^1.3.4",
29
29
  "terminal-kit": "^3.1.2"
30
30
  },