@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
@@ -0,0 +1,85 @@
1
+ import { existsSync, statSync } from "node:fs"
2
+ import { Glob } from "bun"
3
+ import { join, resolve } from "node:path"
4
+ import { source } from "../lib/args"
5
+ import { bundleWith } from "../bundle/bundler"
6
+ import { findProject } from "../lib/project"
7
+ import { findProjectRoot, reportTypes, typecheck } from "./typecheck"
8
+
9
+ // srt check: verify the app without side effects. Bundles in memory (nothing
10
+ // written, so no dev-server reload fires and no build outputs land in the
11
+ // project) and typechecks with the project's own tsc (typecheck.ts).
12
+
13
+ // Check one entry: bundle in memory, then typecheck. Returns whether it passed.
14
+ async function checkEntry(entry: string): Promise<boolean> {
15
+ let failed = false
16
+ // check verifies trees of entries from one cwd, so it is the one command
17
+ // that walks up from each entry to its project.
18
+ let result = await bundleWith({ entry, dev: true, minify: false, project: findProject(entry)?.dir ?? null })
19
+ if (!result) {
20
+ // bundleWith already printed the compile errors.
21
+ failed = true
22
+ }
23
+ let root = findProjectRoot(entry)
24
+ if (!root) {
25
+ console.warn("Typecheck skipped: no tsconfig.json or package.json above the entry")
26
+ } else {
27
+ let types = await typecheck(root, entry)
28
+ if (types && reportTypes(types)) failed = true
29
+ }
30
+ return !failed
31
+ }
32
+
33
+ // The entries `srt check <folder>` covers, relative to the folder (a bare
34
+ // `srt check` is `srt check .`): the app itself, its own examples, and in
35
+ // a monorepo every example app, package example and package demo. The same
36
+ // set CI gates, so one call at the repo root answers "did I break any
37
+ // example" before pushing. Entries, not files: a source no entry imports is
38
+ // not checked.
39
+ const CHECK_ALL_GLOBS = [
40
+ "src/index.tsx",
41
+ "examples/*.tsx",
42
+ "examples/*/src/index.tsx",
43
+ "packages/*/examples/*.tsx",
44
+ "packages/*/demos/src/*.tsx",
45
+ ]
46
+ function discoverEntries(root: string): string[] {
47
+ let entries: string[] = []
48
+ for (let pattern of CHECK_ALL_GLOBS) {
49
+ entries.push(...[...new Glob(pattern).scanSync({ cwd: root })].map((e) => join(root, e)))
50
+ }
51
+ return entries.sort()
52
+ }
53
+
54
+ export async function main() {
55
+ let target = source ?? "."
56
+ if (!existsSync(target)) {
57
+ // Without this, the missing file surfaces later as an internal ENOENT
58
+ // stack trace (scandir/Bun.build), which reads as a CLI bug - the common
59
+ // cause is just running from the wrong directory.
60
+ console.error(`No such entry: ${target} (resolved from ${process.cwd()})`)
61
+ process.exit(1)
62
+ }
63
+ if (!statSync(target).isDirectory()) {
64
+ if (!(await checkEntry(target))) process.exit(1)
65
+ console.log("Check passed")
66
+ process.exit(0)
67
+ }
68
+
69
+ let entries = discoverEntries(target)
70
+ if (entries.length === 0) {
71
+ console.error(`No entries found under ${resolve(target)} (looked for ${CHECK_ALL_GLOBS.join(", ")})`)
72
+ process.exit(1)
73
+ }
74
+ let failures: string[] = []
75
+ for (let entry of entries) {
76
+ console.log(`== ${entry}`)
77
+ if (!(await checkEntry(entry))) failures.push(entry)
78
+ }
79
+ if (failures.length > 0) {
80
+ console.error(`${failures.length} of ${entries.length} entries failed:\n ${failures.join("\n ")}`)
81
+ process.exit(1)
82
+ }
83
+ console.log(`Check passed (${entries.length} entries)`)
84
+ process.exit(0)
85
+ }
@@ -1,14 +1,12 @@
1
1
  import { existsSync, mkdirSync, rmSync } from "node:fs"
