@solidrt/cli 0.0.50 → 0.0.52

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 (98) hide show
  1. package/AGENTS.md +79 -36
  2. package/README.md +86 -15
  3. package/agents/assets.md +46 -0
  4. package/agents/debugging.md +294 -0
  5. package/dist/console.srtapp +94123 -73
  6. package/dist/server.js +3936 -0
  7. package/package.json +11 -10
  8. package/src/android/docs.md +21 -0
  9. package/src/android/main.ts +286 -0
  10. package/src/{bundler.ts → bundle/bundler.ts} +163 -76
  11. package/src/bundle/docs.md +12 -0
  12. package/src/bundle/main.ts +203 -0
  13. package/src/check/docs.md +10 -0
  14. package/src/check/main.ts +85 -0
  15. package/src/{commands/check.ts → check/typecheck.ts} +11 -34
  16. package/src/client/docs.md +9 -0
  17. package/src/client/main.ts +33 -0
  18. package/src/console/docs.md +14 -0
  19. package/src/console/main.ts +21 -0
  20. package/src/demo/docs.md +27 -0
  21. package/src/demo/main.ts +67 -0
  22. package/src/init/docs.md +11 -0
  23. package/src/{commands/init.ts → init/main.ts} +31 -25
  24. package/src/init/scaffold/AGENTS.md +98 -0
  25. package/src/init/scaffold/package.json +23 -0
  26. package/{scaffold → src/init/scaffold}/templates/components/index.tsx +2 -3
  27. package/{scaffold → src/init/scaffold}/templates/default/index.tsx +2 -3
  28. package/src/lib/args.ts +194 -0
  29. package/src/{artifacts.ts → lib/artifacts.ts} +41 -1
  30. package/src/{dev-dir.ts → lib/dev-dir.ts} +10 -8
  31. package/src/{fonts.ts → lib/fonts.ts} +12 -26
  32. package/src/lib/mode.ts +77 -0
  33. package/src/{project.ts → lib/project.ts} +109 -61
  34. package/src/lib/registry.ts +120 -0
  35. package/src/lib/server-bundle.ts +24 -0
  36. package/src/lib/usage.ts +117 -0
  37. package/src/lib/util.ts +36 -0
  38. package/src/main.ts +109 -31
  39. package/src/mcp/docs.md +22 -0
  40. package/src/mcp/main.ts +719 -0
  41. package/src/pack/docs.md +18 -0
  42. package/src/{pack-folder.ts → pack/layout.ts} +13 -22
  43. package/src/pack/main.ts +86 -0
  44. package/src/pack/trailer.ts +97 -0
  45. package/src/render/docs.md +19 -0
  46. package/src/render/main.ts +49 -0
  47. package/src/server/args.ts +126 -0
  48. package/src/server/binaries.ts +47 -0
  49. package/src/server/config.ts +54 -0
  50. package/{server → src/server}/control.ts +246 -78
  51. package/src/server/docs.md +51 -0
  52. package/src/server/line-editor.ts +200 -0
  53. package/src/server/main.ts +473 -0
  54. package/src/server/mode.ts +92 -0
  55. package/src/server/rebuild.ts +90 -0
  56. package/src/server/registry.ts +138 -0
  57. package/src/server/remap.ts +60 -0
  58. package/src/server/repl.ts +223 -0
  59. package/src/server/state.ts +54 -0
  60. package/{server → src/server}/tsconfig.json +1 -1
  61. package/{server → src/server}/tunnel.ts +6 -6
  62. package/src/server/watcher.ts +121 -0
  63. package/src/tool/main.ts +70 -0
  64. package/src/types/bundle.d.ts +24 -0
  65. package/src/types/control.d.ts +90 -0
  66. package/src/types/registry.d.ts +16 -0
  67. package/scaffold/AGENTS.md +0 -630
  68. package/scaffold/package.json +0 -22
  69. package/scaffold/templates/components/icon.tsx +0 -48
  70. package/scaffold/templates/default/icon.tsx +0 -48
  71. package/server/main.ts +0 -308
  72. package/server/rebuild.ts +0 -68
  73. package/server/remap.ts +0 -47
  74. package/server/state.ts +0 -93
  75. package/src/args.ts +0 -212
  76. package/src/bundle-cli.ts +0 -13
  77. package/src/commands/bundle.ts +0 -76
  78. package/src/commands/client.ts +0 -34
  79. package/src/commands/mcp.ts +0 -603
  80. package/src/commands/pack.ts +0 -65
  81. package/src/commands/render.ts +0 -24
  82. package/src/commands/server.ts +0 -73
  83. package/src/dev-android.ts +0 -176
  84. package/src/dev-client.ts +0 -29
  85. package/src/dev-server.ts +0 -302
  86. package/src/packer.ts +0 -103
  87. package/src/repl.ts +0 -233
  88. package/src/util.ts +0 -121
  89. package/src/watcher.ts +0 -69
  90. /package/src/{untyped-deps.d.ts → bundle/untyped-deps.d.ts} +0 -0
  91. /package/src/{prompt.ts → init/prompt.ts} +0 -0
  92. /package/{scaffold → src/init/scaffold}/gitignore +0 -0
  93. /package/{scaffold → src/init/scaffold}/icon.svg +0 -0
  94. /package/{scaffold → src/init/scaffold}/mcp.json +0 -0
  95. /package/{scaffold → src/init/scaffold}/tsconfig.json +0 -0
  96. /package/{server → src/server}/cache.ts +0 -0
  97. /package/{server → src/server}/proxy.ts +0 -0
  98. /package/{server → src/server}/qr.ts +0 -0
