jsmdcui 0.7.0 → 0.9.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 (44) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/README.md +189 -19
  3. package/cdp-maze.js +141 -0
  4. package/demos/maze.md +149 -0
  5. package/demos/todo-zh.md +98 -0
  6. package/demos/todo.md +98 -0
  7. package/edit +9 -0
  8. package/llm-maze.txt +94 -0
  9. package/package.json +1 -1
  10. package/runmd.mjs +44 -2
  11. package/runtime/help/cdp.md +5 -0
  12. package/runtime/help/help.md +189 -19
  13. package/runtime/jsplugins/cdp/cdp-server.js +26 -2
  14. package/runtime/jsplugins/cdp/cdp.js +60 -51
  15. package/runtime/jsplugins/chapter/chapter.js +6 -3
  16. package/runtime/jsplugins/example/example.js +12 -7
  17. package/runtime/syntax/markdown.yaml +1 -0
  18. package/single-exe/README.md +223 -45
  19. package/single-exe/compiled.js +6 -3
  20. package/single-exe/entry.mjs +4 -3
  21. package/single-exe/packAssets.sh +1 -1
  22. package/src/cui/fence-events.mjs +106 -0
  23. package/src/cui/id-collision.mjs +10 -28
  24. package/src/cui/kitty-debug.mjs +1 -1
  25. package/src/cui/kitty-images.mjs +11 -1
  26. package/src/cui/rpc.mjs +6 -3
  27. package/src/cui/server.mjs +13 -2
  28. package/src/index.js +425 -72
  29. package/src/lua/engine.js +1 -4
  30. package/src/platform/terminal.js +5 -0
  31. package/src/plugins/js-bridge.js +157 -8
  32. package/tests/compiled-runtime.test.js +8 -0
  33. package/tests/demo.test.js +40 -0
  34. package/tests/fence-events.test.js +405 -0
  35. package/tests/id-collision.test.js +11 -0
  36. package/tests/js-plugin-prompts.test.js +46 -0
  37. package/tests/key-event.md +10 -0
  38. package/tests/kitty-demo.md +1 -1
  39. package/tests/kitty-images.test.js +22 -0
  40. package/tests/platform-terminal.test.js +8 -0
  41. package/tests/textarea.md +8 -0
  42. package/tests/wui.test.js +16 -1
  43. package/demo.resized.jpg +0 -0
  44. package/src/runtime/compiled.js +0 -25
package/CHANGELOG.md CHANGED
@@ -2,6 +2,133 @@
2
2
 
3
3
  All notable user-visible changes to jsmdcui are documented here.
4
4
 
