@solidrt/cli 0.0.51 → 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 (93) hide show
  1. package/AGENTS.md +68 -38
  2. package/README.md +86 -15
  3. package/agents/assets.md +19 -5
  4. package/agents/debugging.md +169 -17
  5. package/dist/console.srtapp +94123 -73
  6. package/dist/server.js +3936 -0
  7. package/package.json +10 -11
  8. package/src/android/docs.md +21 -0
  9. package/src/android/main.ts +286 -0
  10. package/src/{bundler.ts → bundle/bundler.ts} +67 -55
  11. package/src/bundle/docs.md +12 -0
  12. package/src/{commands/bundle.ts → bundle/main.ts} +67 -25
  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 -41
  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} +21 -15
  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/{commands/pack.ts → pack/main.ts} +28 -17
  44. package/src/{packer.ts → pack/trailer.ts} +32 -42
  45. package/src/render/docs.md +19 -0
  46. package/src/{commands/render.ts → render/main.ts} +17 -15
  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 +239 -81
  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/repl.ts +223 -0
  58. package/src/server/state.ts +54 -0
  59. package/{server → src/server}/tsconfig.json +1 -1
  60. package/{server → src/server}/tunnel.ts +6 -6
  61. package/src/server/watcher.ts +121 -0
  62. package/src/tool/main.ts +70 -0
  63. package/src/types/bundle.d.ts +24 -0
  64. package/src/types/control.d.ts +90 -0
  65. package/src/types/registry.d.ts +16 -0
  66. package/scaffold/AGENTS.md +0 -185
  67. package/scaffold/package.json +0 -22
  68. package/scaffold/templates/components/icon.tsx +0 -48
  69. package/scaffold/templates/default/icon.tsx +0 -48
  70. package/server/main.ts +0 -308
  71. package/server/rebuild.ts +0 -76
  72. package/server/state.ts +0 -94
  73. package/src/args.ts +0 -210
  74. package/src/bundle-cli.ts +0 -13
  75. package/src/commands/client.ts +0 -34
  76. package/src/commands/mcp.ts +0 -617
  77. package/src/commands/server.ts +0 -73
  78. package/src/dev-android.ts +0 -176
  79. package/src/dev-client.ts +0 -29
  80. package/src/dev-server.ts +0 -302
  81. package/src/repl.ts +0 -249
  82. package/src/util.ts +0 -122
  83. package/src/watcher.ts +0 -73
  84. /package/src/{untyped-deps.d.ts → bundle/untyped-deps.d.ts} +0 -0
  85. /package/src/{prompt.ts → init/prompt.ts} +0 -0
  86. /package/{scaffold → src/init/scaffold}/gitignore +0 -0
  87. /package/{scaffold → src/init/scaffold}/icon.svg +0 -0
  88. /package/{scaffold → src/init/scaffold}/mcp.json +0 -0
  89. /package/{scaffold → src/init/scaffold}/tsconfig.json +0 -0
  90. /package/{server → src/server}/cache.ts +0 -0
  91. /package/{server → src/server}/proxy.ts +0 -0
  92. /package/{server → src/server}/qr.ts +0 -0
  93. /package/{server → src/server}/remap.ts +0 -0