package/AGENTS.md CHANGED
@@ -1,41 +1,78 @@
1
1
  # @solidrt/cli - agent notes
2
2
 
3
- Dense, self-contained facts for running and verifying a SolidRT app. Full docs
4
- live in docs/ (and the website). For the authoring model (elements, props,
5
- reactivity), see @solidrt/core (its AGENTS.md).
3
+ Dense, self-contained facts for running and verifying a SolidRT app. The
4
+ prose lives in README.md and src/<command>/docs.md (also the website). For
5
+ the authoring model (elements, props, reactivity), see @solidrt/core (its
6
+ AGENTS.md).
6
7
 
7
8
  `srt` is the dev tool. Bun is a dev prerequisite only; SolidRT apps run on the
8
9
  bundled `flux` runtime, not on Bun. Invoke via `bunx srt <command>`.
9
10
 
11
+ The dev loop against a running app is pause_watch -> edit -> reload ->
12
+ resume_watch -> get_logs -> get_snapshot, with mute_user_input while you
13
+ measure or test and unmute_user_input after; agents/debugging.md has the
14
+ why of each hold. `reload` surfaces build errors but not type errors:
15
+ `bunx srt check` is for those.
16
+
17
+ agents/ carries the depth this one leaves out; read the one that matches
18
+ the work before starting it:
19
+ - agents/debugging.md - driving a running app over MCP, pointing an agent
20
+ client at the `srt mcp` server, what the dev server serves and its control
21
+ API without MCP (a shell, a CI step), and the debugging lessons that cost
22
+ real time. Read before investigating a bug, verifying a change against the
23
+ live app, or scripting against a server.
24
+ - agents/assets.md - the assets/ folder, inlined imports and the bundle
25
+ output; fonts, the `solidrt` package.json identity key, and distribution
26
+ builds. Read before adding an asset or a font, or building to distribute.
27
+
10
28
  ## Commands
11
29
 
12
30
  - `bunx srt init <dir>` - scaffold a new SolidRT project into a new (empty)
13
31
  folder: package.json, tsconfig.json, AGENTS.md, a starter src/index.tsx, an
14
32
  empty assets/ (everything in it ships with the app), then installs deps. Greenfield shortcut (no install needed first):
15
33
  `bun create solidrt <dir>`.
