@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.
- package/AGENTS.md +79 -36
- package/README.md +86 -15
- package/agents/assets.md +46 -0
- package/agents/debugging.md +294 -0
- package/dist/console.srtapp +94123 -73
- package/dist/server.js +3936 -0
- package/package.json +11 -10
- package/src/android/docs.md +21 -0
- package/src/android/main.ts +286 -0
- package/src/{bundler.ts → bundle/bundler.ts} +163 -76
- package/src/bundle/docs.md +12 -0
- package/src/bundle/main.ts +203 -0
- package/src/check/docs.md +10 -0
- package/src/check/main.ts +85 -0
- package/src/{commands/check.ts → check/typecheck.ts} +11 -34
- package/src/client/docs.md +9 -0
- package/src/client/main.ts +33 -0
- package/src/console/docs.md +14 -0
- package/src/console/main.ts +21 -0
- package/src/demo/docs.md +27 -0
- package/src/demo/main.ts +67 -0
- package/src/init/docs.md +11 -0
- package/src/{commands/init.ts → init/main.ts} +31 -25
- package/src/init/scaffold/AGENTS.md +98 -0
- package/src/init/scaffold/package.json +23 -0
- package/{scaffold → src/init/scaffold}/templates/components/index.tsx +2 -3
- package/{scaffold → src/init/scaffold}/templates/default/index.tsx +2 -3
- package/src/lib/args.ts +194 -0
- package/src/{artifacts.ts → lib/artifacts.ts} +41 -1
- package/src/{dev-dir.ts → lib/dev-dir.ts} +10 -8
- package/src/{fonts.ts → lib/fonts.ts} +12 -26
- package/src/lib/mode.ts +77 -0
- package/src/{project.ts → lib/project.ts} +109 -61
- package/src/lib/registry.ts +120 -0
- package/src/lib/server-bundle.ts +24 -0
- package/src/lib/usage.ts +117 -0
- package/src/lib/util.ts +36 -0
- package/src/main.ts +109 -31
- package/src/mcp/docs.md +22 -0
- package/src/mcp/main.ts +719 -0
- package/src/pack/docs.md +18 -0
- package/src/{pack-folder.ts → pack/layout.ts} +13 -22
- package/src/pack/main.ts +86 -0
- package/src/pack/trailer.ts +97 -0
- package/src/render/docs.md +19 -0
- package/src/render/main.ts +49 -0
- package/src/server/args.ts +126 -0
- package/src/server/binaries.ts +47 -0
- package/src/server/config.ts +54 -0
- package/{server → src/server}/control.ts +246 -78
- package/src/server/docs.md +51 -0
- package/src/server/line-editor.ts +200 -0
- package/src/server/main.ts +473 -0
- package/src/server/mode.ts +92 -0
- package/src/server/rebuild.ts +90 -0
- package/src/server/registry.ts +138 -0
- package/src/server/remap.ts +60 -0
- package/src/server/repl.ts +223 -0
- package/src/server/state.ts +54 -0
- package/{server → src/server}/tsconfig.json +1 -1
- package/{server → src/server}/tunnel.ts +6 -6
- package/src/server/watcher.ts +121 -0
- package/src/tool/main.ts +70 -0
- package/src/types/bundle.d.ts +24 -0
- package/src/types/control.d.ts +90 -0
- package/src/types/registry.d.ts +16 -0
- package/scaffold/AGENTS.md +0 -630
- package/scaffold/package.json +0 -22
- package/scaffold/templates/components/icon.tsx +0 -48
- package/scaffold/templates/default/icon.tsx +0 -48
- package/server/main.ts +0 -308
- package/server/rebuild.ts +0 -68
- package/server/remap.ts +0 -47
- package/server/state.ts +0 -93
- package/src/args.ts +0 -212
- package/src/bundle-cli.ts +0 -13
- package/src/commands/bundle.ts +0 -76
- package/src/commands/client.ts +0 -34
- package/src/commands/mcp.ts +0 -603
- package/src/commands/pack.ts +0 -65
- package/src/commands/render.ts +0 -24
- package/src/commands/server.ts +0 -73
- package/src/dev-android.ts +0 -176
- package/src/dev-client.ts +0 -29
- package/src/dev-server.ts +0 -302
- package/src/packer.ts +0 -103
- package/src/repl.ts +0 -233
- package/src/util.ts +0 -121
- package/src/watcher.ts +0 -69
- /package/src/{untyped-deps.d.ts → bundle/untyped-deps.d.ts} +0 -0
- /package/src/{prompt.ts → init/prompt.ts} +0 -0
- /package/{scaffold → src/init/scaffold}/gitignore +0 -0
- /package/{scaffold → src/init/scaffold}/icon.svg +0 -0
- /package/{scaffold → src/init/scaffold}/mcp.json +0 -0
- /package/{scaffold → src/init/scaffold}/tsconfig.json +0 -0
- /package/{server → src/server}/cache.ts +0 -0
- /package/{server → src/server}/proxy.ts +0 -0
- /package/{server → src/server}/qr.ts +0 -0
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
// The animated SolidRT logo: each segment fades in, then out, on its own delay.
|
|
2
|
-
import { For, onFrame, createSignal, createLinearGradient } from "@solidrt/core"
|
|
3
|
-
|
|
4
|
-
const FADE = 360
|
|
5
|
-
|
|
6
|
-
let SEGMENTS = [
|
|
7
|
-
{ base: 0, light: "#3f5494", dark: "#162b6c", d: "M50.000 50.000 L28.330 50.000 C28.330 48.810 27.695 47.711 26.665 47.116 C25.635 46.521 24.365 46.521 23.335 47.116 C22.305 47.711 21.670 48.810 21.670 50.000 L0.000 50.000 L50.000 0.000 L50.000 9.170 C48.810 9.170 47.711 9.805 47.116 10.835 C46.521 11.865 46.521 13.135 47.116 14.165 C47.711 15.195 48.810 15.830 50.000 15.830 L50.000 25.000 L50.000 34.170 C48.810 34.170 47.711 34.805 47.116 35.835 C46.521 36.865 46.521 38.135 47.116 39.165 C47.711 40.195 48.810 40.830 50.000 40.830 L50.000 50.000 Z" },
|
|
8
|
-
{ base: 90, light: "#547ebf", dark: "#2b5696", d: "M50.000 50.000 L50.000 59.170 C48.810 59.170 47.711 59.805 47.116 60.835 C46.521 61.865 46.521 63.135 47.116 64.165 C47.711 65.195 48.810 65.830 50.000 65.830 L50.000 75.000 L50.000 84.170 C48.810 84.170 47.711 84.805 47.116 85.835 C46.521 86.865 46.521 88.135 47.116 89.165 C47.711 90.195 48.810 90.830 50.000 90.830 L50.000 100.000 L0.000 50.000 L21.670 50.000 C21.670 48.810 22.305 47.711 23.335 47.116 C24.365 46.521 25.635 46.521 26.665 47.116 C27.695 47.711 28.330 48.810 28.330 50.000 L50.000 50.000 Z" },
|
|
9
|
-
{ base: 180, light: "#7ea9ea", dark: "#5681c1", d: "M50.000 25.000 L50.000 15.830 C48.810 15.830 47.711 15.195 47.116 14.165 C46.521 13.135 46.521 11.865 47.116 10.835 C47.711 9.805 48.810 9.170 50.000 9.170 L50.000 0.000 L75.000 25.000 L65.830 25.000 C65.830 26.190 65.195 27.289 64.165 27.884 C63.135 28.479 61.865 28.479 60.835 27.884 C59.805 27.289 59.170 26.190 59.170 25.000 L50.000 25.000 Z" },
|
|
10
|
-
{ base: 270, light: "#547ebf", dark: "#2b5696", d: "M50.000 25.000 L59.170 25.000 C59.170 26.190 59.805 27.289 60.835 27.884 C61.865 28.479 63.135 28.479 64.165 27.884 C65.195 27.289 65.830 26.190 65.830 25.000 L75.000 25.000 L75.000 34.170 C73.810 34.170 72.711 34.805 72.116 35.835 C71.521 36.865 71.521 38.135 72.116 39.165 C72.711 40.195 73.810 40.830 75.000 40.830 L75.000 50.000 L65.830 50.000 C65.830 48.810 65.195 47.711 64.165 47.116 C63.135 46.521 61.865 46.521 60.835 47.116 C59.805 47.711 59.170 48.810 59.170 50.000 L50.000 50.000 L50.000 40.830 C48.810 40.830 47.711 40.195 47.116 39.165 C46.521 38.135 46.521 36.865 47.116 35.835 C47.711 34.805 48.810 34.170 50.000 34.170 L50.000 25.000 Z" },
|
|
11
|
-
{ base: 360, light: "#7ea9ea", dark: "#5681c1", d: "M50.000 50.000 L59.170 50.000 C59.170 48.810 59.805 47.711 60.835 47.116 C61.865 46.521 63.135 46.521 64.165 47.116 C65.195 47.711 65.830 48.810 65.830 50.000 L75.000 50.000 L64.855 60.145 C64.013 59.304 62.787 58.976 61.638 59.283 C60.489 59.591 59.591 60.489 59.283 61.638 C58.976 62.787 59.304 64.013 60.145 64.855 L50.000 75.000 L50.000 65.830 C48.810 65.830 47.711 65.195 47.116 64.165 C46.521 63.135 46.521 61.865 47.116 60.835 C47.711 59.805 48.810 59.170 50.000 59.170 L50.000 50.000 Z" },
|
|
12
|
-
{ base: 450, light: "#3f5494", dark: "#162b6c", d: "M75.000 50.000 L75.000 59.170 C73.810 59.170 72.711 59.805 72.116 60.835 C71.521 61.865 71.521 63.135 72.116 64.165 C72.711 65.195 73.810 65.830 75.000 65.830 L75.000 75.000 L50.000 100.000 L50.000 90.830 C48.810 90.830 47.711 90.195 47.116 89.165 C46.521 88.135 46.521 86.865 47.116 85.835 C47.711 84.805 48.810 84.170 50.000 84.170 L50.000 75.000 L60.145 64.855 C59.304 64.013 58.976 62.787 59.283 61.638 C59.591 60.489 60.489 59.591 61.638 59.283 C62.787 58.976 64.013 59.304 64.855 60.145 L75.000 50.000 Z" },
|
|
13
|
-
{ base: 540, light: "#7ea9ea", dark: "#5681c1", d: "M100.000 50.000 L75.000 75.000 L75.000 65.830 C73.810 65.830 72.711 65.195 72.116 64.165 C71.521 63.135 71.521 61.865 72.116 60.835 C72.711 59.805 73.810 59.170 75.000 59.170 L75.000 50.000 L75.000 40.830 C73.810 40.830 72.711 40.195 72.116 39.165 C71.521 38.135 71.521 36.865 72.116 35.835 C72.711 34.805 73.810 34.170 75.000 34.170 L75.000 25.000 L100.000 50.000 Z" },
|
|
14
|
-
]
|
|
15
|
-
|
|
16
|
-
let LAST = SEGMENTS[SEGMENTS.length - 1]!.base
|
|
17
|
-
let IN_DONE = LAST + FADE
|
|
18
|
-
let CYCLE = IN_DONE + LAST + FADE
|
|
19
|
-
|
|
20
|
-
let clamp = (x: number) => (x < 0 ? 0 : x > 1 ? 1 : x)
|
|
21
|
-
let ease = (t: number) => 1 - (1 - t) * (1 - t)
|
|
22
|
-
let byte = (x: number) => Math.round(Math.max(0, Math.min(255, x))).toString(16).padStart(2, "0")
|
|
23
|
-
|
|
24
|
-
let fill = (seg: (typeof SEGMENTS)[number], a: number) =>
|
|
25
|
-
createLinearGradient(0, 0, 1, 1, [
|
|
26
|
-
{ offset: 0, color: seg.light + byte(a * 255) },
|
|
27
|
-
{ offset: 1, color: seg.dark + byte(a * 255) },
|
|
28
|
-
])
|
|
29
|
-
|
|
30
|
-
export function Icon() {
|
|
31
|
-
let [clock, setClock] = createSignal(0)
|
|
32
|
-
let start = -1
|
|
33
|
-
onFrame((t) => (start < 0 && (start = t), setClock((t - start) % CYCLE)))
|
|
34
|
-
return (
|
|
35
|
-
<view width={300} height={300} viewBox={[100, 100]}>
|
|
36
|
-
<For each={SEGMENTS}>
|
|
37
|
-
{(seg) => {
|
|
38
|
-
let a = () => {
|
|
39
|
-
let t = clock(), end = IN_DONE + seg.base + FADE
|
|
40
|
-
if (t < seg.base || t >= end) return 0
|
|
41
|
-
return ease(Math.min(clamp((t - seg.base) / FADE), clamp((end - t) / FADE)))
|
|
42
|
-
}
|
|
43
|
-
return <d-path d={seg.d} color={fill(seg, a())} />
|
|
44
|
-
}}
|
|
45
|
-
</For>
|
|
46
|
-
</view>
|
|
47
|
-
)
|
|
48
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
// The animated SolidRT logo: each segment fades in, then out, on its own delay.
|
|
2
|
-
import { For, onFrame, createSignal, createLinearGradient } from "@solidrt/core"
|
|
3
|
-
|
|
4
|
-
const FADE = 360
|
|
5
|
-
|
|
6
|
-
let SEGMENTS = [
|
|
7
|
-
{ base: 0, light: "#3f5494", dark: "#162b6c", d: "M50.000 50.000 L28.330 50.000 C28.330 48.810 27.695 47.711 26.665 47.116 C25.635 46.521 24.365 46.521 23.335 47.116 C22.305 47.711 21.670 48.810 21.670 50.000 L0.000 50.000 L50.000 0.000 L50.000 9.170 C48.810 9.170 47.711 9.805 47.116 10.835 C46.521 11.865 46.521 13.135 47.116 14.165 C47.711 15.195 48.810 15.830 50.000 15.830 L50.000 25.000 L50.000 34.170 C48.810 34.170 47.711 34.805 47.116 35.835 C46.521 36.865 46.521 38.135 47.116 39.165 C47.711 40.195 48.810 40.830 50.000 40.830 L50.000 50.000 Z" },
|
|
8
|
-
{ base: 90, light: "#547ebf", dark: "#2b5696", d: "M50.000 50.000 L50.000 59.170 C48.810 59.170 47.711 59.805 47.116 60.835 C46.521 61.865 46.521 63.135 47.116 64.165 C47.711 65.195 48.810 65.830 50.000 65.830 L50.000 75.000 L50.000 84.170 C48.810 84.170 47.711 84.805 47.116 85.835 C46.521 86.865 46.521 88.135 47.116 89.165 C47.711 90.195 48.810 90.830 50.000 90.830 L50.000 100.000 L0.000 50.000 L21.670 50.000 C21.670 48.810 22.305 47.711 23.335 47.116 C24.365 46.521 25.635 46.521 26.665 47.116 C27.695 47.711 28.330 48.810 28.330 50.000 L50.000 50.000 Z" },
|
|
9
|
-
{ base: 180, light: "#7ea9ea", dark: "#5681c1", d: "M50.000 25.000 L50.000 15.830 C48.810 15.830 47.711 15.195 47.116 14.165 C46.521 13.135 46.521 11.865 47.116 10.835 C47.711 9.805 48.810 9.170 50.000 9.170 L50.000 0.000 L75.000 25.000 L65.830 25.000 C65.830 26.190 65.195 27.289 64.165 27.884 C63.135 28.479 61.865 28.479 60.835 27.884 C59.805 27.289 59.170 26.190 59.170 25.000 L50.000 25.000 Z" },
|
|
10
|
-
{ base: 270, light: "#547ebf", dark: "#2b5696", d: "M50.000 25.000 L59.170 25.000 C59.170 26.190 59.805 27.289 60.835 27.884 C61.865 28.479 63.135 28.479 64.165 27.884 C65.195 27.289 65.830 26.190 65.830 25.000 L75.000 25.000 L75.000 34.170 C73.810 34.170 72.711 34.805 72.116 35.835 C71.521 36.865 71.521 38.135 72.116 39.165 C72.711 40.195 73.810 40.830 75.000 40.830 L75.000 50.000 L65.830 50.000 C65.830 48.810 65.195 47.711 64.165 47.116 C63.135 46.521 61.865 46.521 60.835 47.116 C59.805 47.711 59.170 48.810 59.170 50.000 L50.000 50.000 L50.000 40.830 C48.810 40.830 47.711 40.195 47.116 39.165 C46.521 38.135 46.521 36.865 47.116 35.835 C47.711 34.805 48.810 34.170 50.000 34.170 L50.000 25.000 Z" },
|
|
11
|
-
{ base: 360, light: "#7ea9ea", dark: "#5681c1", d: "M50.000 50.000 L59.170 50.000 C59.170 48.810 59.805 47.711 60.835 47.116 C61.865 46.521 63.135 46.521 64.165 47.116 C65.195 47.711 65.830 48.810 65.830 50.000 L75.000 50.000 L64.855 60.145 C64.013 59.304 62.787 58.976 61.638 59.283 C60.489 59.591 59.591 60.489 59.283 61.638 C58.976 62.787 59.304 64.013 60.145 64.855 L50.000 75.000 L50.000 65.830 C48.810 65.830 47.711 65.195 47.116 64.165 C46.521 63.135 46.521 61.865 47.116 60.835 C47.711 59.805 48.810 59.170 50.000 59.170 L50.000 50.000 Z" },
|
|
12
|
-
{ base: 450, light: "#3f5494", dark: "#162b6c", d: "M75.000 50.000 L75.000 59.170 C73.810 59.170 72.711 59.805 72.116 60.835 C71.521 61.865 71.521 63.135 72.116 64.165 C72.711 65.195 73.810 65.830 75.000 65.830 L75.000 75.000 L50.000 100.000 L50.000 90.830 C48.810 90.830 47.711 90.195 47.116 89.165 C46.521 88.135 46.521 86.865 47.116 85.835 C47.711 84.805 48.810 84.170 50.000 84.170 L50.000 75.000 L60.145 64.855 C59.304 64.013 58.976 62.787 59.283 61.638 C59.591 60.489 60.489 59.591 61.638 59.283 C62.787 58.976 64.013 59.304 64.855 60.145 L75.000 50.000 Z" },
|
|
13
|
-
{ base: 540, light: "#7ea9ea", dark: "#5681c1", d: "M100.000 50.000 L75.000 75.000 L75.000 65.830 C73.810 65.830 72.711 65.195 72.116 64.165 C71.521 63.135 71.521 61.865 72.116 60.835 C72.711 59.805 73.810 59.170 75.000 59.170 L75.000 50.000 L75.000 40.830 C73.810 40.830 72.711 40.195 72.116 39.165 C71.521 38.135 71.521 36.865 72.116 35.835 C72.711 34.805 73.810 34.170 75.000 34.170 L75.000 25.000 L100.000 50.000 Z" },
|
|
14
|
-
]
|
|
15
|
-
|
|
16
|
-
let LAST = SEGMENTS[SEGMENTS.length - 1]!.base
|
|
17
|
-
let IN_DONE = LAST + FADE
|
|
18
|
-
let CYCLE = IN_DONE + LAST + FADE
|
|
19
|
-
|
|
20
|
-
let clamp = (x: number) => (x < 0 ? 0 : x > 1 ? 1 : x)
|
|
21
|
-
let ease = (t: number) => 1 - (1 - t) * (1 - t)
|
|
22
|
-
let byte = (x: number) => Math.round(Math.max(0, Math.min(255, x))).toString(16).padStart(2, "0")
|
|
23
|
-
|
|
24
|
-
let fill = (seg: (typeof SEGMENTS)[number], a: number) =>
|
|
25
|
-
createLinearGradient(0, 0, 1, 1, [
|
|
26
|
-
{ offset: 0, color: seg.light + byte(a * 255) },
|
|
27
|
-
{ offset: 1, color: seg.dark + byte(a * 255) },
|
|
28
|
-
])
|
|
29
|
-
|
|
30
|
-
export function Icon() {
|
|
31
|
-
let [clock, setClock] = createSignal(0)
|
|
32
|
-
let start = -1
|
|
33
|
-
onFrame((t) => (start < 0 && (start = t), setClock((t - start) % CYCLE)))
|
|
34
|
-
return (
|
|
35
|
-
<view width={300} height={300} viewBox={[100, 100]}>
|
|
36
|
-
<For each={SEGMENTS}>
|
|
37
|
-
{(seg) => {
|
|
38
|
-
let a = () => {
|
|
39
|
-
let t = clock(), end = IN_DONE + seg.base + FADE
|
|
40
|
-
if (t < seg.base || t >= end) return 0
|
|
41
|
-
return ease(Math.min(clamp((t - seg.base) / FADE), clamp((end - t) / FADE)))
|
|
42
|
-
}
|
|
43
|
-
return <d-path d={seg.d} color={fill(seg, a())} />
|
|
44
|
-
}}
|
|
45
|
-
</For>
|
|
46
|
-
</view>
|
|
47
|
-
)
|
|
48
|
-
}
|
package/server/main.ts
DELETED
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
// The srt dev server as a flux script. srt (Bun) spawns this with one JSON
|
|
2
|
-
// config argument; bundling, file watching, and the repl stay in srt, which
|
|
3
|
-
// drives this process over the loopback-only /__internal__/ routes. The
|
|
4
|
-
// shutdown-when-empty policy also lives in srt (it polls /__internal__/clients);
|
|
5
|
-
// this process runs until srt kills it. See docs/flux-dev-server-plan.md.
|
|
6
|
-
|
|
7
|
-
import { serve } from "flux:http"
|
|
8
|
-
import type { FluxRequest, Server } from "flux:http"
|
|
9
|
-
import { file } from "flux:fs"
|
|
10
|
-
import { argv } from "flux:process"
|
|
11
|
-
import { resolveWithin } from "flux:path"
|
|
12
|
-
import { state, type Config } from "./state"
|
|
13
|
-
import * as cache from "./cache"
|
|
14
|
-
import { handleProxy } from "./proxy"
|
|
15
|
-
import { appendLog, clientList, handleControl, resolveQuery } from "./control"
|
|
16
|
-
import { printQr } from "./qr"
|
|
17
|
-
import { createTunnelEndpoint, TUNNEL_PROTOCOL } from "./tunnel"
|
|
18
|
-
|
|
19
|
-
let config: Config = JSON.parse(argv[0]!)
|
|
20
|
-
state.config = config
|
|
21
|
-
state.sourceDir = config.sourceDir
|
|
22
|
-
state.projectDir = config.projectDir
|
|
23
|
-
state.stats = config.stats
|
|
24
|
-
state.serverUrl = `${config.address}:${config.port}`
|
|
25
|
-
|
|
26
|
-
if (config.cache) {
|
|
27
|
-
await cache.initCache({ dir: config.cacheDir })
|
|
28
|
-
console.log("[cli] HTTP cache enabled")
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (config.capture) {
|
|
32
|
-
// Start each capture from an empty file: appends would otherwise tack onto
|
|
33
|
-
// whatever a previous run left behind.
|
|
34
|
-
await file(config.capture).write("")
|
|
35
|
-
state.captureStartMs = Date.now()
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Split an origin-form request URL ("/path?a=1&b=2") into its decoded path and
|
|
39
|
-
// query parameters. flux has no URL global; this covers what the routes need.
|
|
40
|
-
function splitQuery(url: string): { path: string; query: Map<string, string> } {
|
|
41
|
-
let i = url.indexOf("?")
|
|
42
|
-
let path = i < 0 ? url : url.slice(0, i)
|
|
43
|
-
let query = new Map<string, string>()
|
|
44
|
-
if (i >= 0) {
|
|
45
|
-
for (let pair of url.slice(i + 1).split("&")) {
|
|
46
|
-
if (!pair) continue
|
|
47
|
-
let j = pair.indexOf("=")
|
|
48
|
-
let k = j < 0 ? pair : pair.slice(0, j)
|
|
49
|
-
let v = j < 0 ? "" : pair.slice(j + 1)
|
|
50
|
-
query.set(decodeURIComponent(k), decodeURIComponent(v.replace(/\+/g, " ")))
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return { path: decodeURIComponent(path), query }
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Send `text` to the clients with the given ids, or to every client when
|
|
57
|
-
// `ids` is omitted.
|
|
58
|
-
function sendTo(ids: number[] | undefined, text: string) {
|
|
59
|
-
for (let [ws, info] of state.clients) {
|
|
60
|
-
if (!ids || ids.includes(info.id)) ws.send(text)
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// The srt -> server IPC under /__internal__/: only the srt process on this
|
|
65
|
-
// machine may drive it, so reject any peer that is not loopback.
|
|
66
|
-
async function handleInternal(req: FluxRequest, server: Server, path: string): Promise<Response> {
|
|
67
|
-
let ip = server.requestIP(req)
|
|
68
|
-
let loopback = ip && (ip.address === "127.0.0.1" || ip.address === "::1" || ip.address === "::ffff:127.0.0.1")
|
|
69
|
-
if (!loopback) return new Response("Forbidden", { status: 403 })
|
|
70
|
-
|
|
71
|
-
if (path === "/__internal__/clients") return Response.json(clientList(true))
|
|
72
|
-
if (path === "/__internal__/watch" && req.method === "GET") return Response.json({ enabled: state.watch })
|
|
73
|
-
if (req.method !== "POST") return new Response("Method not allowed", { status: 405 })
|
|
74
|
-
|
|
75
|
-
switch (path) {
|
|
76
|
-
case "/__internal__/reload": {
|
|
77
|
-
// { message, clients?, latch?, sourceDir?, map? }: send `message` (a
|
|
78
|
-
// full client-protocol message, built by srt) to the listed client ids,
|
|
79
|
-
// or to all when omitted. `latch` keeps it for late-joining clients
|
|
80
|
-
// (code reloads latch, one-shot bytecode loads do not); `sourceDir`
|
|
81
|
-
// moves the file-serving root (repl `load`; the project root - and with
|
|
82
|
-
// it the /assets/ root - is fixed for the life of the run); `map` is
|
|
83
|
-
// the bundle's sourcemap for log remapping, replaced on every reload
|
|
84
|
-
// (absent means none).
|
|
85
|
-
let body = await req.json()
|
|
86
|
-
if (typeof body.sourceDir === "string") state.sourceDir = body.sourceDir
|
|
87
|
-
// Keep the rebuild entry in sync when `load` moves it, so a later MCP
|
|
88
|
-
// reload bundles the newly loaded file, not the launch-time one.
|
|
89
|
-
if (typeof body.entry === "string") state.config.entry = body.entry
|
|
90
|
-
state.currentMap = typeof body.map === "string" ? body.map : null
|
|
91
|
-
let text = JSON.stringify(body.message)
|
|
92
|
-
if (body.latch) state.currentReload = text
|
|
93
|
-
sendTo(body.clients, text)
|
|
94
|
-
return new Response("", { status: 204 })
|
|
95
|
-
}
|
|
96
|
-
case "/__internal__/stop": {
|
|
97
|
-
let body = await req.json()
|
|
98
|
-
// A broadcast stop also forgets the latched reload, so a client that
|
|
99
|
-
// connects afterwards starts clean.
|
|
100
|
-
if (!body.clients) {
|
|
101
|
-
state.currentReload = null
|
|
102
|
-
state.currentMap = null
|
|
103
|
-
}
|
|
104
|
-
sendTo(body.clients, JSON.stringify({ type: "stop" }))
|
|
105
|
-
return new Response("", { status: 204 })
|
|
106
|
-
}
|
|
107
|
-
case "/__internal__/watch": {
|
|
108
|
-
// The repl's `watch on|off`; agents use /__control__/watch instead.
|
|
109
|
-
let body = await req.json()
|
|
110
|
-
state.watch = !!body.enabled
|
|
111
|
-
return new Response("", { status: 204 })
|
|
112
|
-
}
|
|
113
|
-
case "/__internal__/stats": {
|
|
114
|
-
let body = await req.json()
|
|
115
|
-
state.stats = !!body.stats
|
|
116
|
-
sendTo(undefined, JSON.stringify({ type: "stats", stats: state.stats }))
|
|
117
|
-
return new Response("", { status: 204 })
|
|
118
|
-
}
|
|
119
|
-
default:
|
|
120
|
-
return Response.json({ error: "Unknown internal endpoint" }, { status: 404 })
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// The file routes: GET file with single-range 206 support. All paths are
|
|
125
|
-
// contained in `root` (the source directory, or the project dir for the
|
|
126
|
-
// /assets/ convention route).
|
|
127
|
-
async function handleFiles(req: FluxRequest, path: string, root: string): Promise<Response> {
|
|
128
|
-
let filePath = resolveWithin(root, "." + path)
|
|
129
|
-
if (!filePath) {
|
|
130
|
-
return new Response("Forbidden", { status: 403 })
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
console.log("[cli] get " + path)
|
|
134
|
-
|
|
135
|
-
let stat
|
|
136
|
-
try {
|
|
137
|
-
stat = await file(filePath).stat()
|
|
138
|
-
} catch {
|
|
139
|
-
console.log(`[cli] file not found ${path}`)
|
|
140
|
-
return new Response("Not found", { status: 404 })
|
|
141
|
-
}
|
|
142
|
-
if (stat.type === "directory") {
|
|
143
|
-
return new Response("Not found", { status: 404 })
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
let baseHeaders: Record<string, string> = { "Accept-Ranges": "bytes" }
|
|
147
|
-
|
|
148
|
-
// Honor a single byte-range request (e.g. streaming audio decoding on the
|
|
149
|
-
// client, which seeks and reads on demand). Only the common "bytes=a-b" /
|
|
150
|
-
// "bytes=a-" / "bytes=-n" forms; anything else falls through to the whole
|
|
151
|
-
// file. Range makes proxied streaming viable without pulling the whole
|
|
152
|
-
// track over the wire.
|
|
153
|
-
let range = req.headers.get("range")
|
|
154
|
-
let match = range ? /^bytes=(\d*)-(\d*)$/.exec(range.trim()) : null
|
|
155
|
-
if (match) {
|
|
156
|
-
let size = stat.size
|
|
157
|
-
let start: number
|
|
158
|
-
let end: number
|
|
159
|
-
if (match[1] === "") {
|
|
160
|
-
// Suffix range: the last N bytes.
|
|
161
|
-
let n = parseInt(match[2]!, 10)
|
|
162
|
-
start = isNaN(n) ? 0 : Math.max(0, size - n)
|
|
163
|
-
end = size - 1
|
|
164
|
-
} else {
|
|
165
|
-
start = parseInt(match[1]!, 10)
|
|
166
|
-
end = match[2] === "" ? size - 1 : Math.min(parseInt(match[2]!, 10), size - 1)
|
|
167
|
-
}
|
|
168
|
-
if (start > end || start >= size) {
|
|
169
|
-
return new Response("Range not satisfiable", {
|
|
170
|
-
status: 416,
|
|
171
|
-
headers: { ...baseHeaders, "Content-Range": `bytes */${size}` },
|
|
172
|
-
})
|
|
173
|
-
}
|
|
174
|
-
return new Response(await file(filePath).read(start, end - start + 1), {
|
|
175
|
-
status: 206,
|
|
176
|
-
headers: {
|
|
177
|
-
...baseHeaders,
|
|
178
|
-
"Content-Range": `bytes ${start}-${end}/${size}`,
|
|
179
|
-
"Content-Length": String(end - start + 1),
|
|
180
|
-
},
|
|
181
|
-
})
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return new Response(await file(filePath).bytes(), { headers: baseHeaders })
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Ticket-paired clients connect through this endpoint; serve() accepts its
|
|
188
|
-
// connections directly alongside the TCP listener.
|
|
189
|
-
let tunnel = config.tunnel ? await createTunnelEndpoint(config.port, config.keyDir) : null
|
|
190
|
-
|
|
191
|
-
serve({
|
|
192
|
-
port: config.port,
|
|
193
|
-
p2p: tunnel ? { endpoint: tunnel, protocol: TUNNEL_PROTOCOL } : undefined,
|
|
194
|
-
async fetch(req, server) {
|
|
195
|
-
if (server.upgrade(req)) return
|
|
196
|
-
|
|
197
|
-
let { path, query } = splitQuery(req.url)
|
|
198
|
-
|
|
199
|
-
if (path === "/__proxy__") {
|
|
200
|
-
return handleProxy(req)
|
|
201
|
-
}
|
|
202
|
-
if (path.startsWith("/__control__/")) {
|
|
203
|
-
return handleControl(req, path, query)
|
|
204
|
-
}
|
|
205
|
-
if (path.startsWith("/__internal__/")) {
|
|
206
|
-
return handleInternal(req, server, path)
|
|
207
|
-
}
|
|
208
|
-
// The assets/ convention roots at the project dir (package.json), which is
|
|
209
|
-
// not necessarily the entry's dir the file routes serve; clients fetch
|
|
210
|
-
// manifest asset paths here (live proxy reads and store installs alike).
|
|
211
|
-
if (path === "/assets" || path.startsWith("/assets/")) {
|
|
212
|
-
return handleFiles(req, path, state.projectDir)
|
|
213
|
-
}
|
|
214
|
-
// Isolate bundles are build outputs, not project files: srt (and the
|
|
215
|
-
// rebuild here) write them under .srt-data/isolates/, and the manifest
|
|
216
|
-
// lists them as isolates/<id>.js.
|
|
217
|
-
if (path.startsWith("/isolates/")) {
|
|
218
|
-
return handleFiles(req, path, config.cacheDir)
|
|
219
|
-
}
|
|
220
|
-
return handleFiles(req, path, state.sourceDir)
|
|
221
|
-
},
|
|
222
|
-
websocket: {
|
|
223
|
-
open(ws) {
|
|
224
|
-
let id = state.nextClientId++
|
|
225
|
-
state.clients.set(ws, {
|
|
226
|
-
platform: "unknown",
|
|
227
|
-
version: "unknown",
|
|
228
|
-
profile: "unknown",
|
|
229
|
-
id,
|
|
230
|
-
capabilities: [],
|
|
231
|
-
queries: [],
|
|
232
|
-
})
|
|
233
|
-
console.log(`[cli] Client connected ${ws.remoteAddr ?? "unknown"}`)
|
|
234
|
-
// Advertise our real LAN address so clients dialed over a loopback hop
|
|
235
|
-
// can show/remember the directly reachable address (see connection.rs).
|
|
236
|
-
ws.send(
|
|
237
|
-
JSON.stringify({ type: "welcome", address: state.serverUrl, stats: state.stats, capture: !!config.capture }),
|
|
238
|
-
)
|
|
239
|
-
if (state.currentReload) {
|
|
240
|
-
ws.send(state.currentReload)
|
|
241
|
-
}
|
|
242
|
-
},
|
|
243
|
-
close(ws) {
|
|
244
|
-
let info = state.clients.get(ws)
|
|
245
|
-
state.clients.delete(ws)
|
|
246
|
-
console.log(`[cli] Client disconnected: ${info?.platform ?? "unknown"}`)
|
|
247
|
-
},
|
|
248
|
-
message(ws, msg) {
|
|
249
|
-
try {
|
|
250
|
-
let data = JSON.parse(typeof msg === "string" ? msg : new TextDecoder().decode(msg))
|
|
251
|
-
if (data.type === "info") {
|
|
252
|
-
let existing = state.clients.get(ws)
|
|
253
|
-
state.clients.set(ws, {
|
|
254
|
-
platform: data.platform ?? "unknown",
|
|
255
|
-
version: data.version ?? "unknown",
|
|
256
|
-
profile: data.profile ?? "unknown",
|
|
257
|
-
id: existing?.id ?? state.nextClientId++,
|
|
258
|
-
capabilities: Array.isArray(data.capabilities) ? data.capabilities.map(String) : [],
|
|
259
|
-
queries: Array.isArray(data.queries) ? data.queries.map(String) : [],
|
|
260
|
-
})
|
|
261
|
-
console.log(`[cli] Client info ${ws.remoteAddr ?? "unknown"} ${data.platform} (${data.version})`)
|
|
262
|
-
} else if (data.type === "log") {
|
|
263
|
-
// Forwarded console output / runtime errors from the client's
|
|
264
|
-
// engine logger, buffered for the control API (see control.ts).
|
|
265
|
-
// Not printed here: the local client already writes to this
|
|
266
|
-
// terminal, so echoing would duplicate every line.
|
|
267
|
-
let device = state.clients.get(ws)?.id ?? -1
|
|
268
|
-
appendLog(device, String(data.level ?? "log"), String(data.text ?? ""))
|
|
269
|
-
} else if (data.type === "result") {
|
|
270
|
-
// Reply to a query the control API forwarded to this client.
|
|
271
|
-
resolveQuery(data)
|
|
272
|
-
} else if (data.type === "capture" && config.capture) {
|
|
273
|
-
let device = state.clients.get(ws)?.id ?? -1
|
|
274
|
-
// Milliseconds, integer: Date.now() is already integer ms, so the
|
|
275
|
-
// delta needs no rounding.
|
|
276
|
-
let at = Date.now() - state.captureStartMs
|
|
277
|
-
let after = at - state.captureLastAt
|
|
278
|
-
state.captureLastAt = at
|
|
279
|
-
// JSON Lines: one event object per line, streamed to disk as it
|
|
280
|
-
// arrives rather than buffered - no in-memory growth for a long
|
|
281
|
-
// capture, and the file is always complete on disk mid-session.
|
|
282
|
-
// Appends are chained so events land in arrival order.
|
|
283
|
-
let line = JSON.stringify({ after, type: data.kind, key: data.key, device }) + "\n"
|
|
284
|
-
state.captureChain = state.captureChain.then(() => file(config.capture!).append(line))
|
|
285
|
-
}
|
|
286
|
-
} catch {}
|
|
287
|
-
},
|
|
288
|
-
},
|
|
289
|
-
})
|
|
290
|
-
|
|
291
|
-
// One QR on screen: with the tunnel on, the ticket QR (printed by
|
|
292
|
-
// createTunnelEndpoint) is the pairing story and the address stays text-only;
|
|
293
|
-
// without it, the address QR is the scan target as before.
|
|
294
|
-
if (!config.tunnel) {
|
|
295
|
-
console.log("")
|
|
296
|
-
printQr(state.serverUrl)
|
|
297
|
-
console.log("")
|
|
298
|
-
}
|
|
299
|
-
console.log(`[cli] WebSocket server on ws://${state.serverUrl}`)
|
|
300
|
-
// mDNS advertise is intentionally not implemented here: the p2p ticket is the
|
|
301
|
-
// cross-device connect story (see docs/flux-dev-server-plan.md).
|
|
302
|
-
|
|
303
|
-
// Keepalive
|
|
304
|
-
setInterval(() => {
|
|
305
|
-
for (let ws of state.clients.keys()) {
|
|
306
|
-
ws.ping()
|
|
307
|
-
}
|
|
308
|
-
}, 5000)
|
package/server/rebuild.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { command } from "flux:subprocess"
|
|
2
|
-
import { dir, file } from "flux:fs"
|
|
3
|
-
import { state } from "./state"
|
|
4
|
-
|
|
5
|
-
// Server-owned "rebuild and push": the single place the running app is rebuilt
|
|
6
|
-
// from source on demand (an MCP reload). The srt repl still bundles in-process
|
|
7
|
-
// for its own keystroke reloads, but both routes call the same bundle-cli, so
|
|
8
|
-
// the bundling logic cannot drift. Making the server the rebuild authority is
|
|
9
|
-
// the interim step toward folding the whole CLI into flux (see
|
|
10
|
-
// okf/backlog/cli-flux-migration.md).
|
|
11
|
-
|
|
12
|
-
// Build the reload message the same way srt's buildReload does, so a
|
|
13
|
-
// server-triggered reload is indistinguishable from a repl-triggered one to
|
|
14
|
-
// clients. proxyHttp is a message flag, not a build input.
|
|
15
|
-
function buildReload(code: string, manifest?: string) {
|
|
16
|
-
let config = state.config
|
|
17
|
-
return {
|
|
18
|
-
type: "reload",
|
|
19
|
-
proxyHttp: config.proxyHttp,
|
|
20
|
-
args: config.args,
|
|
21
|
-
...(manifest ? { manifest } : {}),
|
|
22
|
-
code,
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Rebuild from state.config.entry via the external bundle-cli subprocess, then
|
|
27
|
-
// latch (for late-joining clients) and broadcast the reload to every connected
|
|
28
|
-
// client. Resolves with an error message on failure (no entry configured, or a
|
|
29
|
-
// build error), or null on success.
|
|
30
|
-
export async function rebuildAndBroadcast(): Promise<string | null> {
|
|
31
|
-
let config = state.config
|
|
32
|
-
if (!config.entry) {
|
|
33
|
-
return "No app entry to rebuild. Start srt with a source file (srt run src/index.tsx)."
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
let params = JSON.stringify({
|
|
37
|
-
entry: config.entry,
|
|
38
|
-
devBase: state.serverUrl,
|
|
39
|
-
dev: true,
|
|
40
|
-
minify: config.minify,
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
let result = await command(config.bundlerCmd[0]!, [...config.bundlerCmd.slice(1), params]).output()
|
|
44
|
-
if (!result.success) {
|
|
45
|
-
let stderr = typeof result.stderr === "string" ? result.stderr : ""
|
|
46
|
-
return `Rebuild failed:\n${stderr.trim()}`
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// bundle-cli writes one JSON object { code, map, manifest, isolates } to stdout.
|
|
50
|
-
let bundle: { code?: string; map?: string | null; manifest?: string; isolates?: { id: string; code: string }[] }
|
|
51
|
-
try {
|
|
52
|
-
bundle = JSON.parse(typeof result.stdout === "string" ? result.stdout : "")
|
|
53
|
-
} catch {
|
|
54
|
-
return "Rebuild failed: unreadable bundler output"
|
|
55
|
-
}
|
|
56
|
-
// Isolate bundles are manifest assets clients fetch from our /isolates/
|
|
57
|
-
// route (served from cacheDir), so they must be on disk before the push.
|
|
58
|
-
for (let isolate of bundle.isolates ?? []) {
|
|
59
|
-
let path = `${config.cacheDir}/isolates/${isolate.id}.js`
|
|
60
|
-
await dir(path.slice(0, path.lastIndexOf("/"))).create()
|
|
61
|
-
await file(path).write(isolate.code)
|
|
62
|
-
}
|
|
63
|
-
state.currentMap = bundle.map ?? null
|
|
64
|
-
let text = JSON.stringify(buildReload(bundle.code ?? "", bundle.manifest))
|
|
65
|
-
state.currentReload = text
|
|
66
|
-
for (let ws of state.clients.keys()) ws.send(text)
|
|
67
|
-
return null
|
|
68
|
-
}
|
package/server/remap.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { TraceMap, originalPositionFor } from "@jridgewell/trace-mapping"
|
|
2
|
-
|
|
3
|
-
// Stack-trace remapping for forwarded client logs. The runtime evaluates the
|
|
4
|
-
// bundle as module "main", so QuickJS frames cite bundle positions like
|
|
5
|
-
// "at boom (main:212:9)". With the current reload's sourcemap latched on the
|
|
6
|
-
// server (state.currentMap), those positions are rewritten to the original
|
|
7
|
-
// .tsx sources before a log entry is buffered.
|
|
8
|
-
|
|
9
|
-
// The parsed map is cached per map text; a reload swaps the text and the next
|
|
10
|
-
// lookup rebuilds the tracer.
|
|
11
|
-
let cachedText: string | null = null
|
|
12
|
-
let tracer: TraceMap | null = null
|
|
13
|
-
|
|
14
|
-
function tracerFor(map: string | null): TraceMap | null {
|
|
15
|
-
if (map !== cachedText) {
|
|
16
|
-
cachedText = map
|
|
17
|
-
tracer = null
|
|
18
|
-
if (map) {
|
|
19
|
-
try {
|
|
20
|
-
tracer = new TraceMap(JSON.parse(map))
|
|
21
|
-
} catch {
|
|
22
|
-
// A malformed map disables remapping until the next reload.
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return tracer
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Rewrite every "main:LINE:COL" (or "main:LINE") position in `text` to its
|
|
31
|
-
* original source position, e.g. "src/app.tsx:42:7". Positions the map has no
|
|
32
|
-
* entry for, and all text when `map` is null, pass through unchanged.
|
|
33
|
-
* QuickJS lines and columns are 1-based; sourcemap columns are 0-based.
|
|
34
|
-
*/
|
|
35
|
-
export function remapPositions(text: string, map: string | null): string {
|
|
36
|
-
if (!map || !text.includes("main:")) return text
|
|
37
|
-
let t = tracerFor(map)
|
|
38
|
-
if (!t) return text
|
|
39
|
-
return text.replace(/\bmain:(\d+)(?::(\d+))?\b/g, (frame, line, column) => {
|
|
40
|
-
let pos = originalPositionFor(t, {
|
|
41
|
-
line: parseInt(line, 10),
|
|
42
|
-
column: column ? Math.max(parseInt(column, 10) - 1, 0) : 0,
|
|
43
|
-
})
|
|
44
|
-
if (pos.source == null || pos.line == null) return frame
|
|
45
|
-
return `${pos.source}:${pos.line}:${(pos.column ?? 0) + 1}`
|
|
46
|
-
})
|
|
47
|
-
}
|
package/server/state.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
// Server-side state shared by the route handlers. The srt process keeps the
|
|
2
|
-
// bundler, watcher, and repl; this state is only what the protocol needs.
|
|
3
|
-
|
|
4
|
-
import type { ServerWebSocket } from "flux:http"
|
|
5
|
-
|
|
6
|
-
export type Config = {
|
|
7
|
-
port: number
|
|
8
|
-
/** Directory served by the file routes (updatable via /__internal__/reload). */
|
|
9
|
-
sourceDir: string
|
|
10
|
-
/** Project root whose assets/ folder the /assets/ route serves (updatable
|
|
11
|
-
* via /__internal__/reload, moves with the repl `load`). */
|
|
12
|
-
projectDir: string
|
|
13
|
-
/** The address clients can reach this machine on (LAN IP or 127.0.0.1). */
|
|
14
|
-
address: string
|
|
15
|
-
proxyHttp: boolean
|
|
16
|
-
/** The app entry (absolute .tsx/.jsx path) the server rebuilds on an
|
|
17
|
-
* MCP-triggered reload, or undefined when srt was started without a source.
|
|
18
|
-
* Moved by the repl `load` command via /__internal__/reload. */
|
|
19
|
-
entry?: string
|
|
20
|
-
/** The session's app arguments (the srt command-line tail after a bare
|
|
21
|
-
* "--"), included in every reload push as flux:process argv. */
|
|
22
|
-
args: string[]
|
|
23
|
-
/** Minify the rebuild output, mirroring the srt --minify flag. */
|
|
24
|
-
minify: boolean
|
|
25
|
-
/** How the server invokes the external bundler: [bunPath, bundleCliPath],
|
|
26
|
-
* spawned with a JSON params argument appended (see rebuild.ts). */
|
|
27
|
-
bundlerCmd: string[]
|
|
28
|
-
/** Enable the sqlite-backed proxy cache. */
|
|
29
|
-
cache: boolean
|
|
30
|
-
/** Directory holding the proxy cache db (the project-local .srt-data). */
|
|
31
|
-
cacheDir: string
|
|
32
|
-
/** Directory holding tunnel.key (the server's ~/.solidrt/servers/<port>/ folder). */
|
|
33
|
-
keyDir: string
|
|
34
|
-
/** Destination for captured key events, or unset when off. */
|
|
35
|
-
capture?: string
|
|
36
|
-
stats: boolean
|
|
37
|
-
/** Accept ticket-paired clients through the p2p tunnel. */
|
|
38
|
-
tunnel: boolean
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export type ClientInfo = {
|
|
42
|
-
platform: string
|
|
43
|
-
version: string
|
|
44
|
-
profile: string
|
|
45
|
-
id: number
|
|
46
|
-
capabilities: string[]
|
|
47
|
-
/** Query kinds this client's runtime answers (empty on runtimes that predate
|
|
48
|
-
* the advertisement); dev tools plan their verification surface from it. */
|
|
49
|
-
queries: string[]
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export let state = {
|
|
53
|
-
config: undefined as unknown as Config,
|
|
54
|
-
clients: new Map<ServerWebSocket, ClientInfo>(),
|
|
55
|
-
nextClientId: 0,
|
|
56
|
-
/**
|
|
57
|
-
* Identity of this server run, included in control responses that carry
|
|
58
|
-
* cross-call state (client ids, log seq cursors). Both reset on restart, so
|
|
59
|
-
* a consumer that sees the generation change knows its ids and cursors are
|
|
60
|
-
* from a dead server and must be re-fetched.
|
|
61
|
-
*/
|
|
62
|
-
generation: Date.now(),
|
|
63
|
-
/**
|
|
64
|
-
* The latched reload message (JSON text), replayed to late-joining clients.
|
|
65
|
-
* Set by /__internal__/reload posts with `latch`, cleared by a broadcast stop.
|
|
66
|
-
*/
|
|
67
|
-
currentReload: null as string | null,
|
|
68
|
-
/**
|
|
69
|
-
* The running bundle's sourcemap (JSON text, bundle -> .tsx sources), used
|
|
70
|
-
* to remap stack traces in forwarded client logs (see control.ts). Replaced
|
|
71
|
-
* on every reload; a reload without a map clears it so frames are never
|
|
72
|
-
* remapped against a stale map.
|
|
73
|
-
*/
|
|
74
|
-
currentMap: null as string | null,
|
|
75
|
-
sourceDir: "",
|
|
76
|
-
projectDir: "",
|
|
77
|
-
serverUrl: "",
|
|
78
|
-
stats: false,
|
|
79
|
-
/**
|
|
80
|
-
* Whether srt's file watcher may auto-reload on source changes. Agents
|
|
81
|
-
* pause it (MCP watch tool -> /__control__/watch) while creating or
|
|
82
|
-
* editing files; a successful /reload or /load re-enables it. srt reads
|
|
83
|
-
* it via /__internal__/watch before acting on a change event.
|
|
84
|
-
*/
|
|
85
|
-
watch: true,
|
|
86
|
-
// Capture events from all connected clients share one clock (captureStartMs,
|
|
87
|
-
// integer milliseconds) so they merge into one coherent timeline, tagged by
|
|
88
|
-
// `device`. Streamed to disk as JSON Lines - see main.ts's "capture" handling.
|
|
89
|
-
captureStartMs: 0,
|
|
90
|
-
captureLastAt: 0, // ms, same clock as captureStartMs
|
|
91
|
-
/** Serializes capture appends so events land on disk in arrival order. */
|
|
92
|
-
captureChain: Promise.resolve(),
|
|
93
|
-
}
|