2
2
  import { dirname, join, resolve } from "node:path"
3
- import { source } from "../args"
4
- import { bundleWith, findIsolateModules } from "../bundler"
3
+ import { findIsolateModules } from "../bundle/bundler"
5
4
 
6
- // srt check: verify the app without side effects. Bundles in memory (nothing
7
- // written, so no dev-server reload fires and no build outputs land in the
8
- // project) and typechecks with the project's own tsc, reporting only
9
- // diagnostics in app code. @solidrt packages ship raw .ts sources, so a strict
10
- // consumer config surfaces their internal errors too; those are counted and
11
- // hidden, not the caller's problem to wade through.
5
+ // Typechecking an app entry with the project's own tsc, reporting only
6
+ // diagnostics in app code. Used by `srt check` (the hard gate) and by the dev
7
+ // server's startup check (it spawns `srt check <entry>`). @solidrt packages ship
8
+ // raw .ts sources, so a strict consumer config surfaces their internal errors
9
+ // too; those are counted and hidden, not the caller's problem to wade through.
12
10
 
13
11
  // Walk up from the entry to the enclosing project (tsconfig.json or, failing
14
12
  // that, package.json).
@@ -27,7 +25,7 @@ export function findProjectRoot(entry: string): string | null {
27
25
 
28
26
  // One tsc --pretty false diagnostic: the "path(line,col): error TS...: ..."
29
27
  // head line plus any indented continuation lines.
30
- type Diagnostic = { head: string; lines: string[]; inDependencies: boolean }
28
+ export type Diagnostic = { head: string; lines: string[]; inDependencies: boolean }
31
29
 
32
30
  function parseDiagnostics(output: string): Diagnostic[] {
33
31
  let diagnostics: Diagnostic[] = []
@@ -58,6 +56,8 @@ function findTsc(fromDir: string): string | null {
58
56
  }
59
57
  }
60
58
 
59
+ export type TypecheckResult = { app: Diagnostic[]; hidden: number }
60
+
61
61
  // Typecheck the entry's program, not the enclosing project: a transient
62
62
  // config extends the project's tsconfig and roots the program at the entry
63
63
  // alone (plus the project's ambient declarations), so tsc checks exactly the
@@ -65,7 +65,7 @@ function findTsc(fromDir: string): string | null {
65
65
  // config lives in the project-local .srt-data (the dev-artifact dir; absolute
66
66
  // paths inside, so its location only matters for type-package resolution,
67
67
  // which walks up to the project's node_modules from there).
68
- export async function typecheck(root: string, entry: string): Promise<{ app: Diagnostic[]; hidden: number } | null> {
68
+ export async function typecheck(root: string, entry: string): Promise<TypecheckResult | null> {
69
69
  let tsconfig = join(root, "tsconfig.json")
70
70
  if (!existsSync(tsconfig)) {
71
71
  console.warn("Typecheck skipped: no tsconfig.json above the entry")
@@ -111,7 +111,7 @@ export async function typecheck(root: string, entry: string): Promise<{ app: Dia
111
111
  // whether app-code errors were found. Callers pass repl-aware printers when
112
112
  // the output lands over a live prompt (the dev-server startup check).
113
113
  export function reportTypes(
114
- types: { app: Diagnostic[]; hidden: number },
114
+ types: TypecheckResult,
115
115
  log: (...args: any[]) => void = console.log,
116
116
  error: (...args: any[]) => void = console.error,
117
117
  ): boolean {
@@ -125,26 +125,3 @@ export function reportTypes(
125
125
  else log("Types OK")
126
126
  return false
127
127
  }
128
-
129
- export async function runCheckCommand() {
130
- let entry = source!
131
- let failed = false
132
-
133
- let result = await bundleWith({ entry, dev: true, minify: false })
134
- if (!result) {
135
- // bundleWith already printed the compile errors.
136
- failed = true
137
- }
138
-
139
- let root = findProjectRoot(entry)
140
- if (!root) {
141
- console.warn("Typecheck skipped: no tsconfig.json or package.json above the entry")
142
- } else {
143
- let types = await typecheck(root, entry)
144
- if (types && reportTypes(types)) failed = true
145
- }
146
-
147
- if (failed) process.exit(1)
148
- console.log("Check passed")
149
- process.exit(0)
150
- }
@@ -0,0 +1,9 @@
1
+ # srt client
2
+
3
+ {{ usage client }}
4
+
5
+ The client half of [srt run](../server/docs.md), on its own. Without flags
6
+ it attaches to the dev server of the project (or file) in the current
7
+ directory; `--port` picks a local server by port and `--server` names any
8
+ address, which is how a second machine joins a server started with `--lan`.
9
+ A phone or tablet is [srt android](../android/docs.md).
@@ -0,0 +1,33 @@
1
+ import { values, port, clientStorageArgs } from "../lib/args"
2
+ import { requireBinary, run } from "../lib/util"
3
+ import { resolveFromCwd } from "../lib/registry"
4
+
5
+ // Standalone solidrt-go client (no dev server of its own). Without flags it
6
+ // attaches to the dev server of the project (or file) in the current
7
+ // directory, resolved from the registry; --port picks a local server by port
8
+ // and --server names any address. A device is `srt android`.
9
+ export async function main() {
10
+ let runner = requireBinary("solidrt-go")
11
+ let args: string[] = [...clientStorageArgs()]
12
+ if (values.size) args.push("--size", values.size)
13
+ let address: string
14
+ if (values.server) {
15
+ if (!values.server.includes(":")) {
16
+ console.error(`--server needs host:port (got "${values.server}"); dev servers have no fixed port`)
17
+ process.exit(1)
18
+ }
19
+ address = values.server
20
+ } else if (port !== undefined) {
21
+ address = `127.0.0.1:${port}`
22
+ } else {
23
+ let resolved = await resolveFromCwd(process.cwd())
24
+ if (!resolved.ok) {
25
+ console.error(resolved.message)
26
+ process.exit(1)
27
+ }
28
+ address = `127.0.0.1:${resolved.record.port}`
29
+ }
30
+ args.push("--dev-server", address)
31
+ let exit = await run(runner, args)
32
+ process.exit(exit)
33
+ }
@@ -0,0 +1,14 @@
1
+ # srt console
2
+
3
+ {{ usage console }}
4
+
5
+ The dev console: the dev servers running on this machine, the clients
6
+ attached to each, and a button to start a local client for one. It is not
7
+ served by a dev server: the CLI ships the console pre-compiled
8
+ (`dist/console.srtapp`, see [srt pack --app](../pack/docs.md)) and this
9
+ command starts the `solidrt` runner on that file, so it opens at once and
10
+ runs the same whether a project is open or not.
11
+
12
+ In a checkout the file is built with `make -C packages/cli
13
+ dist/console.srtapp`; rerun it after editing `apps/console`. Everything
14
+ after `--` reaches the console as its arguments.
@@ -0,0 +1,21 @@
1
+ import { existsSync } from "node:fs"
2
+ import { fileURLToPath } from "node:url"
3
+ import { appArgs } from "../lib/args"
4
+ import { requireBinary, run } from "../lib/util"
5
+
6
+ // srt console: the dev console (apps/console) as a packed app on the plain
7
+ // runner. The console is pre-compiled: dist/console.srtapp (manifest +
8
+ // bytecode + assets, `srt pack --app`), built by `make -C packages/cli
9
+ // dist/console.srtapp` - the release workflow before publishing, a checkout
10
+ // after editing the console. This command builds nothing: it points the
11
+ // runner at the file, and the runner is used in place, so a signed runner
12
+ // stays signed.
13
+ export async function main() {
14
+ let app = fileURLToPath(new URL("../../dist/console.srtapp", import.meta.url))
15
+ if (!existsSync(app)) {
16
+ console.error("Console not built: run make -C packages/cli dist/console.srtapp")
17
+ process.exit(1)
18
+ }
19
+ let exit = await run(requireBinary("solidrt"), [app, ...appArgs])
20
+ process.exit(exit)
21
+ }
@@ -0,0 +1,27 @@
1
+ # srt demo
2
+
3
+ {{ usage demo }}
4
+
5
+ The demos that come with the packages this project installs. Without an
6
+ argument it lists them, numbered:
7
+
8
+ ```
9
+ 1 3d/the-third-dimension
10
+ ```
11
+
12
+ `srt demo 1` runs that one, exactly as [srt run](../server/docs.md) runs any
13
+ project: a dev server, a local client window, reload on save, and the same
14
+ server options (`--lan` to reach it from a device, `--port`, `--tunnel`). The qualified
15
+ name works too (`srt demo 3d/the-third-dimension`), which is the stable way to
16
+ name one in a script - the numbers follow the list, and the list follows what
17
+ is installed.
18
+
19
+ A package keeps its demos in `demos/`, and that folder is one project: its own
20
+ package.json, one `assets/` folder, and `src/<name>.tsx` per demo. So the
21
+ demos of one package share a dev server and a port - start a second one while
22
+ the first is up and it says so. Demos from different packages run side by
23
+ side.
24
+
25
+ The demos ship inside the packages, so `node_modules` is where they are found:
26
+ `srt demo` looks in the current directory and nowhere above it. Run it from
27
+ the project root.
@@ -0,0 +1,67 @@
1
+ import { readdirSync } from "node:fs"
2
+ import { join, resolve } from "node:path"
3
+ import { source } from "../lib/args"
4
+
5
+ // srt demo: the demos the installed @solidrt packages ship. A package's
6
+ // demos are ONE project (`<package>/demos/`: package.json, one assets/, and
7
+ // src/<name>.tsx per demo), so a demo runs as the project it lives in - this
8
+ // file only lists and resolves, and main.ts starts the ordinary dev server
9
+ // with its cwd set to that project. Nothing downstream knows about demos.
10
+
11
+ export type Demo = { name: string; cwd: string; entry: string }
12
+
13
+ const SCOPE = join("node_modules", "@solidrt")
14
+
15
+ /** Every demo installed here, sorted so the printed numbers are stable.
16
+ * The cwd and nothing above it - the same rule the server's mode resolution
17
+ * follows (server/mode.ts), so this lists what THIS project installed. */
18
+ function discover(): Demo[] {
19
+ let demos: Demo[] = []
20
+ for (let pkg of names(SCOPE)) {
21
+ let dir = join(SCOPE, pkg, "demos")
22
+ for (let file of names(join(dir, "src"))) {
23
+ if (!file.endsWith(".tsx")) continue
24
+ demos.push({ name: `${pkg}/${file.slice(0, -".tsx".length)}`, cwd: resolve(dir), entry: join("src", file) })
25
+ }
26
+ }
27
+ return demos
28
+ }
29
+
30
+ // A missing folder is the normal case (most packages ship no demos), so it
31
+ // reads as an empty one rather than an error.
32
+ function names(dir: string): string[] {
33
+ try {
34
+ return readdirSync(dir).sort()
35
+ } catch {
36
+ return []
37
+ }
38
+ }
39
+
40
+ function list(demos: Demo[]) {
41
+ for (let [i, demo] of demos.entries()) console.log(` ${String(i + 1).padStart(2)} ${demo.name}`)
42
+ }
43
+
44
+ /** Lists (no argument) or resolves one demo for the dev server to serve. */
45
+ export async function main(): Promise<{ cwd: string; entry: string } | undefined> {
46
+ let demos = discover()
47
+ if (demos.length === 0) {
48
+ console.error(`No demos installed in ${process.cwd()} (looked in ${SCOPE}/*/demos/src/)`)
49
+ process.exit(1)
50
+ }
51
+
52
+ if (source === undefined) {
53
+ list(demos)
54
+ console.log("\nRun one with: srt demo <number>")
55
+ return undefined
56
+ }
57
+
58
+ // A number picks from the printed list; the qualified name it printed
59
+ // works too, so a demo can be named in a script without a fixed index.
60
+ let picked = /^\d+$/.test(source) ? demos[Number(source) - 1] : demos.find((d) => d.name === source)
61
+ if (!picked) {
62
+ console.error(`No such demo: ${source}`)
63
+ list(demos)
64
+ process.exit(1)
65
+ }
66
+ return { cwd: picked.cwd, entry: picked.entry }
67
+ }
@@ -0,0 +1,11 @@
1
+ # srt init
2
+
3
+ {{ usage init }}
4
+
5
+ Scaffolds a new SolidRT project into a new (empty) folder: package.json,
6
+ tsconfig.json, AGENTS.md, a starter src/index.tsx, an empty assets/
7
+ (everything in it ships with the app) and an `.mcp.json`, then installs the
8
+ dependencies. The picker offers extensions on an interactive terminal.
9
+
10
+ The public entry point is `bun create solidrt <dir>`, which forwards here and
11
+ needs nothing installed first.
@@ -1,15 +1,15 @@
1
1
  import { cp, mkdir, readFile, readdir, writeFile } from "node:fs/promises"
2
2
  import { basename, dirname, join, resolve } from "node:path"
3
- import { source, values } from "../args"
4
- import { multiselect, note, text } from "../prompt"
3
+ import { source, values } from "../lib/args"
4
+ import { multiselect, note, text } from "./prompt"
5
5
 
6
6
  const DEFAULT_NAME = "solidrt-app"
7
7
 
8
- const SCAFFOLD_DIR = join(import.meta.dir, "../../scaffold")
8
+ const SCAFFOLD_DIR = join(import.meta.dir, "scaffold")
9
9
  const TEMPLATES_DIR = join(SCAFFOLD_DIR, "templates")
10
10
 
11
11
  // Shared project files written for every template. Sources live in
12
- // cli/scaffold/. The .gitignore is stored there as `gitignore` because npm
12
+ // cli/src/init/scaffold/. The .gitignore is stored there as `gitignore` because npm
13
13
  // strips files literally named `.gitignore` from published packages, so it is
14
14
  // renamed on the way out. The per-template src/ comes from scaffold/templates/.
15
15
  const TEMPLATE_FILES: Array<{ from: string; to: string }> = [
@@ -30,11 +30,27 @@ function packageName(dir: string): string {
30
30
 
31
31
  const DEFAULT_TEMPLATE = "default"
32
32
 
33
+ // One AGENTS.md serves every template, so the lines that point an agent at
34
+ // an extension's docs are fenced between `<!-- <key>:begin/end -->` markers:
35
+ // with the extension selected only the markers go, without it the block goes
36
+ // too, so an app never ships references to files that are not installed.
37
+ function resolveMarkers(text: string, extensions: Extension[]): string {
38
+ for (let ext of EXTENSIONS) {
39
+ let selected = extensions.includes(ext)
40
+ let block = new RegExp(`^<!-- ${ext.key}:begin -->\\n[\\s\\S]*?^<!-- ${ext.key}:end -->\\n`, "gm")
41
+ let marker = new RegExp(`^<!-- ${ext.key}:(?:begin|end) -->\\n`, "gm")
42
+ text = selected ? text.replace(marker, "") : text.replace(block, "")
43
+ }
44
+ return text
45
+ }
46
+
33
47
  // Optional packages an app can opt into on top of core. Each maps to a
34
48
  // dependency in the scaffold package.json (kept when selected, removed
35
- // otherwise) and optionally to a starter under scaffold/templates/.
49
+ // otherwise), to a marker key fencing its lines in scaffold/AGENTS.md, and
50
+ // optionally to a starter under scaffold/templates/.
36
51
  interface Extension {
37
52
  pkg: string
53
+ key: string
38
54
  template?: string
39
55
  description: string
40
56
  }
@@ -42,30 +58,18 @@ interface Extension {
42
58
  const EXTENSIONS: Extension[] = [
43
59
  {
44
60
  pkg: "@solidrt/components",
61
+ key: "components",
45
62
  template: "components",
46
63
  description: "component framework: widgets, theming, navigation",
47
64
  },
48
- { pkg: "@solidrt/3d", description: "general purpose 3D library" },
65
+ { pkg: "@solidrt/2d", key: "2d", description: "general purpose 2D library" },
66
+ { pkg: "@solidrt/3d", key: "3d", description: "general purpose 3D library" },
49
67
  ]
50
68
 
51
- // Resolve which extensions the app takes: an explicit --with list if valid,
52
- // an interactive picker on a TTY, else none (core only).
69
+ // Resolve which extensions the app takes: an interactive picker on a TTY,
70
+ // else none (core only). Extensions are ordinary dependencies, so a script
71
+ // adds them afterwards with `bun add`.
53
72
  async function resolveExtensions(): Promise<Extension[]> {
54
- let raw = values.with
55
- if (raw !== undefined) {
56
- let names = raw.split(",").map((n) => n.trim()).filter(Boolean)
57
- let chosen: Extension[] = []
58
- for (let name of names) {
59
- let found = EXTENSIONS.find((e) => e.pkg === name)
60
- if (!found) {
61
- let all = EXTENSIONS.map((e) => e.pkg).join(", ")
62
- console.error(`!! Unknown extension "${name}"; choose from: ${all}`)
63
- process.exit(1)
64
- }
65
- if (!chosen.includes(found)) chosen.push(found)
66
- }
67
- return chosen
68
- }
69
73
  if (!process.stdin.isTTY) return []
70
74
  // Core is the runtime every app has, so it is not a choice.
71
75
  note("@solidrt/core is always included", "Packages")
@@ -82,7 +86,7 @@ function resolveTemplate(extensions: Extension[]): string {
82
86
  return extensions.find((e) => e.template)?.template ?? DEFAULT_TEMPLATE
83
87
  }
84
88
 
85
- export async function runInitCommand() {
89
+ export async function main() {
86
90
  // The target folder comes from the positional arg, or an interactive prompt
87
91
  // (defaulting to a suggested name) when omitted.
88
92
  let dir = source
@@ -109,7 +113,9 @@ export async function runInitCommand() {
109
113
  for (let { from, to } of TEMPLATE_FILES) {
110
114
  let dest = join(dir, to)
111
115
  await mkdir(dirname(dest), { recursive: true })
112
- await writeFile(dest, await readFile(join(SCAFFOLD_DIR, from)))
116
+ let body: string | Buffer = await readFile(join(SCAFFOLD_DIR, from))
117
+ if (to === "AGENTS.md") body = resolveMarkers(body.toString("utf8"), extensions)
118
+ await writeFile(dest, body)
113
119
  console.log(` Write ${to}`)
114
120
  }
115
121
 
@@ -0,0 +1,98 @@
1
+ # SolidRT app - agent notes
2
+
3
+ This project uses SolidRT: a custom SolidJS renderer that paints through a Rust
4
+ runtime. No DOM, no HTML, no CSS cascade. If you are an AI assistant, read this
5
+ whole file before writing or editing code here - it is short on purpose. The
6
+ depth lives in the topic files listed under "Read before you", one of which you
7
+ should open whenever the work matches its trigger.
8
+
9
+ ## Levels: core, and extensions on top
10
+
11
+ - @solidrt/core is the low-level foundation: host intrinsics (`<window>`,
12
+ `<view>`, `<text>`, the detached `d-*` drawing primitives) with flat props
13
+ that feed the layout and paint engine directly. An app can be written
14
+ entirely at this level.
15
+ - Extensions build on core. The first-party ones are @solidrt/components
16
+ (UI components), @solidrt/2d (2D graphics) and @solidrt/3d (3D graphics).
17
+ None is privileged - an extension is just functions returning core JSX,
18
+ and an app can use a third-party one or grow its own.
19
+
20
+ Match the level the code you are editing already uses. package.json shows
21
+ the choice this app made: only the extensions listed there are installed -
22
+ do not add one for a change core covers.
23
+
24
+ Convention: every @solidrt package describes itself in
25
+ node_modules/@solidrt/<name>/AGENTS.md, carries its prose in docs/, and
26
+ ships working code in examples/. AGENTS.md is the reference for the package;
27
+ open it before using anything from the package, and read the extensions'
28
+ files only when they are installed.
29
+
30
+ ## Read before you
31
+
32
+ The authoritative references ship inside the installed packages. Open the one
33
+ that matches the work; do not work from memory of what a web framework does.
34
+
35
+ - write any reactive code (signals, effects, control flow) ->
36
+ node_modules/solid-js/CHEATSHEET.md - the SolidJS 2.0 model
37
+ - touch elements, props, events, gestures or text ->
38
+ node_modules/@solidrt/core/AGENTS.md, and
39
+ node_modules/@solidrt/core/src/types.d.ts + jsx-runtime.d.ts (source of truth)
40
+ - style a screen: a background, a gradient, a shadow, an effect, vector art,
41
+ a chart -> node_modules/@solidrt/core/agents/painting.md
42
+ - write per-frame code, an animation, or anything writing properties in a
43
+ loop -> node_modules/@solidrt/core/agents/performance.md
44
+ - use an installed extension (UI components, 2D, 3D) ->
45
+ node_modules/@solidrt/<name>/AGENTS.md and its examples/
46
+ - debug a running app, or drive it over MCP to verify a change ->
47
+ node_modules/@solidrt/cli/agents/debugging.md
48
+ - add an asset or font, set the app's identity, or build for distribution ->
49
+ node_modules/@solidrt/cli/agents/assets.md
50
+ - run, bundle, typecheck or render headlessly ->
51
+ node_modules/@solidrt/cli/AGENTS.md
52
+ - copy a working pattern -> node_modules/@solidrt/core/examples/ (see its
53
+ README.md index)
54
+
55
+ <!-- Claude Code auto-imports these; other tools read the paths above. -->
56
+ @./node_modules/solid-js/CHEATSHEET.md
57
+ @./node_modules/@solidrt/core/AGENTS.md
58
+ <!-- components:begin -->
59
+ @./node_modules/@solidrt/components/AGENTS.md
60
+ <!-- components:end -->
61
+ <!-- 2d:begin -->
62
+ @./node_modules/@solidrt/2d/AGENTS.md
63
+ <!-- 2d:end -->
64
+ <!-- 3d:begin -->
65
+ @./node_modules/@solidrt/3d/AGENTS.md
66
+ <!-- 3d:end -->
67
+ @./node_modules/@solidrt/cli/AGENTS.md
68
+
69
+ ## The three traps that cost the most (this is not React/DOM)
70
+
71
+ Each package's AGENTS.md carries its own trap list; these three are
72
+ platform-wide and bite in every app:
73
+
74
+ 1. Reading a signal/prop/store at the top level of a component body (not
75
+ inside JSX, a `createMemo`, or an effect's compute phase) reads it
76
+ untracked - it silently freezes at the initial value. `createEffect` is
77
+ two-argument here: `(compute, apply)`; the Solid 1.x single-arg form does
78
+ not track.
79
+ 2. Writing a signal or store from inside an owned scope - a component body,
80
+ a `createMemo`, an effect's compute phase - throws
81
+ `REACTIVE_WRITE_IN_OWNED_SCOPE` in dev. Move the write into an event
82
+ handler, an effect's apply phase, `onSettled`, or an `untrack` block; opt
83
+ in with `createSignal(v, { ownedWrite: true })` for internal state.
84
+ 3. An element-valued prop (children, a content/icon slot) builds a fresh
85
+ native subtree on EVERY read, and an uninserted subtree is never freed -
86
+ a permanent memory leak, not wasted work. Read such props exactly once,
87
+ where they mount; inspect them through the `children()` helper.
88
+
89
+ ## Run / verify
90
+
91
+ - FIRST check whether a dev server and a client are already running and
92
+ build against those; do not start a second `srt run` when one is up.
93
+ - The dev loop (reload, logs, snapshots, the holds on reload-on-save and on
94
+ the user's input), typechecking, headless rendering and the MCP tools:
95
+ node_modules/@solidrt/cli/AGENTS.md and its agents/debugging.md. Read it
96
+ before the first reload. The `.mcp.json` here is Claude Code's convention;
97
+ if your client lists no `solidrt` tools, debugging.md has the entry to add
98
+ to its own config.
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "solidrt-app",
3
+ "type": "module",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "srt run",
7
+ "pack": "srt pack -o out",
8
+ "server": "srt server",
9
+ "client": "srt client",
10
+ "android": "srt android"
11
+ },
12
+ "dependencies": {
13
+ "@solidrt/core": "0.0.52",
14
+ "@solidrt/components": "0.0.52",
15
+ "@solidrt/2d": "0.0.52",
16
+ "@solidrt/3d": "0.0.52"
17
+ },
18
+ "devDependencies": {
19
+ "@solidrt/cli": "0.0.52",
20
+ "@solidrt/flux-types": "0.0.52",
21
+ "typescript": "^7"
22
+ }
23
+ }
@@ -1,7 +1,6 @@
1
1
  // Components template: built with the @solidrt/components framework.
2
- import { render, createLinearGradient } from "@solidrt/core"
2
+ import { render, createLinearGradient, Logo } from "@solidrt/core"
3
3
  import { Window, SafeArea, View, Text } from "@solidrt/components"
4
- import { Icon } from "./icon"
5
4
 
6
5
  function App() {
7
6
  let backgroundColor = createLinearGradient(0, 0, 1, 1, [
@@ -13,7 +12,7 @@ function App() {
13
12
  <Window title="The Solid Runtime" style={{ backgroundColor }}>
14
13
  <SafeArea>
15
14
  <View layout={{ flex: 1, gap: 20, alignItems: "center", justifyContent: "center" }}>
16
- <Icon />
15
+ <Logo size={300} animation="loop" />
17
16
  <Text layout={{ fontSize: 40 }} style={{ color: "#ccc" }}>
18
17
  The Solid Runtime
19
18
  </Text>
@@ -1,5 +1,4 @@
1
- import { render, createLinearGradient, safeArea } from "@solidrt/core"
2
- import { Icon } from "./icon"
1
+ import { render, createLinearGradient, safeArea, Logo } from "@solidrt/core"
3
2
 
4
3
  function App() {
5
4
  let backgroundColor = createLinearGradient(0, 0, 1, 1, [
@@ -18,7 +17,7 @@ function App() {
18
17
  paddingTop={safeArea().top}
19
18
  paddingBottom={safeArea().bottom}
20
19
  >
21
- <Icon />
20
+ <Logo size={300} animation="loop" />
22
21
  <text fontSize={40} color="#ccc">The Solid Runtime</text>
23
22
  </view>
24
23
  </window>