5
+ ## [0.9.0] - 2026-07-21
6
+
7
+ This update expands Chrome DevTools Protocol automation for the terminal UI,
8
+ adds a complete maze-solving example, and makes the optional Bun single-file
9
+ executable bootstrap easier to reuse and more reliable across platforms.
10
+
11
+ ### Added
12
+
13
+ - Add CDP automation support for reading the rendered ANSI document, activating
14
+ a 1-based terminal-buffer cell, and sending keyboard input through the TUI's
15
+ normal input parser and event pipeline. Key presses support Alt, Ctrl, Meta,
16
+ Shift, navigation keys, editing keys, text, and auto-repeat without emitting
17
+ duplicate input for paired CDP `keyDown` and `char` events.
18
+ - Add `micro.getAllAnsiText()` and `micro.clickBufferCell(x, y)` to the JS
19
+ plugin bridge. MDCUI cell clicks now activate the same callbacks as mouse
20
+ input, while clicks in ordinary buffers retain the previous `goto` behavior.
21
+ - Add `cdp-maze.js` and `llm-maze.txt`, a documented Bun.WebView example that
22
+ reads the maze from the running TUI, solves it with breadth-first search, and
23
+ completes it by clicking controls and sending arrow-key events through CDP.
24
+ - Add `--cdp-maze` to open the bundled maze, start a local CDP server on port
25
+ 9222, and automatically import and run the maze solver after a three-second
26
+ delay. The completed result is returned to the main program and displayed in
27
+ the TUI status message.
28
+ - Add `--export-cdp-maze` to write or overwrite `./cdp-maze.js` from the
29
+ bundled asset first, with a source-tree fallback, then exit. Include the
30
+ solver in the single-executable asset archive.
31
+ - Add a dedicated CDP automation section to the README and bundled help,
32
+ covering command-line and command-prompt startup, bind-address safety,
33
+ connection methods, and the maze solver workflow.
34
+ - Add a reusable single-file executable guide covering asset packing, embedded
35
+ and external resource fallbacks, build commands, cross-compilation, Node.js
36
+ compatibility, and adapting the bootstrap to another project.
37
+
38
+ ### Changed
39
+
40
+ - Move responsibility for awaiting bundled assets into the Bun-only
41
+ `single-exe/entry.mjs` bootstrap, then dynamically import the regular main
42
+ module. This keeps the main module free of Bun-specific asset-loader state
43
+ and preserves its uncompiled Node.js execution path.
44
+ - Detect Bun's compiled virtual paths on both POSIX (`/$bunfs/`) and Windows
45
+ (`B:/~BUN`) when resolving single-executable resources, and consolidate
46
+ compiled-runtime handling in `single-exe/compiled.js`.
47
+ - When initially launched with Node.js, restart under Bun with inherited stdio
48
+ and propagate its exit status instead of replacing the current process with
49
+ `process.execve()`.
50
+ - Display the maze completion message inside the game instead of opening a
51
+ blocking alert, and include reset instructions in the result.
52
+ - Export the maze solver as a reusable `runCdpMaze()` function while retaining
53
+ direct `bun cdp-maze.js` execution. Suppress its console result while stdin
54
+ is in terminal raw mode so TUI rendering is not corrupted.
55
+ - Replace the manual two-process maze automation instructions with the single
56
+ `--cdp-maze` workflow in the README and bundled help.
57
+ - Update the homepage and Kitty test images to use the smaller current demo and
58
+ maze screenshots; exclude the maze screenshot from the npm package.
59
+ - Document theme preview from the TUI command prompt and state Bun 1.3.12 as
60
+ the minimum required version.
61
+
62
+ ### Fixed
63
+
64
+ - Make CDP `view.click()` activate MDCUI links and controls at the requested
65
+ buffer coordinate instead of only moving the cursor there.
66
+ - Allow CDP input dispatch without an explicit session ID by selecting the
67
+ implicit target, matching other single-target CDP operations.
68
+ - Wait until the embedded asset archive is ready before starting the compiled
69
+ application, preventing startup from racing bundled resource loading.
70
+
71
+ ## [0.8.0] - 2026-07-20
72
+
73
+ This update adds portable inline keydown handling to Markdown text controls,
74
+ including mobile software-keyboard fallback behavior, and makes interactive
75
+ prompts safe across every TUI frontend action path.
76
+
77
+ ### Added
78
+
79
+ - Add quoted `@keydown="..."` attributes to named `text` and `textarea`
80
+ fences. The TUI preserves source event metadata and evaluates it before key
81
+ handling; the WUI emits a native inline event attribute. Add `.prevent` to
82
+ apply `event.preventDefault()` before the handler runs. Keyup is intentionally
83
+ not exposed because traditional terminals do not report key releases
84
+ reliably.
85
+ - In the WUI, automatically map `beforeinput.data` back through `onkeydown`
86
+ when a mobile software keyboard reports `event.key` as `Unidentified`.
87
+ Unidentified events are hidden from application handlers, while identified
88
+ desktop keydowns are marked briefly to prevent duplicate calls.
89
+ - Add native multiline editing to TUI `textarea` controls. Enter splits a body
90
+ row and expands the frame; Backspace at the start of a later row and Delete
91
+ at the end of a row join adjacent body rows without exposing frame borders.
92
+ - Add the `bun ./edit` launcher as a short equivalent of
93
+ `bun src/index.js --edit`, opening Markdown as ordinary editable UTF-8 source.
94
+ - Add the automatically discovered `--demo-maze` example, demonstrating
95
+ portable `@keydown.prevent` controls with keyboard and arrow-key navigation;
96
+ add `Ctrl-R` as a keyboard shortcut for resetting the maze.
97
+
98
+ ### Changed
99
+
100
+ - Install protected `alert`, `confirm`, and `prompt` globals once for the TUI
101
+ lifetime and share them across OSC 8 actions, fenced keyboard handlers, and
102
+ JS plugins. Restore Bun's original globals when the TUI stops.
103
+ - Make `micro.alert()`, `micro.confirm()`, and `micro.prompt()` synchronous like
104
+ their native counterparts, and update the bundled JS plugin examples so they
105
+ do not `await` these calls.
106
+ - Add TUI `.val(value)` replacements to Undo/Redo history, including multiline
107
+ layout, ANSI rendering metadata, image positions, and task-list anchors.
108
+
109
+ ### Fixed
110
+
111
+ - Prevent `alert()` called from a keydown handler from competing with the TUI
112
+ for stdin by suspending raw mode and screen rendering around native prompts.
113
+ - Restore the terminal's previous raw-mode and input-listener state after a
114
+ protected prompt, including setup, prompt, and cleanup error paths.
115
+ - Index fenced keyboard-event regions once and use binary lookup for each key,
116
+ instead of rescanning the rendered Markdown document on every keypress.
117
+ - Open Windows controlling-terminal input through read-only `CONIN$` when stdin
118
+ is redirected, so piped Markdown retains keyboard and protected-prompt input.
119
+ - Preserve `ctrlKey`, `shiftKey`, `altKey`, and `metaKey` when the WUI maps an
120
+ unidentified mobile keydown through its `beforeinput` fallback.
121
+ - Recover modified A-Z keys in the WUI from key codes 65-90 or physical
122
+ `KeyA`-`KeyZ` codes, including Android browsers that transform Alt-S into
123
+ `ß`; exclude AltGraph so international text composition remains intact.
124
+ - Add matching non-enumerable `event.toJSON()` methods to TUI and WUI keydown
125
+ events, allowing `JSON.stringify(event)` to produce portable event details.
126
+ - Keep fenced-block IDs visible to collision checking when the info string also
127
+ contains inline event attributes such as `@keydown="..."`.
128
+ - When the WUI's default port 3000 is already in use, retry with port 0 so the
129
+ operating system selects an available port, and print that actual port in the
130
+ generated URL. Other server startup errors continue to fail normally.
131
+
5
132
  ## [0.7.0] - 2026-07-20
