diff --git a/.github/workflows/specs-ci.yml b/.github/workflows/specs-ci.yml index 1c74aa8..c1ea8f1 100644 --- a/.github/workflows/specs-ci.yml +++ b/.github/workflows/specs-ci.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.gitignore b/.gitignore index a70be9a..aa2acd0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -.DS_Store dist/ node_modules/ targets/*.lock.json diff --git a/LICENSE b/LICENSE index 28a50fa..7d30d37 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright GitHub, Inc. +Copyright (c) 2026 GitHub, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 3c2b350..d524f43 100644 --- a/README.md +++ b/README.md @@ -22,25 +22,11 @@ flowchart LR 5. Generated code goes to **dist/** — specs stay clean 6. **Lock files** track which spec versions have been compiled -## Positioning - -TUIkit applies the headless UI primitive model to terminal interfaces. Like -Radix Primitives and Base UI, it treats components as accessible, composable, -unstyled building blocks. Unlike web-first headless libraries, TUIkit keeps the -primitive contract in language-agnostic specs and uses an LLM agent as the -compiler backend to generate idiomatic implementations for each TUI framework. - -This makes the markdown specs an intermediate representation: they capture -behavior, accessibility expectations, semantic tokens, tests, and previews, -while target specs define how those contracts map into Go/Bubbletea, -TypeScript/Ink, Bun/OpenTUI, Rust/Ratatui, or future terminal UI stacks. - ## Quick start ### 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 @@ -185,139 +171,63 @@ 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. Compile interactively (prompts for model, effort, output dir) -bun run compile build --target bun +# 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/ -# 3. Or compile non-interactively with all options -bun run compile build --target bun --model claude-sonnet-4 --out dist/bun-claude +# 4. Verify: run tests, check the demo CLI +cd dist/go && go test ./... && go run ./cmd/demo -# 4. Or fire-and-forget with autopilot (SDK handles everything) -bun run compile build --target bun --model claude-sonnet-4 --autopilot +# 5. Lock the hashes +bun run compile lock --target go ``` ### Multi-pass compilation -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. +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: | Pass | Focus | Typical outcome | | ---- | ----- | --------------- | -| **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. | - -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. - -### Component locking - -The agent locks components individually as it completes them by running: - -```bash -bun run compile lock --target bun --component Select -``` - -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: +| **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. | -```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`: +To run a follow-up pass, generate a new prompt and tell the agent to review +and complete its existing work: ```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 -# Generate a prompt for a target +# Generate a fresh prompt (it sees the current dist/ state) bun run compile prompt --target 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 +# 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 prompt is written to `//_compile-prompt.md` (e.g. `dist/go/_compile-prompt.md`). It contains: +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. -- 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. +### Custom output directory -Feed this file to any LLM agent, then lock manually once verified: +By default, compiled code goes to `dist/`. Override with `--out`: ```bash -# After the agent generates code and tests pass: -bun run compile lock --target go +# Output to a separate repo or directory +bun run compile prompt --target go --out ~/my-tuikit-go + +# The prompt and generated code go to ~/my-tuikit-go/go/ ``` ### Adding a new target @@ -327,7 +237,7 @@ bun run compile lock --target 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 build --target {name}` to compile +4. Run `bun run compile prompt --target {name}` and compile ## Linting @@ -391,55 +301,6 @@ For TUI design foundations — color systems, typography, iconography, layout grids, accessibility patterns, keybinding conventions, and buffer management — see [`docs/foundations.md`](docs/foundations.md). -## Bibliography - -### Headless UI primitives - -- [Radix Primitives](https://www.radix-ui.com/primitives/docs/overview/introduction) - — low-level, accessible, unstyled React primitives for building design - systems. TUIkit follows the same separation of behavior from presentation for - terminal UI components. -- [Base UI](https://base-ui.com/) — unstyled, accessible React components from - the creators of Radix, Floating UI, and Material UI. Its emphasis on - composability, consistency, and no visual opinions mirrors TUIkit's - spec-first primitive model. -- [WAI-ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/) — - reference patterns for accessible web widgets. TUIkit uses analogous - accessibility contracts for terminal interactions, keyboard behavior, and - announcements. -- [React Aria](https://react-spectrum.adobe.com/react-aria/) — accessibility - primitives separated from styling. Useful precedent for defining interaction - behavior independently from visual rendering. - -### LLM compilers and specification-to-code systems - -- [Language Models as Compilers: Simulating Pseudocode Execution Improves - Algorithmic Reasoning in Language Models](https://arxiv.org/abs/2404.02575) - — frames language models as systems that infer reusable task-level logic and - execute it for specific instances. TUIkit similarly separates reusable specs - from target-specific generation. -- [Requirements are All You Need: From Requirements to Code with - LLMs](https://arxiv.org/abs/2406.10101) — explores progressive prompting - from requirements to tests and implementation. TUIkit's specs, tests, and - compile prompts follow a similar structured requirements-to-code workflow. -- [Iterative Refinement of Project-Level Code Context for Precise Code - Generation with Compiler Feedback](https://aclanthology.org/2024.findings-acl.138/) - — introduces compiler/static-analysis feedback loops for improving generated - code. TUIkit's lint, test, prompt, and lock workflow provides a similar - verification loop for generated component implementations. -- [Combining LLM Code Generation with Formal Specifications and Reactive - Program Synthesis](https://arxiv.org/abs/2410.19736) — combines LLM code - generation with formal methods-based synthesis. This points toward stronger - future conformance checks for TUIkit specs. -- [SpecifyUI: Supporting Iterative UI Design Intent Expression through - Structured Specifications and Generative AI](https://arxiv.org/abs/2509.07334) - — introduces a structured UI intermediate representation for controllable - generative design. TUIkit uses markdown specs as a terminal UI-oriented - intermediate representation. -- [A2UI](https://a2ui.org/) — a declarative, framework-agnostic protocol for - agent-generated UI surfaces. It is adjacent to TUIkit's goal of expressing UI - intent once and rendering it across target environments. - ## License This project is licensed under the [MIT License](LICENSE). \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md index abe011d..8100fc1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -12,7 +12,7 @@ If you believe you have found a security vulnerability in any GitHub-owned repos **Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** -Instead, please send an email to opensource-security[@]github.com. +Instead, please send an email to . Please include as much of the information listed below as you can to help us better understand and resolve the issue: diff --git a/bun.lock b/bun.lock index d6668d6..46dc876 100644 --- a/bun.lock +++ b/bun.lock @@ -4,13 +4,9 @@ "workspaces": { "": { "dependencies": { - "@clack/prompts": "^1.2.0", - "@github/copilot-sdk": "^0.3.0", "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", @@ -18,36 +14,12 @@ }, }, "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=="], @@ -56,14 +28,6 @@ "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], - "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@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - - "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=="], @@ -72,20 +36,8 @@ "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-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=="], @@ -94,14 +46,6 @@ "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], - "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "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=="], @@ -112,12 +56,6 @@ "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=="], @@ -126,22 +64,14 @@ "format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="], - "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], - "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=="], @@ -154,10 +84,6 @@ "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=="], @@ -218,16 +144,6 @@ "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=="], @@ -240,40 +156,20 @@ "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@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=="], - - "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "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=="], - "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=="], @@ -288,24 +184,8 @@ "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=="], - - "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=="], - - "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=="], - - "cli-highlight/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - - "parse5-htmlparser2-tree-adapter/parse5": ["parse5@6.0.1", "", {}, "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="], - - "strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], } } diff --git a/package.json b/package.json index f44393b..a763e54 100644 --- a/package.json +++ b/package.json @@ -4,13 +4,9 @@ "compile": "bun scripts/compile.ts" }, "dependencies": { - "@clack/prompts": "^1.2.0", - "@github/copilot-sdk": "^0.3.0", "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 8ad83c0..31969a5 100644 --- a/scripts/compile.ts +++ b/scripts/compile.ts @@ -3,26 +3,20 @@ * TUIkit spec compiler * * Detects changed specs via content hashing and generates self-contained - * compilation prompts for LLM agents. The `build` command uses the Copilot SDK - * to launch an agent session that compiles specs into code automatically. + * compilation prompts for LLM agents. Lock files track which spec versions + * have been compiled per target. * * Usage: - * 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 + * 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 */ 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"; - -marked.use(markedTerminal()); // biome-ignore lint/suspicious/noConsole: CLI tool — stdout is the interface const log = (...args: unknown[]) => console.log(...args); @@ -64,25 +58,6 @@ 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; - lastAssistantMessage: string; - errors: string[]; -} - // ── Helpers ──────────────────────────────────────────────────────────────── function sha256(content: string): string { @@ -343,680 +318,43 @@ 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. 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("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(` 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 — 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("## Demo specification"); sections.push(""); - 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("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(""); } sections.push("---"); sections.push("## Verification (REQUIRED)"); sections.push(""); - sections.push("After implementing each component (not just at the end), verify:"); + sections.push("After generating ALL files, you MUST verify in this order:"); sections.push(""); - 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("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("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.) */ -const SCAN_IGNORE = new Set([ - "node_modules", ".git", "__pycache__", ".mypy_cache", ".pytest_cache", - "target", "vendor", ".build", "build", "DerivedData", ".gradle", - ".dart_tool", ".packages", "Pods", -]); - -function countOutputDir(dir: string): { files: number; lines: number } { - let files = 0; - let lines = 0; - if (!existsSync(dir)) return { files, lines }; - - const walk = (d: string) => { - for (const entry of readdirSync(d, { withFileTypes: true })) { - if (entry.name.startsWith(".") || SCAN_IGNORE.has(entry.name)) continue; - const full = join(d, entry.name); - if (entry.isDirectory()) { - walk(full); - } else if (entry.isFile()) { - files++; - try { - lines += readFileSync(full, "utf-8").split("\n").length; - } catch { - /* binary or unreadable — skip */ - } - } - } - }; - walk(dir); - 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, dirtyCount: number): void { - const effortStr = config.effort ? `, ${config.effort} effort` : ""; - clack.log.step(`${chalk.bold(target)} · ${config.model}${effortStr} · ${mode}`); - clack.log.info(chalk.dim(`${dirtyCount} dirty specs to compile`)); -} - -function printSummary( - target: string, - config: BuildConfig, - metrics: CompileMetrics, - outDir: string, - noLock: boolean, - passNumber = 1, -): void { - const elapsed = Date.now() - metrics.startTime; - const { files, lines } = countOutputDir(outDir); - 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 body = [ - `Model: ${config.model}${config.effort ? ` (${config.effort} effort)` : ""}`, - `Time: ${formatDuration(elapsed)}`, - `Files: ${files}`, - `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))}`, - ].join("\n"); - - clack.log.success(`Compilation complete — target: ${target}${passLabel}`); - clack.log.message(chalk.dim(body)); -} - -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, dirty.length); - - // 6. Metrics - const metrics: CompileMetrics = { - startTime: Date.now(), - inputTokens: 0, - outputTokens: 0, - reasoningTokens: 0, - toolCalls: 0, - 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) { - clack.log.error("Failed to create agent session."); - if (err instanceof Error) clack.log.message(chalk.dim(`Error: ${err.message}`)); - clack.log.message( - "This could mean:\n" + - " • The model is unavailable or unsupported\n" + - " • Your Copilot subscription doesn't include this model\n" + - " • A transient service error — try again", - ); - clack.outro(chalk.red("Exiting")); - 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; - clack.outro(chalk.yellow("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 using clack timeline ── - 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) { - if (currentPhase !== "Starting") { - clack.log.success(currentPhase); - } - currentPhase = phase; - clack.log.step(phase); - } - - log(`${chalk.gray("│")} ${chalk.dim(`${toolName}${argStr ? ` ${argStr}` : ""}`)}`); - - }); - } - - // Common event handlers for both modes - let pendingDelta = ""; - session.on("assistant.message_delta", (event) => { - pendingDelta += event.data.deltaContent; - }); - - session.on("assistant.message", (event) => { - const content = event.data.content || pendingDelta; - if (content) { - metrics.lastAssistantMessage = content; - } - pendingDelta = ""; - }); - - 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", () => { - metrics.toolCalls++; - }); - - 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 { - clack.log.error(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") { - clack.log.success(currentPhase); - } - - // Show the agent's last message as a pass recap - if (metrics.lastAssistantMessage) { - const rendered = marked(metrics.lastAssistantMessage.trim()) as string; - clack.note(rendered.trimEnd(), "Agent summary"); - } - - // Show summary for this pass - printSummary(target, config, metrics, outDir, noLock, passNumber); - - if (metrics.errors.length > 0) { - clack.log.warn("Completed with errors:\n" + metrics.errors.map((e) => ` ${chalk.red("•")} ${e}`).join("\n")); - } - - // 11. Multi-pass loop — user can always trigger additional passes - while (!aborted && process.stdin.isTTY) { - const wantMore = await confirmPass(); - if (!wantMore) break; - - passNumber++; - currentPhase = "Starting"; - metrics.errors = []; - - clack.log.step(`Pass ${passNumber} — sending improvement prompt`); - - 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") { - clack.log.success(currentPhase); - } - - if (metrics.lastAssistantMessage) { - const rendered = marked(metrics.lastAssistantMessage.trim()) as string; - clack.note(rendered.trimEnd(), "Agent summary"); - } - - printSummary(target, config, metrics, outDir, noLock, passNumber); - - if (metrics.errors.length > 0) { - clack.log.warn("Pass completed with errors:\n" + metrics.errors.map((e) => ` ${chalk.red("•")} ${e}`).join("\n")); - } - } - - // 12. Cleanup - clack.outro(chalk.dim("Session ended")); - try { - await session.disconnect(); - await client.stop(); - } catch { - /* best-effort */ - } - process.removeListener("SIGINT", sigintHandler); -} - // ── Commands ─────────────────────────────────────────────────────────────── function cmdStatus(targetFilter?: string): void { @@ -1067,15 +405,14 @@ 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 outDir = join(distDir, target); - const prompt = generatePrompt(target, dirtySpecs, specs, outDir); + const prompt = generatePrompt(target, dirtySpecs, specs, distDir); // Write prompt to dist directory + const outDir = join(distDir, target); mkdirSync(outDir, { recursive: true }); const outPath = join(outDir, "_compile-prompt.md"); writeFileSync(outPath, prompt); @@ -1148,65 +485,36 @@ function cmdClean(target: string, distDir: string = DEFAULT_DIST_DIR): void { function usage(): void { log(` -TUIkit spec compiler — detect changes, generate prompts, compile via Copilot SDK. +TUIkit spec compiler — detect changes, generate prompts, track state. 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 -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/) +Options: + --out Output directory for compiled code (default: specs/dist/) Examples: bun run compile status bun run compile prompt --target go - 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 prompt --target go --out ./my-tuikit + bun run compile prompt --target rust --component HintBar bun run compile lock --target go bun run compile clean --target bun `); } -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 { +function parseArgs(argv: string[]): { command: string; target?: string; allTargets: boolean; component?: string; out?: string } { 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]) { @@ -1217,20 +525,10 @@ function parseArgs(argv: string[]): ParsedArgs { 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, model, effort, verbose, noLock, autopilot }; + return { command, target, allTargets, component, out }; } const args = parseArgs(process.argv.slice(2)); @@ -1254,18 +552,6 @@ 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()) {