diff --git a/AGENTS.md b/AGENTS.md index b0bb2e1..4f1015f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,26 +1,36 @@ # lmc -Bun CLI tool for streaming LLM chat with inline markdown rendering. +TypeScript CLI tool for streaming LLM chat with inline markdown rendering. ## Commands ``` -bun run dev # dev run -bun run prod # run bundled output -bun run check # biome lint + format + organize imports +pnpm run dev # dev run (rolldown bundle + node) +pnpm run check # biome lint + format + organize imports + typecheck +pnpm run build # check + bundle index.ts and tts.ts (minified ESM) +pnpm run build:bin # check + bundle with esbuild (CJS) + build SEA binaries +pnpm run clean # remove dist/ ``` ## Architecture -- `index.ts` — CLI entry point, orchestration +- `src/index.ts` — LMC CLI entry point, orchestration +- `src/tts.ts` — TTSC CLI entry point (text-to-speech, Kokoro pipeline via `uv run`) +- `src/config.ts` — `initConfig()`, `loadConfig()`: manages `~/.lmc_config.json` +- `src/conversation.ts` — `Conversation` class: load/save to `~/.lmc_convo.json` +- `src/errors.ts` — `handleTopLevel()`: TTY-aware error wrapping +- `src/prompts.ts` — `commitDiff()`, `reviewFile()`, `reviewDiff()`, `file()`: prompt builders - `src/render/index.ts` — `Renderer` class: streams LLM response, buffers markdown, renders to stdout/stderr -- `src/render/markdown.ts` — `renderMarkdown()`: syntax-highlighted markdown via `@comark/ansi` + Shiki +- `src/render/markdown.ts` — `renderMarkdown()`: markdown via `marked` + Shiki + `@cliffy/table` +- `src/render/codeToANSI.ts` — Shiki token output to ANSI escape sequences ## Gotchas -- **Bun only** — not Node. Use `Bun.file()`, `Bun.argv`, `Bun.sleep()`. +- **Node.js** — Use modern Node APIs (`node:fs/promises`, `node:stream/consumers`, etc.). - **No tests** — none exist. -- **Biome config**: double quotes, space indent, auto-import-organize on. Run `bun run check` before committing. +- **Biome config**: double quotes, space indent, auto-import-organize on. `pnpm run check` also runs `tsc --noEmit`. +- **Build tools**: rolldown for ESM bundles, esbuild for CJS binaries, `node --build-sea` for SEA single-executable apps. +- **TTS**: requires Python with `uv` — Kokoro pipeline lives in `tts/`. ## Style diff --git a/README.md b/README.md index 7c56c4c..d8ddfa9 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,130 @@ # lmc -To install dependencies: +A CLI tool for LLM chats with terminal native streaming. + +## Features + +- **Streaming markdown rendering** — incremental rendering with syntax highlighting +- **Conversation persistence** — chat history saved to JSON +- **Model configuration** — persist model preferences to JSON +- **Reasoning display** — thinking tokens rendered _in style_ on stderr +- **Usage stats** — input/output tokens and tokens/sec rate after each response +- **TTY-aware** — automatically disables markdown rendering when output is piped +- **Text-to-speech** — `ttsc` sub-command for streaming TTS via Kokoro model + +## Requirements + +- [pnpm](https://pnpm.io) v11+ +- [Node.js](https://nodejs.org) v22+ +- [uv](https://docs.astral.sh/uv/) — only for `ttsc` (text-to-speech) + +## Installation ```bash -bun install +# Bundle and install locally +pnpm build +pnpm add --global . + +# Build a single-file exe +pnpm build:bin +cp dist/lmc dist/ttsc /somewhere/bin/ + +# For ttsc only +cp -r tts ~/.ttsc ``` -To run: +_Package to come later_ + +## Usage + +### Chat (lmc) ```bash -bun run index.ts +lmc [options] ``` -This project was created using `bun init` in bun v1.3.11. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. +| Flag | Short | Type | Description | +| ------------ | ----- | ------- | ----------------------------------------------------- | +| `--new` | `-n` | boolean | Start a new conversation (clear history) | +| `--model` | `-m` | string | Set model name for this session | +| `--config` | `-c` | boolean | Initialize a new config file (`~/.lmc_config.json`) | +| `--plain` | — | boolean | Disable markdown rendering for both stdout and stderr | +| `--plainOut` | — | boolean | Disable markdown rendering for stdout only | +| `--plainErr` | — | boolean | Disable markdown rendering for stderr only | + +**Examples:** + +```bash +lmc --config # Initialize/edit config +lmc --new "Introduce yourself." # New conversation +lmc "What is TypeScript?" # Continue most recent chat +lmc -m llama3 "Explain quantum computing" # Chat with specific model on-the-fly +lmc --plain "Plain text output" # No markdown rendering +``` + +### Text-to-Speech (ttsc) + +```bash +echo "text" | ttsc [options] +``` + +| Flag | Short | Type | Description | +| -------- | ----- | ------- | -------------------------------------------------------- | +| `--wait` | `-w` | boolean | Wait for all processing before exiting (don't auto-play) | +| `--url` | `-u` | boolean | Fetch text from a URL instead of stdin | +| `--edit` | `-e` | boolean | Edit the input with your $EDITOR before processing | + +**Examples:** + +```bash +echo "Hello, world!" | ttsc +echo "Read this aloud" | ttsc --wait +echo "https://example.com" | ttsc --url +``` + +Requires: [uv](https://docs.astral.sh/uv/) + +Wants: + +- `mpv` for playback +- `sox` for wav merging with `--wait` + +## Development + +```bash +pnpm run dev # dev run +pnpm run check # biome lint + format + organize imports +``` + +## Architecture + +``` +index.ts — LMC entry point, orchestration +tts.ts — TTSC entry point, orchestration +src/render/index.ts — Renderer class: streams LLM response, buffers markdown +src/render/markdown.ts — renderMarkdown(): syntax-highlighted markdown via Marked + Shiki +src/conversation/ — Conversation persistence +src/config/ — App config persistence +tts/ — TTS python stub (Kokoro pipeline) +out/ — Bundled output (index.js, tts.js) +``` + +## Configuration + +- **Conversation history:** `~/.lmc_convo.json` +- **Model config:** `~/.lmc_config.json` +- **TTS files:** `~/.ttsc/` + +## Dependencies + +| Package | Purpose | +| ---------------------- | ------------------------------- | +| `ai` (Vercel AI SDK) | Streaming text/usage | +| `@clack/prompts` | Interactive prompts and spinner | +| `@cliffy/table` | ASCII table rendering | +| `@mozilla/readability` | HTML-to-text extraction (ttsc) | +| `chalk` | Terminal string styling | +| `happy-dom` | DOM parsing for URLs (ttsc) | +| `shiki` | Syntax highlighting | +| `terminal-link` | Clickable terminal hyperlinks | +| `marked` | Markdown parsing | diff --git a/bun.lock b/bun.lock deleted file mode 100644 index 6b1f630..0000000 --- a/bun.lock +++ /dev/null @@ -1,196 +0,0 @@ -{ - "lockfileVersion": 1, - "configVersion": 1, - "workspaces": { - "": { - "name": "aicl", - "dependencies": { - "@ai-sdk/openai-compatible": "^2.0.41", - "ai": "^6.0.161", - "ansi-styles": "^6.2.3", - "chalk": "^5.6.2", - "cli-table3": "^0.6.5", - "shiki": "^4.0.2", - "terminal-link": "^5.0.0", - }, - "devDependencies": { - "@biomejs/biome": "2.4.12", - "@types/bun": "latest", - }, - "peerDependencies": { - "typescript": "^5", - }, - }, - }, - "packages": { - "@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.98", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Ol+nP8PIlj8FjN8qKlxhE89N0woqAaGi9CUBGp1boe3RafpphJ7WMuq/RErSvxtwTqje03TP+zIdzP113krxRg=="], - - "@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], - - "@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="], - - "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.23", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-z8GlDaCmRSDlqkMF2f4/RFgWxdarvIbyuk+m6WXT1LYgsnGiXRJGTD2Z1+SDl3LqtFuRtGX1aghYvQLoHL/9pg=="], - - "@biomejs/biome": ["@biomejs/biome@2.4.12", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.12", "@biomejs/cli-darwin-x64": "2.4.12", "@biomejs/cli-linux-arm64": "2.4.12", "@biomejs/cli-linux-arm64-musl": "2.4.12", "@biomejs/cli-linux-x64": "2.4.12", "@biomejs/cli-linux-x64-musl": "2.4.12", "@biomejs/cli-win32-arm64": "2.4.12", "@biomejs/cli-win32-x64": "2.4.12" }, "bin": { "biome": "bin/biome" } }, "sha512-Rro7adQl3NLq/zJCIL98eElXKI8eEiBtoeu5TbXF/U3qbjuSc7Jb5rjUbeHHcquDWeSf3HnGP7XI5qGrlRk/pA=="], - - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BnMU4Pc3ciEVteVpZ0BK33MLr7X57F5w1dwDLDn+/iy/yTrA4Q/N2yftidFtsA4vrDh0FMXDpacNV/Tl3fbmng=="], - - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-x9uJ0bI1rJsWICp3VH8w/5PnAVD3A7SqzDpbrfoUQX1QyWrK5jSU4fRLo/wSgGeplCivbxBRKmt5Xq4/nWvq8A=="], - - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-tOwuCuZZtKi1jVzbk/5nXmIsziOB6yqN8c9r9QM0EJYPU6DpQWf11uBOSCfFKKM4H3d9ZoarvlgMfbcuD051Pw=="], - - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-FhfpkAAlKL6kwvcVap0Hgp4AhZmtd3YImg0kK1jd7C/aSoh4SfsB2f++yG1rU0lr8Y5MCFJrcSkmssiL9Xnnig=="], - - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.12", "", { "os": "linux", "cpu": "x64" }, "sha512-8pFeAnLU9QdW9jCIslB/v82bI0lhBmz2ZAKc8pVMFPO0t0wAHsoEkrUQUbMkIorTRIjbqyNZHA3lEXavsPWYSw=="], - - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.12", "", { "os": "linux", "cpu": "x64" }, "sha512-dwTIgZrGutzhkQCuvHynCkyW6hJxUuyZqKKO0YNfaS2GUoRO+tOvxXZqZB6SkWAOdfZTzwaw8IEdUnIkHKHoew=="], - - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-B0DLnx0vA9ya/3v7XyCaP+/lCpnbWbMOfUFFve+xb5OxyYvdHaS55YsSddr228Y+JAFk58agCuZTsqNiw2a6ig=="], - - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.12", "", { "os": "win32", "cpu": "x64" }, "sha512-yMckRzTyZ83hkk8iDFWswqSdU8tvZxspJKnYNh7JZr/zhZNOlzH13k4ecboU6MurKExCe2HUkH75pGI/O2JwGA=="], - - "@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="], - - "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - - "@shikijs/core": ["@shikijs/core@4.0.2", "", { "dependencies": { "@shikijs/primitive": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw=="], - - "@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag=="], - - "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg=="], - - "@shikijs/langs": ["@shikijs/langs@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2" } }, "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg=="], - - "@shikijs/primitive": ["@shikijs/primitive@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw=="], - - "@shikijs/themes": ["@shikijs/themes@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2" } }, "sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA=="], - - "@shikijs/types": ["@shikijs/types@4.0.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg=="], - - "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], - - "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "@types/bun": ["@types/bun@1.3.12", "", { "dependencies": { "bun-types": "1.3.12" } }, "sha512-DBv81elK+/VSwXHDlnH3Qduw+KxkTIWi7TXkAeh24zpi5l0B2kUg9Ga3tb4nJaPcOFswflgi/yAvMVBPrxMB+A=="], - - "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], - - "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], - - "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], - - "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], - - "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], - - "@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="], - - "ai": ["ai@6.0.161", "", { "dependencies": { "@ai-sdk/gateway": "3.0.98", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ufhmijmx2YyWTPAicGgtpLOB/xD7mG8zKs1pT1Trj+JL/3r1rS8fkMi/cHZoChSAQSGB4pgmcWVxDrVTUvK2IQ=="], - - "ansi-escapes": ["ansi-escapes@7.3.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="], - - "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - - "bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="], - - "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], - - "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - - "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], - - "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], - - "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=="], - - "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], - - "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], - - "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=="], - - "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], - - "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="], - - "has-flag": ["has-flag@5.0.1", "", {}, "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA=="], - - "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], - - "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], - - "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], - - "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], - - "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], - - "mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="], - - "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], - - "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], - - "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], - - "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], - - "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], - - "oniguruma-parser": ["oniguruma-parser@0.12.2", "", {}, "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw=="], - - "oniguruma-to-es": ["oniguruma-to-es@4.3.6", "", { "dependencies": { "oniguruma-parser": "^0.12.2", "regex": "^6.1.0", "regex-recursion": "^6.0.2" } }, "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA=="], - - "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], - - "regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="], - - "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="], - - "regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="], - - "shiki": ["shiki@4.0.2", "", { "dependencies": { "@shikijs/core": "4.0.2", "@shikijs/engine-javascript": "4.0.2", "@shikijs/engine-oniguruma": "4.0.2", "@shikijs/langs": "4.0.2", "@shikijs/themes": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ=="], - - "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], - - "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=="], - - "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], - - "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], - - "supports-hyperlinks": ["supports-hyperlinks@4.4.0", "", { "dependencies": { "has-flag": "^5.0.1", "supports-color": "^10.2.2" } }, "sha512-UKbpT93hN5Nr9go5UY7bopIB9YQlMz9nm/ct4IXt/irb5YRkn9WaqrOBJGZ5Pwvsd5FQzSVeYlGdXoCAPQZrPg=="], - - "terminal-link": ["terminal-link@5.0.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "supports-hyperlinks": "^4.1.0" } }, "sha512-qFAy10MTMwjzjU8U16YS4YoZD+NQLHzLssFMNqgravjbvIPNiqkGFR4yjhJfmY9R5OFU7+yHxc6y+uGHkKwLRA=="], - - "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], - - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - - "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], - - "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], - - "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], - - "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], - - "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="], - - "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], - - "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], - - "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], - - "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - - "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], - } -} diff --git a/index.ts b/index.ts deleted file mode 100644 index e30be42..0000000 --- a/index.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { parseArgs } from "node:util"; -import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; -import { streamText } from "ai"; - -import { loadConfig, saveConfig } from "./src/config"; -import { - type Conversation, - loadConversation, - saveConversation, -} from "./src/conversation"; -import Renderer from "./src/render"; - -const { values: args, positionals } = parseArgs({ - args: Bun.argv, - options: { - model: { type: "string" }, - m: { type: "string" }, - new: { type: "boolean" }, - n: { type: "boolean" }, - save: { type: "boolean" }, - s: { type: "boolean" }, - plain: { type: "boolean" }, - plainOut: { type: "boolean" }, - plainErr: { type: "boolean" }, - }, - strict: true, - allowPositionals: true, -}); - -async function main() { - const newConvo = args.new ?? args.n; - const plainOut = args.plainOut ?? args.plain ?? !process.stdout.isTTY; - const plainErr = args.plainErr ?? args.plain ?? !process.stderr.isTTY; - const modelFlag = args.model ?? args.m; - const saveConfigFlag = args.save ?? args.s; - - const config = await loadConfig(); - - config.model = modelFlag ?? config.model; - - if (!config.model) { - console.error( - "Please run 'lmc --model --save' to set a default model.", - ); - process.exit(1); - } - - if (saveConfigFlag) { - await saveConfig(config); - return; - } - - const renderer = new Renderer(plainOut, plainErr); - - const provider = createOpenAICompatible({ - name: "llama.cpp", - apiKey: "local", - baseURL: "http://127.0.0.1:8080/v1", - includeUsage: true, // Include usage information in streaming responses - }); - - let convo: Conversation = { - model: config.model, - messages: [], - }; - - if (!newConvo) { - convo = await loadConversation(config.model); - - if (args.model || args.m) { - convo.model = modelFlag ?? config.model; - } - } - - const prompt = positionals.slice(2).join(" ") || "Introduce yourself."; - convo.messages.push({ role: "user", content: prompt }); - - const { fullStream, text, totalUsage } = streamText({ - model: provider(convo.model), - messages: convo.messages, - }); - - const start = Date.now(); - await renderer.renderStream(fullStream); - const stop = Date.now(); - - convo.messages.push({ role: "assistant", content: await text }); - await saveConversation(convo); - - await renderer.renderStats(totalUsage, start, stop); -} - -await main(); diff --git a/package.json b/package.json index 85b43af..b27a415 100644 --- a/package.json +++ b/package.json @@ -1,38 +1,41 @@ { "name": "lmc", - "module": "index.ts", "type": "module", "private": true, "bin": { - "lmc": "out/index.js", - "ttsc": "out/tts.js" + "lmc": "dist/index.js", + "ttsc": "dist/tts.js" }, "scripts": { - "dev": "bun run index.ts", - "prod": "bun run out/index.js", - "check": "bunx --bun @biomejs/biome check --write && bun run tc", - "build:js": "bun run check && bun run --parallel build:bundle build:tts && bun run --parallel shebang shebang:tts", - "build:bundle": "bun build --target=bun --production --sourcemap --outdir=out index.ts", - "build:tts": "bun build --target=bun --production --sourcemap --outdir=out tts.ts", - "build:bin": "bun build --compile --outfile=out/lmc index.ts", - "shebang": "sed -i '1i#!\\/usr\\/bin\\/env bun' out/index.js", - "shebang:tts": "sed -i '1i#!\\/usr\\/bin\\/env bun' out/tts.js", + "dev": "pnpm run bundle src/index.ts >/dev/null && node dist/index.js", + "check": "biome check --write && pnpm run tc", + "clean": "rm -rf dist", + "build": "pnpm run check && pnpm run bundle --minify src/index.ts && pnpm run bundle --minify src/tts.ts", + "build:bin": "pnpm run check && pnpm run bundle:bin --minify src/index.ts && pnpm run bundle:bin --minify src/tts.ts && pnpm run bin", + "bundle": "rolldown --dir=dist --platform=node --transform.target=node22 --format=esm", + "bundle:bin": "esbuild --bundle --outdir=dist --platform=node --target=node26 --format=cjs", + "bin": "node --build-sea sea-config.json && node --build-sea sea-config-tts.json", "tc": "tsc --noEmit --incremental" }, "devDependencies": { + "@ai-sdk/openai-compatible": "^2.0.50", "@biomejs/biome": "2.4.12", - "@types/bun": "latest" - }, - "peerDependencies": { - "typescript": "^5" - }, - "dependencies": { - "@ai-sdk/openai-compatible": "^2.0.41", - "ai": "^6.0.161", + "@clack/prompts": "^1.5.1", + "@cliffy/table": "jsr:^1.2.1", + "@mozilla/readability": "^0.6.0", + "@types/node": "^25.9.3", + "ai": "^6.0.206", "ansi-styles": "^6.2.3", "chalk": "^5.6.2", - "cli-table3": "^0.6.5", - "shiki": "^4.0.2", - "terminal-link": "^5.0.0" + "esbuild": "^0.28.1", + "execa": "^9.6.1", + "happy-dom": "^20.10.3", + "marked": "^18.0.5", + "rolldown": "^1.1.1", + "shiki": "^4.2.0", + "string-width": "^8.2.1", + "terminal-link": "^5.0.0", + "typescript": "^5.9.3", + "wrap-ansi": "^10.0.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..d46eb49 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1504 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@ai-sdk/openai-compatible': + specifier: ^2.0.50 + version: 2.0.50(zod@4.4.3) + '@biomejs/biome': + specifier: 2.4.12 + version: 2.4.12 + '@clack/prompts': + specifier: ^1.5.1 + version: 1.5.1 + '@cliffy/table': + specifier: jsr:^1.2.1 + version: '@jsr/cliffy__table@1.2.1' + '@mozilla/readability': + specifier: ^0.6.0 + version: 0.6.0 + '@types/node': + specifier: ^25.9.3 + version: 25.9.3 + ai: + specifier: ^6.0.206 + version: 6.0.206(zod@4.4.3) + ansi-styles: + specifier: ^6.2.3 + version: 6.2.3 + chalk: + specifier: ^5.6.2 + version: 5.6.2 + esbuild: + specifier: ^0.28.1 + version: 0.28.1 + execa: + specifier: ^9.6.1 + version: 9.6.1 + happy-dom: + specifier: ^20.10.3 + version: 20.10.3 + marked: + specifier: ^18.0.5 + version: 18.0.5 + rolldown: + specifier: ^1.1.1 + version: 1.1.1 + shiki: + specifier: ^4.2.0 + version: 4.2.0 + string-width: + specifier: ^8.2.1 + version: 8.2.1 + terminal-link: + specifier: ^5.0.0 + version: 5.0.0 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + wrap-ansi: + specifier: ^10.0.0 + version: 10.0.0 + +packages: + + '@ai-sdk/gateway@3.0.132': + resolution: {integrity: sha512-sFZFGk6aVSNKmgDrb14efaAbvM71AB3UUvaTsU+bvhWP9jrkRrwix/jFX8IoOAJk0/X7Xf7p3m0J2O2G6ljZbA==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/openai-compatible@2.0.50': + resolution: {integrity: sha512-HyuxddF2Yv5G8qxK/0uksAINjQ4h6TpwOqHuqzsCM0u78/JWAW2OXcIplQeB44PIAORgPjbMzrw9DhnPYHMskA==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider-utils@4.0.29': + resolution: {integrity: sha512-uhukHaCBvqkwBHkT8C2PrnqKTCoLn3pdHXqtcR9I8ErH+flbzgW4o7VHSNIup9LRu+WBvZIZDQLsx6rwl2tiOA==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider@3.0.10': + resolution: {integrity: sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw==} + engines: {node: '>=18'} + + '@biomejs/biome@2.4.12': + resolution: {integrity: sha512-Rro7adQl3NLq/zJCIL98eElXKI8eEiBtoeu5TbXF/U3qbjuSc7Jb5rjUbeHHcquDWeSf3HnGP7XI5qGrlRk/pA==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@2.4.12': + resolution: {integrity: sha512-BnMU4Pc3ciEVteVpZ0BK33MLr7X57F5w1dwDLDn+/iy/yTrA4Q/N2yftidFtsA4vrDh0FMXDpacNV/Tl3fbmng==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@2.4.12': + resolution: {integrity: sha512-x9uJ0bI1rJsWICp3VH8w/5PnAVD3A7SqzDpbrfoUQX1QyWrK5jSU4fRLo/wSgGeplCivbxBRKmt5Xq4/nWvq8A==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@2.4.12': + resolution: {integrity: sha512-FhfpkAAlKL6kwvcVap0Hgp4AhZmtd3YImg0kK1jd7C/aSoh4SfsB2f++yG1rU0lr8Y5MCFJrcSkmssiL9Xnnig==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@biomejs/cli-linux-arm64@2.4.12': + resolution: {integrity: sha512-tOwuCuZZtKi1jVzbk/5nXmIsziOB6yqN8c9r9QM0EJYPU6DpQWf11uBOSCfFKKM4H3d9ZoarvlgMfbcuD051Pw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@biomejs/cli-linux-x64-musl@2.4.12': + resolution: {integrity: sha512-dwTIgZrGutzhkQCuvHynCkyW6hJxUuyZqKKO0YNfaS2GUoRO+tOvxXZqZB6SkWAOdfZTzwaw8IEdUnIkHKHoew==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@biomejs/cli-linux-x64@2.4.12': + resolution: {integrity: sha512-8pFeAnLU9QdW9jCIslB/v82bI0lhBmz2ZAKc8pVMFPO0t0wAHsoEkrUQUbMkIorTRIjbqyNZHA3lEXavsPWYSw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@biomejs/cli-win32-arm64@2.4.12': + resolution: {integrity: sha512-B0DLnx0vA9ya/3v7XyCaP+/lCpnbWbMOfUFFve+xb5OxyYvdHaS55YsSddr228Y+JAFk58agCuZTsqNiw2a6ig==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@2.4.12': + resolution: {integrity: sha512-yMckRzTyZ83hkk8iDFWswqSdU8tvZxspJKnYNh7JZr/zhZNOlzH13k4ecboU6MurKExCe2HUkH75pGI/O2JwGA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + + '@clack/core@1.4.1': + resolution: {integrity: sha512-FILJa1gGKEFTGZAJE9RpVhrjKz3c3h4ar60dSv6cGuDqufQ84YEIS3GAGvZiN+H6yaLbbvTFNejjCC4tXpZEuw==} + engines: {node: '>= 20.12.0'} + + '@clack/prompts@1.5.1': + resolution: {integrity: sha512-zccHj2z2oCCO4yrDiRSlFOxWerGqRiysP7a5jPK6uoI9URKAquwY42Dd/iUP8JWHxEzdRe4TlbvZCo8z1/mhrw==} + engines: {node: '>= 20.12.0'} + + '@emnapi/core@1.11.0': + resolution: {integrity: sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==} + + '@emnapi/runtime@1.11.0': + resolution: {integrity: sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==} + + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jsr/cliffy__table@1.2.1': + resolution: {integrity: sha512-C/edvcMwtnbFx0zyoeGRcC+ZRvGYqxEhRi19HpfbfiYS4ZErKjEwMJmavO/DN7Wxbk34UpJP+UQKL7nKe+dYkA==, tarball: https://npm.jsr.io/~/11/@jsr/cliffy__table/1.2.1.tgz} + + '@jsr/std__fmt@1.0.10': + resolution: {integrity: sha512-Q2N8XDNkh/c1IKTi+sO4Vm64M5m4W0o47qTURmMX0vgHm9AJ6jL7jEQ2kw0M1T2rBZiwAY78eUbYzFyOYWaCqQ==, tarball: https://npm.jsr.io/~/11/@jsr/std__fmt/1.0.10.tgz} + + '@mozilla/readability@0.6.0': + resolution: {integrity: sha512-juG5VWh4qAivzTAeMzvY9xs9HY5rAcr2E4I7tiSSCokRFi7XIZCAu92ZkSTsIj1OPceCifL3cpfteP3pDT9/QQ==} + engines: {node: '>=14.0.0'} + + '@napi-rs/wasm-runtime@1.1.5': + resolution: {integrity: sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@opentelemetry/api@1.9.1': + resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} + engines: {node: '>=8.0.0'} + + '@oxc-project/types@0.135.0': + resolution: {integrity: sha512-wR+xRdFkUBMvcAjBJ2q2kcZM6d+DKu2NgoOyxZgYwZdLhmiv6+rnO8PZ/P68kMiZtIKm+pW7zyEJ4kSOs0vo+Q==} + + '@rolldown/binding-android-arm64@1.1.1': + resolution: {integrity: sha512-BLf9Wak/gfwVb7NQTQW4wBgL3oAfPy7ArEkhwV543OVw/uY6B47z5xYsqPSZ9PDOorvURPinws6ThaFuNgGLgA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.1.1': + resolution: {integrity: sha512-rRZRPy/Ynb+Mxu0O6tfPldHeDgAn0sRij+IOUy6sFdUlv3hArGW/DloE3GfAxtqpOJuRNgF74Nr5gM4xBeU2jQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.1.1': + resolution: {integrity: sha512-/MtefPxhKPyWWFM8L45OWiEqRf+eSU2Qv9ZAyTaoZOoGcoPKxbbhjTJO2/U2IThv0uDZ4NWHc3/oTsR6IEOtww==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.1.1': + resolution: {integrity: sha512-202K+cpIi1kx/Zn7AtxBi4LTXSY67Aszb2K9rNsuW7FeBeh0nqoNmYLOSZidV0p88VPBzMmTZcHAdPNo3kRYzQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.1.1': + resolution: {integrity: sha512-wl9NfeXNUwrXtUc063tddmZFUI6qiNs1CNOwni0OL4vC7MqVSYugra3ZgtDmtVy8e0DluJTENmzIv2BwqLzT4Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.1.1': + resolution: {integrity: sha512-at2EO4o7D/PJLC4Xik16bU4CcjQE2tSv1LfqMA0TRYQYQihRm3gZeDB8xaX28A9SFedibcAk5DeMCKt4REKG0A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.1.1': + resolution: {integrity: sha512-5PUjZx366h9tkJTPJF5eibxOlK3sGoeRiBJLLjjEB5/kLDuhr6qB3LkhqLz1smXNgsX+pBhnbcJBrPE30HznAA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.1.1': + resolution: {integrity: sha512-1WK84XPeio3tjP1sM/TMXiC0G1i1iq1qGZ71KfNQjEFLU1kwD+Cv5T8nGySg/JUFwLbaScu6ve9DmeXlmqpkFA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.1.1': + resolution: {integrity: sha512-1nS1X5z1uMJ369RU25hTpKCFvUwXZp12dIzlzk4S+UxCTcSVGsAE6tzkOSufv/7jnmAtK0ZlrsJxh2fGmsnVSw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.1.1': + resolution: {integrity: sha512-NwX/wspnq4vYyMFsqbYvzums3ki/Tk8FZbMzMAovPDp3OfLeYKby/D+9osokadXuYEV3OvpeHlwnr/bG8QMixA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.1.1': + resolution: {integrity: sha512-+n46LhDrJFQM+229y4oXtVpj1G50U/+XuHMlpnisFTEXhrg9f/YIjp/HymX+PVJjBEr7XHRs3CFLelV464pqwA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.1.1': + resolution: {integrity: sha512-qGwEu47zOWYo7LdRHhCWTNhzwGtxXpdY6CERs8QEOqC0PXGGics/e3vHnyEUKt8xK6YkbZXFUCeklrpB6js8ag==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.1.1': + resolution: {integrity: sha512-qczfgEH8u0wHGGOXtA7UMAybNKuQjjEXairyQaw4WzjiMztfbgatG1h4OKays/smhtwbWltpKCRGtVhU6h40Sg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.1.1': + resolution: {integrity: sha512-4psXSh63mSbwJF+mB8/9yfUUEzBiHYcUjxa32EO9ZwKy0Ypwjcg4F10D8SvVXgd+isy2UUUjF9HJJnDu1T/4Gg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.1.1': + resolution: {integrity: sha512-MUvC/HLXVjzkQkWiExdVTEEWf0py+GfWm8WKSZsekG3ih6a21iy0BHPF07X3JIf3ifoklZXTIaHTLPBgH1C3dw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + + '@shikijs/core@4.2.0': + resolution: {integrity: sha512-Hc87Ab1Ld/vEbZRCbwx344I5v+4RU8CVToUTRkqXL1+TjbuOp9U5Xa0M23V4GEWHxVn+yO5otb+HkQVm3ptWQQ==} + engines: {node: '>=20'} + + '@shikijs/engine-javascript@4.2.0': + resolution: {integrity: sha512-fjETeq1k5ffyXqRgS6+3hpvqseLalp1kjNfRbXpUgWR8FpZ1CmQfiNHovc5lncYjt/Vg5JK/WJEmLahjwMa0og==} + engines: {node: '>=20'} + + '@shikijs/engine-oniguruma@4.2.0': + resolution: {integrity: sha512-hTorK1dffPkpbMUk6Z+828PgRo7d07HbnizoP0hNPFjhxMHctj0Px/qoHeGMYafc6ju+u9iMldN4JbVzNQM++g==} + engines: {node: '>=20'} + + '@shikijs/langs@4.2.0': + resolution: {integrity: sha512-bwrVRlJ0wUhZxAbVdvBbv2TTC9yLsh4C/IO5Ofz0T8MQntgDvyVnkbjw9vi50r1kx7RCIJdnJnjZAwmAsXFLZQ==} + engines: {node: '>=20'} + + '@shikijs/primitive@4.2.0': + resolution: {integrity: sha512-NOq+DtUkVBJtZMVXL5A0vI0Xk8nvDYaXetFHSJFlOqjDZIVhIPRYFdGkSoElDqNuegikcc3A76SNUa8dTqtAYA==} + engines: {node: '>=20'} + + '@shikijs/themes@4.2.0': + resolution: {integrity: sha512-RX8IHYeLv8Cu2W6ruc3RxUqWn0IYCqSrMBzi/uRGAmfyDNOnNO5BF/Px7o97n4XTpmFTo5GbRaazuOWj+2ak2w==} + engines: {node: '>=20'} + + '@shikijs/types@4.2.0': + resolution: {integrity: sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw==} + engines: {node: '>=20'} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/node@25.9.3': + resolution: {integrity: sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@types/whatwg-mimetype@3.0.2': + resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==} + + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + + '@ungap/structured-clone@1.3.1': + resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} + + '@vercel/oidc@3.2.0': + resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==} + engines: {node: '>= 20'} + + ai@6.0.206: + resolution: {integrity: sha512-hVdB5PozMMxmkPBfbCxkFOn0eVeCMi/imkfPvk65cxL4O8oIrvjUxDUX8dGkdkWG1No+R7e7D9ti2DHO61Z4YQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + buffer-image-size@0.6.4: + resolution: {integrity: sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==} + engines: {node: '>=4.0'} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + engines: {node: '>=18'} + hasBin: true + + eventsource-parser@3.1.0: + resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==} + engines: {node: '>=18.0.0'} + + execa@9.6.1: + resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} + engines: {node: ^18.19.0 || >=20.5.0} + + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + engines: {node: '>=18'} + + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + + happy-dom@20.10.3: + resolution: {integrity: sha512-Hjdiy8RziuCcn5z04QI/rlsNuQoG8P0xxjgvsSMpi89cvIXIOcucQtiHS1yHSShxoBcSCeYqAskINmTiy/mlfw==} + engines: {node: '>=20.0.0'} + + has-flag@5.0.1: + resolution: {integrity: sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==} + engines: {node: '>=12'} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + human-signals@8.0.1: + resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} + engines: {node: '>=18.18.0'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + + marked@18.0.5: + resolution: {integrity: sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==} + engines: {node: '>= 20'} + hasBin: true + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + + oniguruma-parser@0.12.2: + resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} + + oniguruma-to-es@4.3.6: + resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} + + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + pretty-ms@9.3.0: + resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} + engines: {node: '>=18'} + + property-information@7.2.0: + resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} + + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + + rolldown@1.1.1: + resolution: {integrity: sha512-IN750c0p+s3jqJIsFLRZrQazmbAB1kkQDTtQjSt/gbS2ywLhlv4R5Shazer0FZKmuo/BsO3/w2UoYnUjuOZqHg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shiki@4.2.0: + resolution: {integrity: sha512-hjNax6o/ylDy9lefQEaSDtzaT3iVNtZ3WmpQnbuQNoG4xvnSKf2kSKbihZVO4JRG1TTMejs7CmNRYlWgAL66pQ==} + engines: {node: '>=20'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + string-width@8.2.1: + resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==} + engines: {node: '>=20'} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + + supports-hyperlinks@4.4.0: + resolution: {integrity: sha512-UKbpT93hN5Nr9go5UY7bopIB9YQlMz9nm/ct4IXt/irb5YRkn9WaqrOBJGZ5Pwvsd5FQzSVeYlGdXoCAPQZrPg==} + engines: {node: '>=20'} + + terminal-link@5.0.0: + resolution: {integrity: sha512-qFAy10MTMwjzjU8U16YS4YoZD+NQLHzLssFMNqgravjbvIPNiqkGFR4yjhJfmY9R5OFU7+yHxc6y+uGHkKwLRA==} + engines: {node: '>=20'} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wrap-ansi@10.0.0: + resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==} + engines: {node: '>=20'} + + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@ai-sdk/gateway@3.0.132(zod@4.4.3)': + dependencies: + '@ai-sdk/provider': 3.0.10 + '@ai-sdk/provider-utils': 4.0.29(zod@4.4.3) + '@vercel/oidc': 3.2.0 + zod: 4.4.3 + + '@ai-sdk/openai-compatible@2.0.50(zod@4.4.3)': + dependencies: + '@ai-sdk/provider': 3.0.10 + '@ai-sdk/provider-utils': 4.0.29(zod@4.4.3) + zod: 4.4.3 + + '@ai-sdk/provider-utils@4.0.29(zod@4.4.3)': + dependencies: + '@ai-sdk/provider': 3.0.10 + '@standard-schema/spec': 1.1.0 + eventsource-parser: 3.1.0 + zod: 4.4.3 + + '@ai-sdk/provider@3.0.10': + dependencies: + json-schema: 0.4.0 + + '@biomejs/biome@2.4.12': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.4.12 + '@biomejs/cli-darwin-x64': 2.4.12 + '@biomejs/cli-linux-arm64': 2.4.12 + '@biomejs/cli-linux-arm64-musl': 2.4.12 + '@biomejs/cli-linux-x64': 2.4.12 + '@biomejs/cli-linux-x64-musl': 2.4.12 + '@biomejs/cli-win32-arm64': 2.4.12 + '@biomejs/cli-win32-x64': 2.4.12 + + '@biomejs/cli-darwin-arm64@2.4.12': + optional: true + + '@biomejs/cli-darwin-x64@2.4.12': + optional: true + + '@biomejs/cli-linux-arm64-musl@2.4.12': + optional: true + + '@biomejs/cli-linux-arm64@2.4.12': + optional: true + + '@biomejs/cli-linux-x64-musl@2.4.12': + optional: true + + '@biomejs/cli-linux-x64@2.4.12': + optional: true + + '@biomejs/cli-win32-arm64@2.4.12': + optional: true + + '@biomejs/cli-win32-x64@2.4.12': + optional: true + + '@clack/core@1.4.1': + dependencies: + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + + '@clack/prompts@1.5.1': + dependencies: + '@clack/core': 1.4.1 + fast-string-width: 3.0.2 + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + + '@emnapi/core@1.11.0': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.28.1': + optional: true + + '@esbuild/android-arm64@0.28.1': + optional: true + + '@esbuild/android-arm@0.28.1': + optional: true + + '@esbuild/android-x64@0.28.1': + optional: true + + '@esbuild/darwin-arm64@0.28.1': + optional: true + + '@esbuild/darwin-x64@0.28.1': + optional: true + + '@esbuild/freebsd-arm64@0.28.1': + optional: true + + '@esbuild/freebsd-x64@0.28.1': + optional: true + + '@esbuild/linux-arm64@0.28.1': + optional: true + + '@esbuild/linux-arm@0.28.1': + optional: true + + '@esbuild/linux-ia32@0.28.1': + optional: true + + '@esbuild/linux-loong64@0.28.1': + optional: true + + '@esbuild/linux-mips64el@0.28.1': + optional: true + + '@esbuild/linux-ppc64@0.28.1': + optional: true + + '@esbuild/linux-riscv64@0.28.1': + optional: true + + '@esbuild/linux-s390x@0.28.1': + optional: true + + '@esbuild/linux-x64@0.28.1': + optional: true + + '@esbuild/netbsd-arm64@0.28.1': + optional: true + + '@esbuild/netbsd-x64@0.28.1': + optional: true + + '@esbuild/openbsd-arm64@0.28.1': + optional: true + + '@esbuild/openbsd-x64@0.28.1': + optional: true + + '@esbuild/openharmony-arm64@0.28.1': + optional: true + + '@esbuild/sunos-x64@0.28.1': + optional: true + + '@esbuild/win32-arm64@0.28.1': + optional: true + + '@esbuild/win32-ia32@0.28.1': + optional: true + + '@esbuild/win32-x64@0.28.1': + optional: true + + '@jsr/cliffy__table@1.2.1': + dependencies: + '@jsr/std__fmt': 1.0.10 + + '@jsr/std__fmt@1.0.10': {} + + '@mozilla/readability@0.6.0': {} + + '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)': + dependencies: + '@emnapi/core': 1.11.0 + '@emnapi/runtime': 1.11.0 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@opentelemetry/api@1.9.1': {} + + '@oxc-project/types@0.135.0': {} + + '@rolldown/binding-android-arm64@1.1.1': + optional: true + + '@rolldown/binding-darwin-arm64@1.1.1': + optional: true + + '@rolldown/binding-darwin-x64@1.1.1': + optional: true + + '@rolldown/binding-freebsd-x64@1.1.1': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.1.1': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.1.1': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.1.1': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.1.1': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.1.1': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.1.1': + optional: true + + '@rolldown/binding-linux-x64-musl@1.1.1': + optional: true + + '@rolldown/binding-openharmony-arm64@1.1.1': + optional: true + + '@rolldown/binding-wasm32-wasi@1.1.1': + dependencies: + '@emnapi/core': 1.11.0 + '@emnapi/runtime': 1.11.0 + '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.1.1': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.1.1': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@sec-ant/readable-stream@0.4.1': {} + + '@shikijs/core@4.2.0': + dependencies: + '@shikijs/primitive': 4.2.0 + '@shikijs/types': 4.2.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@4.2.0': + dependencies: + '@shikijs/types': 4.2.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.6 + + '@shikijs/engine-oniguruma@4.2.0': + dependencies: + '@shikijs/types': 4.2.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@4.2.0': + dependencies: + '@shikijs/types': 4.2.0 + + '@shikijs/primitive@4.2.0': + dependencies: + '@shikijs/types': 4.2.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/themes@4.2.0': + dependencies: + '@shikijs/types': 4.2.0 + + '@shikijs/types@4.2.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@sindresorhus/merge-streams@4.0.0': {} + + '@standard-schema/spec@1.1.0': {} + + '@tybys/wasm-util@0.10.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/node@25.9.3': + dependencies: + undici-types: 7.24.6 + + '@types/unist@3.0.3': {} + + '@types/whatwg-mimetype@3.0.2': {} + + '@types/ws@8.18.1': + dependencies: + '@types/node': 25.9.3 + + '@ungap/structured-clone@1.3.1': {} + + '@vercel/oidc@3.2.0': {} + + ai@6.0.206(zod@4.4.3): + dependencies: + '@ai-sdk/gateway': 3.0.132(zod@4.4.3) + '@ai-sdk/provider': 3.0.10 + '@ai-sdk/provider-utils': 4.0.29(zod@4.4.3) + '@opentelemetry/api': 1.9.1 + zod: 4.4.3 + + ansi-escapes@7.3.0: + dependencies: + environment: 1.1.0 + + ansi-regex@6.2.2: {} + + ansi-styles@6.2.3: {} + + buffer-image-size@0.6.4: + dependencies: + '@types/node': 25.9.3 + + ccount@2.0.1: {} + + chalk@5.6.2: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + comma-separated-tokens@2.0.3: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + dequal@2.0.3: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + entities@7.0.1: {} + + environment@1.1.0: {} + + esbuild@0.28.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + + eventsource-parser@3.1.0: {} + + execa@9.6.1: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.6 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 8.0.1 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 6.0.0 + pretty-ms: 9.3.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.1.2 + + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + + get-east-asian-width@1.6.0: {} + + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + + happy-dom@20.10.3: + dependencies: + '@types/node': 25.9.3 + '@types/whatwg-mimetype': 3.0.2 + '@types/ws': 8.18.1 + buffer-image-size: 0.6.4 + entities: 7.0.1 + whatwg-mimetype: 3.0.0 + ws: 8.21.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + has-flag@5.0.1: {} + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + html-void-elements@3.0.0: {} + + human-signals@8.0.1: {} + + is-plain-obj@4.1.0: {} + + is-stream@4.0.1: {} + + is-unicode-supported@2.1.0: {} + + isexe@2.0.0: {} + + json-schema@0.4.0: {} + + marked@18.0.5: {} + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.1 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-encode@2.0.1: {} + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + + oniguruma-parser@0.12.2: {} + + oniguruma-to-es@4.3.6: + dependencies: + oniguruma-parser: 0.12.2 + regex: 6.1.0 + regex-recursion: 6.0.2 + + parse-ms@4.0.0: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + pretty-ms@9.3.0: + dependencies: + parse-ms: 4.0.0 + + property-information@7.2.0: {} + + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + + rolldown@1.1.1: + dependencies: + '@oxc-project/types': 0.135.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.1.1 + '@rolldown/binding-darwin-arm64': 1.1.1 + '@rolldown/binding-darwin-x64': 1.1.1 + '@rolldown/binding-freebsd-x64': 1.1.1 + '@rolldown/binding-linux-arm-gnueabihf': 1.1.1 + '@rolldown/binding-linux-arm64-gnu': 1.1.1 + '@rolldown/binding-linux-arm64-musl': 1.1.1 + '@rolldown/binding-linux-ppc64-gnu': 1.1.1 + '@rolldown/binding-linux-s390x-gnu': 1.1.1 + '@rolldown/binding-linux-x64-gnu': 1.1.1 + '@rolldown/binding-linux-x64-musl': 1.1.1 + '@rolldown/binding-openharmony-arm64': 1.1.1 + '@rolldown/binding-wasm32-wasi': 1.1.1 + '@rolldown/binding-win32-arm64-msvc': 1.1.1 + '@rolldown/binding-win32-x64-msvc': 1.1.1 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shiki@4.2.0: + dependencies: + '@shikijs/core': 4.2.0 + '@shikijs/engine-javascript': 4.2.0 + '@shikijs/engine-oniguruma': 4.2.0 + '@shikijs/langs': 4.2.0 + '@shikijs/themes': 4.2.0 + '@shikijs/types': 4.2.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + signal-exit@4.1.0: {} + + sisteransi@1.0.5: {} + + space-separated-tokens@2.0.2: {} + + string-width@8.2.1: + dependencies: + get-east-asian-width: 1.6.0 + strip-ansi: 7.2.0 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + strip-final-newline@4.0.0: {} + + supports-color@10.2.2: {} + + supports-hyperlinks@4.4.0: + dependencies: + has-flag: 5.0.1 + supports-color: 10.2.2 + + terminal-link@5.0.0: + dependencies: + ansi-escapes: 7.3.0 + supports-hyperlinks: 4.4.0 + + trim-lines@3.0.1: {} + + tslib@2.8.1: + optional: true + + typescript@5.9.3: {} + + undici-types@7.24.6: {} + + unicorn-magic@0.3.0: {} + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + whatwg-mimetype@3.0.0: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wrap-ansi@10.0.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 8.2.1 + strip-ansi: 7.2.0 + + ws@8.21.0: {} + + yoctocolors@2.1.2: {} + + zod@4.4.3: {} + + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..8ad854b --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +minimumReleaseAge: 4320 +allowBuilds: + esbuild: true diff --git a/sea-config-tts.json b/sea-config-tts.json new file mode 100644 index 0000000..1f147f7 --- /dev/null +++ b/sea-config-tts.json @@ -0,0 +1,6 @@ +{ + "main": "dist/tts.js", + "output": "dist/ttsc", + "useCodeCache": true, + "disableExperimentalSEAWarning": true +} diff --git a/sea-config.json b/sea-config.json new file mode 100644 index 0000000..4beec69 --- /dev/null +++ b/sea-config.json @@ -0,0 +1,6 @@ +{ + "main": "dist/index.js", + "output": "dist/lmc", + "useCodeCache": true, + "disableExperimentalSEAWarning": true +} diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..f885994 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,79 @@ +import fs from "node:fs/promises"; +import os from "node:os"; +import { autocomplete, intro, isCancel, outro, text } from "@clack/prompts"; +import chalk from "chalk"; + +const home = os.homedir(); +const configPath = `${home}/.lmc_config.json`; + +export interface AppConfig { + baseURL: string; + model: string; +} + +export async function initConfig(old: AppConfig | null): Promise { + console.log(); + intro(chalk.blue("Welcome to LMC. Let's make a new config!")); + + const baseURL = await text({ + message: "What's your base URL:", + defaultValue: old?.baseURL ?? "http://127.0.0.1:8080/v1", + placeholder: old?.baseURL ?? "http://127.0.0.1:8080/v1", + }); + if (isCancel(baseURL)) process.exit(1); + + const models = await getModels(baseURL); + const initialValue = models.includes(old?.model ?? "") + ? old?.model + : models[0]; + const model = await autocomplete({ + message: "Choose a default model:", + initialValue, + placeholder: initialValue ?? "Start typing...", + options: models.map((model) => ({ value: model })), + validate: (value) => + models.includes(typeof value === "string" ? value : "") + ? undefined + : `Unable to find model "${value}"`, + }); + if (!model || isCancel(model)) process.exit(1); + + const config = { baseURL, model } satisfies AppConfig; + + await fs.writeFile(configPath, JSON.stringify(config, null, " ")); + + outro(chalk.green("Configuration saved!")); + + return config; +} + +type Models = { + data: { + created: number; + id: string; + object: "model"; + owned_by: string; + }[]; +}; + +async function getModels(url: string) { + const response = await fetch(`${url}/models`); + const models = (await response.json()) as Models; + return models.data.map((model) => model.id); +} + +export async function loadConfig( + configFlag: boolean | undefined, +): Promise { + let config: AppConfig; + try { + const text = await fs.readFile(configPath, "utf8"); + config = JSON.parse(text); + } catch { + if (configFlag) return null; + console.error(`Config not found at ${configPath}`); + return null; + } + + return config; +} diff --git a/src/config/index.ts b/src/config/index.ts deleted file mode 100644 index 8281bf7..0000000 --- a/src/config/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -import os from "node:os"; -import chalk from "chalk"; - -const home = os.homedir(); -const configFile = Bun.file(`${home}/.lmc_config.json`); - -export interface AppConfig { - model?: string; -} - -export async function loadConfig(): Promise { - let config: AppConfig; - try { - config = await configFile.json(); - } catch { - config = {}; - } - return config; -} - -export async function saveConfig(config: AppConfig): Promise { - await configFile.write(JSON.stringify(config, null, " ")); - console.log(chalk.green("Configuration saved!")); -} diff --git a/src/conversation.ts b/src/conversation.ts new file mode 100644 index 0000000..96b1923 --- /dev/null +++ b/src/conversation.ts @@ -0,0 +1,38 @@ +import fs from "node:fs/promises"; +import os from "node:os"; +import type { ModelMessage } from "ai"; + +const home = os.homedir(); +const convoPath = `${home}/.lmc_convo.json`; + +export interface IConversation { + model: string; + messages: ModelMessage[]; +} + +export class Conversation implements IConversation { + messages: ModelMessage[] = []; + + constructor(public model: string) {} + + async load() { + try { + const text = await fs.readFile(convoPath, "utf8"); + const convo: IConversation = JSON.parse(text); + this.model = convo.model; + this.messages = convo.messages; + } catch { + console.error("No conversation found, starting one."); + } + } + + async save() { + await fs.writeFile( + convoPath, + JSON.stringify({ + model: this.model, + messages: this.messages, + } satisfies IConversation), + ); + } +} diff --git a/src/conversation/index.ts b/src/conversation/index.ts deleted file mode 100644 index 557e6b5..0000000 --- a/src/conversation/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -import os from "node:os"; -import type { ModelMessage } from "ai"; - -const home = os.homedir(); -export const convoFile = Bun.file(`${home}/.lmc_convo.json`); - -export interface Conversation { - model: string; - messages: ModelMessage[]; -} - -export async function loadConversation( - defaultModel: string, -): Promise { - try { - return await convoFile.json(); - } catch { - return { model: defaultModel, messages: [] }; - } -} - -export async function saveConversation(convo: Conversation): Promise { - await convoFile.write(JSON.stringify(convo)); -} diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 0000000..2eac281 --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,27 @@ +import { chalkStderr } from "chalk"; +import wrapAnsi from "wrap-ansi"; + +export interface ErrorLike { + message: string; +} + +export const isErrorLike = (err: unknown): err is ErrorLike => + typeof err === "object" && + err !== null && + "message" in err && + typeof err.message === "string"; + +export function handleTopLevel(err: unknown) { + if (isErrorLike(err)) { + if (process.stderr.isTTY) { + const [columns] = process.stderr.getWindowSize(); + console.error(wrapAnsi(chalkStderr.red(err.message), columns)); + } else { + console.error(err.message); + } + } else { + console.error(String(err)); + } + + process.exit(1); +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..56c93ed --- /dev/null +++ b/src/index.ts @@ -0,0 +1,107 @@ +#!/usr/bin/env node +import fs from "node:fs/promises"; +import consumers from "node:stream/consumers"; +import util from "node:util"; +import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; +import { streamText } from "ai"; + +import { initConfig, loadConfig } from "./config"; +import { Conversation } from "./conversation"; +import { handleTopLevel } from "./errors"; +import { + base, + commitDiff, + contextFence, + diffFence, + fileFence, + review, +} from "./prompts"; +import Renderer from "./render"; + +async function main() { + const { values: args, positionals } = util.parseArgs({ + args: process.argv.slice(2), + options: { + model: { type: "string", short: "m" }, + new: { type: "boolean", short: "n" }, + config: { type: "boolean", short: "c" }, + plain: { type: "boolean" }, + "plain-out": { type: "boolean" }, + "plain-err": { type: "boolean" }, + version: { type: "boolean", short: "v" }, + "git-message": { type: "boolean", short: "g" }, + review: { type: "boolean", short: "r" }, + file: { type: "string", short: "f", multiple: true }, + stdin: { type: "boolean", short: "i" }, + }, + strict: true, + allowPositionals: true, + }); + + if (args.version) { + console.log("0.0.0"); + return; + } + + const config = await loadConfig(args.config); + + if (args.config || !config) { + await initConfig(config); + return; + } + + const model = args.model ?? config.model; + + const plainOut = args["plain-out"] ?? args.plain ?? !process.stdout.isTTY; + const plainErr = args["plain-err"] ?? args.plain ?? !process.stderr.isTTY; + const renderer = new Renderer(plainOut, plainErr); + + const provider = createOpenAICompatible({ + name: "llama.cpp", + apiKey: "local", + baseURL: config.baseURL, + includeUsage: true, // Include usage information in streaming responses + }); + + const convo = new Conversation(model); + + if (!args.new) { + await convo.load(); + if (args.model) convo.model = args.model; + } + + const input = positionals.join(" ").trim(); + let stdinText = ""; + if (args.stdin) stdinText = await consumers.text(process.stdin); + const files = []; + for (const file of args.file ?? []) + files.push(fileFence(file, await fs.readFile(file, "utf8"))); + + let prompt = base(input, [ + ...files, + stdinText ? contextFence(stdinText) : "", + ]); + if (args["git-message"]) { + prompt = commitDiff(stdinText, [...files]); + } else if (args.review) { + prompt = review([...files, stdinText ? diffFence(stdinText) : "", input]); + } + + if (!prompt) throw new Error("No prompt given."); + + convo.messages.push({ role: "user", content: prompt }); + + const { fullStream, text, totalUsage } = streamText({ + model: provider(convo.model), + messages: convo.messages, + }); + + await renderer.renderStream(fullStream); + + convo.messages.push({ role: "assistant", content: await text }); + await convo.save(); + + await renderer.renderStats(totalUsage); +} + +main().catch(handleTopLevel); diff --git a/src/prompts.ts b/src/prompts.ts new file mode 100644 index 0000000..31fae99 --- /dev/null +++ b/src/prompts.ts @@ -0,0 +1,31 @@ +const blocks = (input?: string[]) => + input?.map((block) => (block ? `${block}\n` : "")).join("") ?? ""; + +export const base = (input: string, extra?: string[]) => + `${blocks(extra)}${input}`; + +export const fileFence = (name: string, input: string) => ` +${input} + +`; + +export const diffFence = (input: string) => ` +${input} +`; + +export const contextFence = (input: string) => ` +${input} +`; + +export const commitDiff = ( + input: string, + extra?: string[], +) => `${blocks(extra)}${diffFence(input)} +Generate a conventional commit message for the included context. If you are unable to generate a reasonable option, ask for more context.`; + +export const review = (input: string[]) => `${blocks(input)} +Review the included context for any potential bugs, best practices, performance issues, or security flaws. + +Today's date is ${new Date().toISOString()}, your version info is likely out-of-date. + +Let me know if you need any more context to complete your review.`; diff --git a/src/render/codeToANSI.ts b/src/render/codeToANSI.ts index d5a081e..fc54b8e 100644 --- a/src/render/codeToANSI.ts +++ b/src/render/codeToANSI.ts @@ -24,7 +24,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import c from "chalk"; -import type { BundledLanguage, BundledTheme, HighlighterGeneric } from "shiki"; +import type { BundledLanguage, BundledTheme, HighlighterCore } from "shiki"; // From @shikijs/vscode-textmate enum FontStyle { @@ -37,7 +37,7 @@ enum FontStyle { } export function codeToANSI( - highlighter: HighlighterGeneric, + highlighter: HighlighterCore, code: string, lang: BundledLanguage, theme: BundledTheme, diff --git a/src/render/index.ts b/src/render/index.ts index 0e78648..2819467 100644 --- a/src/render/index.ts +++ b/src/render/index.ts @@ -1,4 +1,5 @@ import type { WriteStream } from "node:tty"; +import { spinner } from "@clack/prompts"; import type { AsyncIterableStream, LanguageModelUsage, @@ -7,34 +8,29 @@ import type { } from "ai"; import styles from "ansi-styles"; import { chalkStderr, supportsColorStderr } from "chalk"; +import stringWidth from "string-width"; import { renderMarkdown } from "./markdown"; export default class Renderer { private lineCount = 0; private charsSinceNL = 0; - private memOk = true; private buffer = ""; private unsafeIndex = 0; + private start = 0; + private stop = 0; constructor( private isPlain = false, private isPlainErr = false, - ) { - process.stdout.addListener("drain", () => { - this.memOk = true; - }); - process.stderr.addListener("drain", () => { - this.memOk = true; - }); - } + ) {} private write(text: string, pipe: WriteStream = process.stdout) { - this.memOk = pipe.write(text); + pipe.write(text); } private moveClearReset(y: number, pipe: WriteStream = process.stdout) { - this.memOk = pipe.moveCursor(-9999, y); - this.memOk = pipe.clearScreenDown(); + pipe.moveCursor(-9999, y); + pipe.clearScreenDown(); this.resetCounts(); } @@ -45,16 +41,17 @@ export default class Renderer { private checkContainment(text: string, pipe: WriteStream = process.stdout) { const [columns, rows] = pipe.getWindowSize(); - this.charsSinceNL += text.length; + this.charsSinceNL += stringWidth(text); if (text.includes("\n")) { this.lineCount += text.match(/\n/g)?.length ?? 1; - this.charsSinceNL = text.length - text.lastIndexOf("\n") - 1; + this.charsSinceNL = + stringWidth(text) - stringWidth(text.slice(0, text.lastIndexOf("\n"))); } else if (this.charsSinceNL > columns) { this.lineCount++; this.charsSinceNL = this.charsSinceNL - columns; } - if (this.lineCount >= Math.max(rows - 10, 1)) { + if (this.lineCount >= Math.max(rows - 4, 1)) { this.moveClearReset(-this.lineCount, pipe); } } @@ -87,9 +84,22 @@ export default class Renderer { async renderStream(stream: AsyncIterableStream>) { this.resetCounts(); + const spin = spinner({ + withGuide: false, + indicator: "timer", + onCancel: () => { + process.exit(130); + }, + }); + if (!this.isPlain) { + spin.start("Waiting for response"); + } + for await (const part of stream) { switch (part.type) { case "reasoning-start": { + this.start = Date.now(); + spin.clear(); if (supportsColorStderr) this.write( `${styles.dim.open}${styles.italic.open}`, @@ -111,6 +121,8 @@ export default class Renderer { } else { this.write("\n--- Done Thinking ---\n", process.stderr); } + const time = (Date.now() - this.start) / 1000; + this.write(`Thought for ${time.toFixed(2)}s\n`, process.stderr); if (supportsColorStderr) this.write( @@ -121,6 +133,8 @@ export default class Renderer { break; } case "text-start": { + if (this.start === 0) this.start = Date.now(); + spin.clear(); this.write("\n"); break; } @@ -133,6 +147,7 @@ export default class Renderer { break; } case "text-end": { + this.stop = Date.now(); if (this.buffer) { await this.render(this.buffer); this.buffer = ""; @@ -142,21 +157,15 @@ export default class Renderer { break; } } - - while (!this.memOk) await Bun.sleep(10); } } - async renderStats( - usage: PromiseLike, - start: number, - stop: number, - ) { + async renderStats(usage: PromiseLike) { const { inputTokens, outputTokens } = await usage; const context = (inputTokens ?? 0) / 1000; const output = (outputTokens ?? 0) / 1000; - const time = (stop - start) / 1000; - const template = `Context: ${context.toFixed(1)}k, Output: ${output.toFixed(1)}k, Time: ${time.toFixed(2)}s\n`; + const toks = (outputTokens ?? 0) / ((this.stop - this.start) / 1000); + const template = `Context: ${context.toFixed(1)}k, Output: ${output.toFixed(1)}k, Toks: ${toks.toFixed(2)}/s\n`; this.write(chalkStderr.dim(template), process.stderr); } } diff --git a/src/render/markdown.ts b/src/render/markdown.ts index b7f5c7e..7190de8 100644 --- a/src/render/markdown.ts +++ b/src/render/markdown.ts @@ -1,17 +1,192 @@ +import { Cell, Table } from "@cliffy/table"; import chalk from "chalk"; -import CliTable3 from "cli-table3"; -import { - type BundledLanguage, - type BundledTheme, - createHighlighter, - type HighlighterGeneric, -} from "shiki"; +import { marked, Renderer, type Tokens } from "marked"; +import type { BundledLanguage, HighlighterCore } from "shiki"; +import { createHighlighterCore } from "shiki/core"; +import { createOnigurumaEngine } from "shiki/engine/oniguruma"; +import stringWidth from "string-width"; import terminalLink from "terminal-link"; +import wrapAnsi from "wrap-ansi"; import { codeToANSI } from "./codeToANSI"; -let highlighter: HighlighterGeneric | undefined; +let highlighter: HighlighterCore | undefined; let highligherLangs = ""; +class SWCell extends Cell { + public override get length() { + return stringWidth(this.toString()); + } +} + +class LmcRenderer extends Renderer { + private columns: number; + + constructor(columns: number) { + super(); + this.columns = columns; + } + + override heading({ tokens, depth }: Tokens.Heading) { + const colors = [ + chalk.cyan.underline, + chalk.blue, + chalk.green, + chalk.cyanBright, + chalk.blueBright, + chalk.greenBright, + ]; + const color = colors[depth - 1] ?? chalk.white; + const text = this.parser.parseInline(tokens); + return color.bold(`${"#".repeat(depth)} ${text}\n`); + } + + override paragraph({ tokens }: Tokens.Paragraph) { + const text = wrapAnsi(this.parser.parseInline(tokens), this.columns - 4); + return `\n${text}\n`; + } + + override blockquote({ tokens }: Tokens.Blockquote) { + const text = this.parser.parse(tokens).trim(); + const lines = `\n${text}\n`.split("\n"); + const toRender = lines.map((line) => `> ${line}`).join("\n"); + return `\n${toRender.replaceAll(">", chalk.dim(">"))}\n`; + } + + override code({ text, lang }: Tokens.Code) { + let toRender = text; + + if (highlighter && lang) { + try { + toRender = codeToANSI( + highlighter, + text, + lang as BundledLanguage, + "kanagawa-wave", + ); + } catch {} + } + + return `\n\`\`\`${chalk.blue(lang ?? "")}\n${toRender}\n\`\`\`\n`; + } + + override list({ items, ordered, start }: Tokens.List) { + const num = start || 1; + const renderedItems = items + .map((item, index) => + this.listitem(item, ordered ? num + index : undefined), + ) + .join("\n"); + + return `\n${renderedItems}\n`; + } + + override listitem({ tokens }: Tokens.ListItem, num?: number) { + const text = this.parser.parse(tokens).trim(); + let marker = ""; + if (num !== undefined) { + marker = `${num}.`; + } else { + marker = "•"; + } + + const item = `${marker} ${text}`; + return item + .split("\n") + .filter((line) => line.length > 0) + .map((line) => ` ${line}`) + .join("\n"); + } + + override checkbox({ checked }: Tokens.Checkbox): string { + return checked ? "[✓] " : "[ ] "; + } + + override hr() { + return `\n${chalk.dim("─".repeat(this.columns))}\n`; + } + + override table({ header, rows }: Tokens.Table) { + const headers = header.map((col) => { + const text = this.parser.parseInline(col.tokens); + return new SWCell(chalk.red.bold(text)); + }); + + const body = rows.map((row) => + row.map((col) => { + const text = this.parser.parseInline(col.tokens); + return new SWCell(text); + }), + ); + + const cols = this.columns - (headers.length * 3 + 1); + const maxWidth = Math.floor(cols / headers.length); + const table = new Table() + .header(headers) + .body(body) + .border() + .maxColWidth(maxWidth) + .toString(); + + return `\n${table}\n`; + } + + override strong({ tokens }: Tokens.Strong) { + const text = this.parser.parseInline(tokens); + return chalk.bold.rgb(245, 245, 245)(text); + } + + override em({ tokens }: Tokens.Em) { + const text = this.parser.parseInline(tokens); + return chalk.italic.rgb(245, 245, 245)(text); + } + + override del({ tokens }: Tokens.Del) { + const text = this.parser.parseInline(tokens); + return chalk.grey.strikethrough(text); + } + + override codespan({ text }: Tokens.Codespan) { + return chalk.bgRgb(165, 165, 192).rgb(0, 0, 0).bold(text); + } + + override link({ href, tokens }: Tokens.Link) { + const text = this.parser.parseInline(tokens); + return chalk.blueBright.underline(terminalLink(text, href)); + } + + override image({ text: _ }: Tokens.Image) { + return chalk.red("\nImages are unsupported (for now)\n"); + } + + override text(token: Tokens.Text | Tokens.Escape) { + if (token.type === "text" && token.tokens) { + const parsed = this.parser.parseInline(token.tokens); + return wrapAnsi(parsed, this.columns - 12); + } + return this.replaceLatex(token.text); + } + + override html({ text }: Tokens.HTML | Tokens.Tag) { + if (text === "
") return this.br(); + return chalk.red(text); + } + + override br() { + return "\n"; + } + + private replaceLatex(text: string) { + return text + .replaceAll("$\\rightarrow$", "-->") + .replaceAll("$\\leftarrow$", "<--") + .replaceAll("$\\leftrightarrow$", "<->") + .replaceAll("$\\Rightarrow$", "==>") + .replaceAll("$\\Leftarrow$", "<==") + .replaceAll("$\\Leftrightarrow$", "<=>") + .replaceAll("$\\to$", "->"); + } +} + export async function renderMarkdown( text: PromiseLike | string, columns: number, @@ -19,98 +194,29 @@ export async function renderMarkdown( const md = await text; const langs = md - .match(/```[a-z]+\n/g) - ?.map((lang) => lang.replace("```", "").trimEnd()); + .match(/```\S+\n/g) + ?.map((lang) => lang.replace("```", "").trimEnd()) + .sort(); if (langs && langs?.join() !== highligherLangs) { + const { bundledThemes, bundledLanguages, bundledLanguagesAlias } = + await import("shiki/bundle/full"); try { highligherLangs = langs.join(); - highlighter = await createHighlighter({ - langs, - themes: ["kanagawa-wave", "kanagawa-lotus"], + highlighter = await createHighlighterCore({ + langs: langs.map( + (lang) => + bundledLanguages[lang as BundledLanguage] || + bundledLanguagesAlias[lang], + ), + themes: [bundledThemes["kanagawa-wave"]], + engine: createOnigurumaEngine(import("shiki/wasm")), }); } catch {} } - let currentTable = new CliTable3(); + const renderer = new LmcRenderer(columns); + const rendered = await marked.parse(md, { renderer }); - const rendered = Bun.markdown.render(md, { - heading: (children, { level }) => { - const colors = [ - chalk.cyan.underline, - chalk.blue, - chalk.green, - chalk.cyanBright, - chalk.blueBright, - chalk.greenBright, - ]; - const color = colors[level - 1] ?? chalk.white; - return color.bold(`${"#".repeat(level)} ${children}\n\n`); - }, - paragraph: (children) => `${children}\n\n`, - blockquote: (children) => { - const lines = children.split("\n"); - return lines - .map((line) => `${chalk.dim("> ")}${line}`) - .join("\n") - .replace(/\n$/, "\n\n"); - }, - code: (children, meta) => { - let toRender = children; - - if (highlighter && meta?.language) { - try { - toRender = codeToANSI( - highlighter, - children, - meta.language as BundledLanguage, - "kanagawa-wave", - ); - } catch {} - } - - return `\`\`\`${chalk.blue(meta?.language ?? "")}\n${toRender}\`\`\`\n`; - }, - list: (children, { depth }) => - `${depth > 0 ? "\n" : ""}${children.trimEnd()}${depth === 0 ? "\n\n" : ""}`, - listItem: (children, { index, depth, ordered, start, checked }) => { - const indent = " ".repeat(depth); - let marker = ""; - if (checked !== undefined) { - marker = checked ? "[✓]" : "[ ]"; - } else if (ordered) { - const n = (start ?? 1) + index; - marker = `${n}.`; - } else { - marker = "•"; - } - return `${indent} ${marker} ${children}\n`; - }, - hr: () => `${chalk.dim("─".repeat(columns))}\n\n`, - table: (_children) => { - const toRender = currentTable.toString(); - currentTable = new CliTable3(); - return `${toRender}\n\n`; - }, - tr: (children) => { - if (children) currentTable.push(children.split(":tablesep:").slice(1)); - return ""; - }, - td: (children) => `:tablesep:${children}`, - th: (children) => { - currentTable.options.head.push(children); - return ""; - }, - strong: (children) => chalk.bold(children), - emphasis: (children) => chalk.italic(children), - strikethrough: (children) => chalk.grey.strikethrough(children), - codespan: (children) => chalk.bgWhite.black(` ${children} `), - link: (children, { href }) => - chalk.blueBright.underline(terminalLink(children, href)), - image: () => "", - text: (children) => children, - html: () => "", - }); - - return rendered.replace(/\n\n$/, "\n"); + return rendered.trimStart(); } diff --git a/src/tts.ts b/src/tts.ts new file mode 100644 index 0000000..e6aab05 --- /dev/null +++ b/src/tts.ts @@ -0,0 +1,180 @@ +#!/usr/bin/env node +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import process from "node:process"; +import readline from "node:readline"; +import consumers from "node:stream/consumers"; +import util from "node:util"; +import { Readability } from "@mozilla/readability"; +import chalk from "chalk"; +import { ExecaError, execa } from "execa"; +import { Window } from "happy-dom"; +import wrapAnsi from "wrap-ansi"; +import { handleTopLevel } from "./errors"; + +async function main() { + const start = Date.now(); + + const { values: args, positionals } = util.parseArgs({ + args: process.argv.slice(2), + options: { + wait: { type: "boolean" }, + w: { type: "boolean" }, + pause: { type: "boolean" }, + p: { type: "boolean" }, + url: { type: "boolean" }, + u: { type: "boolean" }, + edit: { type: "boolean" }, + e: { type: "boolean" }, + }, + strict: true, + allowPositionals: true, + }); + + const waitAndCombine = args.wait ?? args.w; + const pausePlayer = args.pause ?? args.p; + const readURL = args.url ?? args.u; + const editContent = args.edit ?? args.e; + + let input = positionals.join(" ").trim(); + + if (!input) input = (await consumers.text(process.stdin)).trim(); + + if (!input) { + throw new Error("No input was given. Use positional arguments or stdin."); + } + + if (readURL) { + const result = await fetch(input); + const window = new Window({ url: input }); + const doc = window.document; + doc.write(await result.text()); + await window.happyDOM.waitUntilComplete(); + const readable = new Readability(doc).parse(); + if (!readable?.textContent) { + throw new Error( + "Unable to read webpage. This usually means it's blocking scrapers.", + ); + } + input = readable.textContent.trim(); + } + + const home = os.homedir(); + const cwd = path.join(home, ".ttsc"); + + if (editContent) { + const file = path.join(cwd, "input.txt"); + await fs.writeFile(file, input, "utf8"); + const editor = process.env.EDITOR || "nano"; + await execa({ + cwd, + stdin: "inherit", + stdout: "inherit", + stderr: "inherit", + })`${editor} ${file}`; + input = await fs.readFile(file, "utf8"); + } + + console.log(chalk.blue("Welcome to TTSC!")); + console.log( + chalk.yellow( + "Beginning TTS processing... (This will take extra time the first time)", + ), + ); + + const wavs = fs.glob(path.join(cwd, "*.wav")); + for await (const wav of wavs) await fs.rm(wav); + + const koko = execa({ + cwd, + input, + stdout: "pipe", + stderr: "inherit", + })`uv run -- python -u main.py`; + + if (!waitAndCombine) { + koko.catch(() => {}); + koko.then(() => { + const stop = Date.now(); + const duration = ((stop - start) / 1000).toFixed(2); + console.log(chalk.green(`\nProcessing finished: ${duration}s`)); + }); + } + + let first = true; + const files = []; + const rl = readline.createInterface({ input: koko.stdout }); + + for await (const line of rl) { + const trimmed = line.trim(); + if (!trimmed) continue; + + let info: { file: number; text: string }; + try { + info = JSON.parse(trimmed); + } catch { + console.log(trimmed); + continue; + } + + if (first) { + first = false; + const stop = Date.now(); + const duration = ((stop - start) / 1000).toFixed(2); + console.log(chalk.green(`\nTime-to-audio: ${duration}s`)); + } + + const file = path.join(cwd, `${info.file}.wav`); + + if (waitAndCombine) { + files.push(file); + process.stdout.write("."); + } else { + const [columns] = process.stdout.getWindowSize(); + const text = wrapAnsi(info.text, columns); + console.log(`\n${chalk.blue(file)}\n${text}\n`); + await execa({ + cwd, + stdin: "ignore", + stdout: "inherit", + stderr: "inherit", + })`mpv${pausePlayer ? " --pause" : ""} ${file}`; + } + } + + await koko; + if (!waitAndCombine) return; + + const stop = Date.now(); + const duration = ((stop - start) / 1000).toFixed(2); + console.log(chalk.green(`\nProcessing finished: ${duration}s`)); + + console.log(chalk.yellow(`Combining audio with sox...\n`)); + try { + await execa({ cwd, shell: true })`sox ${files} output.wav`; + } catch (err) { + if (err instanceof ExecaError && err.code === "ENOENT") { + console.error("Sox not found. Unable to combine outputs."); + console.log(chalk.yellow(`Audio available in ${cwd}`)); + return; + } else { + throw err; + } + } + + const [columns] = process.stdout.getWindowSize(); + console.log(wrapAnsi(input, columns)); + + console.log( + chalk.green(`Playing audio from ${path.join(cwd, "output.wav")}`), + ); + await execa({ + cwd, + stdin: "ignore", + stdout: "inherit", + stderr: "inherit", + })`mpv${pausePlayer ? " --pause" : ""} output.wav`; +} + +main().catch(handleTopLevel); diff --git a/testRender.ts b/testRender.ts deleted file mode 100644 index a31dfe4..0000000 --- a/testRender.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { renderMarkdown } from "./src/render/markdown.ts"; - -const text = await Bun.file("test.md").text(); - -const [columns] = process.stdout.getWindowSize(); -process.stdout.write(await renderMarkdown(text, columns)); diff --git a/tsconfig.json b/tsconfig.json index bfa0fea..d593bb5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,29 +1,39 @@ { + // Visit https://aka.ms/tsconfig to read more about this file "compilerOptions": { - // Environment setup & latest features - "lib": ["ESNext"], - "target": "ESNext", - "module": "Preserve", - "moduleDetection": "force", - "jsx": "react-jsx", - "allowJs": true, - - // Bundler mode + // Environment Settings + // See also https://aka.ms/tsconfig/module + "module": "esnext", + "target": "esnext", "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, + "esModuleInterop": true, "noEmit": true, + // For nodejs: + "lib": ["esnext"], + "types": ["node"], + // and npm install -D @types/node - // Best practices - "strict": true, - "skipLibCheck": true, - "noFallthroughCasesInSwitch": true, + // Stricter Typechecking Options "noUncheckedIndexedAccess": true, - "noImplicitOverride": true, + "exactOptionalPropertyTypes": true, - // Some stricter flags (disabled by default) - "noUnusedLocals": false, - "noUnusedParameters": false, - "noPropertyAccessFromIndexSignature": false + // Style Options + "noImplicitReturns": true, + "noImplicitOverride": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": false, + + // Recommended Options + "strict": true, + "jsx": "react-jsx", + "allowImportingTsExtensions": false, + "allowArbitraryExtensions": true, + "verbatimModuleSyntax": true, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true } } diff --git a/tts.ts b/tts.ts deleted file mode 100644 index 16f8b8b..0000000 --- a/tts.ts +++ /dev/null @@ -1,98 +0,0 @@ -import os from "node:os"; -import { parseArgs } from "node:util"; -import { $, which } from "bun"; -import chalk from "chalk"; - -const { values: args } = parseArgs({ - args: Bun.argv, - options: { - wait: { type: "boolean" }, - w: { type: "boolean" }, - }, - strict: true, - allowPositionals: true, -}); - -const wait = args.wait ?? args.w; -const text = await Bun.stdin.text(); - -if (!text) { - console.error("No input given."); - process.exit(1); -} - -const home = os.homedir(); -const cwd = `${home}/.ttsc/`; -$.cwd(cwd); - -await $`rm *.wav`.nothrow(); - -console.log(chalk.blue("Welcome to TTSC!")); -console.log( - chalk.yellow( - "Beginning TTS processing... (This will take extra time the first time)", - ), -); - -const koko = Bun.spawn({ - cmd: ["uv", "run", "--", "python", "-u", "main.py"], - cwd, - stdin: Buffer.from(text), - stdout: "pipe", - stderr: "inherit", -}); - -if (!wait) { - koko.exited.then(() => console.log(chalk.green("\nProcessing finished."))); -} - -const decoder = new TextDecoder(); -for await (const line of koko.stdout) { - const decoded = decoder.decode(line).trim(); - - let infos: { file: number; text: string }[]; - try { - infos = Bun.JSONL.parse(decoded) as { file: number; text: string }[]; - } catch { - console.log(decoded); - continue; - } - - for (const info of infos) { - const file = `${home}/.ttsc/${info.file}.wav`; - console.log(`\n${chalk.blue(file)}:\n${info.text}`); - - if (!wait) { - await Bun.spawn({ - cmd: ["mpv", file], - cwd, - stdin: "ignore", - stdout: "inherit", - stderr: "inherit", - }).exited; - } - } -} - -await koko.exited; -if (!wait) process.exit(); - -console.log(chalk.green("\nProcessing finished.")); - -console.log(chalk.yellow(`Combining audio with sox...`)); -if (which("sox")) { - await $`sox *.wav output.wav`; - console.log(chalk.green(`Playing audio from ${cwd}output.wav`)); -} else { - console.error("Sox not found. Unable to combine outputs."); - console.log(chalk.yellow(`Audio available in ${cwd}`)); - process.exit(); -} - -await Bun.spawn({ - cmd: ["mpv", "--pause", "output.wav"], - cwd, - stdin: "ignore", - stdout: "inherit", - stderr: "inherit", -}).exited;