6
133
 
7
134
  This update adds native Kitty image rendering, turns heading task lists into
package/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  ![Demo](https://raw.githubusercontent.com/jjtseng93/jsmdcui/main/demo.jpg)
2
2
 
3
+ - Maze Game
4
+ * The TUI can be controlled through the Chrome DevTools Protocol (CDP). Codex successfully solved this maze using it.
5
+
6
+ ![Maze Game](https://raw.githubusercontent.com/jjtseng93/jsmdcui/main/maze.jpg)
7
+
3
8
  # Demo App
4
9
  ## My app
5
10
 
@@ -36,7 +41,8 @@ npx jsmdcui@latest README.md
36
41
 
37
42
  ## Quick start
38
43
 
39
- jsmdcui requires [Bun](https://bun.com). On Android, install it in Termux:
44
+ jsmdcui requires [Bun ≥ 1.3.12](https://bun.com).
45
+ On Android, install it in Termux:
40
46
 
41
47
  ```sh
42
48
  npm install -g bun
@@ -89,6 +95,18 @@ npx jsmdcui --demo-imgtool
89
95
  npx jsmdcui --demo-imgtool-zh
90
96
  ```
91
97
 
98
+ ```sh
99
+ # Maze game demo
100
+ npx jsmdcui --demo-maze
101
+ ```
102
+
103
+ To watch jsmdcui start the maze with a local CDP server and solve it
104
+ automatically after three seconds, run:
105
+
106
+ ```sh
107
+ npx jsmdcui --cdp-maze
108
+ ```
109
+
92
110
  ```sh
93
111
  # WUI(Web User Interface) Demo
94
112
  npx jsmdcui --wui
@@ -134,7 +152,7 @@ bun src/index.js --wui testapp.md
134
152
  | --- | --- |
135
153
  | `bun src/index.js app.md` | Render `app.md` as a read-only terminal UI and write five generated files beside it. |
136
154
  | `bun src/index.js --kitty app.md` | Display Markdown images with Kitty graphics and the jsgotty MIME extension. |
137
- | `bun src/index.js --kitty-compat app.md` | Display Markdown images with Kitty graphics without the non-standard MIME `U` field. |
155
+ | `bun src/index.js --kitty-compat app.md` | Convert Markdown images to PNG with `Bun.Image` and display them using the standard Kitty graphics protocol without the non-standard MIME `U` field. |
138
156
  | `bun src/index.js --kitty --allow-url URL.md` | Download trusted HTTP(S) Markdown and its HTTP(S) images, then display supported images with Kitty graphics. |
139
157
  | `JSMDCUI_KITTY_DEBUG=1 bun src/index.js --kitty app.md` | Enable Kitty image placement logging to `kitty-placement.log`. |
140
158
  | `bun src/index.js --check app.md` | Check heading and fenced-block IDs for collisions, print line-by-line details, and exit. |
@@ -142,11 +160,13 @@ bun src/index.js --wui testapp.md
142
160
  | `bun src/index.js --cat app.md` | Render the terminal version to stdout, write five generated files beside it, and exit. |
143
161
  | `bun src/index.js --testapp.md` | Write the bundled `testapp.md` source to stdout and exit. |
144
162
  | `bun src/index.js --export-readme` | Write or overwrite `./README.md` with the bundled README source and exit. |
163
+ | `bun src/index.js --export-cdp-maze` | Write or overwrite `./cdp-maze.js` with the bundled CDP maze solver and exit. |
145
164
  | `bun src/index.js --demo-list` | List `testapp.md` and every bundled `demos/*.md` example with its command-line option, then exit. |
146
165
  | `bun src/index.js --demo` | Use local `testapp.md` when present, otherwise write the bundled demo; open it in the terminal UI and write five generated files beside it. |
147
166
  | `bun src/index.js --demo-<filename>` | Load `demos/<filename>.md`; preserve an existing local copy or write the bundled copy, then open it and generate its five companion files. New files added under `demos/` work automatically. |
148
167
  | `bun src/index.js --demo-imgtool` | Compatibility alias for `--demo-image-processor`. |
149
168
  | `bun src/index.js --demo-imgtool-zh` | Compatibility alias for `--demo-image-processor.zh-TW`. |
169
+ | `bun src/index.js --cdp-maze` | Load the maze demo, start CDP on `127.0.0.1:9222`, and run the bundled solver after three seconds. |
150
170
  | `bun src/index.js --allow-url URL.md` | Download HTTP(S) Markdown to the current directory and, with Kitty mode enabled, download its HTTP(S) images; write 5 generated files and allow embedded code to run. Only use trusted URLs. |
151
171
  | `bun src/index.js --wui` | Use local `testapp.md` when present, otherwise write the bundled demo; write five generated files in the current directory, then print and serve a random URL. |
152
172
  | `bun src/index.js --wui app.md` | Write five generated files beside `app.md`, then print and serve a random URL. |
@@ -239,12 +259,81 @@ native `<textarea>` with the declared ID and classes. Long text wraps
239
259
  automatically, and the field height is recalculated when the user types, the
240
260
  window is resized, or frontend code calls `.val(value)`.
241
261
 
262
+ In the TUI, `text` remains a single-line control while `textarea` supports
263
+ native multiline editing. Enter splits the current body row and grows the
264
+ frame, Backspace at the start of a later row joins it to the previous row, and
265
+ Delete at the end of a row joins the following row. When the expanded control
266
+ does not fit on screen, the document viewport scrolls to keep the cursor
267
+ visible. The closing border and following Markdown content move with the
268
+ resized control.
269
+
270
+ A named `text` or `textarea` block can run inline front-end code before it
271
+ handles a key by placing a quoted HTML-style `@keydown` attribute after its
272
+ identity:
273
+
274
+ ````md
275
+ ```text#command.field @keydown="handleCommand(event)"
276
+ Initial value
277
+ ```
278
+
279
+ ```js front
280
+ export function handleCommand(event) {
281
+ if (event.key !== 'Enter') return;
282
+ event.preventDefault();
283
+ alert(`Command: ${$('#command').val()}`);
284
+ }
285
+ ```
286
+ ````
287
+
288
+ The block must have a unique ID. In the WUI, jsmdcui writes the code directly
289
+ to the generated textarea's native `onkeydown` attribute. Mobile browsers may
290
+ report ordinary software-keyboard letters as `Unidentified`, so the WUI also
291
+ generates an `onbeforeinput` fallback. The unidentified event is hidden from
292
+ the handler; `beforeinput.data` becomes `event.key`, then the same keydown code
293
+ runs once. The fallback also preserves `ctrlKey`, `shiftKey`, `altKey`, and
294
+ `metaKey` from the unidentified keydown. A short zero-delay timer distinguishes
295
+ it from the `beforeinput` that normally follows an already identified keydown,
296
+ preventing duplicate calls.
297
+
298
+ On Android browsers, `Alt-E`, `Alt-N`, `Alt-U`, and `Alt-I` may be consumed or
299
+ transformed by the software keyboard and therefore cannot always be observed
300
+ reliably by a keydown handler. Avoid relying on these combinations for portable
301
+ WUI controls.
302
+
303
+ In the TUI, the full source info string is kept in an event table before Bun
304
+ renders it. The keydown statements run with a synthetic `event` before the
305
+ text control handles the key. Both interfaces expose `event.key`, modifier
306
+ flags, `event.target.id`, and `event.target.value`. Use `event`, the native
307
+ inline-handler variable, rather than Vue's `$event` alias. Double quotes
308
+ delimit the handler; use single-quoted JavaScript strings inside it or escape
309
+ an embedded double quote as `\"`.
310
+
311
+ Both interfaces add a non-enumerable `event.toJSON()` method with matching
312
+ keyboard, modifier, prevention, and target fields. `JSON.stringify(event)`
313
+ calls it automatically, so the resulting JSON is portable between the TUI and
314
+ WUI.
315
+
316
+ A keydown handler can call `event.preventDefault()` to stop text insertion or
317
+ cursor movement. The `.prevent` modifier applies this automatically:
318
+
319
+ ````md
320
+ ```text#command @keydown.prevent="handleCommand(event)"
321
+ ```
322
+ ````
323
+
324
+ `@keydown` is the only keyboard event exposed by jsmdcui. Traditional terminal
325
+ input does not report physical key releases reliably, so jsmdcui does not
326
+ provide or emulate `@keyup` in either interface.
327
+
242
328
  In the terminal TUI, only content after the protected `│ ` or `| ` prefix can
243
- be edited. The frame prefix cannot be deleted, Enter cannot insert a newline,
244
- Delete at the end of a row cannot join the next row, and multiline paste is
245
- blocked. Activate the lower-left frame corner to add a row. Activate the
246
- upper-left frame corner to remove the trailing row only when it is empty;
247
- non-empty content is never removed.
329
+ be edited, and the frame prefix cannot be deleted. Multiline paste remains
330
+ blocked. For single-line `text` controls, activate the lower-left frame corner
331
+ to add a row. Activate the upper-left frame corner to remove the trailing row
332
+ only when it is empty; non-empty content is never removed.
333
+
334
+ TUI text-control changes made through `.val(value)` participate in the same
335
+ history as direct editing. `Ctrl-Z` undoes the replacement and `Ctrl-Y` redoes
336
+ it, including multiline frame resizing and its associated rendered metadata.
248
337
 
249
338
  ### Heading task lists and selector API
250
339
 
@@ -384,7 +473,7 @@ The available selector methods are:
384
473
  | Method | TUI | WUI |
385
474
  | --- | --- | --- |
386
475
  | `.val()` | Read text blocks or heading task-list values. | Read textareas/controls or heading task-list values. |
387
- | `.val(value)` | Replace text-block contents and resize multiline values. | Set textarea/control values and resize textareas. |
476
+ | `.val(value)` | Replace text-block contents, resize multiline values, and record Undo/Redo history. | Set textarea/control values and resize textareas. |
388
477
  | `.html()` | Return a selected heading's inner HTML. | Return any successfully selected DOM element's `innerHTML`. |
389
478
  | `.line()` | Return a heading's current 1-based TUI row, or `0` if missing. | Not available. |
390
479
  | `.push(...items)` | Append unchecked strings or `{ value, checked }` task items; return the new direct-item count. | Same. |
@@ -397,16 +486,16 @@ The available selector methods are:
397
486
  TUI heading rows are recalculated after text-block rows are added, removed, or
398
487
  replaced with multiline `.val(value)` content.
399
488
 
400
- The three UI building blocks are:
489
+ The 3 UI building blocks are:
401
490
 
402
- - Regular Markdown provides headings, text, lists, task checkboxes, code, and
491
+ - 1. `Regular Markdown` provides headings, text, lists, task checkboxes, code, and
403
492
  links.
404
- - A `js front` block contains UI code. Exported functions can use
493
+ - 2. `js front` block contains UI code. Exported functions can use
405
494
  `alert`, `confirm`, `prompt`, and the generated `rpc` client.
406
- - A front module may export `async function onMdcuiExit({ reason, path, $ })`.
407
- The terminal UI awaits it before closing an `mdcui` buffer. Modified
408
- `mdcui` buffers close without a save prompt.
409
- - A `js back` block exports trusted backend functions. In the browser WUI,
495
+ * A front module may export `async function onMdcuiExit({ reason, path, $ })`.
496
+ * The terminal UI awaits it before closing an `mdcui` buffer.
497
+ * Modified `mdcui` buffers close without a save prompt.
498
+ - 3. `js back` block exports trusted backend functions. In the browser WUI,
410
499
  `rpc` publishes only exported functions whose exported names do not start
411
500
  with `_`. Call a published function from the front end with
412
501
  `await rpc.functionName(arg1, arg2)`.
@@ -463,6 +552,10 @@ selection, search, and copy remain available.
463
552
  | `Alt-G` | Show or hide the shortcut bar. |
464
553
  | `Ctrl-Q` or `Alt-Q` | Close the current UI. |
465
554
 
555
+ To preview another color theme, press `Ctrl-E` or click on `€`, type `theme `, then use `Tab`
556
+ and the arrow keys to browse the available themes. Press `Enter` to switch to
557
+ the selected theme, or `Esc` to cancel and restore the previous one.
558
+
466
559
  The terminal automatically reflows the Markdown when its width changes.
467
560
  Only `javascript:` links execute in the TUI; ordinary web links behave as
468
561
  normal links in the browser.
@@ -480,6 +573,63 @@ supported HTTP(S) images with Kitty graphics, combine the options:
480
573
  bun src/index.js --kitty --allow-url https://example.com/app.md
481
574
  ```
482
575
 
576
+ ### CDP control for TUI automation
577
+
578
+ The terminal UI can expose a Chrome DevTools Protocol (CDP) server, so another
579
+ Bun process can inspect the terminal buffer, click buffer coordinates, and send
580
+ real keyboard events to the running TUI.
581
+
582
+ Start CDP from the command line when launching jsmdcui:
583
+
584
+ ```sh
585
+ bun src/index.js --remote-debugging-port=9222 demos/maze.md
586
+ bun src/index.js --remote-debugging-port=9222 --remote-debugging-address=127.0.0.1 demos/maze.md
587
+ ```
588
+
589
+ Or start it from inside the TUI editor command prompt(Ctrl-E or €):
590
+
591
+ ```text
592
+ Ctrl-E cdp
593
+ Ctrl-E cdp 9000
594
+ Ctrl-E cdp --address=127.0.0.1
595
+ Ctrl-E cdp 9000 --public
596
+ ```
597
+
598
+ The default bind address is `127.0.0.1` and the default port is `9222`.
599
+ Use `--public` or `--address=0.0.0.0` only on a trusted network.
600
+
601
+ Once CDP is running, control the TUI with `Bun.WebView`. For more info, enter
602
+ jsmdcui and use `Ctrl-E` or `€` → `help cdp`.
603
+
604
+ The cli flag `--cdp-maze` is a combination of
605
+ 1. Start the demos/maze.md
606
+ 2. Start a local CDP server
607
+ 3. Wait three seconds
608
+ 4. Run the solver cdp-maze.js automatically
609
+
610
+ The solver was
611
+ generated from the `llm-maze.txt` instructions; it focuses the maze controls,
612
+ resets the game, reads the maze from the TUI, solves it with breadth-first
613
+ search, and sends arrow-key input until the maze is escaped:
614
+
615
+ ```sh
616
+ npx jsmdcui@latest --cdp-maze
617
+ ```
618
+
619
+ From the source tree, run:
620
+
621
+ ```sh
622
+ bun src/index.js --cdp-maze
623
+ ```
624
+
625
+ Useful automation methods used by `cdp-maze.js`:
626
+
627
+ - `view.evaluate(js)`
628
+ - `view.evaluate("micro.getAllText()")`
629
+ - `view.evaluate("micro.getAllAnsiText()")`
630
+ - `view.click(column, line)`
631
+ - `view.press(key, options)`
632
+
483
633
  ## Browser interaction
484
634
 
485
635
  The WUI uses normal browser mouse and keyboard behavior. Clicking a
@@ -489,15 +639,35 @@ its associated text. `alert`, `confirm`, and `prompt` use the browser's built-in
489
639
  dialogs. Checkbox changes exist only in the current page: refreshing does not
490
640
  preserve them and does not update the Markdown file.
491
641
 
492
- The WUI uses port `3000` by default and accepts connections through the
493
- machine's available network interfaces. The printed `localhost` URL is for the
494
- same machine. From another device on the same network, replace `localhost` with
495
- the server machine's IP address and keep the same port and full path.
642
+ The WUI first tries port `3000` and accepts connections through the machine's
643
+ available network interfaces. If port `3000` is already in use, the operating
644
+ system selects an available port instead; the printed URL always contains the
645
+ actual port. Set `PORT` to request another fixed port. The printed `localhost`
646
+ URL is for the same machine. From another device on the same network, replace
647
+ `localhost` with the server machine's IP address and keep the printed port and
648
+ full path.
496
649
 
497
650
  Each server start prints a new random path. The old URL stops working after the
498
651
  server is stopped or restarted. Keep the process running while using the page,
499
652
  and press `Ctrl-C` in its terminal to stop it.
500
653
 
654
+ ## Editing Markdown source
655
+
656
+ From a cloned repository, use the `edit` launcher to open a Markdown file as
657
+ ordinary editable UTF-8 source instead of rendering it as a Markdown UI:
658
+
659
+ ```sh
660
+ bun ./edit app.md
661
+ ```
662
+
663
+ This is equivalent to:
664
+
665
+ ```sh
666
+ bun src/index.js --edit app.md
667
+ ```
668
+
669
+ Additional file and cursor arguments are forwarded unchanged.
670
+
501
671
  ## Generated files
502
672
 
503
673
  Opening a local Markdown UI generates these files beside the source file:
package/cdp-maze.js ADDED
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env bun
2
+
3
+ // Generated from llm-maze.txt instructions.
4
+ // Start the maze TUI first:
5
+ // bun src/index.js --remote-debugging-port=9222 demos/maze.md
6
+ // Then run:
7
+ // bun cdp-maze.js
8
+
9
+ const DEFAULT_CDP_URL = "ws://127.0.0.1:9222/devtools/browser/cdp-server";
10
+
11
+ const dirs = [
12
+ [1, 0, "ArrowDown"],
13
+ [-1, 0, "ArrowUp"],
14
+ [0, 1, "ArrowRight"],
15
+ [0, -1, "ArrowLeft"],
16
+ ];
17
+
18
+ function parseMaze(text) {
19
+ return text.trimEnd().split("\n").map(line => {
20
+ const cells = [];
21
+
22
+ for (let i = 0; i < line.length;) {
23
+ const ch = String.fromCodePoint(line.codePointAt(i));
24
+
25
+ if (ch === "🧱" || ch === "😀") {
26
+ cells.push(ch);
27
+ i += ch.length;
28
+ } else if (line[i] === " ") {
29
+ cells.push(" ");
30
+ i += 2;
31
+ } else {
32
+ cells.push(ch);
33
+ i += ch.length;
34
+ }
35
+ }
36
+
37
+ return cells;
38
+ });
39
+ }
40
+
41
+ function solveMaze(grid) {
42
+ let start;
43
+
44
+ for (let row = 0; row < grid.length; row++) {
45
+ for (let col = 0; col < grid[row].length; col++) {
46
+ if (grid[row][col] === "😀") start = [row, col];
47
+ }
48
+ }
49
+
50
+ if (!start) throw new Error("Maze start not found");
51
+
52
+ const queue = [{ row: start[0], col: start[1], path: [] }];
53
+ const seen = new Set([start.join(",")]);
54
+
55
+ while (queue.length) {
56
+ const { row, col, path } = queue.shift();
57
+
58
+ for (const [dr, dc, key] of dirs) {
59
+ const nextRow = row + dr;
60
+ const nextCol = col + dc;
61
+
62
+ if (
63
+ nextRow < 0 ||
64
+ nextRow >= grid.length ||
65
+ nextCol < 0 ||
66
+ nextCol >= grid[row].length
67
+ ) {
68
+ if (path.length > 0) return path.concat(key);
69
+ continue;
70
+ }
71
+
72
+ if (nextCol >= grid[nextRow].length) continue;
73
+ if (grid[nextRow][nextCol] === "🧱") continue;
74
+
75
+ const id = `${nextRow},${nextCol}`;
76
+ if (seen.has(id)) continue;
77
+
78
+ seen.add(id);
79
+ queue.push({
80
+ row: nextRow,
81
+ col: nextCol,
82
+ path: path.concat(key),
83
+ });
84
+ }
85
+ }
86
+
87
+ throw new Error("No path found");
88
+ }
89
+
90
+ export async function runCdpMaze(cdpUrl = DEFAULT_CDP_URL) {
91
+ const view = new Bun.WebView({
92
+ backend: {
93
+ type: "chrome",
94
+ url: cdpUrl,
95
+ },
96
+ });
97
+
98
+ try {
99
+ const control = await view.evaluate(`
100
+ (() => {
101
+ const lines = micro.getAllText().split("\\n");
102
+ const y = lines.findIndex(line =>
103
+ line.includes("Put the cursor here")
104
+ );
105
+
106
+ return {
107
+ x: lines[y].indexOf("Put the cursor here") + 1,
108
+ y: y + 1,
109
+ };
110
+ })()
111
+ `);
112
+
113
+ await view.click(control.x, control.y);
114
+ await view.press("r", { modifiers: ["Control"] });
115
+ await Bun.sleep(100);
116
+
117
+ const mazeText = await view.evaluate(`$("#character").val()`);
118
+ const path = solveMaze(parseMaze(mazeText));
119
+
120
+ for (const key of path) {
121
+ await view.press(key);
122
+ await Bun.sleep(100);
123
+
124
+ const status = await view.evaluate(`$("#last-key").val()`);
125
+ if (status.includes("Escaped the maze")) break;
126
+ if (status.includes("Wall")) {
127
+ throw new Error(`Hit a wall after ${key}: ${status}`);
128
+ }
129
+ }
130
+
131
+ const result = await view.evaluate(`$("#last-key").val()`);
132
+ if (!process.stdin.isRaw) console.log(result);
133
+ return result;
134
+ } finally {
135
+ view.close();
136
+ }
137
+ }
138
+
139
+ if (import.meta.main) {
140
+ await runCdpMaze(Bun.argv[2] ?? DEFAULT_CDP_URL);
141
+ }