@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/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@solidrt/cli",
3
- "version": "0.0.50",
3
+ "version": "0.0.52",
4
4
  "license": "MIT",
5
+ "funding": "https://github.com/sponsors/wellawaretech",
5
6
  "author": "Antoine van Wel",
6
7
  "type": "module",
7
8
  "bin": {
@@ -10,10 +11,10 @@
10
11
  "files": [
11
12
  "bin/",
12
13
  "src/",
13
- "server/",
14
- "scaffold/",
14
+ "dist/",
15
15
  "fonts/",
16
- "AGENTS.md"
16
+ "AGENTS.md",
17
+ "agents/"
17
18
  ],
18
19
  "dependencies": {
19
20
  "@babel/core": "^7.0.0",
@@ -23,22 +24,22 @@
23
24
  "@jridgewell/remapping": "^2.3.0",
24
25
  "@jridgewell/trace-mapping": "^0.3.25",
25
26
  "@modelcontextprotocol/sdk": "^1.29.0",
26
- "babel-preset-solid": "2.0.0-rc.0",
27
+ "babel-preset-solid": "2.0.0-rc.1",
27
28
  "bonjour-service": "^1.4.0",
28
29
  "qrcode-generator": "^2.0.4",
29
30
  "zod": "^4.4.3"
30
31
  },
31
32
  "optionalDependencies": {
32
- "@solidrt/darwin-arm64": "0.0.50",
33
- "@solidrt/linux-arm64-gnu": "0.0.50",
34
- "@solidrt/linux-x64-gnu": "0.0.50",
35
- "@solidrt/win32-x64-msvc": "0.0.50"
33
+ "@solidrt/darwin-arm64": "0.0.52",
34
+ "@solidrt/linux-arm64-gnu": "0.0.52",
35
+ "@solidrt/linux-x64-gnu": "0.0.52",
36
+ "@solidrt/win32-x64-msvc": "0.0.52"
36
37
  },
37
38
  "peerDependencies": {
38
39
  "typescript": "^7"
39
40
  },
40
41
  "devDependencies": {
41
- "@solidrt/flux-types": "0.0.50",
42
+ "@solidrt/flux-types": "0.0.52",
42
43
  "@types/babel__core": "^7.20.5",
43
44
  "@types/bun": "latest"
44
45
  }
@@ -0,0 +1,21 @@
1
+ # srt android
2
+
3
+ {{ usage android }}
4
+
5
+ Launches the SolidRT client installed on a connected Android device (or
6
+ emulator) over adb against the dev server: the one serving the project (or
7
+ file) in the current directory, or `--port`. A running instance is restarted
8
+ so it picks the server up. The command then waits a few seconds for the
9
+ client to appear on the server and reports its client id. The server must
10
+ run with `--lan` so the device can reach it; an emulator reaches a loopback
11
+ server through its host alias. `--device` picks one of several connected
12
+ devices by serial or unique prefix.
13
+
14
+ `--install` installs (or updates) the client first, from the
15
+ `@solidrt/android-<abi>` dev dependency matching the device's ABI; the
16
+ command says which package to add when it is missing. Without `--install`
17
+ the installed client is left as it is, and a note says when its version is
18
+ not the one that package carries - a client you built and installed
19
+ yourself is never replaced unless you ask.
20
+
21
+ adb is a system tool (Android Platform Tools) and is never bundled.
@@ -0,0 +1,286 @@
1
+ import { existsSync } from "node:fs"
2
+ import { networkInterfaces } from "node:os"
3
+ import { resolve } from "node:path"
4
+ import { androidPackageVersion, resolveApk, ANDROID_PKG_MAP } from "../lib/artifacts"
5
+ import { values, port } from "../lib/args"
6
+ import { resolveByPort, resolveFromCwd } from "../lib/registry"
7
+ import type { LiveRecord } from "../types/registry"
8
+
9
+ // `srt android`: the Android client flow, decoupled from `srt client` (a
10
+ // local process) because a device is a different thing: find it over adb and
11
+ // launch the client installed there pointed at the dev server, which is
12
+ // resolved like `srt client` does (the project at the cwd, or --port). The
13
+ // APK is only touched on --install (from the @solidrt/android-<abi> package
14
+ // matching the device's ABI), so a client built and installed by hand stays;
15
+ // without --install the command just notes when the installed version is not
16
+ // the one the project's package carries.
17
+
18
+ // Launch component of the "go" dev-client flavor (see lattice/Makefile.android).
19
+ let PACKAGE_ACTIVITY = "com.solidrt.go/com.solidrt.app.MainActivity"
20
+ let PACKAGE = "com.solidrt.go"
21
+
22
+ let sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
23
+
24
+ // adb is a system tool (Android Platform Tools), never bundled. Look on PATH
25
+ // first, then the standard SDK location.
26
+ function resolveAdb() {
27
+ let exe = process.platform === "win32" ? "adb.exe" : "adb"
28
+ let onPath = Bun.which(exe)
29
+ if (onPath) return onPath
30
+ for (let root of [process.env.ANDROID_HOME, process.env.ANDROID_SDK_ROOT]) {
31
+ if (!root) continue
32
+ let candidate = resolve(root, "platform-tools", exe)
33
+ if (existsSync(candidate)) return candidate
34
+ }
35
+ return null
36
+ }
37
+
38
+ function requireAdb() {
39
+ let path = resolveAdb()
40
+ if (path) return path
41
+ console.error("Could not find adb (Android Platform Tools).")
42
+ console.error("Install it:")
43
+ console.error(" Windows: winget install Google.PlatformTools")
44
+ console.error(" macOS: brew install android-platform-tools")
45
+ console.error(" Linux: install your distro's android-tools / adb package")
46
+ process.exit(1)
47
+ }
48
+
49
+ let ipToInt = (ip: string) => ip.split(".").reduce((acc, o) => (acc << 8) + (parseInt(o, 10) & 255), 0) >>> 0
50
+
51
+ // This host's IPv4 on the same subnet as `deviceIp`: the interface whose
52
+ // (address & netmask) matches the device's. Pure computation over what the OS
53
+ // reports (cross-platform, no routing socket, no hardcoded ranges).
54
+ function hostIpFor(deviceIp: string): string | null {
55
+ let d = ipToInt(deviceIp)
56
+ for (let addrs of Object.values(networkInterfaces())) {
57
+ for (let a of addrs ?? []) {
58
+ if (a.family === "IPv4" && !a.internal && (ipToInt(a.address) & ipToInt(a.netmask)) === (d & ipToInt(a.netmask))) {
59
+ return a.address
60
+ }
61
+ }
62
+ }
63
+ return null
64
+ }
65
+
66
+ // The device's own IPv4 on its primary network: from the serial for wireless adb
67
+ // (ip:port), else queried over adb. `ip route get` reports the source IP of the
68
+ // route to a public address, i.e. the device's main interface IP.
69
+ function deviceIp(adb: string, target: string): string | null {
70
+ let m = target.match(/^(\d+\.\d+\.\d+\.\d+):\d+$/)
71
+ if (m) return m[1] ?? null
72
+ let res = Bun.spawnSync([adb, "-s", target, "shell", "ip", "route", "get", "1.1.1.1"], {
73
+ stdout: "pipe",
74
+ stderr: "pipe",
75
+ })
76
+ return res.stdout.toString().match(/src (\d+\.\d+\.\d+\.\d+)/)?.[1] ?? null
77
+ }
78
+
79
+ // The host:port the client on `target` should dial to reach this machine's dev
80
+ // server, by adb transport: the emulator reaches the host through its NAT alias
81
+ // 10.0.2.2; every other transport shares a LAN with the host, so match the
82
+ // device's IP to the host interface on its subnet. Replaces the old adb-reverse
83
+ // loopback tunnel, which never worked over wireless adb. Returns null when the
84
+ // address cannot be resolved (the client then falls back to QR/recents).
85
+ function devServerAddress(adb: string, target: string, server: LiveRecord): string | null {
86
+ // The emulator's host alias reaches the host's loopback, so a loopback-only
87
+ // server is fine there; a real device needs a server started with --lan.
88
+ if (target.startsWith("emulator-")) return `10.0.2.2:${server.port}`
89
+ if (server.address === "127.0.0.1") {
90
+ console.log("[cli] The dev server is loopback-only; restart it with --lan so the device can reach it")
91
+ return null
92
+ }
93
+ let dip = deviceIp(adb, target)
94
+ if (!dip) return null
95
+ let host = hostIpFor(dip)
96
+ return host ? `${host}:${server.port}` : null
97
+ }
98
+
99
+ // The dev server the device should dial: --port picks a local server by
100
+ // port, otherwise the project (or file) in the current directory.
101
+ async function resolveServer(): Promise<LiveRecord> {
102
+ let resolved = port !== undefined ? await resolveByPort(port) : await resolveFromCwd(process.cwd())
103
+ if (!resolved.ok) {
104
+ console.error(resolved.message)
105
+ process.exit(1)
106
+ }
107
+ return resolved.record
108
+ }
109
+
110
+ // Serials of connected, authorized devices (excludes offline/unauthorized).
111
+ function listDevices(adb: string): string[] {
112
+ let listed = Bun.spawnSync([adb, "devices"], { stdout: "pipe", stderr: "pipe" })
113
+ return listed.stdout
114
+ .toString()
115
+ .split("\n")
116
+ .slice(1)
117
+ .map((l) => l.trim())
118
+ .filter(Boolean)
119
+ .filter((l) => l.endsWith("\tdevice"))
120
+ .map((l) => l.split("\t")[0])
121
+ .filter((s): s is string => Boolean(s))
122
+ }
123
+
124
+ // Primary ABI of the connected device (e.g. "arm64-v8a", "armeabi-v7a"), used
125
+ // to pick the matching APK -- a fat (multi-ABI) APK reports its 64-bit ABI
126
+ // here and still installs fine, so this only matters for single-ABI builds.
127
+ function deviceAbi(adb: string, target: string): string {
128
+ let res = Bun.spawnSync([adb, "-s", target, "shell", "getprop", "ro.product.cpu.abi"], {
129
+ stdout: "pipe",
130
+ stderr: "pipe",
131
+ })
132
+ return res.stdout.toString().trim()
133
+ }
134
+
135
+ // Always shown up front, regardless of how many devices are connected or
136
+ // whether --device narrows it down: one line per device with the ABI its APK
137
+ // build would target, then (with --install) a dev-dependency hint for each
138
+ // ABI that isn't resolvable locally yet. Returns the device->abi map so
139
+ // callers don't have to re-query it.
140
+ function printDeviceStatus(adb: string, devices: string[]): Map<string, string> {
141
+ let abiByDevice = new Map<string, string>()
142
+ let missingAbis = new Set<string>()
143
+ for (let d of devices) {
144
+ let abi = deviceAbi(adb, d)
145
+ abiByDevice.set(d, abi)
146
+ console.log(`${d} - ${abi}`)
147
+ if (!resolveApk(abi)) missingAbis.add(abi)
148
+ }
149
+ let missingPkgs = [...missingAbis].map((abi) => ANDROID_PKG_MAP[abi]).filter((pkg): pkg is string => Boolean(pkg))
150
+ if (values.install && missingPkgs.length > 0) {
151
+ console.log(`Add dev dependencies with: bun add -d ${missingPkgs.join(" ")}`)
152
+ }
153
+ return abiByDevice
154
+ }
155
+
156
+ // The versionName of the client installed on `target`, null when none is.
157
+ function installedVersion(adb: string, target: string): string | null {
158
+ let res = Bun.spawnSync([adb, "-s", target, "shell", "dumpsys", "package", PACKAGE], { stdout: "pipe", stderr: "pipe" })
159
+ return res.stdout.toString().match(/versionName=(\S+)/)?.[1] ?? null
160
+ }
161
+
162
+ type Client = { id: number; platform: string; version: string }
163
+
164
+ // The clients the dev server currently lists (empty when it cannot be asked).
165
+ async function connectedClients(server: LiveRecord): Promise<Client[]> {
166
+ try {
167
+ let resp = await fetch(`http://127.0.0.1:${server.port}/__control__/clients`, { signal: AbortSignal.timeout(1000) })
168
+ return ((await resp.json()) as { clients?: Client[] }).clients ?? []
169
+ } catch {
170
+ return []
171
+ }
172
+ }
173
+
174
+ // The first Android client that appears beyond `before`, or null after ~10 s.
175
+ async function waitForClient(server: LiveRecord, before: Set<number>): Promise<Client | null> {
176
+ for (let attempt = 0; attempt < 20; attempt++) {
177
+ await sleep(500)
178
+ let fresh = (await connectedClients(server)).find((c) => c.platform === "android" && !before.has(c.id))
179
+ if (fresh) return fresh
180
+ }
181
+ return null
182
+ }
183
+
184
+ // Resolve the target device serial (and its ABI). With --device, treat the
185
+ // value as a serial prefix and require it to match exactly one connected
186
+ // device; without it, use the sole connected device. Exits with a clear
187
+ // message on any ambiguity.
188
+ function resolveTarget(adb: string): { target: string; abi: string } {
189
+ let devices = listDevices(adb)
190
+ let abiByDevice = printDeviceStatus(adb, devices)
191
+
192
+ if (values.device) {
193
+ let prefix = values.device
194
+ let matches = devices.filter((d) => d.startsWith(prefix))
195
+ if (matches.length > 1) {
196
+ console.error(`--device "${prefix}" is ambiguous; matches: ${matches.join(", ")}`)
197
+ process.exit(1)
198
+ }
199
+ let [match] = matches
200
+ if (!match) {
201
+ console.error(`No connected device matches --device "${prefix}".`)
202
+ process.exit(1)
203
+ }
204
+ return { target: match, abi: abiByDevice.get(match)! }
205
+ }
206
+
207
+ if (devices.length > 1) {
208
+ console.error("Pick one with --device <serial or prefix>.")
209
+ process.exit(1)
210
+ }
211
+ let [only] = devices
212
+ if (!only) {
213
+ console.error("No authorized Android device found. Enable USB debugging and check `adb devices`.")
214
+ process.exit(1)
215
+ }
216
+ return { target: only, abi: abiByDevice.get(only)! }
217
+ }
218
+
219
+ // Launch the Android client on a connected device over adb (installing it
220
+ // first on --install), passing it the dev-server address to dial as a
221
+ // launch-intent extra (see devServerAddress), then wait briefly for it to show
222
+ // up on the dev server. The client is not a child process here: its lifecycle
223
+ // is the WS connect/disconnect the server sees.
224
+ export async function main() {
225
+ let server = await resolveServer()
226
+ let adb = requireAdb()
227
+
228
+ let { target, abi } = resolveTarget(adb)
229
+
230
+ if (values.install) {
231
+ let apk = resolveApk(abi)
232
+ if (!apk) {
233
+ console.error(`Could not find a SolidRT-Go APK for ABI "${abi}".`)
234
+ let pkg = ANDROID_PKG_MAP[abi]
235
+ if (pkg) console.error(`Add it with: bun add -d ${pkg}`)
236
+ process.exit(1)
237
+ }
238
+ console.log(`[cli] Installing SolidRT-Go on ${target}`)
239
+ let install = Bun.spawn([adb, "-s", target, "install", "-r", apk], { stdout: "pipe", stderr: "pipe" })
240
+ if ((await install.exited) !== 0) {
241
+ console.error("adb install failed:\n" + (await new Response(install.stderr).text()))
242
+ process.exit(1)
243
+ }
244
+ } else {
245
+ let installed = installedVersion(adb, target)
246
+ if (installed === null) {
247
+ console.error(`No SolidRT-Go client on ${target}; install one with srt android --install`)
248
+ process.exit(1)
249
+ }
250
+ let expected = androidPackageVersion(abi)
251
+ if (expected !== null && expected !== installed) {
252
+ console.log(
253
+ `[cli] Installed client is ${installed}; the project's ${ANDROID_PKG_MAP[abi]} is ${expected} (srt android --install updates it)`,
254
+ )
255
+ }
256
+ }
257
+
258
+ // Hand the client the dev-server address to dial, as a launch-intent extra that
259
+ // MainActivity forwards to native argv (--dev-server); the client auto-connects
260
+ // to it. Replaces adb reverse, which never worked over wireless adb. -S stops
261
+ // a running instance first: a delivered intent does not reach one, so without
262
+ // it the client would keep whatever server it had.
263
+ let before = new Set((await connectedClients(server)).map((c) => c.id))
264
+ let devServer = devServerAddress(adb, target, server)
265
+ let launchArgs = [adb, "-s", target, "shell", "am", "start", "-S", "-n", PACKAGE_ACTIVITY]
266
+ if (devServer) {
267
+ console.log(`[cli] Client will dial dev server at ${devServer}`)
268
+ launchArgs.push("--es", "srt_dev_server", devServer)
269
+ } else {
270
+ console.log("[cli] Could not resolve a host address for the device; client will need a manual/QR connect")
271
+ }
272
+
273
+ let start = Bun.spawn(launchArgs, { stdout: "pipe", stderr: "pipe" })
274
+ if ((await start.exited) !== 0) {
275
+ console.error("adb start failed:\n" + (await new Response(start.stderr).text()))
276
+ process.exit(1)
277
+ }
278
+
279
+ console.log(`[cli] Launched SolidRT-Go on ${target}; waiting for it to connect to the dev server...`)
280
+ let client = await waitForClient(server, before)
281
+ if (client) {
282
+ console.log(`[cli] Client ${client.id} connected (${client.platform}, ${client.version})`)
283
+ } else {
284
+ console.log(`[cli] No connection after 10 s. The server must run with --lan and the device must reach this machine.`)
285
+ }
286
+ }