diff --git a/README.md b/README.md index d524f43..00165be 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ flowchart LR ### Prerequisites - [Bun](https://bun.sh/) 1.1+ installed (`bun --version`) +- A [GitHub Copilot](https://github.com/features/copilot) subscription (for the `compile build` command) ### Install dependencies @@ -171,63 +172,139 @@ All normative sections (Visual rules, Behavior, Edge cases) use | `bun` | TypeScript | OpenTUI + React (Bun) | `targets/bun.md` | | `rust` | Rust | Ratatui + Crossterm | `targets/rust.md` | +### Commands + +| Command | Purpose | +| ------- | ------- | +| `compile status` | Show dirty/locked specs per target | +| `compile prompt` | Generate a compilation prompt file | +| `compile build` | Run an agent session to compile specs (requires Copilot) | +| `compile lock` | Lock spec hashes after verified compilation | +| `compile clean` | Remove lock file for a target | + +### Build flags + +``` +bun run compile build --target [flags] + +Required: + --target Target to compile (go, node, bun, rust) + +Optional: + --component Compile a single component/token only + --out Output directory (default: dist/) + --model Model to use (e.g. claude-sonnet-4, gpt-5) + --effort Reasoning effort: low | medium | high | xhigh + --verbose Show full agent transcript (raw streaming) + --no-lock Prevent the agent from locking components + --autopilot Use SDK autopilot mode (agent runs fully autonomously) + --all-targets Compile all targets sequentially +``` + ### Workflow ```bash # 1. See what's changed bun run compile status -# 2. Generate the compilation prompt -bun run compile prompt --target go - -# 3. Feed dist/go/_compile-prompt.md to an LLM agent -# The agent generates code into dist/go/ +# 2. Compile interactively (prompts for model, effort, output dir) +bun run compile build --target bun -# 4. Verify: run tests, check the demo CLI -cd dist/go && go test ./... && go run ./cmd/demo +# 3. Or compile non-interactively with all options +bun run compile build --target bun --model claude-sonnet-4 --out dist/bun-claude -# 5. Lock the hashes -bun run compile lock --target go +# 4. Or fire-and-forget with autopilot (SDK handles everything) +bun run compile build --target bun --model claude-sonnet-4 --autopilot ``` ### Multi-pass compilation -A single compilation pass across the full component suite (17 components + -tokens + demo) is usually not enough to reach production quality. We've found -that **2–3 passes** produce notably better results: +The compiler supports two modes, controlled by the `--autopilot` flag: + +**Interactive mode** (default): The SDK agent runs in `interactive` mode. +After the initial compilation pass, the compiler asks whether to continue with +another pass. Each pass sends an improvement prompt — the agent reviews, fixes, +and extends its own work. You see a boxed markdown summary after each pass. + +**Autopilot mode** (`--autopilot`): Sets the SDK agent mode to `autopilot`. +The agent runs fully autonomously — it decides when to iterate, how many passes +to make, and when the work is complete. No user confirmation is needed. | Pass | Focus | Typical outcome | | ---- | ----- | --------------- | -| **1st** | Initial generation | All components scaffold correctly, most tests pass, demo wires up. Expect rough edges — missing edge cases, incomplete keybindings, demo wiring bugs. | -| **2nd** | Review & fix | Agent reviews its own output against specs, fixes test failures, fills in missing behavior, improves demo interactivity. Test count typically grows 30–50%. | -| **3rd** | Polish | Catches subtle spec violations, improves accessibility, hardens demo `--snapshot` smoke tests. Diminishing returns after this point. | +| **1st** | Initial generation | Core tokens, first components fully wired into interactive demo. | +| **2nd** | Extend & fix | More components added, test failures fixed, demo polished. | +| **3rd** | Polish | Catches subtle spec violations, hardens edge cases. | -To run a follow-up pass, generate a new prompt and tell the agent to review -and complete its existing work: +The agent is instructed to follow a **depth-over-breadth** philosophy: it fully +completes each component (implementation + tests + interactive demo) before +moving to the next one. -```bash -# Generate a fresh prompt (it sees the current dist/ state) -bun run compile prompt --target go +### Component locking -# Feed to the agent with instructions like: -# "Review your existing implementation against the specs. -# Fix any test failures, fill in missing behavior, -# and ensure all --snapshot smoke tests pass." +The agent locks components individually as it completes them by running: + +```bash +bun run compile lock --target bun --component Select ``` -Each pass is fast because the agent builds on its own prior output rather than -starting from scratch. The demo's `--list` and `--snapshot` flags make it easy -for the agent to self-verify between passes. +This records the spec hash so the component won't be recompiled unless its spec +changes. You can also lock manually after verifying generated code: + +```bash +# Lock a single component +bun run compile lock --target go --component Input + +# Lock all specs for a target +bun run compile lock --target go + +# Lock all targets +bun run compile lock --all-targets +``` ### Custom output directory -By default, compiled code goes to `dist/`. Override with `--out`: +By default, compiled code goes to `dist//`. Override with `--out`: + +```bash +# Output to a custom directory +bun run compile build --target go --out dist/go-experimental + +# The prompt and generated code go directly to dist/go-experimental/ +``` + +### Generating prompts manually + +If you prefer to feed the prompt to an external agent (Claude, ChatGPT, Copilot +Chat, etc.) instead of using `compile build`, use the `prompt` command: ```bash -# Output to a separate repo or directory -bun run compile prompt --target go --out ~/my-tuikit-go +# Generate a prompt for a target +bun run compile prompt --target go -# The prompt and generated code go to ~/my-tuikit-go/go/ +# Generate for a single component +bun run compile prompt --target bun --component Select + +# Generate to a custom directory +bun run compile prompt --target node --out ~/my-project +``` + +The prompt is written to `//_compile-prompt.md` (e.g. `dist/go/_compile-prompt.md`). It contains: + +- The target definition (framework, paradigm, file structure) +- An index of all dirty specs with file paths and summaries +- Instructions for the agent (depth-first, verification steps) +- Demo specification reference + +> **Important:** Any coding session that uses this prompt should set its working +> directory to the repository root (where `components/`, `tokens/`, and `docs/` +> live). The prompt references spec files using paths relative to the repo root. + +Feed this file to any LLM agent, then lock manually once verified: + +```bash +# After the agent generates code and tests pass: +bun run compile lock --target go ``` ### Adding a new target @@ -237,7 +314,7 @@ bun run compile prompt --target go --out ~/my-tuikit-go machine pattern, token access, styling, composition, test pattern, key mapping, dependencies, and demo CLI 3. Run `bun run compile status` — your target will show up with all specs dirty -4. Run `bun run compile prompt --target {name}` and compile +4. Run `bun run compile build --target {name}` to compile ## Linting diff --git a/bun.lock b/bun.lock index 46dc876..d0a19c9 100644 --- a/bun.lock +++ b/bun.lock @@ -4,9 +4,14 @@ "workspaces": { "": { "dependencies": { + "@clack/prompts": "^1.2.0", + "@github/copilot-sdk": "^0.3.0", + "boxen": "^8.0.1", "chalk": "^5.6.2", "fast-glob": "^3.3.3", "gray-matter": "^4.0.3", + "marked": "14", + "marked-terminal": "7", "remark": "^15.0.1", "remark-frontmatter": "^5.0.0", "zod": "^4.3.6", @@ -14,12 +19,36 @@ }, }, "packages": { + "@clack/core": ["@clack/core@1.2.0", "", { "dependencies": { "fast-wrap-ansi": "^0.1.3", "sisteransi": "^1.0.5" } }, "sha512-qfxof/3T3t9DPU/Rj3OmcFyZInceqj/NVtO9rwIuJqCUgh32gwPjpFQQp/ben07qKlhpwq7GzfWpST4qdJ5Drg=="], + + "@clack/prompts": ["@clack/prompts@1.2.0", "", { "dependencies": { "@clack/core": "1.2.0", "fast-string-width": "^1.1.0", "fast-wrap-ansi": "^0.1.3", "sisteransi": "^1.0.5" } }, "sha512-4jmztR9fMqPMjz6H/UZXj0zEmE43ha1euENwkckKKel4XpSfokExPo5AiVStdHSAlHekz4d0CA/r45Ok1E4D3w=="], + + "@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="], + + "@github/copilot": ["@github/copilot@1.0.36", "", { "optionalDependencies": { "@github/copilot-darwin-arm64": "1.0.36", "@github/copilot-darwin-x64": "1.0.36", "@github/copilot-linux-arm64": "1.0.36", "@github/copilot-linux-x64": "1.0.36", "@github/copilot-win32-arm64": "1.0.36", "@github/copilot-win32-x64": "1.0.36" }, "bin": { "copilot": "npm-loader.js" } }, "sha512-x0N5wLzw+tANzb+vCFYLHn3BV3qii2oyn14wC20RO7SsS8/YeBH8olvwlDLJ4PB0mL17QOiytNCdkvjvprm28w=="], + + "@github/copilot-darwin-arm64": ["@github/copilot-darwin-arm64@1.0.36", "", { "os": "darwin", "cpu": "arm64", "bin": { "copilot-darwin-arm64": "copilot" } }, "sha512-5qkb7frTS4K/LdTDLrzKo78VR4aw/EZ6JzLz4KfmaW4UYyPiNirExDFXa/By22X0o8YMfOp4MCA2KSCAxKdgTg=="], + + "@github/copilot-darwin-x64": ["@github/copilot-darwin-x64@1.0.36", "", { "os": "darwin", "cpu": "x64", "bin": { "copilot-darwin-x64": "copilot" } }, "sha512-AdsM8QtM5QSzMLpavLREh8HALO5G+VWzGNQqIHu4f0YQC/s1cGoiwo3wsgkpxRcLGBykFc+bDX3yK3MDQ8XvSw=="], + + "@github/copilot-linux-arm64": ["@github/copilot-linux-arm64@1.0.36", "", { "os": "linux", "cpu": "arm64", "bin": { "copilot-linux-arm64": "copilot" } }, "sha512-n7K1I6r0ggOJ4A9uAMS11USTvn6BKtAwvrOkzEaeRK89VNUJzpTe6p0mE13ItzRe5eot9WLBQOxvXLtL9f6E+g=="], + + "@github/copilot-linux-x64": ["@github/copilot-linux-x64@1.0.36", "", { "os": "linux", "cpu": "x64", "bin": { "copilot-linux-x64": "copilot" } }, "sha512-wBtCdR3ITZcq07BJbkwHfwI6ayiwbH5pF1ex+Ycl4UI+Lf1vP9eQD6wJppPgsrjwFcdeWRThaYTPCRTkSGHv5g=="], + + "@github/copilot-sdk": ["@github/copilot-sdk@0.3.0", "", { "dependencies": { "@github/copilot": "^1.0.36-0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" } }, "sha512-SUo35k56pzzgYgwmDPHcu7kZxPrzXbH66IWXaEf6pmb94DlA709F82HrrDeja087TL4djJ9OuvRFWWOKCosAsg=="], + + "@github/copilot-win32-arm64": ["@github/copilot-win32-arm64@1.0.36", "", { "os": "win32", "cpu": "arm64", "bin": { "copilot-win32-arm64": "copilot.exe" } }, "sha512-0GzZUZQn07alI8BgbzK0NlR5+ta/Rd0sWmd8kbRCns7oybAIkSALy6BKVwJmVHtXUi6h4iUE8oiFhkn0spymvw=="], + + "@github/copilot-win32-x64": ["@github/copilot-win32-x64@1.0.36", "", { "os": "win32", "cpu": "x64", "bin": { "copilot-win32-x64": "copilot.exe" } }, "sha512-UBX9qj0McCK/SLq93XIr1i80fj3b3XmE3befVFrzxQuTeOoxLURN35vi7W+4x+4ZfsDHQpRTlJNjZw9w0fPr+Q=="], + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + "@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="], + "@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], @@ -28,16 +57,44 @@ "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + "ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="], + + "ansi-escapes": ["ansi-escapes@7.3.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="], + + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], + "argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], + "boxen": ["boxen@8.0.1", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^8.0.0", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "string-width": "^7.2.0", "type-fest": "^4.21.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0" } }, "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw=="], + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + "camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="], + "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="], + "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], + "cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="], + + "cli-highlight": ["cli-highlight@2.1.11", "", { "dependencies": { "chalk": "^4.0.0", "highlight.js": "^10.7.1", "mz": "^2.4.0", "parse5": "^5.1.1", "parse5-htmlparser2-tree-adapter": "^6.0.0", "yargs": "^16.0.0" }, "bin": { "highlight": "bin/highlight" } }, "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg=="], + + "cli-table3": ["cli-table3@0.6.5", "", { "dependencies": { "string-width": "^4.2.0" }, "optionalDependencies": { "@colors/colors": "1.5.0" } }, "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ=="], + + "cliui": ["cliui@7.0.4", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], @@ -46,6 +103,14 @@ "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + + "emojilib": ["emojilib@2.4.0", "", {}, "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw=="], + + "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], @@ -56,6 +121,12 @@ "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + "fast-string-truncated-width": ["fast-string-truncated-width@1.2.1", "", {}, "sha512-Q9acT/+Uu3GwGj+5w/zsGuQjh9O1TyywhIwAxHudtWrgF09nHOPrvTLhQevPbttcxjr/SNN7mJmfOw/B1bXgow=="], + + "fast-string-width": ["fast-string-width@1.1.0", "", { "dependencies": { "fast-string-truncated-width": "^1.2.0" } }, "sha512-O3fwIVIH5gKB38QNbdg+3760ZmGz0SZMgvwJbA1b2TGXceKE6A2cOlfogh1iw8lr049zPyd7YADHy+B7U4W9bQ=="], + + "fast-wrap-ansi": ["fast-wrap-ansi@0.1.6", "", { "dependencies": { "fast-string-width": "^1.1.0" } }, "sha512-HlUwET7a5gqjURj70D5jl7aC3Zmy4weA1SHUfM0JFI0Ptq987NH2TwbBFLoERhfwk+E+eaq4EK3jXoT+R3yp3w=="], + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], "fault": ["fault@2.0.1", "", { "dependencies": { "format": "^0.2.0" } }, "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ=="], @@ -64,14 +135,24 @@ "format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="], + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], + "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="], + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="], + "is-extendable": ["is-extendable@0.1.1", "", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="], "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], @@ -84,6 +165,10 @@ "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], + "marked": ["marked@14.1.4", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-vkVZ8ONmUdPnjCKc5uTRvmkRbx4EAi2OkTOXmfTDhZz3OFqMNBM1oTTWwTr4HY4uAEojhzPf+Fy8F1DWa3Sndg=="], + + "marked-terminal": ["marked-terminal@7.3.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "ansi-regex": "^6.1.0", "chalk": "^5.4.1", "cli-highlight": "^2.1.11", "cli-table3": "^0.6.5", "node-emoji": "^2.2.0", "supports-hyperlinks": "^3.1.0" }, "peerDependencies": { "marked": ">=1 <16" } }, "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw=="], + "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], "mdast-util-frontmatter": ["mdast-util-frontmatter@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "escape-string-regexp": "^5.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-extension-frontmatter": "^2.0.0" } }, "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA=="], @@ -144,6 +229,16 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], + + "node-emoji": ["node-emoji@2.2.0", "", { "dependencies": { "@sindresorhus/is": "^4.6.0", "char-regex": "^1.0.2", "emojilib": "^2.4.0", "skin-tone": "^2.0.0" } }, "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw=="], + + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "parse5": ["parse5@5.1.1", "", {}, "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug=="], + + "parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@6.0.1", "", { "dependencies": { "parse5": "^6.0.1" } }, "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA=="], + "picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], @@ -156,20 +251,42 @@ "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], "section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="], + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], + + "skin-tone": ["skin-tone@2.0.0", "", { "dependencies": { "unicode-emoji-modifier-base": "^1.0.0" } }, "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA=="], + "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], + "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + "strip-bom-string": ["strip-bom-string@1.0.0", "", {}, "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g=="], + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "supports-hyperlinks": ["supports-hyperlinks@3.2.0", "", { "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" } }, "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig=="], + + "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], + + "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + + "unicode-emoji-modifier-base": ["unicode-emoji-modifier-base@1.0.0", "", {}, "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g=="], + "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], @@ -184,8 +301,62 @@ "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], + "vscode-jsonrpc": ["vscode-jsonrpc@8.2.1", "", {}, "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ=="], + + "widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="], + + "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="], + + "yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="], + "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + + "ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "cli-highlight/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "cli-table3/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "parse5-htmlparser2-tree-adapter/parse5": ["parse5@6.0.1", "", {}, "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="], + + "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "ansi-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "cli-highlight/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "cli-table3/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "cli-table3/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "cli-table3/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], } } diff --git a/package.json b/package.json index a763e54..5c8a32b 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,14 @@ "compile": "bun scripts/compile.ts" }, "dependencies": { + "@clack/prompts": "^1.2.0", + "@github/copilot-sdk": "^0.3.0", + "boxen": "^8.0.1", "chalk": "^5.6.2", "fast-glob": "^3.3.3", "gray-matter": "^4.0.3", + "marked": "14", + "marked-terminal": "7", "remark": "^15.0.1", "remark-frontmatter": "^5.0.0", "zod": "^4.3.6" diff --git a/scripts/compile.ts b/scripts/compile.ts index 31969a5..0f86ad2 100644 --- a/scripts/compile.ts +++ b/scripts/compile.ts @@ -3,20 +3,27 @@ * TUIkit spec compiler * * Detects changed specs via content hashing and generates self-contained - * compilation prompts for LLM agents. Lock files track which spec versions - * have been compiled per target. + * compilation prompts for LLM agents. The `build` command uses the Copilot SDK + * to launch an agent session that compiles specs into code automatically. * * Usage: - * bun run compile status [--target ] - * bun run compile prompt --target [--component ] - * bun run compile lock --target [--component ] | --all-targets - * bun run compile clean --target | --all-targets + * bun run compile status [--target ] + * bun run compile prompt --target [--component ] + * bun run compile build --target [--component ] [--model ] [--effort ] [--verbose] [--no-lock] [--autopilot] + * bun run compile lock --target [--component ] | --all-targets + * bun run compile clean --target | --all-targets */ import { createHash } from "node:crypto"; import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs"; import { dirname, join, relative } from "node:path"; import chalk from "chalk"; +import * as clack from "@clack/prompts"; +import { marked } from "marked"; +import { markedTerminal } from "marked-terminal"; +import boxen from "boxen"; + +marked.use(markedTerminal()); // biome-ignore lint/suspicious/noConsole: CLI tool — stdout is the interface const log = (...args: unknown[]) => console.log(...args); @@ -58,6 +65,27 @@ interface LockFile { entries: Record; } +type ReasoningEffort = "low" | "medium" | "high" | "xhigh"; + +interface BuildConfig { + model: string; + effort: ReasoningEffort | undefined; + distDir: string; + supportsEffort: boolean; +} + +interface CompileMetrics { + startTime: number; + inputTokens: number; + outputTokens: number; + reasoningTokens: number; + toolCalls: number; + filesWritten: Set; + filesDeleted: Set; + lastAssistantMessage: string; + errors: string[]; +} + // ── Helpers ──────────────────────────────────────────────────────────────── function sha256(content: string): string { @@ -318,43 +346,698 @@ function generatePrompt(target: string, specs: SpecEntry[], allSpecs: SpecEntry[ sections.push(""); sections.push("IMPORTANT: Do NOT spawn sub-agents or delegate to the task tool. Do ALL work yourself directly."); sections.push(""); + sections.push("### Philosophy: depth over breadth"); + sections.push(""); + sections.push("It is MUCH better to have a few components that work perfectly — with full"); + sections.push("interactivity, passing tests, and a working interactive demo — than many"); + sections.push("components that are half-baked. Each component you implement must be"); + sections.push("**complete and polished** before moving to the next one."); + sections.push(""); + sections.push("### Workflow"); + sections.push(""); sections.push("1. Read the target definition to understand the framework and paradigm."); - sections.push("2. For each component listed above, **read the full spec file** from disk, then implement it."); - sections.push("3. For each component with a test file, **read the test spec** and implement runnable tests."); - sections.push("4. For each component with a preview file, **read the preview spec** and build a demo screen."); - sections.push("5. For each token listed above, **read the full spec file** from disk, then implement it."); - sections.push(`6. Output all files to: \`${relative(SPECS_DIR, join(distDir, target))}/\``); + sections.push("2. Implement all **tokens first** — read each token spec from disk, implement it."); + sections.push("3. Then implement components **one at a time, fully**, in this order:"); + sections.push(" a. Read the full spec file from disk."); + sections.push(" b. Implement the component with all variants and interactions."); + sections.push(" c. Read the test spec and implement runnable tests. Run them — they must pass."); + sections.push(" d. Wire the component into the interactive demo (see below)."); + sections.push(" e. Verify the component works in the demo with `--component --snapshot`."); + sections.push(" f. Only then move to the next component."); + sections.push(`4. Output all files to: \`${relative(SPECS_DIR, distDir)}/\``); sections.push(` This is the dist directory — keep all generated code here, separate from specs.`); sections.push(""); if (existsSync(DEMO_PATH)) { sections.push("---"); - sections.push("## Demo specification"); + sections.push("## Demo specification — INTERACTIVE PLAYGROUND (required)"); + sections.push(""); + sections.push("The demo is NOT a static listing. It is a **fully interactive playground**"); + sections.push("where you can navigate between components and interact with live instances."); + sections.push(`Read the full spec: \`${relative(SPECS_DIR, DEMO_PATH)}\``); sections.push(""); - sections.push("The demo app is an interactive component preview browser."); - sections.push(`Read the full spec before building the demo: \`${relative(SPECS_DIR, DEMO_PATH)}\``); + sections.push("Key requirements:"); + sections.push("- `--interactive` MUST launch a full-screen TUI with sidebar + preview panel."); + sections.push("- Every previewed component MUST be a live, interactive instance (e.g., you can"); + sections.push(" type in an Input, navigate a Select, scroll a ScrollBox)."); + sections.push("- Implement the interactive mode **from the first component** — do not leave it"); + sections.push(" as a stub. It's better to have 3 components in a working playground than"); + sections.push(" 10 components with `--interactive` not implemented."); + sections.push("- `--list` and `--snapshot` modes are secondary — they must work, but the"); + sections.push(" interactive playground is the primary output."); sections.push(""); } sections.push("---"); sections.push("## Verification (REQUIRED)"); sections.push(""); - sections.push("After generating ALL files, you MUST verify in this order:"); + sections.push("After implementing each component (not just at the end), verify:"); sections.push(""); - sections.push("1. **Run unit tests**: Execute the target's test command and ensure ALL tests pass."); - sections.push(" Fix any failures before proceeding."); - sections.push("2. **Build the demo**: Compile/build the demo CLI and verify it starts without errors."); - sections.push("3. **Verify demo --list**: Run the demo with `--list` and confirm all components/tokens appear."); - sections.push("4. **Verify demo --snapshot**: For EVERY component from `--list`, run"); - sections.push(" `--component --snapshot` and confirm it exits 0 with non-empty output."); - sections.push(" If any snapshot fails, fix the demo wiring before continuing."); - sections.push("5. **Run demo smoke tests**: Execute the demo test file and ensure all snapshot tests pass."); + sections.push("1. **Unit tests pass**: Run the target's test command for that component."); + sections.push("2. **Demo snapshot works**: `--component --snapshot` exits 0 with output."); + sections.push("3. **Interactive demo works**: `--interactive` launches and the component is navigable."); + sections.push(""); + sections.push("After ALL components are done:"); + sections.push(""); + sections.push("4. **Full test suite**: Run all tests, ensure everything passes."); + sections.push("5. **Demo smoke tests**: Run the demo test file, all snapshots pass."); sections.push("6. **Report**: State the final unit test count, demo smoke test count, and pass/fail status."); sections.push(""); return sections.join("\n"); } +// ── Build helpers ────────────────────────────────────────────────────────── + +function formatDuration(ms: number): string { + const secs = Math.floor(ms / 1000); + if (secs < 60) return `${secs}s`; + const mins = Math.floor(secs / 60); + const rem = secs % 60; + return `${mins}m ${rem}s`; +} + +/** Count LOC across files the agent actually wrote (ignores node_modules etc.) */ +function countAgentOutput(filesWritten: Set): { files: number; lines: number } { + let lines = 0; + let files = 0; + for (const fp of filesWritten) { + if (!existsSync(fp)) continue; + files++; + lines += readFileSync(fp, "utf-8").split("\n").length; + } + return { files, lines }; +} + +function summarizeArgs(args: unknown): string { + if (!args || typeof args !== "object") return ""; + const obj = args as Record; + const path = obj.path ?? obj.file_path ?? obj.command; + if (typeof path === "string") { + const short = path.length > 60 ? `…${path.slice(-57)}` : path; + return short; + } + return ""; +} + +function detectPhase(toolName: string, args: unknown): string { + const obj = (args ?? {}) as Record; + const path = String(obj.path ?? obj.file_path ?? obj.filePath ?? obj.file ?? ""); + const cmd = String(obj.command ?? ""); + const tn = toolName.toLowerCase(); + + if (tn.includes("read") || tn === "view") { + if (path.includes("tokens/") || path.includes("components/") || path.includes("docs/")) { + return "Reading specs"; + } + return "Reading files"; + } + if (tn.includes("edit") || tn.includes("create") || tn.includes("write")) { + const match = path.match(/components\/(\w+)/); + if (match) return `Implementing ${match[1]}`; + if (path.includes("tokens/")) return "Implementing tokens"; + if (path.includes("demo")) return "Building demo"; + return "Writing files"; + } + if (tn === "bash" || tn === "shell" || tn.includes("terminal") || tn.includes("command")) { + if (cmd.includes("test")) return "Running tests"; + if (cmd.includes("build") || cmd.includes("compile")) return "Building"; + if (cmd.includes("run")) return "Running"; + return "Executing command"; + } + if (tn === "glob" || tn === "grep" || tn.includes("search") || tn.includes("find")) return "Searching files"; + if (tn.includes("delete")) return "Cleaning up"; + return "Working"; +} + +// ── Build command ────────────────────────────────────────────────────────── + +async function confirmPass(): Promise { + const result = await clack.confirm({ + message: "Do another pass? (improves consistency)", + initialValue: true, + }); + if (clack.isCancel(result)) return false; + return result; +} + +async function ensureCopilotAuth(): Promise { + const { CopilotClient } = await import("@github/copilot-sdk"); + const client = new CopilotClient({ useLoggedInUser: true }); + + try { + await client.start(); + await client.ping(); + } catch (err) { + log(chalk.red("✗") + " Copilot authentication failed.\n"); + log(" The build command requires a valid GitHub Copilot subscription."); + log(" Try one of:\n"); + log(` ${chalk.cyan("copilot auth login")} Sign in via browser`); + log(` ${chalk.cyan("export GITHUB_TOKEN=ghp_...")} Use a personal access token`); + log(` ${chalk.cyan("export GH_TOKEN=ghp_...")} GitHub CLI token\n`); + if (err instanceof Error) log(chalk.dim(` Error: ${err.message}`)); + process.exit(1); + } + + return client; +} + +async function pickModel( + client: import("@github/copilot-sdk").CopilotClient, + preselected?: string, +): Promise<{ id: string; name: string }> { + const models = await client.listModels(); + if (models.length === 0) { + log(chalk.red("✗") + " No models available. Check your Copilot subscription."); + process.exit(1); + } + + const defaultModel = + (preselected ? models.find((m) => m.id === preselected) : undefined) ?? + models.find((m) => m.id === "claude-sonnet-4") ?? + models[0]; + + if (!process.stdin.isTTY) { + return { id: defaultModel.id, name: defaultModel.name }; + } + + const result = await clack.select({ + message: "Select model:", + options: models.map((m) => ({ value: m.id, label: `${m.name} (${m.id})` })), + initialValue: defaultModel.id, + }); + + if (clack.isCancel(result)) { + clack.cancel("Build cancelled."); + process.exit(0); + } + + const model = models.find((m) => m.id === result) ?? defaultModel; + return { id: model.id, name: model.name }; +} + +async function pickEffort(preselected?: string): Promise { + const defaultEffort = (preselected && ["low", "medium", "high", "xhigh"].includes(preselected)) + ? preselected + : "high"; + + if (!process.stdin.isTTY) return defaultEffort as ReasoningEffort; + + const result = await clack.select({ + message: "Reasoning effort:", + options: [ + { value: "low", label: "low" }, + { value: "medium", label: "medium" }, + { value: "high", label: "high" }, + { value: "xhigh", label: "xhigh" }, + ], + initialValue: defaultEffort, + }); + + if (clack.isCancel(result)) { + clack.cancel("Build cancelled."); + process.exit(0); + } + + return result as ReasoningEffort; +} + +async function pickOutputDir(target: string, preselected?: string): Promise { + const defaultDir = preselected + ? join(process.cwd(), preselected) + : join(DEFAULT_DIST_DIR, target); + const displayDefault = relative(SPECS_DIR, defaultDir) || "."; + + if (!process.stdin.isTTY) return defaultDir; + + const result = await clack.text({ + message: "Output directory:", + initialValue: displayDefault, + }); + + if (clack.isCancel(result)) { + clack.cancel("Build cancelled."); + process.exit(0); + } + + if (!result || result === displayDefault) return defaultDir; + return join(SPECS_DIR, result); +} + +async function promptBuildConfig( + client: import("@github/copilot-sdk").CopilotClient, + flags: { model?: string; effort?: string; out?: string }, + target: string, +): Promise { + clack.intro(chalk.cyan("TUIkit compiler")); + + // Fetch available models to validate and check capabilities + const models = await client.listModels(); + + // Always prompt for model (flag value becomes the pre-selected default) + const model = await pickModel(client, flags.model); + + // Check if model supports reasoning effort + const modelInfo = models.find((m) => m.id === model.id); + const supportsEffort = !!(modelInfo?.supportedReasoningEfforts && modelInfo.supportedReasoningEfforts.length > 0); + + // Always prompt for effort if model supports it (flag becomes default) + let effort: ReasoningEffort | undefined; + if (supportsEffort) { + effort = await pickEffort(flags.effort); + } + + // Always prompt for output location (flag or dist/ as default) + const distDir = await pickOutputDir(target, flags.out); + + return { model: model.id, effort, distDir, supportsEffort }; +} + +function printBuildHeader(target: string, config: BuildConfig, mode: string): void { + const effortStr = config.effort ? ` · Effort: ${config.effort}` : ""; + log(`\n${chalk.cyan("●")} ${chalk.bold("TUIkit compiler")}`); + log(` Target: ${chalk.bold(target)} · Model: ${chalk.bold(config.model)}${effortStr}`); + log(` Output: ${relative(SPECS_DIR, config.distDir)}/ · Mode: ${mode}\n`); +} + +function printSummary( + target: string, + config: BuildConfig, + metrics: CompileMetrics, + outDir: string, + noLock: boolean, + passNumber = 1, +): void { + const elapsed = Date.now() - metrics.startTime; + const { files, lines } = countAgentOutput(metrics.filesWritten); + const deleted = metrics.filesDeleted.size; + const totalTokens = metrics.inputTokens + metrics.outputTokens; + + const tokenDetail = + `(${metrics.inputTokens.toLocaleString()} in / ${metrics.outputTokens.toLocaleString()} out` + + `${metrics.reasoningTokens ? ` / ${metrics.reasoningTokens.toLocaleString()} reasoning` : ""})`; + + const passLabel = passNumber > 1 ? ` (pass ${passNumber})` : ""; + const filesLine = deleted > 0 ? `${files} written, ${deleted} deleted` : `${files} written`; + const body = [ + `${chalk.green("✓")} Compilation complete — target: ${target}${passLabel}`, + ``, + ` Model: ${config.model}${config.effort ? ` (${config.effort} effort)` : ""}`, + ` Time: ${formatDuration(elapsed)}`, + ` Files: ${filesLine}`, + ` LOC: ~${lines.toLocaleString()} lines`, + ` Tokens: ~${totalTokens.toLocaleString()} total ${tokenDetail}`, + ` Tools: ${metrics.toolCalls} calls`, + ` Passes: ${passNumber}`, + ``, + ` Output: ${relative(SPECS_DIR, outDir)}/`, + noLock ? ` Lock: skipped (--no-lock)` : ` Lock: ${relative(SPECS_DIR, lockPath(target))} updated`, + ].join("\n"); + + log(""); + log(body); + log(""); +} + +async function cmdBuild( + target: string, + componentFilter?: string, + _distDir: string = DEFAULT_DIST_DIR, + flagModel?: string, + flagEffort?: string, + verbose = false, + noLock = false, + autopilot = false, +): Promise { + const { approveAll } = await import("@github/copilot-sdk"); + + // 1. Quick check — any dirty specs at all? + const specs = discoverSpecs(); + const schemaHash = sha256(readFile(SCHEMA_PATH)); + const lock = readLock(target); + let dirty = computeDirty(specs, lock, schemaHash); + + if (componentFilter) { + dirty = dirty.filter( + (d) => d.spec.name === `components/${componentFilter}` || d.spec.name === `tokens/${componentFilter}`, + ); + } + + if (dirty.length === 0) { + log(`${chalk.green("✓")} No dirty specs for target "${target}". Nothing to compile.`); + log(` ${chalk.dim(`Lock: ${relative(SPECS_DIR, lockPath(target))}`)}`); + return; + } + + // 2. Auth first (fail fast before interactive prompts) + log(chalk.dim(" Authenticating with Copilot...")); + const client = await ensureCopilotAuth(); + + // 3. Interactive config — always prompts with good defaults + // Flags pre-select the default; user can still change it. + const config = await promptBuildConfig( + client, + { + model: flagModel, + effort: flagEffort, + out: _distDir !== DEFAULT_DIST_DIR ? relative(process.cwd(), _distDir) : undefined, + }, + target, + ); + + // 4. Generate prompt (uses config.distDir chosen by the user) + const distDir = config.distDir; + const dirtySpecs = dirty.map((d) => d.spec); + const prompt = generatePrompt(target, dirtySpecs, specs, distDir); + const outDir = distDir; + mkdirSync(outDir, { recursive: true }); + const promptPath = join(outDir, "_compile-prompt.md"); + writeFileSync(promptPath, prompt); + + // 5. Print header + const sessionMode = autopilot ? "autopilot" : "interactive"; + printBuildHeader(target, config, sessionMode); + log(` ${chalk.dim(`${dirty.length} dirty specs to compile`)}\n`); + + // 6. Metrics + const metrics: CompileMetrics = { + startTime: Date.now(), + inputTokens: 0, + outputTokens: 0, + reasoningTokens: 0, + toolCalls: 0, + filesWritten: new Set(), + filesDeleted: new Set(), + lastAssistantMessage: "", + errors: [], + }; + + // 7. Create session + const sessionConfig: Record = { + model: config.model, + onPermissionRequest: approveAll, + streaming: true, + systemMessage: { + content: ` + +You are a TUIkit spec compiler. Your job is to read component specifications +and generate idiomatic code for the target framework. + +Working directory: ${SPECS_DIR} +Output directory: ${relative(SPECS_DIR, outDir)} + +PHILOSOPHY: Depth over breadth. +It is far better to deliver a few components that are fully complete — +with passing tests and working interactive demo — than many components +that are half-implemented. Completeness means: the component renders +correctly, responds to user input, is wired into the interactive +playground, and all tests pass. + +RULES: +- Do NOT spawn sub-agents or delegate to the task tool. Do ALL work yourself directly. +- Do NOT ask the user questions. Proceed with your best judgment. +- Read ALL referenced spec files from disk before implementing. +- Output all generated code to the specified output directory. +- Implement one component at a time, fully, before starting the next. +- The interactive demo (--interactive) is the PRIMARY deliverable, not an afterthought. +- Run tests after EACH component and fix any failures before moving on. + +${noLock ? "" : `LOCKING COMPLETED COMPONENTS: +After you fully complete a component (implementation + tests passing + demo wired), +lock it by running: + bun run compile lock --target ${target} --component +This records the component as compiled so it won't be recompiled in future runs. +Only lock a component when you are confident it is DONE — tests pass, demo works. +Lock tokens the same way: bun run compile lock --target ${target} --component +`} + +DEPENDENCIES & KNOWLEDGE CUTOFF: +Your training data may be outdated. Before assuming a library doesn't exist or +falling back to self-contained polyfills, you MUST use web browsing / fetch to +check the library's actual npm registry page, GitHub repo, or documentation. +Install the real package if it exists. Only polyfill if you've confirmed the +package genuinely isn't published. This applies to ALL dependencies referenced +in the target spec (e.g., @opentui/*, ink, bubbletea crates, etc.). + +MULTI-PASS APPROACH: +This session may receive multiple passes. At the END of each pass, you MUST +include a clear summary of what was accomplished and what remains. Structure +your final message like this: + +## Pass summary +- What was completed (components, tests, demo wiring) +- Current test results (X passing, Y failing) +- Interactive demo status + +## Next pass priorities +- List specific components or work items that should be tackled next +- Note any known issues or failing tests to fix +- If everything is complete, say so explicitly + +`, + }, + }; + if (config.effort && config.supportsEffort) { + sessionConfig.reasoningEffort = config.effort; + } + + let session: Awaited>; + try { + // biome-ignore lint/suspicious/noExplicitAny: SDK config types are complex + session = await client.createSession(sessionConfig as any); + } catch (err) { + log(chalk.red("✗") + " Failed to create agent session."); + if (err instanceof Error) log(chalk.dim(` Error: ${err.message}`)); + log("\n This could mean:"); + log(" • The model is unavailable or unsupported"); + log(" • Your Copilot subscription doesn't include this model"); + log(" • A transient service error — try again\n"); + await client.stop(); + process.exit(1); + } + + // 8. Set SDK agent mode + await session.rpc.mode.set({ mode: sessionMode }); + if (verbose) { + log(chalk.dim(` Agent mode: ${sessionMode}`)); + } + + // 9. SIGINT handler + let aborted = false; + const sigintHandler = async () => { + if (aborted) return; + aborted = true; + log(chalk.yellow("\n\n⚠ Compilation interrupted")); + try { + await session.abort(); + await session.disconnect(); + await client.stop(); + } catch { + /* best-effort cleanup */ + } + process.exit(130); + }; + process.on("SIGINT", sigintHandler); + + // 10. Event handlers + let currentPhase = "Starting"; + + if (verbose) { + // ── Verbose mode: raw transcript ── + session.on("assistant.message_delta", (event) => { + process.stdout.write(event.data.deltaContent); + }); + + session.on("assistant.reasoning_delta", (event) => { + process.stdout.write(chalk.dim(event.data.deltaContent)); + }); + + session.on("tool.execution_start", (event) => { + const { toolName } = event.data; + const argStr = summarizeArgs(event.data.arguments); + log(chalk.dim(`\n ${toolName}${argStr ? ` ${argStr}` : ""}`)); + }); + + session.on("tool.execution_complete", (event) => { + const icon = event.data.success ? chalk.green("✓") : chalk.red("✗"); + const toolId = event.data.toolCallId.slice(0, 8); + log(chalk.dim(` ${icon} ${toolId}`)); + }); + } else { + // ── Normal mode: compact status ── + session.on("assistant.message_delta", () => { + // Suppress in normal mode — we show phase-level status instead + }); + + session.on("tool.execution_start", (event) => { + const { toolName } = event.data; + const argStr = summarizeArgs(event.data.arguments); + const phase = detectPhase(toolName, event.data.arguments); + + if (phase !== currentPhase) { + // Complete previous phase + if (currentPhase !== "Starting") { + log(` ${chalk.green("✓")} ${currentPhase}`); + } + currentPhase = phase; + } + + // Show current tool activity + log(chalk.dim(` ${toolName}${argStr ? ` ${argStr}` : ""}`)); + }); + } + + // Common event handlers for both modes + session.on("assistant.message", (event) => { + metrics.lastAssistantMessage = event.data.content; + }); + + session.on("assistant.usage", (event) => { + metrics.inputTokens += event.data.inputTokens ?? 0; + metrics.outputTokens += event.data.outputTokens ?? 0; + metrics.reasoningTokens += event.data.reasoningTokens ?? 0; + }); + + session.on("tool.execution_start", (event) => { + metrics.toolCalls++; + const { toolName } = event.data; + const args = event.data.arguments as Record | undefined; + if (args) { + const filePath = (args.path ?? args.file_path ?? args.filePath ?? args.file) as string | undefined; + const isWrite = + toolName === "edit_file" || + toolName === "create_file" || + toolName === "write_file" || + toolName === "create" || + toolName === "edit" || + toolName === "write" || + toolName === "write_to_file" || + toolName === "str_replace_editor" || + toolName === "insert_edit_into_file" || + toolName.includes("edit") || + toolName.includes("create") || + toolName.includes("write"); + const isDelete = toolName === "delete_file" || toolName === "delete" || toolName.includes("delete"); + + if (isDelete && filePath) { + metrics.filesDeleted.add(filePath); + metrics.filesWritten.delete(filePath); + } else if (isWrite && filePath) { + metrics.filesWritten.add(filePath); + } + } + }); + + session.on("session.error", (event) => { + const msg = (event.data as { message?: string }).message ?? "Unknown error"; + metrics.errors.push(msg); + if (verbose) { + log(chalk.red(`\n✗ Session error: ${msg}`)); + } else { + log(` ${chalk.red("✗")} ${msg}`); + } + }); + + // 10. Send prompt and wait for idle — with multi-pass loop + let passNumber = 1; + + const waitForIdle = (): Promise => + new Promise((resolve) => { + const unsub = session.on("session.idle", () => { + unsub(); + resolve(); + }); + }); + + await session.send({ prompt }); + await waitForIdle(); + + // Complete final phase in normal mode + if (!verbose && currentPhase !== "Starting") { + log(` ${chalk.green("✓")} ${currentPhase}`); + } + + // Show the agent's last message as a pass recap + if (metrics.lastAssistantMessage) { + const rendered = marked(metrics.lastAssistantMessage.trim()) as string; + log(`\n${boxen(rendered.trimEnd(), { padding: 1, dimBorder: true, title: "Agent summary", titleAlignment: "left" })}`); + } + + // Show summary for this pass + printSummary(target, config, metrics, outDir, noLock, passNumber); + + if (metrics.errors.length > 0) { + log(chalk.yellow("⚠ Completed with errors:")); + for (const err of metrics.errors) { + log(` ${chalk.red("•")} ${err}`); + } + log(""); + } + + // 11. Multi-pass loop (interactive mode only — autopilot is handled by the SDK) + if (!autopilot) { + while (!aborted && process.stdin.isTTY) { + const wantMore = await confirmPass(); + if (!wantMore) break; + + passNumber++; + currentPhase = "Starting"; + metrics.errors = []; + + log(`\n${chalk.cyan("●")} Pass ${passNumber} — sending improvement prompt...\n`); + + await session.send({ + prompt: [ + "Do another pass over the compilation output.", + "Re-read the original spec files and the compile prompt at " + + `\`${relative(SPECS_DIR, promptPath)}\` to check what you may have missed.`, + "", + "Remember: DEPTH OVER BREADTH. A few components working perfectly", + "(with interactive demo) is better than many half-working ones.", + "", + "Focus on:", + "- The interactive demo (`--interactive`) — it MUST work as a full-screen playground", + "- Components already implemented: polish, fix bugs, ensure full interactivity", + "- Tests that are failing or missing", + "- Add the NEXT component (fully: implementation + tests + demo wiring)", + "- Token usage correctness", + "After fixing, run the tests and verify `--interactive` works, then report results.", + ].join("\n"), + }); + + await waitForIdle(); + + if (!verbose && currentPhase !== "Starting") { + log(` ${chalk.green("✓")} ${currentPhase}`); + } + + if (metrics.lastAssistantMessage) { + const rendered = marked(metrics.lastAssistantMessage.trim()) as string; + log(`\n${boxen(rendered.trimEnd(), { padding: 1, dimBorder: true, title: "Agent summary", titleAlignment: "left" })}`); + } + + printSummary(target, config, metrics, outDir, noLock, passNumber); + + if (metrics.errors.length > 0) { + log(chalk.yellow("⚠ Pass completed with errors:")); + for (const err of metrics.errors) { + log(` ${chalk.red("•")} ${err}`); + } + log(""); + } + } + } + + // 12. Cleanup + try { + await session.disconnect(); + await client.stop(); + } catch { + /* best-effort */ + } + process.removeListener("SIGINT", sigintHandler); +} + // ── Commands ─────────────────────────────────────────────────────────────── function cmdStatus(targetFilter?: string): void { @@ -405,14 +1088,15 @@ function cmdPrompt(target: string, componentFilter?: string, distDir: string = D if (dirty.length === 0) { log(`${chalk.green("✓")} No dirty specs for target "${target}".`); + log(` ${chalk.dim(`Lock: ${relative(SPECS_DIR, lockPath(target))}`)}`); return; } const dirtySpecs = dirty.map((d) => d.spec); - const prompt = generatePrompt(target, dirtySpecs, specs, distDir); + const outDir = join(distDir, target); + const prompt = generatePrompt(target, dirtySpecs, specs, outDir); // Write prompt to dist directory - const outDir = join(distDir, target); mkdirSync(outDir, { recursive: true }); const outPath = join(outDir, "_compile-prompt.md"); writeFileSync(outPath, prompt); @@ -485,36 +1169,65 @@ function cmdClean(target: string, distDir: string = DEFAULT_DIST_DIR): void { function usage(): void { log(` -TUIkit spec compiler — detect changes, generate prompts, track state. +TUIkit spec compiler — detect changes, generate prompts, compile via Copilot SDK. Commands: status [--target ] Show dirty/clean status prompt --target [--component ] Generate compilation prompt --all-targets Generate prompts for all targets + build --target [--component ] Compile specs via Copilot SDK agent + --all-targets Build all targets sequentially lock --target [--component ] Snapshot spec hashes to lock file --all-targets Lock all targets clean --target Remove lock file + prompt --all-targets Clean all targets -Options: - --out Output directory for compiled code (default: specs/dist/) +Build options: + --model Model to use (e.g. claude-sonnet-4, gpt-5). Prompts if omitted. + --effort Reasoning effort: low | medium | high | xhigh (default: high) + --verbose Show full agent transcript (raw streaming output) + --no-lock Suppress agent lock instructions (agent won't lock components) + --autopilot Use SDK autopilot mode — agent runs all passes autonomously + +Common options: + --out Output directory for compiled code (default: dist/) Examples: bun run compile status bun run compile prompt --target go - bun run compile prompt --target go --out ./my-tuikit - bun run compile prompt --target rust --component HintBar + bun run compile build --target go + bun run compile build --target rust --model claude-sonnet-4 --effort high --verbose + bun run compile build --target node --component HintBar + bun run compile build --all-targets --model gpt-5 --effort xhigh bun run compile lock --target go bun run compile clean --target bun `); } -function parseArgs(argv: string[]): { command: string; target?: string; allTargets: boolean; component?: string; out?: string } { +interface ParsedArgs { + command: string; + target?: string; + allTargets: boolean; + component?: string; + out?: string; + model?: string; + effort?: string; + verbose: boolean; + noLock: boolean; + autopilot: boolean; +} + +function parseArgs(argv: string[]): ParsedArgs { const command = argv[0] || "status"; let target: string | undefined; let allTargets = false; let component: string | undefined; let out: string | undefined; + let model: string | undefined; + let effort: string | undefined; + let verbose = false; + let noLock = false; + let autopilot = false; for (let i = 1; i < argv.length; i++) { if (argv[i] === "--target" && argv[i + 1]) { @@ -525,10 +1238,20 @@ function parseArgs(argv: string[]): { command: string; target?: string; allTarge component = argv[++i]; } else if (argv[i] === "--out" && argv[i + 1]) { out = argv[++i]; + } else if (argv[i] === "--model" && argv[i + 1]) { + model = argv[++i]; + } else if (argv[i] === "--effort" && argv[i + 1]) { + effort = argv[++i]; + } else if (argv[i] === "--verbose") { + verbose = true; + } else if (argv[i] === "--no-lock") { + noLock = true; + } else if (argv[i] === "--autopilot") { + autopilot = true; } } - return { command, target, allTargets, component, out }; + return { command, target, allTargets, component, out, model, effort, verbose, noLock, autopilot }; } const args = parseArgs(process.argv.slice(2)); @@ -552,6 +1275,18 @@ switch (args.command) { process.exit(1); } break; + case "build": + if (args.allTargets) { + for (const t of discoverTargets()) { + await cmdBuild(t, args.component, distDir, args.model, args.effort, args.verbose, args.noLock, args.autopilot); + } + } else if (args.target) { + await cmdBuild(args.target, args.component, distDir, args.model, args.effort, args.verbose, args.noLock, args.autopilot); + } else { + log("Error: --target or --all-targets is required for build command"); + process.exit(1); + } + break; case "lock": if (args.allTargets) { for (const t of discoverTargets()) {