16
- - `bunx srt run src/index.tsx` - dev server + a local client window, watches and
17
- hot-reloads. NEEDS A DISPLAY (opens a GUI window). Not usable headless.
18
- - `bunx srt bundle src/index.tsx` - transpile to `<file>.srt.js`. With
19
- `--compile`, emits `.srt.bin` bytecode. `--minify`, `--dev`, `--stdout`,
20
- `--output` also available.
21
- - `bunx srt render src/index.tsx [flags]` - render OFFSCREEN to PNG frames,
34
+ - `bunx srt run` - dev server + a local client window, from the project root
35
+ (entry `solidrt.entry` in package.json, default src/index.tsx); `bunx srt run
36
+ <file>` serves a single file outside a project. NEEDS A DISPLAY (opens a GUI
37
+ window). Not usable headless. Reloads on save (the bundle's inputs and
38
+ `assets/`); an agent pauses that with `pause_watch` and pushes its edits
39
+ with the MCP `reload` tool.
40
+ - `bunx srt tool` - list the build-time tools the installed `@solidrt/*`
41
+ packages ship (`<package>/tools/<name>.ts`, named `<package>/<name>`);
42
+ `bunx srt tool <package>/<name> [arguments]` runs one under bun in the
43
+ project, everything after the name passed through as the tool's own
44
+ arguments (each tool prints its own usage on `--help`). What a tool does
45
+ is the package's business (e.g. `3d/model` bakes a glTF into a model
46
+ file); srt only finds and runs them.
47
+ - `bunx srt check [file|dir]` - build in memory and typecheck, no output
48
+ and no reload. With a folder it covers every entry under it (src/index.tsx
49
+ and examples/*), so `bunx srt check .` answers "did I break any example"
50
+ in one call. The dev server runs it once at startup without gating on it.
51
+ - `bunx srt bundle` - bundle the project into `dist/bundle/` (or
52
+ `--output <dir>`): `<name>.srt.js` plus the app's isolate modules as
53
+ `isolates/<id>.js`. With `--compile`, bytecode (`.srt.bin`/`.bin`)
54
+ instead. Move the dir, not the bare file - a bundle loaded without its
55
+ isolates/ dir loses them (`--stdout` cannot carry them at all).
56
+ `--minify`, `--dev` also available.
57
+ - `bunx srt render [flags]` - render the project OFFSCREEN to PNG frames,
22
58
  optionally replaying a `--script` file recorded via `--capture`.
23
59
  - `bunx srt server [file]` / `bunx srt client` - the two halves of `run`
24
60
  separately (server distributes code; clients on other devices connect to it).
25
- - `bunx srt run src/index.tsx --capture out.script.json` - records keydown/keyup
61
+ - `bunx srt run --capture out.script.json` - records keydown/keyup
26
62
  from every connected client into one script file (written on client
27
63
  disconnect), for replaying later with `render --script`. The file is JSON
28
- Lines and hand-authorable - the exact shape (`after` ms, `type`, `key`,
29
- `device`) is in `docs/cli.md` under `--capture`. For probing app state
30
- without a display, `-- <args...>` reaches the app as `flux:process` argv
31
- (also `docs/cli.md`), which is often simpler than scripting input.
64
+ Lines and hand-authorable: one object per line,
65
+ `{"after": <ms since previous event>, "type": "keydown" | "keyup",
66
+ "key": <W3C KeyboardEvent.key>, "device": <client id>}`. For probing app
67
+ state without a display, `-- <args...>` reaches the app as `flux:process`
68
+ argv, which is often simpler than scripting input.
32
69
 
33
70
  ## Verifying without a display (headless / CI / agent box)
34
71
 
35
72
  Two reliable checks that need no GUI:
36
73
 
37
- 1. `bunx srt bundle src/index.tsx` - exit 0 means the app compiles. Fast.
38
- 2. `bunx srt render src/index.tsx --size 480x640 --duration 1 --fps 2` -
74
+ 1. `bunx srt bundle` - exit 0 means the app compiles. Fast.
75
+ 2. `bunx srt render --size 480x640 --duration 1 --fps 2` -
39
76
  renders offscreen via EGL and writes `frame-NNNNNN.png`. This actually
40
77
  proves the app renders. Combine with `--fps`/`--duration` (defaults
41
78
  1280x720, 60fps, 1s). No display needed: rendering uses SDL's offscreen
@@ -63,29 +100,35 @@ behavior in isolation.
63
100
  (Wayland) and the pbuffer path on Windows from a desktop session; a
64
101
  non-interactive Windows session and macOS are unverified.
65
102
 
66
- ## Sessions (parallel dev servers on one machine)
67
-
68
- - `-s <N>` / `--session <N>` (default 0) picks the dev server: port
69
- `34884 + N`. Valid on `run`, `server`, `client`, `mcp`. `srt run -s1` is a
70
- second, fully independent dev setup; `srt client -s1 -c2` attaches another
71
- client to it.
72
- - `-c <N>` / `--client <N>` picks the client data tree, defaulting to the
73
- session number. (`--compile` gave its short to `--client`.)
74
- - Dev state lives in `~/.solidrt/`: `servers/<port>/` holds each server's tunnel
75
- key and `live.json` (the registry record MCP resolution reads, removed at
76
- exit), `clients/client<M>/` the client trees (srt passes
77
- `--data-root ~/.solidrt/clients` to every locally spawned client).
78
- - A server run serves the project it was started in; `load` outside the
79
- project root is refused. Restart the server in another project to switch.
80
- - `srt mcp` needs no port: each tool call resolves the server serving the
81
- project the bridge runs in (registry match + probe); `-s`/`--port` pin it.
103
+ ## Servers (what is served, and finding it again)
104
+
105
+ - A dev server serves a project (started in its root: the cwd must hold the
106
+ package.json) or a single file (`srt run <file>` outside a project). Both
107
+ in one place is ambiguous, so `srt run <file>` in a project root needs
108
+ `--project` (the project, with this entry) or `--file` (the file alone).
109
+ Nothing searches upward for a package.json.
110
+ - One server per project or file. The server binds the port it had last
111
+ time, else the first free one from 34884 up, and prints it; `--port <N>`
112
+ pins it. Loopback only
113
+ unless `--lan`, which is what phones and other devices (and `srt android`
114
+ on a real device) need; `--tunnel` works without it.
115
+ - `-c <N>` / `--client <N>` picks the client data tree (default 0). Storage
116
+ is per app inside a tree, so two projects share client 0; only two clients
117
+ of the same app need distinct slots.
118
+ - Dev state lives in `~/.solidrt/`: `servers/<key hash>/` holds each server's
119
+ `live.json` (the registry record, written by the server and removed at
120
+ exit), its remembered `port` and tunnel key; `clients/client<M>/` the
121
+ client trees (srt passes `--data-root ~/.solidrt/clients` to every locally
122
+ spawned client). A record left behind by a crash is pruned when the next
123
+ server starts, and `srt client`, `srt mcp` and `srt android` confirm a
124
+ record against the server (its control API names the key it serves)
125
+ before using it.
126
+ - `srt client` and `srt mcp` need no port: run from the project root (or the
127
+ directory of a served file) they resolve the server from the registry;
128
+ `--port` pins one.
82
129
 
83
130
  ## Dev server proxies (when clients on other devices need your machine's data)
84
131
 
85
132
  - `--proxy-http` - route `fetch` through the dev server; responses cached in
86
133
  `.srt-data/http-cache.db` in the project root (delete the file to clear).
87
134
 
88
- ## REPL (opened by `run`/`server`)
89
-
90
- `load <file>`, `reload [n]`, `stop [n]`, `list`, `!<cmd>`, `quit`/`exit`.
91
- `load` is bound to the project root the server was started in.
package/README.md CHANGED
@@ -1,26 +1,97 @@
1
1
  # @solidrt/cli
2
2
 
3
- Developer tooling for SolidRT. Provides a development environment for `@solidrt/core` applications.
3
+ Developer tooling for SolidRT: `srt`, one command-line tool covering the
4
+ whole cycle of a `@solidrt/core` application: scaffold, develop, inspect,
5
+ ship. A GUI inspector, itself a SolidRT app, is in progress alongside it.
4
6
 
5
- > LLM agents: see [AGENTS.md](./AGENTS.md) for a dense, self-contained quickstart.
7
+ > LLM agents: `AGENTS.md` in this package is a dense, self-contained quickstart.
8
+
9
+ Bun is a dev prerequisite only; apps run on the bundled `flux` runtime.
10
+ Invoke via `bunx srt <command>`. A scaffolded project wires the common
11
+ commands into scripts, so day-to-day work is `bun run dev`, `bun run
12
+ android` (and `server`, `client`, `pack` for the rest).
6
13
 
7
14
  ## Commands
8
15
 
16
+ | Command | |
17
+ | --- | --- |
18
+ | [`srt init <dir>`](src/init/docs.md) | scaffold a new project into a new (empty) folder |
19
+ | [`srt run [file]`](src/server/docs.md) | dev server + local client window |
20
+ | [`srt server [file]`](src/server/docs.md) | dev server only |
21
+ | [`srt client`](src/client/docs.md) | client only, attached to the project's dev server |
22
+ | [`srt android`](src/android/docs.md) | install and launch the client on a connected Android device |
23
+ | [`srt demo [n]`](src/demo/docs.md) | list the demos the installed packages ship, or run one |
24
+ | [`srt console`](src/console/docs.md) | start the dev console: the dev servers on this machine and their clients |
25
+ | [`srt check [file]`](src/check/docs.md) | build and typecheck, writing nothing |
26
+ | [`srt bundle [file]`](src/bundle/docs.md) | transpile to JS or bytecode (dist/bundle/) |
27
+ | [`srt render [file]`](src/render/docs.md) | render frames offscreen, optionally replaying a script |
28
+ | [`srt pack [file]`](src/pack/docs.md) | bundle + compile to a standalone executable (experimental) |
29
+ | [`srt mcp`](src/mcp/docs.md) | MCP server (stdio) exposing the running dev server to agents |
30
+
31
+ `srt --help` lists every command and option; `srt --version` prints the
32
+ version. Run from the project root to work on the project (its entry is
33
+ `solidrt.entry` in package.json, default `src/index.tsx`); pass a file to
34
+ work on that file on its own.
35
+
36
+ ## Develop
37
+
38
+ ```sh
39
+ srt run
40
+ ```
41
+
42
+ Starts the dev server and a local client window against it. The server
43
+ pushes the bundle to every connected client, so one server can drive a
44
+ desktop window and a phone at the same time; edits reach them on an
45
+ explicit reload (the MCP `reload` tool) or on save.
46
+
47
+ Split them when you need to:
48
+
49
+ ```sh
50
+ srt server # server only
51
+ srt client # client only, the project's server (from its root)
52
+ srt client --server 192.168.1.5:34884 # client only, pointed at that address
53
+ srt android # install and launch on a connected device
54
+ ```
55
+
56
+ ## Inspect
57
+
58
+ ```sh
59
+ srt mcp
60
+ ```
61
+
62
+ Exposes the running app to a coding agent: logs, stats, the live render
63
+ tree, screenshots, GPU resources, input injection, a virtual-time transport,
64
+ reload, and the app's own debug commands. A scaffolded project ships an
65
+ `.mcp.json`, so Claude Code attaches with no setup.
66
+
67
+ ## Record and replay
68
+
69
+ ```sh
70
+ srt run --capture session.json
71
+ srt render --script session.json --fps 60 --duration 5
72
+ ```
73
+
74
+ `--capture` records key events from connected clients to a script; `render`
75
+ replays it headlessly and writes frames, which makes bug reports
76
+ reproducible and turns an interaction into a video.
77
+
78
+ ## Ship
79
+
9
80
  ```sh
10
- bunx srt run [file] # start dev server + client, optionally load a file
11
- bunx srt run --server [file] # start dev server only
12
- bunx srt run --client # start dev client only
81
+ srt check . # build and typecheck every entry, no build output
82
+ srt bundle # transpile to JS or bytecode
83
+ srt pack # standalone executable (experimental)
13
84
  ```
14
85
 
15
- ## REPL
86
+ `srt bundle --flux` and `srt pack --flux` target the bare Flux runtime
87
+ instead of a SolidRT app, for scripts and servers with no UI.
16
88
 
17
- Running `bunx srt run` opens an interactive REPL. The loaded file is watched for changes and automatically pushed to connected clients.
89
+ ## Layout
18
90
 
19
- | Command | Description |
20
- | --------------- | ------------------------------------------------ |
21
- | `load <file>` | load and push a `.tsx`, `.srt.js`, or `.srt.bin` |
22
- | `reload [n]` | rebuild and push to all clients, or client `n` |
23
- | `stop [n]` | stop all clients, or client `n` |
24
- | `list` | list connected clients |
25
- | `!<cmd>` | run a shell command |
26
- | `quit` / `exit` | exit the dev server |
91
+ One folder per command under `src/`, named for what it does; the first line
92
+ of each says its runtime. `src/server/` is the dev server, a flux script
93
+ (`bun`-free at runtime, its own tsconfig); every other command runs on bun.
94
+ `src/lib/` is what the bun commands share, `src/types/` the type-only
95
+ contracts between the two runtimes. Each command folder carries its
96
+ `docs.md` (this site); the depth agents need lives in `agents/` at the
97
+ package root, as in the other packages.
@@ -0,0 +1,46 @@
1
+ # Assets, fonts, identity and distribution
2
+
3
+ Read this before adding an asset or a font, setting the app's identity, or
4
+ preparing a build for distribution.
5
+
6
+ ## Assets and inlined imports
7
+
8
+ - Everything under `assets/` ships with the app: the folder is collected
9
+ wholesale into each build's version manifest (no bundler analysis, no
10
+ registration step). Reference assets by path - `file("assets/sounds/x.ogg")`
11
+ from `flux:fs` - and treat them as read-only at runtime; writes belong in
12
+ plain relative paths, which land in the app's private data dir.
13
+ - Small text-like assets (SVG documents, shaders) can instead be inlined via
14
+ imports. An import attribute picks the form and works on any extension:
15
+ `import src from "./effect.glsl" with { type: "text" }` yields the file's
16
+ contents as a string, `with { type: "binary" }` yields a Uint8Array. `.svg`
17
+ is text-loaded with no attribute needed. Shader sources (`.glsl`/`.vert`/
18
+ `.frag`) are declared as text modules out of the box, so they typecheck
19
+ without setup. Inlining trades update granularity for zero I/O - keep big or
20
+ streamable files (audio, images) in `assets/`.
21
+ - `bunx srt bundle` writes `dist/bundle/` (or `--output <dir>`):
22
+ `<name>.srt.js` plus the app's isolate modules as `isolates/<id>.js`; with
23
+ `--compile`, bytecode (`.srt.bin`/`.bin`) instead. Move the dir, not the
24
+ bare file - a bundle loaded without its isolates/ dir loses them
25
+ (`--stdout` cannot carry them at all). Isolates (`"use isolate"` modules)
26
+ exist in projects only, not for a file served on its own.
27
+ - `bunx srt bundle --json` is the dev server's rebuild contract: one JSON
28
+ object (code, sourcemap, manifest, isolates) on stdout. Not for humans.
29
+
30
+ ## Fonts, identity and distribution
31
+
32
+ - Custom fonts go in `assets/fonts/` and are declared in the `solidrt.fonts`
33
+ map in package.json (alias -> file path; role aliases `sans`/`serif`/`mono`
34
+ replace the built-in defaults, `false` drops one, other keys add fonts
35
+ selectable via fontFamily). A newly added font shows after restarting the
36
+ client.
37
+ - The `solidrt` key in package.json is the app's identity: set a stable
38
+ reverse-DNS `appId` before distributing - it keys the app's storage
39
+ folder, defaults from the package name in dev, and `srt pack` warns
40
+ while defaulted. `org` and `displayName` are optional display metadata
41
+ (future launcher/window naming) with no storage meaning.
42
+ - `bunx srt pack` builds a single-file executable (the runner with the
43
+ bytecode, manifest, assets and fonts appended as a trailer);
44
+ `bunx srt pack --folder` writes the flat app folder
45
+ (runner + manifest.json + bundle + assets/, plus the runner's GL
46
+ libraries on Windows and macOS) to `dist/pack/`.
@@ -0,0 +1,294 @@
1
+ # Debugging a running app
2
+
3
+ Read this before investigating a bug in a running app, or before driving the
4
+ app to verify a change: over MCP, or through the same control API from a
5
+ shell or a CI step ("The control API without MCP" below).
6
+
7
+ ## Driving the app over MCP
8
+
9
+ The project ships an MCP server (`srt mcp`) that talks to the dev server
10
+ `bunx srt run` starts. If your client lists no `solidrt` tools, it has not
11
+ been pointed at the server yet - see "Wiring up an agent client" below. Each
12
+ tool documents itself in full - read the tool description rather than guessing
13
+ at its arguments. What the individual descriptions cannot tell you:
14
+
15
+ - If `list_clients` is empty, no app is running: ask the user to start
16
+ `bunx srt run` rather than starting a second one yourself.
17
+ The bridge needs no port: it resolves the server currently serving this
18
+ project, whatever `--port` it was started with, and re-resolves when
19
+ that server goes away or a different project's server takes its port.
20
+ Passing the flag to `srt mcp` pins the bridge to that one server instead.
21
+ - Several clients may be attached at once (desktop window, phone, tablet)
22
+ with different sizes, display scales and safe areas. `reload` pushes to all
23
+ of them, but `call_debug` / `send_input` / `get_snapshot` are per client
24
+ (`get_logs` takes a `client` filter), and interactive state does NOT sync
25
+ - a flow driven on one client leaves the others sitting on the initial
26
+ screen, which reads as a crash to a human holding that device. So: when
27
+ driving state via `call_debug`, send the same call to every client (or say
28
+ which client you are using); and before calling a visual change done,
29
+ snapshot each distinct form factor at least once - a layout that fits one
30
+ window can clip or overflow another.
31
+ - Measuring or testing? `mute_user_input` first: it mutes the user's own
32
+ input on every client (a stray click or keypress mid-measurement corrupts
33
+ the result); `send_input` still works. `unmute_user_input` the moment you
34
+ are done, or whenever you need the human to press something: they see an
35
+ unresponsive client meanwhile. The bridge unmutes when it exits, the
36
+ server when it stops, but neither is a reason to leave a mute on.
37
+ - Editing? `pause_watch` first: the server reloads on save, and a
38
+ half-finished save would land on the user's screens as a build error or
39
+ a broken app. Edit, `reload`, then `resume_watch`. The two holds are
40
+ separate on purpose: the mute keeps the human out while you measure, the
41
+ pause keeps your own saves out while you edit, and a human editing next
42
+ to you keeps auto-reload unless you hold it. The bridge resumes when it
43
+ exits, but do not rely on that.
44
+ - A `shader` on `<window>` runs on the finished frame past the point every
45
+ capture reads: `get_snapshot` returns the UNSHADED content (window node
46
+ included), `get_texture` has no id for the shaded layer, and
47
+ `get_gpu_resources` reports only that the pass exists. `bunx srt render` is
48
+ the only programmatic view of what a window shader produces.
49
+ - Permission prompts: agents typically ask approval per MCP tool. All of
50
+ these tools only talk to the local dev server the user started with
51
+ `bunx srt run` - nothing leaves the machine - so approving the server as a
52
+ whole is a reasonable default. If repeated prompts get in the way, do not
53
+ work around them; tell the user they can pre-approve the server in their
54
+ agent's settings (most agents have a per-server trust or allowlist setting
55
+ - in Claude Code, add "mcp__solidrt" to `permissions.allow` in
56
+ ~/.claude/settings.json to cover every solidrt project). This is the user's
57
+ call to make, once, in their own tooling.
58
+
59
+ ## Wiring up an agent client
60
+
61
+ `.mcp.json` in the project root is Claude Code's convention, and a scaffolded
62
+ app ships one. MCP standardizes the protocol, not how a client discovers
63
+ servers, so every other client reads its own file. The entry is the same
64
+ everywhere, in the client's syntax:
65
+
66
+ ```json
67
+ { "command": "bun", "args": ["node_modules/@solidrt/cli/bin/srt", "mcp"] }
68
+ ```
69
+
70
+ Where it goes (these locations move between client releases; check the
71
+ client's own docs if it does not match):
72
+
73
+ - Claude Code: `.mcp.json` in the project root, under `mcpServers`. Claude
74
+ Code asks once, per project, before it will launch a server from this
75
+ file; until that is approved the `solidrt` tools are absent.
76
+ - Cursor: `.cursor/mcp.json`, under `mcpServers`
77
+ - VS Code / Copilot: `.vscode/mcp.json`, under `servers`
78
+ - Gemini CLI: `.gemini/settings.json`, under `mcpServers`
79
+ - Codex CLI: `~/.codex/config.toml`, under `[mcp_servers.solidrt]`
80
+
81
+ Several of these also ship a command that writes the entry for you
82
+ (`claude mcp add`, `codex mcp add`); prefer it over hand-editing.
83
+
84
+ The one constraint: the bridge must run with the project as its working
85
+ directory. It finds the dev server through the nearest package.json above
86
+ its cwd, and the `args` path is relative to the project root. A per-project
87
+ file gives that for free. A user-level entry works too, but only while the
88
+ client itself was launched from the project root (or the client supports a
89
+ `cwd` field and it is set); launched from elsewhere, every tool call answers
90
+ "No dev server for ..." while one is running.
91
+
92
+ ## The dev server
93
+
94
+ What the dev server (`srt run` / `srt server`) is and what it serves.
95
+
96
+ - A dev server serves a project (started in its root: the cwd must hold the
97
+ package.json) or a single file (`srt run <file>` outside a project). Both
98
+ in one place is ambiguous, so `srt run <file>` in a project root needs
99
+ `--project` (the project, with this entry) or `--file` (the file alone).
100
+ Nothing searches upward for a package.json.
101
+ - One server per project or file. The server binds the port it had last
102
+ time, else the first free one from 34884 up, and prints it; `--port <N>`
103
+ pins it. Loopback only unless `--lan`.
104
+ - Dev state lives in `~/.solidrt/`: `servers/<key hash>/` holds each server's
105
+ `live.json` (the registry record, written by the server and removed at
106
+ exit), its remembered `port` and tunnel key; `clients/client<N>/` the
107
+ client trees. `srt client` and `srt mcp` resolve the server from the
108
+ registry by the project (or served file) at their cwd; `--port` pins one.
109
+ A record whose process died is pruned when the next server starts, and
110
+ resolution confirms a record against the server before using it.
111
+ - The server is one flux process, complete on its own:
112
+ `flux dist/server.js [file] [--project|--file] [--port N] [--lan]
113
+ [--proxy-http] [--capture f] [--tunnel] [--stats] [--client N ...] [-- args]`
114
+ (src/server/args.ts). It finds the platform binaries, bun and srt through
115
+ `SRT_PLATFORM_DIR`, `SRT_BUN` and `SRT_CLI`, which `srt` sets; started by
116
+ hand in a checkout it needs only `SRT_HOME`.
117
+ - Reload-on-save watches the bundle's inputs (every file the running
118
+ bundle was built from, dependencies included) and the `assets/` tree, not
119
+ a directory: a file the app does not import never triggers a rebuild.
120
+ While the last build failed, the source tree is watched as a whole until
121
+ a build succeeds. `POST /__control__/watch?active=false` pauses it (the
122
+ MCP `pause_watch` tool) while an agent edits; `POST /__control__/reload`
123
+ (the `reload` tool) is the agent's push.
124
+
125
+ ## The control API without MCP
126
+
127
+ Every MCP tool is a thin wrapper over the dev server's HTTP control API, so
128
+ a shell script, a CI step, or an agent with no MCP bridge can drive the app
129
+ the same way. Base: `http://127.0.0.1:<port>/__control__/`, where `<port>`
130
+ is the one `srt run` printed at startup (also in the server's
131
+ `~/.solidrt/servers/*/live.json` record). GET unless noted;
132
+ every endpoint answers JSON and an error is `{ "error": "..." }` with a
133
+ 4xx/5xx status, and every response carries `x-solidrt-project` (the key
134
+ served) and `x-solidrt-generation` (the server run) headers, so a caller can
135
+ confirm it reached the server it meant and notice a restart. Endpoints that
136
+ talk to a client take `?client=<id>` (from `/clients`); it may be omitted
137
+ when exactly one client is connected.
138
+
139
+ - `/clients` - `{ generation, key, mode, entry, projectDir, userInputMuted,
140
+ watchPaused, clients: [{ id, ... }] }`. Check `key` (the project root or
141
+ single file served) is the app you mean.
142
+ - `/logs?since=<seq>&level=<lvl>&contains=<text>&wait=<ms>&client=<id>` -
143
+ `{ entries: [{ seq, at, client, level, text, repeats? }], latest,
144
+ generation }`. Pass the previous `latest` as `since` to read only new
145
+ output; `client` keeps one client's entries (all clients without); a
146
+ changed `generation` means the server restarted and cursors and client
147
+ ids are stale.
148
+ - `/tree?query=<text>&root=<id>&depth=<n>&props=true` - `{ limit, matches:
149
+ [{ id, kind, path, x, y, width, height }] }` for a query, the nested tree
150
+ otherwise. Node ids are per client and change on reload; re-query after
151
+ `/reload`.
152
+ - `/snapshot?node=<id>` - `{ width, height, pngBase64 }`, display-scaled;
153
+ add `&format=raw` for `rgbaBase64` (RGBA8 bytes, no decoder needed for
154
+ pixel assertions), `&x=&y=&width=&height=` (all four) to crop,
155
+ `&scale=<1-8>`. Snapshot the smallest node that shows the change, not the
156
+ window root.
157
+ - `/texture?id=<textureId>` - same shape and options as `/snapshot`, at the
158
+ texture's native size (a scene or shader target behind a `<texture>` leaf).
159
+ - `/gpu?label=<text>` - the GPU resource inventory; `label` keeps only the
160
+ resources created with exactly that label (ids change on reload, labels
161
+ do not).
162
+ - `/buffer?id=<bufferId>&offset=<n>&length=<n>&as=<f32|u16|u8>` - vertex
163
+ buffer contents (default f32; reads cap at 64 KiB).
164
+ - `/stats?window=<ms>` - the performance statistics. POST
165
+ `/stats?active=true|false` switches the on-screen stats overlay instead
166
+ (the `set_stats_overlay` tool): one client with `&client=<id>`, every
167
+ client (and the setting new clients join with) without; `/clients`
168
+ reports each client's `stats`.
169
+ - `/debug` - the app's registered debug commands; POST
170
+ `/debug?name=<cmd>` with a JSON body as its args to call one.
171
+ - POST `/input` with `{ "events": [...] }` - synthetic input through the
172
+ real pipeline, same event shape as the `send_input` tool (tap real
173
+ coordinates read from `/tree`).
174
+ - POST `/clock?scale=<x>` (0 pauses) / `?step=<n>` frames while paused;
175
+ `{ scale, pendingSteps }` back. `/clients` reports each client's `timeScale`,
176
+ reset to 1 by every push.
177
+ - POST `/reload` - rebuild and push to every client; `{ ok, clients }` or
178
+ the build error.
179
+ - POST `/load` with `{ "entry": "<path>" }` - switch the entry and push it;
180
+ `{ ok, entry, clients }` or the build error. Relative paths resolve
181
+ against the project root (file mode: the served file's directory). A
182
+ project server only loads files inside its project.
183
+ - POST `/mute?active=true|false` - mute/unmute the user's own input on
184
+ every client, gamepads included (synthetic `/input` still goes through;
185
+ resize and close cannot be muted). `{ ok, active, clients }`. The mute
186
+ lifts when the server stops; unmute yourself when done.
187
+ - POST `/watch?active=true|false` - resume/pause reload-on-save (`active`
188
+ is whether it watches). `{ ok, active }`. Paused, saves push nothing
189
+ until `/reload`; changes made meanwhile are not replayed on resume. The
190
+ pause lifts when the server stops; resume yourself when done.
191
+
192
+ The loop is the same as over MCP: `/reload`, then `/logs?since=`, then
193
+ `/tree` for coordinates and `/snapshot` of the smallest relevant node.
194
+
195
+ ## Lessons that cost real time
196
+
197
+ - console.log + get_logs is your primary probe into runtime state. For state
198
+ you will want repeatedly (a pose, a mode, a counter), bind a debug key that
199
+ logs it and read it back via get_logs.
200
+ - Better than debug keys when driving the app over MCP: register debug
201
+ COMMANDS - `registerDebug(name, fn)` from `srt:dev`, invoked via the
202
+ list_debug/call_debug tools. Use them to SET UP state (jump to a level,
203
+ force a mode, seed a scenario); then the runtime-level tools take over -
204
+ set_time_scale 0 freezes the result for as many snapshots as you need,
205
+ and step_frames walks it forward deterministically. Set state, pause,
206
+ snapshot. Registrations reset on hot reload, so register at module init;
207
+ sync return values only - and note a signal you just wrote flushes on a
208
+ microtask, so returning a signal read straight after setting it returns
209
+ the OLD value.
210
+ - call_debug sets state directly, skipping focus, key routing, and
211
+ TextInput - fine for SETUP, but "the interaction works" is only shown by
212
+ the real pipeline: verify clicks, typing, and drags with send_input,
213
+ which enters events where SDL input does.
214
+ - Key events start at the focused node and bubble to the window root; with
215
+ nothing focused they go to the window root alone. So a debug key bound via
216
+ `<window onKeyDown>` always fires (unless a focused component consumes the
217
+ key with stopPropagation, as TextInput does for editing keys). `key` and
218
+ `code` are W3C KeyboardEvent values, so arrow keys arrive as "ArrowLeft"/
219
+ "ArrowRight"/"ArrowUp"/"ArrowDown" (not "Left"), alongside "Enter",
220
+ "Escape", "a".
221
+ - Idle frames skip work: shaders/pipelines only re-render when an input
222
+ changes - their own params/geometry, or a sampled texture (a data upload,
223
+ or a sampled target re-rendering; chains propagate automatically). Measure
224
+ performance while inputs are actually changing. get_snapshot works on an
225
+ idle client (it requests its own frame); a timeout means the JS thread is
226
+ busy or wedged. get_texture on a pipeline's render target reads the
227
+ current output, pending writes included, without needing a new frame.
228
+ - When a human reports a visual bug: capture a snapshot and SAY WHAT YOU SEE
229
+ in it before investigating, so you agree on the symptom. If you cannot see
230
+ the problem in the capture, say that instead of guessing.
231
+ - Snapshots are downscaled by the time you see them, so a full-window capture
232
+ cannot show you a defect a few pixels across. Whenever you hand-author
233
+ geometry - a `d-path` from raw path math, a `radius` where two shapes meet,
234
+ a stroke join - inspect it MAGNIFIED once, when you write it: get_snapshot
235
+ with a tight crop at scale 4-8 shows the actual rendered pixels enlarged,
236
+ in one call, on the real app. Verifying that a shape is in the right place
237
+ is not the same check as verifying it is drawn right - and get_render_tree
238
+ props answers the third question, whether the value you set is the value
239
+ the renderer holds.
240
+ - GPU/geometry bugs: inspect the actual GPU data FIRST - get_gpu_resources
241
+ for draw counts/uniforms/sizes, get_texture for atlas or data-texture
242
+ contents ("is this tile blank?" is a ten-second question), get_buffer for
243
+ vertex data. The pixels only tell you THAT something is wrong; the
244
+ resources tell you WHERE the data stops being right. In a one-big-pipeline
245
+ app the render tree is a single <texture> leaf and tells you nothing -
246
+ these tools are the visibility layer behind it. Only when the GPU data is
247
+ all correct (so the bug is in producing it, or in the shader), reproduce
248
+ the math CPU-side in a scratch bun script against the app's real data and
249
+ print values.
250
+ - Validate assets at load time and log anomalies (missing lumps/files,
251
+ fully-transparent composites, zero-sized images). Silent fallbacks hide
252
+ bugs for days; a one-line warning surfaces them the first run.
253
+ - After every reload the app restarts from its initial state. If reaching
254
+ the bug site takes navigation, add a dev shortcut (teleport key, noclip,
255
+ initial-state override) before iterating - the round trips add up fast.
256
+ - Clamp onFrame time deltas to [0, cap], not just capped: across a hot
257
+ reload the runtime's tick counter resets AFTER the new instance's first
258
+ frame, so the second frame computes a hugely NEGATIVE delta.
259
+ Math.min(dt, cap) lets it through, and one bad frame can corrupt anything
260
+ integrated from dt (positions fly off, accumulators go so negative they
261
+ never recover). Math.max(0, Math.min(dt, cap)) costs nothing.
262
+ - A registered onFrame is a standing request, not demand-gated: it re-requests
263
+ the next frame every time it runs, so the runtime keeps calling it - and
264
+ presents - every frame at the refresh rate until you deregister it (fps
265
+ stays at the refresh rate on an idle screen; that is the 60fps burn called
266
+ out in @solidrt/core agents/performance.md, rule 4). The upside is that a
267
+ self-running loop - a game clock, a shader driver, a stepped VM doing silent
268
+ CPU work with no console output - keeps advancing on its own: it does NOT
269
+ stall when the body changes nothing and needs no startup "prime" write.
270
+ Deregister onFrame (return its cleanup, or let onCleanup fire) whenever
271
+ there is nothing left to advance.
272
+ - Layout is incremental: a change re-solves only the dirty path, and clean
273
+ subtrees answer from a per-node cache, so long lists no longer cap layout
274
+ (a thousand-node tree relays out in well under a millisecond). If layoutMs
275
+ still grows with tree size, read the get_stats counters - a low
276
+ cacheHits/cacheGets ratio means the layout cache is being defeated, high
277
+ paraShapes means text is actually reshaping. Paint is viewport-culled:
278
+ under an `overflow="hidden"` scroller only the subtrees that can reach the
279
+ visible box are painted, so paintMs tracks what is on screen, not what is
280
+ mounted (nodesPainted in get_stats shows the count). Very long lists still
281
+ pay for the initial mount and for memory, so windowing stays sensible at
282
+ the thousands-of-rows scale.
283
+ - Remote images: createImage (and Image) dedupes repeated URLs, caches the
284
+ bytes on disk, and the runtime rate-limits concurrent asset fetches per
285
+ host - do not build your own promise cache around it. Images are fetched
286
+ with no freshness check (an already-cached URL is never re-checked), so
287
+ use versioned URLs for content that changes. Use Image's `fallback` prop
288
+ (an image source) for the broken-image case instead of catching errors
289
+ yourself.
290
+ - fetch() never caches by default and ignores server cache headers. Caching
291
+ is explicit and per call: `fetch(url, { cache: "force-cache" })` for
292
+ assets (serve from disk or fetch-and-store, no freshness),
293
+ `{ cache: "reload" }` to refresh an entry. Image/createImage already do
294
+ this for you.