package/src/repl.ts DELETED
@@ -1,249 +0,0 @@
1
- import { createInterface } from "node:readline"
2
- import { resolve, dirname } from "path"
3
- import { readdirSync } from "node:fs"
4
- import { state, print, printErr, shutdown } from "./util"
5
- import { buildReload, getClients, sendReload, sendStop, sendStats, sendWatch, showBuildFailure } from "./dev-server"
6
- import { bundle, bundleMaps, prebuiltManifest } from "./bundler"
7
- import { startWatcher, stopWatcher } from "./watcher"
8
-
9
- // Resolve repl client indexes ("0 2") against the server's client list,
10
- // printing a complaint for each invalid token. The list order is connect
11
- // order, matching what `list` shows.
12
- async function indexesToIds(args: string): Promise<number[]> {
13
- let clients = await getClients()
14
- let ids: number[] = []
15
- for (let token of args.split(/\s+/)) {
16
- let idx = parseInt(token, 10)
17
- if (isNaN(idx) || idx < 0 || idx >= clients.length) {
18
- print(`Invalid client index: ${token}`)
19
- continue
20
- }
21
- ids.push(clients[idx]!.id)
22
- }
23
- return ids
24
- }
25
-
26
- async function cmdStop(args: string) {
27
- if (!args) {
28
- stopWatcher()
29
- state.currentCode = null
30
- state.currentMaps = null
31
- state.currentManifest = null
32
- state.source = undefined
33
- await sendStop()
34
- print("[cli] Sent stop to all clients")
35
- return
36
- }
37
- let ids = await indexesToIds(args)
38
- if (ids.length) {
39
- await sendStop(ids)
40
- print(`[cli] Sent stop to client(s) ${ids.join(", ")}`)
41
- }
42
- }
43
-
44
- async function cmdReload(args: string) {
45
- if (state.source && state.source.endsWith(".tsx")) {
46
- let result = await bundle(state.source)
47
- if (!result) {
48
- printErr("[cli] Build failed, reload aborted")
49
- await showBuildFailure()
50
- return
51
- }
52
- state.currentCode = result.code
53
- state.currentMaps = bundleMaps(result)
54
- state.currentManifest = result.manifest
55
- }
56
- let msg = buildReload({ code: state.currentCode, manifest: state.currentManifest })
57
- if (!args) {
58
- await sendReload(msg, { latch: true, maps: state.currentMaps })
59
- print("[cli] Sent reload to all clients")
60
- return
61
- }
62
- let ids = await indexesToIds(args)
63
- if (ids.length) {
64
- await sendReload(msg, { clients: ids, maps: state.currentMaps })
65
- print(`[cli] Sent reload to client(s) ${ids.join(", ")}`)
66
- }
67
- }
68
-
69
- async function cmdStats(args: string) {
70
- if (args === "on") {
71
- state.stats = true
72
- } else if (args === "off") {
73
- state.stats = false
74
- } else if (!args) {
75
- state.stats = !state.stats
76
- } else {
77
- print("Usage: stats [on|off]")
78
- return
79
- }
80
- await sendStats(state.stats)
81
- print(`[cli] Stats overlay ${state.stats ? "on" : "off"}`)
82
- }
83
-
84
- async function cmdList() {
85
- let clients = await getClients()
86
- if (clients.length === 0) {
87
- print("No connected clients")
88
- return
89
- }
90
- print(`${clients.length} connected client(s):`)
91
- let i = 0
92
- for (let c of clients) {
93
- print(` ${i++}: ${c.address ?? "unknown"} [${c.platform}, ${c.version}]`)
94
- }
95
- }
96
-
97
- async function cmdLoad(file: string) {
98
- if (!file) {
99
- print("Usage: load <file.tsx|.srt.js|.srt.bin>")
100
- return
101
- }
102
- let path = resolve(file)
103
- // Same rule as /__control__/load (control.ts): a server run serves the
104
- // project it started in, and an entry outside the project root cannot
105
- // resolve the project's dependencies anyway.
106
- // Windows paths are case-insensitive and the same drive shows up as both
107
- // `c:` and `C:` (an editor-spawned bridge keeps its parent's spelling), so a
108
- // drive-letter path folds case; a POSIX path stays exact.
109
- let norm = (p: string) => {
110
- let s = p.replace(/\\/g, "/")
111
- return /^[a-zA-Z]:\//.test(s) ? s.toLowerCase() : s
112
- }
113
- let root = norm(state.projectDir).replace(/\/+$/, "") + "/"
114
- if (!norm(path).startsWith(root)) {
115
- printErr(`[cli] Entry is outside the project root: ${path} is not under ${state.projectDir}. Restart srt in that project to work on it.`)
116
- return
117
- }
118
- if (file.endsWith(".tsx")) {
119
- let result = await bundle(path)
120
- if (!result) {
121
- printErr("[cli] Build failed")
122
- return
123
- }
124
- state.currentCode = result.code
125
- state.currentMaps = bundleMaps(result)
126
- state.currentManifest = result.manifest
127
- } else if (file.endsWith(".srt.js")) {
128
- state.currentCode = await Bun.file(path).text()
129
- state.currentMaps = null
130
- state.currentManifest = prebuiltManifest(state.currentCode, path, state.projectDir)
131
- } else if (file.endsWith(".srt.bin")) {
132
- let bytes = await Bun.file(path).arrayBuffer()
133
- // One-shot: bytecode loads are pushed but not latched for late joiners.
134
- await sendReload(buildReload({ bytecode: Buffer.from(bytes).toString("base64") }))
135
- print(`[cli] Loaded ${file} (bytecode, ${bytes.byteLength} bytes)`)
136
- return
137
- } else {
138
- print("Unsupported file type. Use .tsx, .srt.js, or .srt.bin")
139
- return
140
- }
141
- state.source = path
142
- state.sourceDir = dirname(path)
143
- startWatcher()
144
- // The load also moves the server's file-serving root to the new source dir
145
- // and its rebuild entry to the new file (for a later MCP reload). The
146
- // project root - and with it the /assets/ root - is fixed for the life of
147
- // the run.
148
- await sendReload(buildReload({ code: state.currentCode, manifest: state.currentManifest }), {
149
- latch: true,
150
- sourceDir: state.sourceDir,
151
- entry: file.endsWith(".tsx") ? path : undefined,
152
- maps: state.currentMaps,
153
- })
154
- print(`[cli] Loaded ${file}`)
155
- }
156
-
157
- let COMMANDS = ["load ", "stop", "reload", "list", "stats", "watch ", "quit", "exit", "help"]
158
- let LOAD_EXTENSIONS = [".tsx", ".srt.js", ".srt.bin"]
159
-
160
- function completer(line: string): [string[], string] {
161
- if (line.startsWith("load ")) {
162
- let partial = line.slice(5)
163
- let dir = partial.includes("/") ? partial.slice(0, partial.lastIndexOf("/") + 1) : ""
164
- let prefix = partial.slice(dir.length)
165
- let absDir = resolve(dir || ".")
166
- try {
167
- let entries = readdirSync(absDir, { withFileTypes: true })
168
- let matches: string[] = []
169
- for (let entry of entries) {
170
- if (!entry.name.startsWith(prefix)) continue
171
- if (entry.isDirectory()) {
172
- matches.push(entry.name + "/")
173
- } else if (LOAD_EXTENSIONS.some((ext) => entry.name.endsWith(ext))) {
174
- matches.push(entry.name)
175
- }
176
- }
177
- return [matches, prefix]
178
- } catch {
179
- return [[], line]
180
- }
181
- }
182
- let matches = COMMANDS.filter((c) => c.startsWith(line))
183
- return [matches, line]
184
- }
185
-
186
- // Run a repl command, reporting a failed server round-trip instead of leaving
187
- // an unhandled rejection (e.g. the server process died mid-command).
188
- function guard(p: Promise<void>) {
189
- p.catch((e) => printErr(`[cli] ${String(e)}`))
190
- }
191
-
192
- export function startRepl() {
193
- // Without a terminal there is nobody to prompt, and stdin is at EOF from the
194
- // start: readline would fire `close` immediately and shutdown() would tear
195
- // down the server, the client and the registry record about a second after
196
- // boot. A backgrounded or supervisor-launched srt therefore runs with no
197
- // repl at all, kept alive by the server process and the watcher, and stopped
198
- // with a signal. See okf/backlog/srt-run-exits-on-stdin-eof.md.
199
- if (!process.stdin.isTTY) {
200
- print("[cli] No terminal on stdin, running without the repl")
201
- return
202
- }
203
-
204
- state.rl = createInterface({ input: process.stdin, output: process.stdout, completer })
205
- state.rl.setPrompt("srt> ")
206
-
207
- state.rl.on("close", shutdown)
208
-
209
- state.rl.on("line", (line) => {
210
- let cmd = line.trim()
211
- if (cmd === "stop" || cmd.startsWith("stop ")) {
212
- guard(cmdStop(cmd.slice(5).trim()))
213
- } else if (cmd === "reload" || cmd.startsWith("reload ")) {
214
- guard(cmdReload(cmd.slice(7).trim()))
215
- } else if (cmd.startsWith("load ")) {
216
- guard(cmdLoad(cmd.slice(5).trim()))
217
- } else if (cmd === "list") {
218
- guard(cmdList())
219
- } else if (cmd === "stats" || cmd.startsWith("stats ")) {
220
- guard(cmdStats(cmd.slice(6).trim()))
221
- } else if (cmd === "watch on" || cmd === "watch off") {
222
- // Manual override for the agent-latched auto-reload pause (an agent
223
- // that died mid-edit leaves it off; its reload normally restores it).
224
- let enabled = cmd === "watch on"
225
- guard(sendWatch(enabled).then(() => print(`[cli] Auto-reload on change ${enabled ? "on" : "off"}`)))
226
- } else if (cmd === "quit" || cmd === "exit") {
227
- shutdown()
228
- } else if (cmd.startsWith("!")) {
229
- let shell = cmd.slice(1)
230
- if (shell) {
231
- Bun.$`${{ raw: shell }}`.quiet().then(
232
- (r) => {
233
- if (r.stdout.length) print(r.text())
234
- },
235
- (e) => {
236
- printErr(e.stderr.toString())
237
- },
238
- )
239
- }
240
- } else if (cmd === "help") {
241
- print("Commands: load, stop, reload, list, stats, watch on|off, !<cmd>, quit, help")
242
- } else if (cmd) {
243
- print(`Unknown command: ${cmd}`)
244
- }
245
- state.rl!.prompt()
246
- })
247
-
248
- state.rl.prompt()
249
- }
package/src/util.ts DELETED
@@ -1,122 +0,0 @@
1
- import { resolveBinary } from "./artifacts"
2
- import { existsSync } from "node:fs"
3
- import { resolve } from "node:path"
4
- import type { Interface as ReadlineInterface } from "node:readline"
5
- // import type { Bonjour } from "bonjour-service" (mDNS advertise, kept - see dev-server.ts)
6
-
7
- export let state = {
8
- // What srt believes the current bundle is; the server process keeps its own
9
- // latched copy for late-joining clients (see packages/cli/server/).
10
- currentCode: null as string | null,
11
- // The bundle's composed sourcemaps (dev builds), keyed by module name
12
- // ("main", each isolate id), sent to the server alongside reloads so it can
13
- // remap logged stack traces to .tsx positions.
14
- currentMaps: null as Record<string, string> | null,
15
- // The bundle's version manifest (canonical JSON string, see buildManifest);
16
- // travels with code reloads so clients install the push as a version.
17
- currentManifest: null as string | null,
18
- source: undefined as string | undefined,
19
- sourceDir: process.cwd(),
20
- // The project root the assets/ convention hangs off (see project.ts
21
- // projectDirFor); the server's /assets/ route serves from under it.
22
- projectDir: process.cwd(),
23
- child: null as ReturnType<typeof Bun.spawn> | null,
24
- // The spawned flux dev-server process (see dev-server.ts startServer).
25
- serverProc: null as ReturnType<typeof Bun.spawn> | null,
26
- shuttingDown: false,
27
- serverUrl: null as string | null,
28
- rl: null as ReadlineInterface | null,
29
- // bonjour: null as Bonjour | null, (mDNS advertise, kept - see dev-server.ts)
30
- stats: false,
31
- // --capture <file>: destination for captured key events, or undefined when
32
- // off; the server process owns the capture file and clock (see
33
- // packages/cli/server/main.ts's "capture" message handling).
34
- capture: undefined as string | undefined,
35
- }
36
-
37
- // Build target per binary, for the "not found" hint. Run from the repo root.
38
- let BUILD_HINTS: Record<string, string> = {
39
- "solidrt-go": "make client",
40
- solidrt: "make runtime",
41
- flux: "make -C flux flux",
42
- fluxc: "make -C flux fluxc",
43
- fluxrt: "make -C flux fluxrt PROFILE=release-opt",
44
- }
45
-
46
- export function requireBinary(name: string) {
47
- let path = resolveBinary(name)
48
- if (path) return path
49
- let hint = BUILD_HINTS[name]
50
- console.error(`Could not find ${name} binary.`)
51
- if (hint) {
52
- console.error(`Build it from source: run ${hint}, with SRT_HOME pointing at your SolidRT checkout.`)
53
- } else {
54
- console.error("Build it from source, with SRT_HOME pointing at your SolidRT checkout.")
55
- }
56
- process.exit(1)
57
- }
58
-
59
- // adb is a system tool (Android Platform Tools), never bundled. Look on PATH
60
- // first, then the standard SDK location.
61
- export function resolveAdb() {
62
- let exe = process.platform === "win32" ? "adb.exe" : "adb"
63
- let onPath = Bun.which(exe)
64
- if (onPath) return onPath
65
- for (let root of [process.env.ANDROID_HOME, process.env.ANDROID_SDK_ROOT]) {
66
- if (!root) continue
67
- let candidate = resolve(root, "platform-tools", exe)
68
- if (existsSync(candidate)) return candidate
69
- }
70
- return null
71
- }
72
-
73
- export function requireAdb() {
74
- let path = resolveAdb()
75
- if (path) return path
76
- console.error("Could not find adb (Android Platform Tools).")
77
- console.error("Install it:")
78
- console.error(" Windows: winget install Google.PlatformTools")
79
- console.error(" macOS: brew install android-platform-tools")
80
- console.error(" Linux: install your distro's android-tools / adb package")
81
- process.exit(1)
82
- }
83
-
84
- export async function run(binary: string, args: string[]) {
85
- let proc = Bun.spawn([binary, ...args], { stdio: ["inherit", "inherit", "inherit"] })
86
- return proc.exited
87
- }
88
-
89
- export function print(...args: any[]) {
90
- process.stdout.write("\r\x1b[K")
91
- console.log(...args)
92
- state.rl?.prompt(true)
93
- }
94
-
95
- export function printErr(...args: any[]) {
96
- process.stdout.write("\r\x1b[K")
97
- console.error(...args)
98
- state.rl?.prompt(true)
99
- }
100
-
101
- // Pipe a child stream to `out` without mangling the repl prompt: clear the
102
- // prompt line, write the chunk, redraw the prompt.
103
- export function pipeAbovePrompt(stream: ReadableStream<Uint8Array>, out: NodeJS.WriteStream) {
104
- let reader = stream.getReader()
105
- ;(async () => {
106
- while (true) {
107
- let { done, value } = await reader.read()
108
- if (done || !value) break
109
- process.stdout.write("\r\x1b[K")
110
- out.write(value)
111
- state.rl?.prompt(true)
112
- }
113
- })()
114
- }
115
-
116
- export function shutdown() {
117
- state.shuttingDown = true
118
- if (state.child) state.child.kill()
119
- if (state.serverProc) state.serverProc.kill()
120
- // if (state.bonjour) state.bonjour.destroy() (mDNS advertise, kept - see dev-server.ts)
121
- process.exit(0)
122
- }
package/src/watcher.ts DELETED
@@ -1,73 +0,0 @@
1
- import { existsSync, watch } from "node:fs"
2
- import { resolve, dirname, sep, basename } from "path"
3
- import { state, print, printErr } from "./util"
4
- import { buildReload, sendReload, showBuildFailure, watchAllowed } from "./dev-server"
5
- import { bundle, bundleMaps } from "./bundler"
6
-
7
- let watchers: ReturnType<typeof watch>[] = []
8
-
9
- export function stopWatcher() {
10
- for (let w of watchers) w.close()
11
- watchers = []
12
- }
13
-
14
- async function rebuild(filename: string) {
15
- if (!(await watchAllowed())) {
16
- print(`[cli] Change detected: ${filename} (auto-reload paused by agent; "watch on" resumes)`)
17
- return
18
- }
19
-
20
- print(`[cli] Change detected: ${filename}`)
21
- let result = await bundle(state.source)
22
- if (!result) {
23
- printErr("[cli] Build failed, waiting for changes...")
24
- await showBuildFailure()
25
- return
26
- }
27
- state.currentCode = result.code
28
- state.currentMaps = bundleMaps(result)
29
- state.currentManifest = result.manifest
30
- await sendReload(buildReload({ code: state.currentCode, manifest: state.currentManifest }), {
31
- latch: true,
32
- maps: state.currentMaps,
33
- })
34
- }
35
-
36
- export function startWatcher() {
37
- if (!state.source) return
38
- let watchDir = dirname(resolve(state.source))
39
-
40
- stopWatcher()
41
-
42
- // An asset edit re-pushes too: the assets/ tree is part of the manifest, so
43
- // clients install the new version. The tree roots at the project dir; the
44
- // project dir is an ancestor of (or equal to) the entry's dir, so assets/
45
- // is inside watchDir exactly when they are the same dir. An assets/ folder
46
- // created after startup needs a restart or `load` to be picked up.
47
- let assetsDir = resolve(state.projectDir, "assets")
48
- let covered = resolve(state.projectDir) === watchDir
49
- let isAsset = (filename: string) =>
50
- (filename === "assets" || filename.startsWith("assets" + sep)) && !basename(filename).startsWith(".")
51
-
52
- print(`[cli] Watching ${watchDir} for changes...`)
53
- watchers.push(
54
- watch(watchDir, { recursive: true }, (_event, filename) => {
55
- if (!filename) return
56
- // Dot directories are never sources: .srt-data in particular is this
57
- // server's own output (isolate bundles are .js files in there), and
58
- // rebuilding on it would rebuild forever.
59
- if (filename.split(sep).some((part, i, parts) => i < parts.length - 1 && part.startsWith("."))) return
60
- if (!/\.(tsx?|jsx?)$/.test(filename) && !(covered && isAsset(filename))) return
61
- rebuild(filename)
62
- }),
63
- )
64
-
65
- if (!covered && existsSync(assetsDir)) {
66
- watchers.push(
67
- watch(assetsDir, { recursive: true }, (_event, filename) => {
68
- if (!filename || basename(filename).startsWith(".")) return
69
- rebuild("assets" + sep + filename)
70
- }),
71
- )
72
- }
73
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes