From fdbc9fbda1f27682d38bedcb96b1ea8fb82909ef Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Sun, 7 Jun 2026 15:44:31 +0200 Subject: [PATCH 01/13] feat(ai-cli): add @tanstack/ai-cli (ts-ai), a CLI over TanStack AI Machine-first CLI exposing the core activities as the `ts-ai` binary: chat, image, video, audio, speech, transcribe, summarize, plus introspect, mcp, and update. - Stateless single-shot subprocess design: --json buffered output, --stream AG-UI events, strict stdout-is-payload / stderr-is-everything-else, typed exit codes (0-4), and structured error objects. - provider/model slug resolution; openai, anthropic, gemini, openrouter and fal bundled for zero-install; keys via --api-key, a conventional .env, or env vars; all options expressible via --config (file or inline JSON). - chat: tools via --mcp servers, sandboxed --code-mode, --schema structured output, stateless --messages history; rich JSON envelope via StreamProcessor. - Lazy Ink TTY layer (never loaded on the machine path): animated home menu, interactive chat REPL, inline image preview. - ts-ai introspect (machine-readable manifest) and ts-ai mcp (every command exposed as an MCP tool over stdio). - New testing/cli subprocess E2E project + unit tests, docs page, changeset. --- .changeset/ai-cli-initial.md | 23 + docs/cli/overview.md | 187 +++ docs/config.json | 10 + knip.json | 9 + packages/ai-cli/package.json | 82 ++ packages/ai-cli/src/cli/activities/audio.ts | 78 ++ packages/ai-cli/src/cli/activities/chat.ts | 183 +++ packages/ai-cli/src/cli/activities/image.ts | 107 ++ packages/ai-cli/src/cli/activities/speech.ts | 79 ++ .../ai-cli/src/cli/activities/summarize.ts | 61 + .../ai-cli/src/cli/activities/transcribe.ts | 74 ++ packages/ai-cli/src/cli/activities/video.ts | 112 ++ packages/ai-cli/src/cli/artifact.ts | 66 + packages/ai-cli/src/cli/bin.ts | 20 + packages/ai-cli/src/cli/code-mode.ts | 44 + packages/ai-cli/src/cli/context.ts | 70 ++ packages/ai-cli/src/cli/dispatch.ts | 69 ++ packages/ai-cli/src/cli/interactive.ts | 68 ++ packages/ai-cli/src/cli/introspect.ts | 10 + packages/ai-cli/src/cli/mcp-clients.ts | 93 ++ packages/ai-cli/src/cli/mcp.ts | 77 ++ packages/ai-cli/src/cli/options.ts | 60 + packages/ai-cli/src/cli/program.ts | 124 ++ packages/ai-cli/src/cli/run.ts | 48 + packages/ai-cli/src/cli/update.ts | 51 + packages/ai-cli/src/core/config.ts | 65 + packages/ai-cli/src/core/emit.ts | 35 + packages/ai-cli/src/core/env.ts | 37 + packages/ai-cli/src/core/exit-codes.ts | 81 ++ packages/ai-cli/src/core/io.ts | 96 ++ packages/ai-cli/src/core/logger.ts | 41 + packages/ai-cli/src/core/output.ts | 31 + packages/ai-cli/src/core/providers.ts | 250 ++++ packages/ai-cli/src/index.ts | 19 + packages/ai-cli/src/manifest/manifest.ts | 206 ++++ packages/ai-cli/src/manifest/types.ts | 73 ++ packages/ai-cli/src/render/ink.tsx | 75 ++ packages/ai-cli/src/render/lazy.ts | 52 + packages/ai-cli/src/render/menu.tsx | 146 +++ packages/ai-cli/src/render/repl.tsx | 105 ++ packages/ai-cli/tests/core.test.ts | 216 ++++ packages/ai-cli/tsconfig.json | 10 + packages/ai-cli/tsup.bin.config.ts | 18 + packages/ai-cli/vite.config.ts | 30 + pnpm-lock.yaml | 1068 ++++++++++++++++- testing/cli/README.md | 23 + testing/cli/package.json | 14 + testing/cli/tests/cli.spec.ts | 179 +++ testing/cli/tests/mcp.spec.ts | 68 ++ testing/cli/vitest.config.ts | 13 + 50 files changed, 4731 insertions(+), 25 deletions(-) create mode 100644 .changeset/ai-cli-initial.md create mode 100644 docs/cli/overview.md create mode 100644 packages/ai-cli/package.json create mode 100644 packages/ai-cli/src/cli/activities/audio.ts create mode 100644 packages/ai-cli/src/cli/activities/chat.ts create mode 100644 packages/ai-cli/src/cli/activities/image.ts create mode 100644 packages/ai-cli/src/cli/activities/speech.ts create mode 100644 packages/ai-cli/src/cli/activities/summarize.ts create mode 100644 packages/ai-cli/src/cli/activities/transcribe.ts create mode 100644 packages/ai-cli/src/cli/activities/video.ts create mode 100644 packages/ai-cli/src/cli/artifact.ts create mode 100644 packages/ai-cli/src/cli/bin.ts create mode 100644 packages/ai-cli/src/cli/code-mode.ts create mode 100644 packages/ai-cli/src/cli/context.ts create mode 100644 packages/ai-cli/src/cli/dispatch.ts create mode 100644 packages/ai-cli/src/cli/interactive.ts create mode 100644 packages/ai-cli/src/cli/introspect.ts create mode 100644 packages/ai-cli/src/cli/mcp-clients.ts create mode 100644 packages/ai-cli/src/cli/mcp.ts create mode 100644 packages/ai-cli/src/cli/options.ts create mode 100644 packages/ai-cli/src/cli/program.ts create mode 100644 packages/ai-cli/src/cli/run.ts create mode 100644 packages/ai-cli/src/cli/update.ts create mode 100644 packages/ai-cli/src/core/config.ts create mode 100644 packages/ai-cli/src/core/emit.ts create mode 100644 packages/ai-cli/src/core/env.ts create mode 100644 packages/ai-cli/src/core/exit-codes.ts create mode 100644 packages/ai-cli/src/core/io.ts create mode 100644 packages/ai-cli/src/core/logger.ts create mode 100644 packages/ai-cli/src/core/output.ts create mode 100644 packages/ai-cli/src/core/providers.ts create mode 100644 packages/ai-cli/src/index.ts create mode 100644 packages/ai-cli/src/manifest/manifest.ts create mode 100644 packages/ai-cli/src/manifest/types.ts create mode 100644 packages/ai-cli/src/render/ink.tsx create mode 100644 packages/ai-cli/src/render/lazy.ts create mode 100644 packages/ai-cli/src/render/menu.tsx create mode 100644 packages/ai-cli/src/render/repl.tsx create mode 100644 packages/ai-cli/tests/core.test.ts create mode 100644 packages/ai-cli/tsconfig.json create mode 100644 packages/ai-cli/tsup.bin.config.ts create mode 100644 packages/ai-cli/vite.config.ts create mode 100644 testing/cli/README.md create mode 100644 testing/cli/package.json create mode 100644 testing/cli/tests/cli.spec.ts create mode 100644 testing/cli/tests/mcp.spec.ts create mode 100644 testing/cli/vitest.config.ts diff --git a/.changeset/ai-cli-initial.md b/.changeset/ai-cli-initial.md new file mode 100644 index 000000000..e4efcc6bb --- /dev/null +++ b/.changeset/ai-cli-initial.md @@ -0,0 +1,23 @@ +--- +'@tanstack/ai-cli': minor +--- + +Add `@tanstack/ai-cli` — a type-safe CLI over TanStack AI exposing the core +activities as the `ts-ai` binary (`chat`, `image`, `video`, `audio`, `speech`, +`transcribe`, `summarize`), plus `introspect` (machine-readable manifest), +`mcp` (expose commands as MCP tools), and `update`. + +Designed machine-first for agent harnesses: every command is a stateless +single-shot subprocess with `--json` buffered output, `--stream` AG-UI event +output, strict stdout-is-payload discipline, typed exit codes, and structured +error objects. Providers resolve from a `provider/model` slug (openai, +anthropic, gemini, openrouter, and fal bundled for zero-install) with keys from +`--apiKey`, a conventional `.env`, or environment variables, and all options are +expressible via `--config` (file or inline JSON). + +For humans there's a lazily-loaded Ink layer: running `ts-ai` with no command on +a TTY opens an animated home screen and menu, `ts-ai chat` with no prompt drops +into an interactive REPL, and image results preview inline. `chat` supports tools +via `--mcp` servers, sandboxed `--code-mode` execution, and `--schema` structured +output, plus `ts-ai introspect` (machine-readable manifest), `ts-ai mcp` (expose +commands as MCP tools), and `ts-ai update`. diff --git a/docs/cli/overview.md b/docs/cli/overview.md new file mode 100644 index 000000000..3af412571 --- /dev/null +++ b/docs/cli/overview.md @@ -0,0 +1,187 @@ +--- +title: ts-ai CLI +id: cli-overview +order: 1 +description: "Run TanStack AI from the terminal or any agent harness with the ts-ai CLI — chat, image, video, audio, speech, transcribe, and summarize, with JSON output, AG-UI streaming, and a self-describing manifest." +keywords: + - tanstack ai + - cli + - ts-ai + - agent harness + - json output +--- + +`@tanstack/ai-cli` installs the `ts-ai` binary — a thin, type-safe CLI over the +core TanStack AI activities. It is built **machine-first**: every command is a +stateless, single-shot subprocess with structured I/O, so it drops cleanly into +an agent harness — while still giving humans a pretty, interactive experience on +a TTY. + +## Install + +```bash +# Zero-install one-off +npx @tanstack/ai-cli image "a watercolor fox" -o fox.png + +# Or install globally +pnpm add -g @tanstack/ai-cli +ts-ai --version +``` + +OpenAI, Anthropic, Gemini, OpenRouter, and Fal are bundled, so those providers +work with no extra install. Other providers (Ollama, Grok, Groq, ElevenLabs) +are loaded on demand — if one isn't installed, `ts-ai` exits with code `4` and +tells you which package to add. + +## Choosing a model and key + +Pick a model with a `provider/model` slug. The API key comes from `--apiKey` +or, by default, the conventional environment variable for that provider +(`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, `OPENROUTER_API_KEY`, +`FAL_KEY`). + +```bash +ts-ai chat "Explain MCP in one sentence" --model openai/gpt-5.5 +ts-ai chat "Summarize this PR" --model anthropic/claude-sonnet-4-6 --api-key sk-... +``` + +`ts-ai` also loads a `.env` from the current directory automatically, so dropping +`OPENAI_API_KEY=...` in a project `.env` is enough. Real environment variables +and `--apiKey` always take precedence over `.env`. + +## Interactive mode + +Run `ts-ai` with no command on a terminal and you get an animated home screen +that asks what you want to do, with a menu (Chat, Image, Video, …). Pick **Chat** +to drop into a live REPL (`/clear` to reset, `/exit` to quit); pick a generation +command to type a prompt and run it inline. + +```bash +ts-ai # animated menu → pick an action +ts-ai chat # no prompt on a TTY → interactive chat REPL +``` + +## Commands + +| Command | What it does | +| --- | --- | +| `ts-ai chat ` | Chat / agentic text generation | +| `ts-ai image ` | Generate an image | +| `ts-ai video ` | Generate a video (async job; blocks until done) | +| `ts-ai audio ` | Generate audio (music / sfx) | +| `ts-ai speech ` | Text-to-speech (alias: `tts`) | +| `ts-ai transcribe ` | Speech-to-text (alias: `stt`) | +| `ts-ai summarize ` | Summarize text | +| `ts-ai introspect` | Print the machine-readable CLI manifest | +| `ts-ai mcp` | Expose every command as an MCP tool over stdio | +| `ts-ai update` | Update `ts-ai` to the latest version | + +The prompt is everything after the command that isn't a flag, so multi-word and +multi-line prompts work without quoting gymnastics. When no prompt is given, +input is read from stdin — handy for pipes: + +```bash +cat article.txt | ts-ai summarize --model openai/gpt-5.5 +``` + +Attach files with the repeatable `--attachment` flag: + +```bash +ts-ai chat "What's in this diagram?" --model openai/gpt-5.5 --attachment diagram.png +``` + +## Output: humans vs harnesses + +`ts-ai` decides how to render based on whether stdout is an interactive TTY: + +- **On a TTY** it renders a pretty, [Ink](https://github.com/vadimdemedes/ink)-based + view: streamed chat text, inline image previews, and saved-file callouts. +- **In `--json` mode, or when stdout is piped/redirected**, it never renders + anything human-facing. stdout carries only the payload; all progress, warnings, + and logs go to stderr. + +### Buffered JSON + +`--json` returns a single JSON object you can parse directly: + +```bash +ts-ai image "a red bicycle" --model openai/gpt-image-1 --json +# {"id":"...","model":"gpt-image-1","images":[{"path":"./ts-ai-image-.png","mimeType":"image/png"}],"usage":{...}} +``` + +Media commands always write the artifact to a file (override with `-o`, or +`-o -` to stream raw bytes to stdout) and report the path in the JSON. + +### Streaming the AG-UI event stream + +`--stream` emits the TanStack AI / AG-UI event stream as newline-delimited JSON, +one event per line, so a harness can reconstruct state incrementally: + +```bash +ts-ai chat "Write a haiku" --model openai/gpt-5.5 --stream +``` + +## Stateless multi-turn + +`chat` keeps no state of its own. Pass the full conversation in with `--messages` +(a JSON array) and thread the returned messages back yourself: + +```bash +ts-ai chat --model openai/gpt-5.5 --json \ + --messages '[{"role":"user","content":"hi"},{"role":"assistant","content":"hello!"},{"role":"user","content":"what did I just say?"}]' +``` + +`--threadId` is accepted purely as a correlation id (for telemetry / AG-UI) and +never causes anything to be persisted. + +## Structured output + +Constrain `chat` to a JSON Schema and get a validated object back under `.data`: + +```bash +ts-ai chat "Classify: 'the app crashes on launch'" \ + --model openai/gpt-5.5 \ + --schema ./ticket.schema.json \ + --json +# {"data":{"severity":"high","area":"startup"},"model":"gpt-5.5"} +``` + +## Configuration + +Every option is settable as a flag. For nested, provider-specific options use +`--config`, which accepts a JSON file path **or** an inline JSON string whose +shape mirrors the command's options. Precedence is **flags > `--config` > env > +defaults**: + +```bash +ts-ai image "a logo" --model openai/gpt-image-1 \ + --config '{"size":"1024x1024","modelOptions":{"background":"transparent"}}' +``` + +Provider-specific options live only under `modelOptions`. + +## Using ts-ai inside an agent harness + +Two patterns make `ts-ai` easy to drive programmatically: + +1. **`ts-ai introspect`** prints a versioned JSON manifest of every command, + flag, type, and exit code — read it once and auto-generate tool definitions. +2. **`ts-ai mcp`** starts an MCP server (stdio) that exposes each command as a + tool, so any MCP-capable agent can register `ts-ai` directly. + +### Exit codes + +| Code | Meaning | +| --- | --- | +| `0` | Success | +| `1` | Generic runtime error | +| `2` | Usage / validation error | +| `3` | Provider/API error or output-schema validation failure | +| `4` | Required provider package not installed | + +In `--json` mode a non-zero exit also prints a structured error object on stdout +so the failure stays parseable: + +```json +{ "error": { "code": "USAGE", "message": "Missing --model (e.g. openai/gpt-5.5).", "provider": "openai" } } +``` diff --git a/docs/config.json b/docs/config.json index f5552703f..55fb526f3 100644 --- a/docs/config.json +++ b/docs/config.json @@ -216,6 +216,16 @@ } ] }, + { + "label": "CLI", + "children": [ + { + "label": "ts-ai CLI", + "to": "cli/overview", + "addedAt": "2026-06-07" + } + ] + }, { "label": "Media", "children": [ diff --git a/knip.json b/knip.json index a5e8a03e1..dbd58f420 100644 --- a/knip.json +++ b/knip.json @@ -27,6 +27,15 @@ "packages/ai": { "ignoreDependencies": ["@opentelemetry/api"] }, + "packages/ai-cli": { + "ignoreDependencies": [ + "@tanstack/ai-openai", + "@tanstack/ai-anthropic", + "@tanstack/ai-gemini", + "@tanstack/ai-openrouter", + "@tanstack/ai-fal" + ] + }, "packages/ai-anthropic": { "ignore": ["src/tools/**"] }, diff --git a/packages/ai-cli/package.json b/packages/ai-cli/package.json new file mode 100644 index 000000000..c73d8cba1 --- /dev/null +++ b/packages/ai-cli/package.json @@ -0,0 +1,82 @@ +{ + "name": "@tanstack/ai-cli", + "version": "0.1.0", + "description": "ts-ai — a type-safe CLI over TanStack AI: chat, image, video, audio, speech, transcribe, and summarize from the terminal or any agent harness.", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/TanStack/ai.git", + "directory": "packages/ai-cli" + }, + "keywords": [ + "ai", + "ai-sdk", + "cli", + "tanstack", + "agent", + "agent-harness", + "llm", + "chat", + "image-generation", + "tts", + "transcription" + ], + "type": "module", + "module": "./dist/esm/index.js", + "types": "./dist/esm/index.d.ts", + "bin": { + "ts-ai": "./dist/bin/bin.js" + }, + "exports": { + ".": { + "types": "./dist/esm/index.d.ts", + "import": "./dist/esm/index.js" + } + }, + "sideEffects": false, + "engines": { + "node": ">=18" + }, + "files": [ + "dist", + "src" + ], + "scripts": { + "build": "vite build && tsup --config tsup.bin.config.ts", + "clean": "premove ./build ./dist", + "lint:fix": "eslint ./src --fix", + "test:build": "publint --strict", + "test:eslint": "eslint ./src", + "test:lib": "vitest", + "test:lib:dev": "pnpm test:lib --watch", + "test:types": "tsc" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.29.0", + "@tanstack/ai": "workspace:*", + "@tanstack/ai-anthropic": "workspace:*", + "@tanstack/ai-code-mode": "workspace:*", + "@tanstack/ai-fal": "workspace:*", + "@tanstack/ai-gemini": "workspace:*", + "@tanstack/ai-isolate-node": "workspace:*", + "@tanstack/ai-mcp": "workspace:*", + "@tanstack/ai-openai": "workspace:*", + "@tanstack/ai-openrouter": "workspace:*", + "commander": "^13.1.0", + "ink": "^7.0.5", + "react": "^19.2.3", + "react-devtools-core": "^6.1.5", + "terminal-image": "^4.3.0" + }, + "peerDependencies": { + "zod": "^3.0.0 || ^4.0.0" + }, + "devDependencies": { + "@types/node": "^24.10.1", + "@types/react": "^19.2.7", + "@vitest/coverage-v8": "4.0.14", + "tsup": "^8.5.1", + "vite": "^7.3.3", + "zod": "^4.2.0" + } +} diff --git a/packages/ai-cli/src/cli/activities/audio.ts b/packages/ai-cli/src/cli/activities/audio.ts new file mode 100644 index 000000000..5d1e97d8a --- /dev/null +++ b/packages/ai-cli/src/cli/activities/audio.ts @@ -0,0 +1,78 @@ +import { generateAudio } from '@tanstack/ai' +import { instantiateAdapter } from '../../core/providers' +import { emitJson } from '../../core/emit' +import { mediaSourceToBytes, writeArtifact } from '../artifact' +import { resolveAdapterContext } from '../context' +import { renderArtifactPath } from '../../render/lazy' +import type { RunContext } from '../context' + +interface AudioResultLike { + id: string + model: string + audio: { url?: string; b64Json?: string; contentType?: string; duration?: number } + usage?: unknown +} + +const EXT_BY_CONTENT_TYPE: Record = { + 'audio/mpeg': 'mp3', + 'audio/mp3': 'mp3', + 'audio/wav': 'wav', + 'audio/ogg': 'ogg', + 'audio/flac': 'flac', +} + +/** `ts-ai audio` (music / sfx) handler. */ +export async function runAudio(ctx: RunContext, prompt: string): Promise { + const { resolved, apiKey, adapterConfig, modelOptions } = resolveAdapterContext( + ctx.options, + ) + const adapter = await instantiateAdapter({ + resolved, + activity: 'audio', + apiKey, + config: adapterConfig, + }) + + ctx.logger.info(`Generating audio with ${resolved.provider}/${resolved.model}…`) + + const duration = + typeof ctx.options.duration === 'number' + ? ctx.options.duration + : typeof ctx.options.duration === 'string' + ? Number(ctx.options.duration) + : undefined + + const result = (await generateAudio({ + adapter: adapter as never, + prompt, + duration, + modelOptions: modelOptions as never, + debug: false, + })) as AudioResultLike + + const bytes = await mediaSourceToBytes(result.audio) + const ext = EXT_BY_CONTENT_TYPE[result.audio.contentType ?? ''] ?? 'mp3' + const output = typeof ctx.options.output === 'string' ? ctx.options.output : undefined + const path = await writeArtifact( + 'audio', + { bytes, ext, mimeType: result.audio.contentType ?? `audio/${ext}` }, + output, + ctx.now, + ) + + if (ctx.mode === 'pretty') { + await renderArtifactPath({ + label: `Audio generated with ${result.model}`, + path: path ?? '(stdout)', + meta: result.audio.duration ? { duration: `${result.audio.duration}s` } : undefined, + }) + return + } + emitJson({ + id: result.id, + model: result.model, + path, + mimeType: result.audio.contentType ?? `audio/${ext}`, + usage: result.usage, + }) +} diff --git a/packages/ai-cli/src/cli/activities/chat.ts b/packages/ai-cli/src/cli/activities/chat.ts new file mode 100644 index 000000000..9eacfcd10 --- /dev/null +++ b/packages/ai-cli/src/cli/activities/chat.ts @@ -0,0 +1,183 @@ +import { + StreamProcessor, + chat, + maxIterations, + modelMessagesToUIMessages, +} from '@tanstack/ai' +import { instantiateAdapter } from '../../core/providers' +import { emitEvent, emitJson } from '../../core/emit' +import { CliError } from '../../core/exit-codes' +import { loadJsonInput } from '../../core/config' +import { loadAttachments } from '../../core/io' +import { resolveAdapterContext } from '../context' +import { renderText } from '../../render/lazy' +import { buildMcpClients } from '../mcp-clients' +import { buildCodeMode } from '../code-mode' +import type { McpClientLike } from '../mcp-clients' +import type { RunContext } from '../context' + +interface ModelMessageLike { + role: string + content: unknown +} + +/** + * `ts-ai chat` handler — stateless one-shot or `--stream`. + * + * History is supplied via `--messages` (a JSON array); nothing is persisted. + * `--thread-id` is accepted purely as a passthrough correlation id. Tools come + * from `--mcp` servers; `--code-mode` wraps those tools in a sandboxed + * `execute_typescript` tool. + */ +export async function runChat(ctx: RunContext, prompt: string): Promise { + const { resolved, apiKey, adapterConfig, modelOptions } = resolveAdapterContext( + ctx.options, + ) + const adapter = await instantiateAdapter({ + resolved, + activity: 'chat', + apiKey, + config: adapterConfig, + }) + + const messages = await buildMessages(ctx, prompt) + const systemPrompts = resolveSystem(ctx) + // --schema accepts a file path / inline JSON string (CLI flag) OR an already + // parsed object (supplied via --config or the MCP `options` bag). + const schemaInput = ctx.options.schema + const schema = + typeof schemaInput === 'string' && schemaInput + ? await loadJsonInput(schemaInput, '--schema') + : typeof schemaInput === 'object' && schemaInput !== null + ? (schemaInput as Record) + : undefined + const maxSteps = + typeof ctx.options.maxSteps === 'number' + ? ctx.options.maxSteps + : typeof ctx.options.maxSteps === 'string' + ? Number(ctx.options.maxSteps) + : undefined + + // Resolve tools from MCP servers, optionally wrapped in Code Mode. + const mcpSpecs = Array.isArray(ctx.options.mcp) ? (ctx.options.mcp as Array) : [] + const useCodeMode = Boolean(ctx.options.codeMode) + const clients = await buildMcpClients(mcpSpecs) + + let tools: Array | undefined + let mcp: { clients: Array } | undefined + const extraSystem: Array = [] + + try { + if (useCodeMode) { + const discovered = ( + await Promise.all(clients.map((c) => c.tools())) + ).flat() + const wiring = await buildCodeMode(discovered) + tools = [wiring.tool] + extraSystem.push(wiring.systemPrompt) + } else if (clients.length > 0) { + mcp = { clients } + } + + const base = { + adapter: adapter as never, + messages: messages as never, + systemPrompts: [...(systemPrompts ?? []), ...extraSystem] as never, + modelOptions: modelOptions as never, + // The CLI owns all output; silence the library's internal logger so it + // never writes to stdout/stderr behind our back. + debug: false as never, + ...(tools ? { tools: tools as never } : {}), + ...(mcp ? { mcp: mcp as never } : {}), + ...(maxSteps ? { agentLoopStrategy: maxIterations(maxSteps) } : {}), + } + + // Structured output: schema-bearing call resolves to the validated object. + if (schema !== undefined) { + const data = await (chat({ + ...base, + outputSchema: schema as never, + }) as Promise) + if (ctx.mode === 'pretty') { + await renderText(JSON.stringify(data, null, 2)) + return + } + emitJson({ data, model: resolved.model }) + return + } + + if (ctx.mode === 'stream') { + const stream = chat({ ...base, stream: true }) as AsyncIterable + for await (const event of stream) { + emitEvent(event) + } + return + } + + // Buffered: accumulate the stream into a rich envelope via StreamProcessor. + const processor = new StreamProcessor() + processor.setMessages(modelMessagesToUIMessages(messages as never)) + const result = await processor.process( + chat({ ...base, stream: true }) as AsyncIterable, + ) + + if (ctx.mode === 'pretty') { + await renderText(result.content || '(no text response)') + return + } + emitJson({ + text: result.content, + ...(result.thinking ? { thinking: result.thinking } : {}), + ...(result.toolCalls ? { toolCalls: result.toolCalls } : {}), + finishReason: result.finishReason ?? null, + messages: processor.toModelMessages(), + model: resolved.model, + }) + } finally { + // When Code Mode owns the tools we passed (mcp not handed to chat), close + // the MCP connections ourselves; otherwise chat() closes them. + if (useCodeMode) { + await Promise.all(clients.map((c) => c.close().catch(() => undefined))) + } + } +} + +async function buildMessages( + ctx: RunContext, + prompt: string, +): Promise> { + const history = parseMessages(ctx.options.messages) + const attachmentPaths = Array.isArray(ctx.options.attachment) + ? (ctx.options.attachment as Array) + : [] + + if (!prompt && history.length === 0) { + throw new CliError('USAGE', 'Provide a prompt or --messages.') + } + if (!prompt) return history + + if (attachmentPaths.length === 0) { + return [...history, { role: 'user', content: prompt }] + } + + const attachments = await loadAttachments(attachmentPaths) + const parts: Array> = [{ type: 'text', text: prompt }] + for (const att of attachments) { + const kind = att.mimeType.startsWith('image/') ? 'image' : 'file' + parts.push({ type: kind, mimeType: att.mimeType, data: att.data }) + } + return [...history, { role: 'user', content: parts }] +} + +function parseMessages(value: unknown): Array { + if (value === undefined) return [] + if (!Array.isArray(value)) { + throw new CliError('USAGE', '--messages must be a JSON array of messages.') + } + return value as Array +} + +function resolveSystem(ctx: RunContext): Array | undefined { + const system = ctx.options.system + return typeof system === 'string' && system ? [system] : undefined +} diff --git a/packages/ai-cli/src/cli/activities/image.ts b/packages/ai-cli/src/cli/activities/image.ts new file mode 100644 index 000000000..91d0c064f --- /dev/null +++ b/packages/ai-cli/src/cli/activities/image.ts @@ -0,0 +1,107 @@ +import { generateImage } from '@tanstack/ai' +import { instantiateAdapter } from '../../core/providers' +import { emitJson } from '../../core/emit' +import { mediaSourceToBytes, writeArtifact } from '../artifact' +import { resolveAdapterContext } from '../context' +import { renderImageResult } from '../../render/lazy' +import type { RunContext } from '../context' + +interface GeneratedImageLike { + url?: string + b64Json?: string + revisedPrompt?: string +} +interface ImageResultLike { + id: string + model: string + images: Array + usage?: unknown +} + +const EXT_BY_MIME: Record = { + 'image/png': 'png', + 'image/jpeg': 'jpg', + 'image/webp': 'webp', + 'image/gif': 'gif', +} + +/** `ts-ai image` handler. */ +export async function runImage(ctx: RunContext, prompt: string): Promise { + const { resolved, apiKey, adapterConfig, modelOptions } = resolveAdapterContext( + ctx.options, + ) + const adapter = await instantiateAdapter({ + resolved, + activity: 'image', + apiKey, + config: adapterConfig, + }) + + ctx.logger.info(`Generating image with ${resolved.provider}/${resolved.model}…`) + + const result = (await generateImage({ + // The CLI resolves adapters at runtime; the static generic shape is erased. + adapter: adapter as never, + prompt, + numberOfImages: numberValue(ctx.options.count) ?? 1, + size: stringValue(ctx.options.size) as never, + modelOptions: modelOptions as never, + debug: false, + })) as ImageResultLike + + const output = stringValue(ctx.options.output) + const written: Array<{ path: string | null; mimeType: string; revisedPrompt?: string }> = [] + + for (const [index, image] of result.images.entries()) { + const bytes = await mediaSourceToBytes(image) + const mimeType = 'image/png' + const ext = EXT_BY_MIME[mimeType] ?? 'png' + // Only the first image honors an explicit -o; subsequent ones get a suffix. + const target = + index === 0 ? output : output && output !== '-' ? suffixPath(output, index) : undefined + const path = await writeArtifact('image', { bytes, ext, mimeType }, target, ctx.now + index) + written.push({ path, mimeType, revisedPrompt: image.revisedPrompt }) + } + + if (ctx.mode === 'pretty') { + const previewable: Array<{ path: string; revisedPrompt?: string }> = [] + for (const w of written) { + if (w.path) previewable.push({ path: w.path, revisedPrompt: w.revisedPrompt }) + } + await renderImageResult({ + model: result.model, + images: previewable, + preview: ctx.options.preview !== false, + }) + return + } + + emitJson({ + id: result.id, + model: result.model, + images: written.map((w) => ({ + path: w.path, + mimeType: w.mimeType, + ...(w.revisedPrompt ? { revisedPrompt: w.revisedPrompt } : {}), + })), + usage: result.usage, + }) +} + +function suffixPath(path: string, index: number): string { + const dot = path.lastIndexOf('.') + if (dot <= 0) return `${path}-${index}` + return `${path.slice(0, dot)}-${index}${path.slice(dot)}` +} + +function stringValue(value: unknown): string | undefined { + return typeof value === 'string' && value ? value : undefined +} +function numberValue(value: unknown): number | undefined { + if (typeof value === 'number') return value + if (typeof value === 'string' && value.trim() !== '') { + const n = Number(value) + return Number.isNaN(n) ? undefined : n + } + return undefined +} diff --git a/packages/ai-cli/src/cli/activities/speech.ts b/packages/ai-cli/src/cli/activities/speech.ts new file mode 100644 index 000000000..0a9b9e659 --- /dev/null +++ b/packages/ai-cli/src/cli/activities/speech.ts @@ -0,0 +1,79 @@ +import { generateSpeech } from '@tanstack/ai' +import { instantiateAdapter } from '../../core/providers' +import { emitJson } from '../../core/emit' +import { writeArtifact } from '../artifact' +import { resolveAdapterContext } from '../context' +import { renderArtifactPath } from '../../render/lazy' +import type { RunContext } from '../context' + +interface TTSResultLike { + id: string + model: string + audio: string + format: string + duration?: number +} + +type SpeechFormat = 'mp3' | 'opus' | 'aac' | 'flac' | 'wav' | 'pcm' + +/** `ts-ai speech` (text-to-speech) handler. */ +export async function runSpeech(ctx: RunContext, text: string): Promise { + const { resolved, apiKey, adapterConfig, modelOptions } = resolveAdapterContext( + ctx.options, + ) + const adapter = await instantiateAdapter({ + resolved, + activity: 'speech', + apiKey, + config: adapterConfig, + }) + + ctx.logger.info(`Synthesizing speech with ${resolved.provider}/${resolved.model}…`) + + const result = (await generateSpeech({ + adapter: adapter as never, + text, + voice: str(ctx.options.voice), + format: str(ctx.options.format) as SpeechFormat | undefined, + speed: num(ctx.options.speed), + modelOptions: modelOptions as never, + debug: false, + })) as TTSResultLike + + const bytes = new Uint8Array(Buffer.from(result.audio, 'base64')) + const ext = result.format || 'mp3' + const path = await writeArtifact( + 'speech', + { bytes, ext, mimeType: `audio/${ext}` }, + str(ctx.options.output), + ctx.now, + ) + + if (ctx.mode === 'pretty') { + await renderArtifactPath({ + label: `Speech generated with ${result.model}`, + path: path ?? '(stdout)', + meta: result.duration ? { duration: `${result.duration}s` } : undefined, + }) + return + } + emitJson({ + id: result.id, + model: result.model, + path, + format: ext, + ...(result.duration ? { duration: result.duration } : {}), + }) +} + +function str(v: unknown): string | undefined { + return typeof v === 'string' && v ? v : undefined +} +function num(v: unknown): number | undefined { + if (typeof v === 'number') return v + if (typeof v === 'string' && v.trim() !== '') { + const n = Number(v) + return Number.isNaN(n) ? undefined : n + } + return undefined +} diff --git a/packages/ai-cli/src/cli/activities/summarize.ts b/packages/ai-cli/src/cli/activities/summarize.ts new file mode 100644 index 000000000..4cc732015 --- /dev/null +++ b/packages/ai-cli/src/cli/activities/summarize.ts @@ -0,0 +1,61 @@ +import { summarize } from '@tanstack/ai' +import { instantiateAdapter } from '../../core/providers' +import { emitJson } from '../../core/emit' +import { resolveAdapterContext } from '../context' +import { renderText } from '../../render/lazy' +import type { RunContext } from '../context' + +interface SummaryResultLike { + id: string + model: string + summary: string + usage?: unknown +} + +type SummaryStyle = 'bullet-points' | 'paragraph' | 'concise' + +/** `ts-ai summarize` handler. */ +export async function runSummarize(ctx: RunContext, text: string): Promise { + const { resolved, apiKey, adapterConfig, modelOptions } = resolveAdapterContext( + ctx.options, + ) + const adapter = await instantiateAdapter({ + resolved, + activity: 'summarize', + apiKey, + config: adapterConfig, + }) + + ctx.logger.info(`Summarizing with ${resolved.provider}/${resolved.model}…`) + + const focus = Array.isArray(ctx.options.focus) + ? (ctx.options.focus as Array) + : undefined + const maxLength = + typeof ctx.options.maxLength === 'number' + ? ctx.options.maxLength + : typeof ctx.options.maxLength === 'string' + ? Number(ctx.options.maxLength) + : undefined + + const result = (await summarize({ + adapter: adapter as never, + text, + maxLength, + style: ctx.options.style as SummaryStyle | undefined, + focus, + modelOptions: modelOptions as never, + debug: false, + })) as SummaryResultLike + + if (ctx.mode === 'pretty') { + await renderText(result.summary) + return + } + emitJson({ + id: result.id, + model: result.model, + summary: result.summary, + usage: result.usage, + }) +} diff --git a/packages/ai-cli/src/cli/activities/transcribe.ts b/packages/ai-cli/src/cli/activities/transcribe.ts new file mode 100644 index 000000000..9139634ae --- /dev/null +++ b/packages/ai-cli/src/cli/activities/transcribe.ts @@ -0,0 +1,74 @@ +import { readFile } from 'node:fs/promises' +import { generateTranscription } from '@tanstack/ai' +import { instantiateAdapter } from '../../core/providers' +import { emitJson } from '../../core/emit' +import { CliError } from '../../core/exit-codes' +import { resolveAdapterContext } from '../context' +import { renderText } from '../../render/lazy' +import type { RunContext } from '../context' + +interface TranscriptionResultLike { + id: string + model: string + text: string + language?: string + duration?: number +} + +/** + * `ts-ai transcribe` (speech-to-text) handler. The audio file is the positional + * argument (`ts-ai transcribe ./talk.mp3`) or the first `--attachment`. + */ +export async function runTranscribe( + ctx: RunContext, + positional: Array, +): Promise { + const attachment = Array.isArray(ctx.options.attachment) + ? (ctx.options.attachment as Array)[0] + : undefined + const audioPath = positional[0] ?? attachment + if (!audioPath) { + throw new CliError('USAGE', 'Provide an audio file: ts-ai transcribe ./audio.mp3') + } + + const { resolved, apiKey, adapterConfig, modelOptions } = resolveAdapterContext( + ctx.options, + ) + const adapter = await instantiateAdapter({ + resolved, + activity: 'transcription', + apiKey, + config: adapterConfig, + }) + + let audio: string + try { + // Adapters accept a base64 string / File / Blob / ArrayBuffer — NOT a Node + // Buffer — so hand over base64. + audio = (await readFile(audioPath)).toString('base64') + } catch (cause) { + throw new CliError('USAGE', `Cannot read audio file "${audioPath}".`, { cause }) + } + + ctx.logger.info(`Transcribing with ${resolved.provider}/${resolved.model}…`) + + const result = (await generateTranscription({ + adapter: adapter as never, + audio, + language: typeof ctx.options.language === 'string' ? ctx.options.language : undefined, + modelOptions: modelOptions as never, + debug: false, + })) as TranscriptionResultLike + + if (ctx.mode === 'pretty') { + await renderText(result.text) + return + } + emitJson({ + id: result.id, + model: result.model, + text: result.text, + ...(result.language ? { language: result.language } : {}), + ...(result.duration ? { duration: result.duration } : {}), + }) +} diff --git a/packages/ai-cli/src/cli/activities/video.ts b/packages/ai-cli/src/cli/activities/video.ts new file mode 100644 index 000000000..74999f20c --- /dev/null +++ b/packages/ai-cli/src/cli/activities/video.ts @@ -0,0 +1,112 @@ +import { generateVideo, getVideoJobStatus } from '@tanstack/ai' +import { instantiateAdapter } from '../../core/providers' +import { emitJson } from '../../core/emit' +import { CliError } from '../../core/exit-codes' +import { fetchBytes, writeArtifact } from '../artifact' +import { resolveAdapterContext } from '../context' +import { renderArtifactPath } from '../../render/lazy' +import type { RunContext } from '../context' + +const POLL_INTERVAL_MS = 3000 + +/** + * `ts-ai video` handler (experimental). Creates a job and, by default, blocks + * until completion (polling, progress to stderr) then downloads the result. + * `--no-wait` returns the job id immediately. + */ +export async function runVideo(ctx: RunContext, prompt: string): Promise { + const { resolved, apiKey, adapterConfig, modelOptions } = resolveAdapterContext( + ctx.options, + ) + const adapter = await instantiateAdapter({ + resolved, + activity: 'video', + apiKey, + config: adapterConfig, + }) + + ctx.logger.info(`Creating video job with ${resolved.provider}/${resolved.model}…`) + + const job = await generateVideo({ + adapter: adapter as never, + prompt, + size: (typeof ctx.options.size === 'string' ? ctx.options.size : undefined) as never, + modelOptions: modelOptions as never, + debug: false, + }) + + if (ctx.options.wait === false) { + if (ctx.mode === 'pretty') { + await renderArtifactPath({ label: `Video job created (${job.model})`, path: job.jobId }) + return + } + emitJson({ jobId: job.jobId, model: job.model, status: 'pending' }) + return + } + + const final = await pollToCompletion(ctx, adapter, job.jobId) + if (final.status === 'failed' || !final.url) { + throw new CliError('PROVIDER', `Video job failed: ${final.error ?? 'no URL returned'}.`, { + provider: resolved.provider, + detail: { jobId: job.jobId }, + }) + } + + const bytes = await fetchBytes(final.url) + const output = typeof ctx.options.output === 'string' ? ctx.options.output : undefined + const path = await writeArtifact('video', { bytes, ext: 'mp4', mimeType: 'video/mp4' }, output, ctx.now) + + if (ctx.mode === 'pretty') { + await renderArtifactPath({ label: `Video generated with ${job.model}`, path: path ?? '(stdout)' }) + return + } + emitJson({ jobId: job.jobId, model: job.model, path, mimeType: 'video/mp4' }) +} + +/** `ts-ai video status ` — one-shot status check for an existing job. */ +export async function runVideoStatus(ctx: RunContext, jobId: string): Promise { + const { resolved, apiKey, adapterConfig } = resolveAdapterContext(ctx.options) + const adapter = await instantiateAdapter({ + resolved, + activity: 'video', + apiKey, + config: adapterConfig, + }) + const status = await getVideoJobStatus({ adapter: adapter as never, jobId }) + + if (ctx.mode === 'pretty') { + await renderArtifactPath({ + label: `Video job ${jobId}`, + path: status.status, + meta: { + ...(status.progress != null ? { progress: `${status.progress}%` } : {}), + ...(status.url ? { url: status.url } : {}), + ...(status.error ? { error: status.error } : {}), + }, + }) + return + } + emitJson({ jobId, ...status }) +} + +async function pollToCompletion( + ctx: RunContext, + adapter: unknown, + jobId: string, +) { + for (;;) { + const status = await getVideoJobStatus({ + adapter: adapter as never, + jobId, + }) + ctx.logger.info( + `job ${jobId}: ${status.status}${status.progress != null ? ` (${status.progress}%)` : ''}`, + ) + if (status.status === 'completed' || status.status === 'failed') return status + await sleep(POLL_INTERVAL_MS) + } +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)) +} diff --git a/packages/ai-cli/src/cli/artifact.ts b/packages/ai-cli/src/cli/artifact.ts new file mode 100644 index 000000000..ffa040f12 --- /dev/null +++ b/packages/ai-cli/src/cli/artifact.ts @@ -0,0 +1,66 @@ +import { writeFile } from 'node:fs/promises' +import { CliError } from '../core/exit-codes' +import { emitBytes } from '../core/emit' + +/** Bytes of a generated artifact plus the file extension to use by default. */ +export interface Artifact { + bytes: Uint8Array + ext: string + mimeType: string +} + +/** + * Resolve where an artifact should be written. Explicit `-o` wins; otherwise a + * timestamped name in the cwd. `-o -` is handled by the caller (stdout). + */ +export function resolveOutputPath( + command: string, + ext: string, + output: string | undefined, + now: number, +): string { + if (output && output !== '-') return output + return `./ts-ai-${command}-${now}.${ext}` +} + +/** + * Persist an artifact. Returns the path written, or null when bytes were sent + * to stdout (`-o -`). + */ +export async function writeArtifact( + command: string, + artifact: Artifact, + output: string | undefined, + now: number, +): Promise { + if (output === '-') { + await emitBytes(artifact.bytes) + return null + } + const path = resolveOutputPath(command, artifact.ext, output, now) + try { + await writeFile(path, artifact.bytes) + } catch (cause) { + throw new CliError('RUNTIME', `Failed to write artifact to "${path}".`, { cause }) + } + return path +} + +/** Fetch bytes from a generated-media URL. */ +export async function fetchBytes(url: string): Promise { + const res = await fetch(url) + if (!res.ok) { + throw new CliError('PROVIDER', `Failed to download artifact (${res.status}) from ${url}.`) + } + return new Uint8Array(await res.arrayBuffer()) +} + +/** Resolve a `{ url } | { b64Json }` media source into raw bytes. */ +export async function mediaSourceToBytes(source: { + url?: string + b64Json?: string +}): Promise { + if (source.b64Json) return new Uint8Array(Buffer.from(source.b64Json, 'base64')) + if (source.url) return fetchBytes(source.url) + throw new CliError('PROVIDER', 'Generated media has neither url nor b64Json.') +} diff --git a/packages/ai-cli/src/cli/bin.ts b/packages/ai-cli/src/cli/bin.ts new file mode 100644 index 000000000..734061f63 --- /dev/null +++ b/packages/ai-cli/src/cli/bin.ts @@ -0,0 +1,20 @@ +// pkg is bundled into the bin by tsup; provides the version reported by +// --version and embedded in the introspect manifest. +import pkg from '../../package.json' +import { loadDotEnv } from '../core/env' +import { run } from './run' + +// Load a conventional .env from cwd before anything resolves API keys. +loadDotEnv() + +const argv = process.argv.slice(2) + +run(argv, pkg.version) + .then((code) => { + process.exitCode = code + }) + .catch((err: unknown) => { + // Last-resort guard; run() is expected to handle everything itself. + process.stderr.write(`error: ${err instanceof Error ? err.message : String(err)}\n`) + process.exitCode = 1 + }) diff --git a/packages/ai-cli/src/cli/code-mode.ts b/packages/ai-cli/src/cli/code-mode.ts new file mode 100644 index 000000000..941b97154 --- /dev/null +++ b/packages/ai-cli/src/cli/code-mode.ts @@ -0,0 +1,44 @@ +import { CliError } from '../core/exit-codes' +import type * as CodeModeModule from '@tanstack/ai-code-mode' +import type * as IsolateNodeModule from '@tanstack/ai-isolate-node' + +export interface CodeModeWiring { + tool: unknown + systemPrompt: string +} + +/** + * Wire up Code Mode: wrap the supplied tools (discovered from `--mcp` servers) + * in a sandboxed `execute_typescript` tool plus its system prompt, using the + * Node isolate driver. Code Mode requires at least one tool to orchestrate, so + * `--code-mode` must be combined with `--mcp`. + * + * `@tanstack/ai-code-mode` and `@tanstack/ai-isolate-node` are imported lazily. + */ +export async function buildCodeMode(tools: Array): Promise { + if (tools.length === 0) { + throw new CliError( + 'USAGE', + '--code-mode needs tools to orchestrate. Combine it with one or more --mcp servers.', + ) + } + + let codeMode: typeof CodeModeModule + let isolate: typeof IsolateNodeModule + try { + codeMode = await import('@tanstack/ai-code-mode') + isolate = await import('@tanstack/ai-isolate-node') + } catch (cause) { + throw new CliError( + 'PROVIDER_NOT_INSTALLED', + '--code-mode requires @tanstack/ai-code-mode and @tanstack/ai-isolate-node.', + { detail: { packages: ['@tanstack/ai-code-mode', '@tanstack/ai-isolate-node'] }, cause }, + ) + } + + const { tool, systemPrompt } = codeMode.createCodeMode({ + driver: isolate.createNodeIsolateDriver(), + tools: tools as never, + }) + return { tool, systemPrompt } +} diff --git a/packages/ai-cli/src/cli/context.ts b/packages/ai-cli/src/cli/context.ts new file mode 100644 index 000000000..4d99b168a --- /dev/null +++ b/packages/ai-cli/src/cli/context.ts @@ -0,0 +1,70 @@ +import { loadConfig, mergeOptions } from '../core/config' +import { resolveOutputMode } from '../core/output' +import { CliLogger } from '../core/logger' +import { resolveApiKey, resolveModelSlug } from '../core/providers' +import { CliError } from '../core/exit-codes' +import type { OutputMode } from '../core/output' +import type { ResolvedModel } from '../core/providers' + +/** + * Everything a command handler needs after common flags are resolved: the + * merged option bag (flags > config), the output mode, and a stderr logger. + */ +export interface RunContext { + mode: OutputMode + logger: CliLogger + /** Merged options: parsed flags layered over the `--config` object. */ + options: Record + /** Wall-clock used for deterministic artifact naming within one invocation. */ + now: number +} + +export async function createRunContext( + rawFlags: Record, +): Promise { + const config = await loadConfig(rawFlags.config as string | undefined) + const options = mergeOptions(rawFlags, config) + const mode = resolveOutputMode({ + json: Boolean(options.json), + stream: Boolean(options.stream), + }) + const logger = new CliLogger({ + verbose: Boolean(options.verbose), + quiet: Boolean(options.quiet), + }) + return { mode, logger, options, now: Date.now() } +} + +export interface ResolvedAdapterContext { + resolved: ResolvedModel + apiKey: string + adapterConfig: Record + modelOptions: Record | undefined +} + +/** Resolve model slug + API key + adapter config from the merged options. */ +export function resolveAdapterContext( + options: Record, +): ResolvedAdapterContext { + const model = options.model + if (typeof model !== 'string' || !model) { + throw new CliError('USAGE', 'Missing --model (e.g. openai/gpt-5.5).') + } + const resolved = resolveModelSlug(model) + const apiKey = resolveApiKey( + resolved.entry, + resolved.provider, + options.apiKey as string | undefined, + ) + const modelOptions = + typeof options.modelOptions === 'object' && options.modelOptions !== null + ? (options.modelOptions as Record) + : undefined + const baseURL = typeof options.baseURL === 'string' ? { baseURL: options.baseURL } : {} + return { + resolved, + apiKey, + adapterConfig: { ...baseURL }, + modelOptions, + } +} diff --git a/packages/ai-cli/src/cli/dispatch.ts b/packages/ai-cli/src/cli/dispatch.ts new file mode 100644 index 000000000..a521e50d9 --- /dev/null +++ b/packages/ai-cli/src/cli/dispatch.ts @@ -0,0 +1,69 @@ +import { CliError } from '../core/exit-codes' +import { resolvePrompt } from '../core/io' +import { createRunContext } from './context' +import { runImage } from './activities/image' +import { runSummarize } from './activities/summarize' +import { runChat } from './activities/chat' +import { runSpeech } from './activities/speech' +import { runAudio } from './activities/audio' +import { runTranscribe } from './activities/transcribe' +import { runVideo } from './activities/video' +import type { CommandSpec } from '../manifest/types' + +/** + * Dispatch a parsed generation command to its activity handler. `chat`, + * `image`, and `summarize` are fully wired; the remaining activities are + * recognized (and introspectable) but not yet implemented in this build. + */ +export async function dispatchCommand( + spec: CommandSpec, + positional: Array, + rawFlags: Record, +): Promise { + const ctx = await createRunContext(rawFlags) + + if (spec.experimental) { + ctx.logger.warn(`"${spec.name}" is experimental and may change.`) + } + + switch (spec.name) { + case 'chat': { + const prompt = await resolvePrompt(positional, { required: false }) + // No prompt on a TTY → drop into the interactive REPL. + if (!prompt && ctx.mode === 'pretty') { + const { runChatRepl } = await import('./interactive') + const model = + typeof ctx.options.model === 'string' && ctx.options.model + ? ctx.options.model + : 'openai/gpt-5.5' + await runChatRepl(model) + return + } + return runChat(ctx, prompt) + } + case 'image': { + const prompt = await resolvePrompt(positional, { required: true }) + return runImage(ctx, prompt) + } + case 'summarize': { + const prompt = await resolvePrompt(positional, { required: true }) + return runSummarize(ctx, prompt) + } + case 'speech': { + const prompt = await resolvePrompt(positional, { required: true }) + return runSpeech(ctx, prompt) + } + case 'audio': { + const prompt = await resolvePrompt(positional, { required: true }) + return runAudio(ctx, prompt) + } + case 'video': { + const prompt = await resolvePrompt(positional, { required: true }) + return runVideo(ctx, prompt) + } + case 'transcribe': + return runTranscribe(ctx, positional) + default: + throw new CliError('USAGE', `Unknown command "${spec.name}".`) + } +} diff --git a/packages/ai-cli/src/cli/interactive.ts b/packages/ai-cli/src/cli/interactive.ts new file mode 100644 index 000000000..9bed437f1 --- /dev/null +++ b/packages/ai-cli/src/cli/interactive.ts @@ -0,0 +1,68 @@ +import { StreamProcessor, chat } from '@tanstack/ai' +import { instantiateAdapter, resolveApiKey, resolveModelSlug } from '../core/providers' +import { findCommand } from '../manifest/manifest' +import { renderChatRepl, renderMenu } from '../render/lazy' +import { dispatchCommand } from './dispatch' +import type { ReplMessage } from '../render/repl' + +/** Best-known default models per command for the zero-config interactive flow. */ +const DEFAULT_MODELS: Record = { + chat: 'openai/gpt-5.5', + summarize: 'openai/gpt-5.5', + image: 'openai/gpt-image-1', + speech: 'openai/gpt-4o-mini-tts', + transcribe: 'openai/gpt-4o-mini-transcribe', +} + +/** + * The home screen shown when `ts-ai` is run with no command on a TTY: an + * animated wordmark + menu. Resolves the chosen action and runs it. + */ +export async function runHome(modelOverride?: string): Promise { + const choice = await renderMenu() + if (choice.command === 'quit') return 0 + + if (choice.command === 'chat') { + return runChatRepl(modelOverride ?? DEFAULT_MODELS['chat'] ?? 'openai/gpt-5.5') + } + + const model = modelOverride ?? DEFAULT_MODELS[choice.command] + if (!model) { + process.stderr.write( + `Run it with a model, e.g.:\n ts-ai ${choice.command} "${choice.prompt ?? ''}" --model \n`, + ) + return 0 + } + + const spec = findCommand(choice.command) + if (!spec) return 0 + await dispatchCommand(spec, choice.prompt ? [choice.prompt] : [], { model, preview: true }) + return 0 +} + +/** Launch the interactive chat REPL (also used by `ts-ai chat` with no prompt on a TTY). */ +export async function runChatRepl(modelSlug: string): Promise { + const resolved = resolveModelSlug(modelSlug) + const apiKey = resolveApiKey(resolved.entry, resolved.provider, undefined) + const adapter = await instantiateAdapter({ + resolved, + activity: 'chat', + apiKey, + }) + + const respond = async (messages: Array): Promise => { + const processor = new StreamProcessor() + const result = await processor.process( + chat({ + adapter: adapter as never, + messages, + stream: true, + debug: false, + }), + ) + return result.content || '(no response)' + } + + await renderChatRepl({ model: `${resolved.provider}/${resolved.model}`, respond }) + return 0 +} diff --git a/packages/ai-cli/src/cli/introspect.ts b/packages/ai-cli/src/cli/introspect.ts new file mode 100644 index 000000000..c68e1aff5 --- /dev/null +++ b/packages/ai-cli/src/cli/introspect.ts @@ -0,0 +1,10 @@ +import { buildManifest } from '../manifest/manifest' +import { emitJson } from '../core/emit' + +/** + * `ts-ai introspect` — emit the machine-readable manifest of the entire CLI + * surface so a harness can auto-generate tool/function definitions. + */ +export function runIntrospect(cliVersion: string): void { + emitJson(buildManifest(cliVersion)) +} diff --git a/packages/ai-cli/src/cli/mcp-clients.ts b/packages/ai-cli/src/cli/mcp-clients.ts new file mode 100644 index 000000000..da0a103c7 --- /dev/null +++ b/packages/ai-cli/src/cli/mcp-clients.ts @@ -0,0 +1,93 @@ +import { CliError } from '../core/exit-codes' +import type * as McpModule from '@tanstack/ai-mcp' +import type * as McpStdioModule from '@tanstack/ai-mcp/stdio' + +/** A connected MCP client, structurally compatible with chat()'s `mcp.clients`. */ +export interface McpClientLike { + tools: (options?: { lazy?: boolean }) => Promise> + close: () => Promise +} + +/** + * Build connected MCP clients from `--mcp` specs. A spec is either an HTTP(S) + * URL (streamable-HTTP transport) or a shell command (stdio transport), e.g. + * --mcp https://example.com/mcp + * --mcp "npx -y @modelcontextprotocol/server-filesystem /tmp" + * + * `@tanstack/ai-mcp` is imported lazily so the machine path that doesn't use + * tools never loads it. + */ +export async function buildMcpClients(specs: Array): Promise> { + if (specs.length === 0) return [] + + let mcp: typeof McpModule + let stdio: typeof McpStdioModule + try { + mcp = await import('@tanstack/ai-mcp') + stdio = await import('@tanstack/ai-mcp/stdio') + } catch (cause) { + throw new CliError( + 'PROVIDER_NOT_INSTALLED', + 'MCP support requires @tanstack/ai-mcp. Install it: pnpm add @tanstack/ai-mcp', + { detail: { package: '@tanstack/ai-mcp' }, cause }, + ) + } + + const clients: Array = [] + for (const spec of specs) { + const httpTransport: { type: 'http'; url: string } = { type: 'http', url: spec } + const transport = isUrl(spec) + ? httpTransport + : stdio.stdioTransport(parseCommand(spec)) + try { + const client = await mcp.createMCPClient({ transport }) + clients.push(client) + } catch (cause) { + throw new CliError('RUNTIME', `Failed to connect to MCP server "${spec}".`, { cause }) + } + } + return clients +} + +function isUrl(spec: string): boolean { + return /^https?:\/\//i.test(spec) +} + +/** + * Tokenize a command string into argv, respecting single/double quotes so paths + * with spaces survive, e.g. `node "C:\Program Files\srv.js" --flag`. + */ +export function tokenizeCommand(spec: string): Array { + const tokens: Array = [] + let current = '' + let quote: '"' | "'" | null = null + let started = false + for (const ch of spec) { + if (quote) { + if (ch === quote) quote = null + else current += ch + } else if (ch === '"' || ch === "'") { + quote = ch + started = true + } else if (/\s/.test(ch)) { + if (started) { + tokens.push(current) + current = '' + started = false + } + } else { + current += ch + started = true + } + } + if (started) tokens.push(current) + return tokens +} + +function parseCommand(spec: string): { command: string; args: Array } { + const [command, ...args] = tokenizeCommand(spec) + if (!command) { + throw new CliError('USAGE', `Invalid --mcp spec: "${spec}".`) + } + return { command, args } +} diff --git a/packages/ai-cli/src/cli/mcp.ts b/packages/ai-cli/src/cli/mcp.ts new file mode 100644 index 000000000..3c18eff80 --- /dev/null +++ b/packages/ai-cli/src/cli/mcp.ts @@ -0,0 +1,77 @@ +import { spawn } from 'node:child_process' +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' +import { z } from 'zod' +import { COMMANDS } from '../manifest/manifest' + +/** + * `ts-ai mcp` — expose each generation command as an MCP tool over stdio. + * + * Each tool call re-invokes the `ts-ai` binary as a subprocess with `--json` + * and an inline `--config` blob, then returns the parsed JSON. Shelling out to + * ourselves keeps the JSON-RPC stdio channel cleanly separated from the + * command's own stdout payload and reuses the entire option/precedence + * pipeline without duplicating logic. + */ +export async function runMcpServer(cliVersion: string): Promise { + const server = new McpServer({ name: 'ts-ai', version: cliVersion }) + // The real CLI entry (bin.js), not this lazily-imported chunk's own path. + const binPath = process.argv[1] ?? '' + + for (const spec of COMMANDS) { + server.registerTool( + spec.name, + { + title: spec.name, + description: spec.description, + inputSchema: { + prompt: z + .string() + .optional() + .describe('Prompt / input text for the command.'), + options: z + .record(z.string(), z.any()) + .optional() + .describe('Command options (model, size, etc.) as a JSON object.'), + }, + }, + async (args: { prompt?: string; options?: Record }) => { + const result = await invokeSelf(binPath, spec.name, args) + return { content: [{ type: 'text' as const, text: result }] } + }, + ) + } + + const transport = new StdioServerTransport() + await server.connect(transport) +} + +function invokeSelf( + binPath: string, + command: string, + args: { prompt?: string; options?: Record }, +): Promise { + // Options first, then a `--` end-of-options terminator before the untrusted + // prompt so an MCP client can't smuggle flags (e.g. a prompt starting with + // `--api-key`) into the spawned CLI. commander treats everything after `--` + // as positional operands. + const argv = [binPath, command, '--json'] + if (args.options && Object.keys(args.options).length > 0) { + argv.push('--config', JSON.stringify(args.options)) + } + if (args.prompt) argv.push('--', args.prompt) + + return new Promise((resolve, reject) => { + const child = spawn(process.execPath, argv, { stdio: ['ignore', 'pipe', 'pipe'] }) + let stdout = '' + let stderr = '' + child.stdout.on('data', (chunk) => (stdout += String(chunk))) + child.stderr.on('data', (chunk) => (stderr += String(chunk))) + child.on('error', reject) + child.on('close', (code) => { + // Non-zero still returns the structured error JSON on stdout; pass it + // through so the MCP client sees a parseable result either way. + resolve(stdout.trim() || stderr.trim() || `exit ${code}`) + }) + }) +} diff --git a/packages/ai-cli/src/cli/options.ts b/packages/ai-cli/src/cli/options.ts new file mode 100644 index 000000000..ed11b3295 --- /dev/null +++ b/packages/ai-cli/src/cli/options.ts @@ -0,0 +1,60 @@ +import { CliError } from '../core/exit-codes' +import { COMMON_FLAGS } from '../manifest/manifest' +import type { CommandSpec, FlagSpec } from '../manifest/types' + +/** + * Coerce commander's raw (mostly-string) option bag into typed values per the + * manifest: numbers parsed, `json` flags JSON-parsed, repeatable flags kept as + * arrays. `--config` is intentionally left as a raw string (loaded later). + */ +export function coerceFlags( + spec: CommandSpec, + raw: Record, +): Record { + const flags = [...COMMON_FLAGS, ...spec.flags] + const byName = new Map(flags.map((f) => [f.name, f])) + const out: Record = {} + + for (const [key, value] of Object.entries(raw)) { + if (value === undefined) continue + const flag = byName.get(key) + if (!flag) { + out[key] = value + continue + } + out[key] = coerceValue(flag, value) + } + return out +} + +function coerceValue(flag: FlagSpec, value: unknown): unknown { + // --config and --schema stay strings; loadJsonInput() handles the + // file-vs-inline distinction and parsing at the handler. + if (flag.name === 'config' || flag.name === 'schema') return value + + switch (flag.type) { + case 'number': { + const n = Number(value) + if (Number.isNaN(n)) { + throw new CliError('USAGE', `--${flag.name} must be a number, got "${String(value)}".`) + } + return n + } + case 'json': + return parseJsonFlag(flag.name, value) + case 'string[]': + return Array.isArray(value) ? value : [value] + case 'string': + case 'boolean': + return value + } +} + +function parseJsonFlag(name: string, value: unknown): unknown { + if (typeof value !== 'string') return value + try { + return JSON.parse(value) + } catch (cause) { + throw new CliError('USAGE', `--${name} must be valid JSON.`, { cause }) + } +} diff --git a/packages/ai-cli/src/cli/program.ts b/packages/ai-cli/src/cli/program.ts new file mode 100644 index 000000000..8d3b3fd21 --- /dev/null +++ b/packages/ai-cli/src/cli/program.ts @@ -0,0 +1,124 @@ +import { Command, Option } from 'commander' +import { COMMANDS, COMMON_FLAGS, toKebabFlag } from '../manifest/manifest' +import { coerceFlags } from './options' +import { dispatchCommand } from './dispatch' +import { runIntrospect } from './introspect' +import type { CommandSpec, FlagSpec } from '../manifest/types' + +/** Build the full commander program from the declarative manifest. */ +export function buildProgram(cliVersion: string): Command { + const program = new Command() + program + .name('ts-ai') + .description('Type-safe CLI over TanStack AI — chat, image, video, audio, speech, transcribe, summarize.') + .version(cliVersion, '--version') + .showHelpAfterError() + + // No command: show the animated home menu on a TTY, help otherwise. + program.action(async () => { + if (process.stdout.isTTY) { + const { runHome } = await import('./interactive') + await runHome() + } else { + program.outputHelp() + } + }) + + for (const spec of COMMANDS) { + registerGenerationCommand(program, spec) + } + + registerIntrospect(program, cliVersion) + registerMcp(program, cliVersion) + registerUpdate(program) + + return program +} + +function registerGenerationCommand(program: Command, spec: CommandSpec): void { + const cmd = program.command(spec.name).description(spec.description) + for (const alias of spec.aliases ?? []) cmd.alias(alias) + + // Positional input: a prompt for prompt-accepting commands, otherwise a + // single optional input (e.g. transcribe's audio file path). + cmd.argument(spec.acceptsPrompt ? '[prompt...]' : '[input]', 'Prompt / input') + + for (const flag of [...COMMON_FLAGS, ...spec.flags]) applyFlag(cmd, flag) + + cmd.action(async (positional: Array | string | undefined, _opts, command: Command) => { + const raw = coerceFlags(spec, command.opts()) + const args = Array.isArray(positional) + ? positional + : positional + ? [positional] + : [] + await dispatchCommand(spec, args, raw) + }) + + // `ts-ai video status ` — poll an existing job. + if (spec.name === 'video') { + const status = cmd + .command('status ') + .description('Check the status of a video generation job.') + for (const flag of COMMON_FLAGS) applyFlag(status, flag) + status.action(async (jobId: string, _opts, command: Command) => { + // Options can land on the parent `video` command or on `status`; merge both. + const merged = { ...(command.parent?.opts() ?? {}), ...command.opts() } + const raw = coerceFlags(spec, merged) + const { createRunContext } = await import('./context') + const { runVideoStatus } = await import('./activities/video') + const ctx = await createRunContext(raw) + await runVideoStatus(ctx, jobId) + }) + } +} + +function registerIntrospect(program: Command, cliVersion: string): void { + program + .command('introspect') + .description('Print a machine-readable manifest of the entire CLI surface as JSON.') + .action(() => runIntrospect(cliVersion)) +} + +function registerMcp(program: Command, cliVersion: string): void { + program + .command('mcp') + .description('Start an MCP server exposing each command as a tool (stdio).') + .action(async () => { + const { runMcpServer } = await import('./mcp') + await runMcpServer(cliVersion) + }) +} + +function registerUpdate(program: Command): void { + program + .command('update') + .description('Update ts-ai to the latest version.') + .action(async () => { + const { runUpdate } = await import('./update') + await runUpdate() + }) +} + +/** Translate a manifest FlagSpec into a commander option. */ +function applyFlag(cmd: Command, flag: FlagSpec): void { + const kebab = toKebabFlag(flag.name) + // A default-true boolean is expressed as a `--no-x` negatable flag. + if (flag.type === 'boolean' && flag.default === true) { + cmd.option(`--no-${kebab}`, flag.description) + return + } + + const long = `--${kebab}` + const namePart = flag.short ? `-${flag.short}, ${long}` : long + const flagStr = + flag.type === 'boolean' ? namePart : `${namePart} ` + + const option = new Option(flagStr, flag.description) + if (flag.hidden) option.hideHelp() + if (flag.repeatable || flag.type === 'string[]') { + option.argParser((value: string, previous: Array = []) => [...previous, value]) + option.default([]) + } + cmd.addOption(option) +} diff --git a/packages/ai-cli/src/cli/run.ts b/packages/ai-cli/src/cli/run.ts new file mode 100644 index 000000000..5ee0236aa --- /dev/null +++ b/packages/ai-cli/src/cli/run.ts @@ -0,0 +1,48 @@ +import { CommanderError } from 'commander' +import { ExitCode, toCliError } from '../core/exit-codes' +import { emitError } from '../core/emit' +import { isMachine, resolveOutputMode } from '../core/output' +import { buildProgram } from './program' +import type { ExitCodeValue } from '../core/exit-codes' + +/** + * Parse argv and run. Returns the process exit code; never throws. All command + * errors are funneled through CliError so the exit code and (in machine mode) + * the structured stdout error object are consistent. + */ +export async function run(argv: Array, cliVersion: string): Promise { + const program = buildProgram(cliVersion) + // Take control of exits so commander's own usage/help/version paths don't + // call process.exit out from under us. + program.exitOverride() + + try { + await program.parseAsync(argv, { from: 'user' }) + return ExitCode.Success + } catch (err) { + if (err instanceof CommanderError) { + // Help and version are successful terminal states. + if ( + err.code === 'commander.helpDisplayed' || + err.code === 'commander.help' || + err.code === 'commander.version' + ) { + return ExitCode.Success + } + // Everything else from commander is a usage/validation problem. + return ExitCode.Usage + } + + const cliError = toCliError(err) + const mode = resolveOutputMode({ + json: argv.includes('--json'), + stream: argv.includes('--stream'), + }) + if (isMachine(mode)) { + emitError(cliError) + } else { + process.stderr.write(`error: ${cliError.message}\n`) + } + return cliError.exitCode + } +} diff --git a/packages/ai-cli/src/cli/update.ts b/packages/ai-cli/src/cli/update.ts new file mode 100644 index 000000000..165fc5e00 --- /dev/null +++ b/packages/ai-cli/src/cli/update.ts @@ -0,0 +1,51 @@ +import { spawn } from 'node:child_process' +import { CliError } from '../core/exit-codes' + +const PKG = '@tanstack/ai-cli' + +/** + * `ts-ai update` — upgrade to the latest published version using whichever + * package manager appears to have installed the binary. Under npx/dlx there is + * nothing to update (each run already fetches on demand). + */ +export async function runUpdate(): Promise { + const agent = process.env.npm_config_user_agent ?? '' + + if (isOnDemand()) { + process.stderr.write( + `You're running ${PKG} on-demand (npx/dlx) — each invocation already uses the latest. Nothing to update.\n`, + ) + return + } + + const { cmd, args } = upgradeCommand(agent) + process.stderr.write(`Updating ${PKG} via: ${cmd} ${args.join(' ')}\n`) + const status = await runProcess(cmd, args) + if (status !== 0) { + throw new CliError('RUNTIME', `Update failed (${cmd} exited with ${status ?? 'signal'}).`) + } +} + +function runProcess(cmd: string, args: Array): Promise { + return new Promise((resolve, reject) => { + const child = spawn(cmd, args, { + stdio: 'inherit', + shell: process.platform === 'win32', + }) + child.on('error', reject) + child.on('close', (code) => resolve(code)) + }) +} + +function isOnDemand(): boolean { + const execPath = process.env.npm_execpath ?? '' + return execPath.includes('_npx') || (process.env.npm_command === 'exec') +} + +function upgradeCommand(agent: string): { cmd: string; args: Array } { + const target = `${PKG}@latest` + if (agent.startsWith('pnpm')) return { cmd: 'pnpm', args: ['add', '-g', target] } + if (agent.startsWith('yarn')) return { cmd: 'yarn', args: ['global', 'add', target] } + if (agent.startsWith('bun')) return { cmd: 'bun', args: ['add', '-g', target] } + return { cmd: 'npm', args: ['install', '-g', target] } +} diff --git a/packages/ai-cli/src/core/config.ts b/packages/ai-cli/src/core/config.ts new file mode 100644 index 000000000..025b57962 --- /dev/null +++ b/packages/ai-cli/src/core/config.ts @@ -0,0 +1,65 @@ +import { readFile } from 'node:fs/promises' +import { existsSync } from 'node:fs' +import { CliError } from './exit-codes' + +/** + * The `--config` value: either a path to a JSON file or an inline JSON string. + * Its shape mirrors the resolved options object for the command; provider + * specific options live under `modelOptions`. + */ +export function loadConfig( + value: string | undefined, +): Promise> { + return loadJsonInput(value, '--config') +} + +/** + * Load a JSON-object input that may be either an inline JSON string (starts with + * `{`) or a path to a JSON file. Used by `--config` and `--schema`. + */ +export async function loadJsonInput( + value: string | undefined, + label: string, +): Promise> { + if (!value) return {} + + const looksInline = value.trimStart().startsWith('{') + let raw: string + if (looksInline) { + raw = value + } else if (existsSync(value)) { + raw = await readFile(value, 'utf8') + } else { + throw new CliError( + 'USAGE', + `${label} "${value}" is neither inline JSON nor an existing file.`, + ) + } + + let parsed: unknown + try { + parsed = JSON.parse(raw) + } catch (cause) { + throw new CliError('USAGE', `${label} is not valid JSON.`, { cause }) + } + if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) { + throw new CliError('USAGE', `${label} must be a JSON object.`) + } + return parsed as Record +} + +/** + * Merge resolved options with precedence flags > config > env-derived defaults. + * `flags` are the values commander parsed (undefined when absent), `config` is + * the parsed `--config` object. Undefined flag values never clobber config. + */ +export function mergeOptions( + flags: Record, + config: Record, +): Record { + const merged: Record = { ...config } + for (const [key, value] of Object.entries(flags)) { + if (value !== undefined) merged[key] = value + } + return merged +} diff --git a/packages/ai-cli/src/core/emit.ts b/packages/ai-cli/src/core/emit.ts new file mode 100644 index 000000000..bd16ca97c --- /dev/null +++ b/packages/ai-cli/src/core/emit.ts @@ -0,0 +1,35 @@ +import type { CliError } from './exit-codes' + +/** + * stdout discipline for the machine path: stdout carries ONLY the payload. + * Everything else (progress, logs, warnings) goes to stderr via the logger. + */ + +/** Write a single buffered JSON object as the command's result. */ +export function emitJson(payload: unknown): void { + process.stdout.write(JSON.stringify(payload) + '\n') +} + +/** Write one NDJSON line (used to serialize the AG-UI event stream). */ +export function emitEvent(event: unknown): void { + process.stdout.write(JSON.stringify(event) + '\n') +} + +/** + * Write raw artifact bytes to stdout (for `-o -` piping), awaiting the write so + * large binary payloads aren't truncated if the process exits before the + * stdout pipe drains. + */ +export function emitBytes(bytes: Uint8Array): Promise { + return new Promise((resolve, reject) => { + process.stdout.write(bytes, (err) => (err ? reject(err) : resolve())) + }) +} + +/** + * Emit a structured error object to stdout in machine mode so the caller can + * parse the failure rather than scraping stderr. + */ +export function emitError(error: CliError): void { + emitJson({ error: error.toErrorObject() }) +} diff --git a/packages/ai-cli/src/core/env.ts b/packages/ai-cli/src/core/env.ts new file mode 100644 index 000000000..0a1ce5df5 --- /dev/null +++ b/packages/ai-cli/src/core/env.ts @@ -0,0 +1,37 @@ +import { existsSync, readFileSync } from 'node:fs' +import { resolve } from 'node:path' + +/** + * Load a conventional `.env` from the current working directory into + * `process.env`. Existing env vars are never overridden, so real environment + * values and `--apiKey` always win. Parsing is intentionally minimal: + * `KEY=VALUE` lines, `#` comments, optional surrounding quotes. + */ +export function loadDotEnv(cwd: string = process.cwd()): void { + const path = resolve(cwd, '.env') + if (!existsSync(path)) return + + let contents: string + try { + contents = readFileSync(path, 'utf8') + } catch { + return + } + + for (const rawLine of contents.split(/\r?\n/)) { + const line = rawLine.trim() + if (!line || line.startsWith('#')) continue + const eq = line.indexOf('=') + if (eq <= 0) continue + const key = line.slice(0, eq).trim() + if (key in process.env) continue + let value = line.slice(eq + 1).trim() + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1) + } + if (value) process.env[key] = value + } +} diff --git a/packages/ai-cli/src/core/exit-codes.ts b/packages/ai-cli/src/core/exit-codes.ts new file mode 100644 index 000000000..584bccf96 --- /dev/null +++ b/packages/ai-cli/src/core/exit-codes.ts @@ -0,0 +1,81 @@ +/** + * Process exit codes. A harness branches on these, so they are part of the + * public contract and must stay stable. + */ +export const ExitCode = { + /** Success. */ + Success: 0, + /** Generic runtime error (unexpected throw, I/O failure, etc.). */ + Runtime: 1, + /** Usage / validation error — bad flags, missing prompt, malformed config. */ + Usage: 2, + /** Provider / API error, or output-schema validation failure. */ + Provider: 3, + /** A required provider package is not installed. */ + ProviderNotInstalled: 4, +} as const + +export type ExitCodeValue = (typeof ExitCode)[keyof typeof ExitCode] + +/** Machine-readable error codes carried in the JSON error envelope. */ +export type CliErrorCode = + | 'USAGE' + | 'PROVIDER' + | 'PROVIDER_NOT_INSTALLED' + | 'OUTPUT_VALIDATION' + | 'RUNTIME' + +const EXIT_BY_CODE: Record = { + USAGE: ExitCode.Usage, + PROVIDER: ExitCode.Provider, + OUTPUT_VALIDATION: ExitCode.Provider, + PROVIDER_NOT_INSTALLED: ExitCode.ProviderNotInstalled, + RUNTIME: ExitCode.Runtime, +} + +/** + * An error carrying everything needed to (a) pick the right exit code and + * (b) emit a structured `{ error }` object on stdout in `--json` mode. + */ +export class CliError extends Error { + readonly code: CliErrorCode + readonly provider?: string + /** Extra machine-readable detail merged into the emitted error object. */ + readonly detail?: Record + + constructor( + code: CliErrorCode, + message: string, + options?: { provider?: string; detail?: Record; cause?: unknown }, + ) { + super(message, options?.cause === undefined ? undefined : { cause: options.cause }) + this.name = 'CliError' + this.code = code + this.provider = options?.provider + this.detail = options?.detail + } + + get exitCode(): ExitCodeValue { + return EXIT_BY_CODE[this.code] + } + + toErrorObject(): { + code: CliErrorCode + message: string + provider?: string + } & Record { + return { + code: this.code, + message: this.message, + ...(this.provider ? { provider: this.provider } : {}), + ...(this.detail ?? {}), + } + } +} + +/** Coerce any thrown value into a CliError for uniform handling. */ +export function toCliError(err: unknown): CliError { + if (err instanceof CliError) return err + const message = err instanceof Error ? err.message : String(err) + return new CliError('RUNTIME', message, { cause: err }) +} diff --git a/packages/ai-cli/src/core/io.ts b/packages/ai-cli/src/core/io.ts new file mode 100644 index 000000000..b4d21079a --- /dev/null +++ b/packages/ai-cli/src/core/io.ts @@ -0,0 +1,96 @@ +import { readFile } from 'node:fs/promises' +import { extname } from 'node:path' +import { CliError } from './exit-codes' + +let stdinCache: string | undefined + +/** + * Read all of stdin as a string. Returns '' if stdin is an interactive TTY. + * The result is memoized: stdin can only be drained once, so a later consumer + * (e.g. `--attachment -` after the prompt was read from stdin) gets the same + * content instead of an empty buffer. + */ +export async function readStdin(): Promise { + if (process.stdin.isTTY) return '' + if (stdinCache !== undefined) return stdinCache + const chunks: Array = [] + for await (const chunk of process.stdin) { + chunks.push(chunk as Buffer) + } + stdinCache = Buffer.concat(chunks).toString('utf8') + return stdinCache +} + +/** + * Resolve the prompt for a command. + * + * Positional args win; stdin is read ONLY when no positional prompt is given. + * Reading stdin unconditionally would block whenever a harness leaves an open + * (non-TTY) stdin pipe attached, so the positional case must never touch it. + */ +export async function resolvePrompt( + positional: Array, + options: { required?: boolean } = {}, +): Promise { + const fromArgs = positional.join(' ').trim() + if (fromArgs) return fromArgs + + const fromStdin = (await readStdin()).trim() + if (!fromStdin && options.required) { + throw new CliError('USAGE', 'No prompt provided. Pass it as arguments or pipe via stdin.') + } + return fromStdin +} + +const MIME_BY_EXT: Record = { + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.gif': 'image/gif', + '.webp': 'image/webp', + '.mp3': 'audio/mpeg', + '.wav': 'audio/wav', + '.ogg': 'audio/ogg', + '.flac': 'audio/flac', + '.m4a': 'audio/mp4', + '.mp4': 'video/mp4', + '.webm': 'video/webm', + '.pdf': 'application/pdf', + '.txt': 'text/plain', + '.md': 'text/markdown', +} + +export interface LoadedAttachment { + path: string + mimeType: string + /** Base64-encoded bytes. */ + data: string +} + +/** Infer a MIME type from a file extension, defaulting to octet-stream. */ +export function inferMimeType(path: string): string { + return MIME_BY_EXT[extname(path).toLowerCase()] ?? 'application/octet-stream' +} + +/** Load `--attachment` files into base64 + inferred mime. `-` reads stdin. */ +export async function loadAttachments( + paths: Array, +): Promise> { + const out: Array = [] + for (const path of paths) { + try { + const buffer = + path === '-' + ? Buffer.from(await readStdin(), 'utf8') + : await readFile(path) + out.push({ + path, + mimeType: path === '-' ? 'application/octet-stream' : inferMimeType(path), + data: buffer.toString('base64'), + }) + } catch (cause) { + throw new CliError('USAGE', `Cannot read attachment "${path}".`, { cause }) + } + } + return out +} diff --git a/packages/ai-cli/src/core/logger.ts b/packages/ai-cli/src/core/logger.ts new file mode 100644 index 000000000..72e8422f7 --- /dev/null +++ b/packages/ai-cli/src/core/logger.ts @@ -0,0 +1,41 @@ +/** + * Stderr logger. All human-facing chatter — progress, warnings, experimental + * notices, debug — goes to stderr so stdout stays a clean machine payload. + */ +export interface LoggerOptions { + verbose?: boolean + quiet?: boolean +} + +export class CliLogger { + private readonly verbose: boolean + private readonly quiet: boolean + + constructor(options: LoggerOptions = {}) { + this.verbose = options.verbose ?? false + this.quiet = options.quiet ?? false + } + + /** Informational progress. Suppressed by --quiet. */ + info(message: string): void { + if (this.quiet) return + process.stderr.write(message + '\n') + } + + /** Warnings (e.g. experimental notice). Suppressed by --quiet. */ + warn(message: string): void { + if (this.quiet) return + process.stderr.write(`warning: ${message}\n`) + } + + /** Always shown, even with --quiet. */ + error(message: string): void { + process.stderr.write(`error: ${message}\n`) + } + + /** Verbose debug, only with --verbose. */ + debug(message: string): void { + if (!this.verbose) return + process.stderr.write(`debug: ${message}\n`) + } +} diff --git a/packages/ai-cli/src/core/output.ts b/packages/ai-cli/src/core/output.ts new file mode 100644 index 000000000..c06bbc6c1 --- /dev/null +++ b/packages/ai-cli/src/core/output.ts @@ -0,0 +1,31 @@ +/** + * Output-mode resolution. + * + * The hard split that lets one binary serve humans and harnesses: pretty (Ink) + * rendering only when stdout is an interactive TTY and no machine mode was + * requested. `--json` and `--stream` are mutually exclusive machine modes. + */ +export type OutputMode = 'pretty' | 'json' | 'stream' + +export interface OutputModeInput { + json?: boolean + stream?: boolean + /** Override TTY detection (tests). */ + isTTY?: boolean +} + +export function resolveOutputMode(input: OutputModeInput): OutputMode { + if (input.json && input.stream) { + // Caller asked for both; stream is the more specific machine mode. + return 'stream' + } + if (input.stream) return 'stream' + if (input.json) return 'json' + const tty = input.isTTY ?? Boolean(process.stdout.isTTY) + return tty ? 'pretty' : 'json' +} + +/** True when stdout must carry only the machine payload. */ +export function isMachine(mode: OutputMode): boolean { + return mode !== 'pretty' +} diff --git a/packages/ai-cli/src/core/providers.ts b/packages/ai-cli/src/core/providers.ts new file mode 100644 index 000000000..c11ccb590 --- /dev/null +++ b/packages/ai-cli/src/core/providers.ts @@ -0,0 +1,250 @@ +import { CliError } from './exit-codes' +import type { Activity } from '../manifest/types' + +/** + * Provider registry. + * + * Maps a `provider/model` slug onto the right `@tanstack/ai-` package, + * dynamically imports it, and instantiates the correct adapter for the requested + * activity. Every adapter factory in the ecosystem follows the uniform shape + * `create(model, apiKey, config?)`, so resolution is a + * matter of (a) picking the package, (b) reading the API key, and (c) calling + * the factory whose name we derive from provider + activity. + */ + +interface ProviderEntry { + /** npm package name. */ + pkg: string + /** Capitalized provider segment used in factory names, e.g. `Openai`. */ + factoryPrefix: string + /** Bundled as a hard dependency (zero-install). */ + bundled: boolean + /** Conventional env vars checked in order for the API key. */ + envKeys: Array + /** + * How the factory receives the API key: + * - `'apiKeyArg'` (default): `create(model, apiKey, config)` — openai, anthropic, … + * - `'configObject'`: `create(model, { apiKey, ...config })` — fal. + */ + configStyle?: 'apiKeyArg' | 'configObject' + /** + * Alternate factory name prefix to try when `create` is + * absent — e.g. fal exposes `falImage` / `falVideo` rather than + * `createFal`. + */ + altFactoryPrefix?: string +} + +const PROVIDERS: Record = { + openai: { + pkg: '@tanstack/ai-openai', + factoryPrefix: 'Openai', + bundled: true, + envKeys: ['OPENAI_API_KEY'], + }, + anthropic: { + pkg: '@tanstack/ai-anthropic', + factoryPrefix: 'Anthropic', + bundled: true, + envKeys: ['ANTHROPIC_API_KEY'], + }, + gemini: { + pkg: '@tanstack/ai-gemini', + factoryPrefix: 'Gemini', + bundled: true, + envKeys: ['GEMINI_API_KEY', 'GOOGLE_API_KEY'], + }, + openrouter: { + pkg: '@tanstack/ai-openrouter', + factoryPrefix: 'OpenRouter', + bundled: true, + envKeys: ['OPENROUTER_API_KEY'], + }, + fal: { + pkg: '@tanstack/ai-fal', + factoryPrefix: 'Fal', + bundled: true, + envKeys: ['FAL_KEY'], + configStyle: 'configObject', + altFactoryPrefix: 'fal', + }, + ollama: { + pkg: '@tanstack/ai-ollama', + factoryPrefix: 'Ollama', + bundled: false, + envKeys: ['OLLAMA_API_KEY'], + }, + grok: { + pkg: '@tanstack/ai-grok', + factoryPrefix: 'Grok', + bundled: false, + envKeys: ['GROK_API_KEY', 'XAI_API_KEY'], + }, + groq: { + pkg: '@tanstack/ai-groq', + factoryPrefix: 'Groq', + bundled: false, + envKeys: ['GROQ_API_KEY'], + }, + elevenlabs: { + pkg: '@tanstack/ai-elevenlabs', + factoryPrefix: 'ElevenLabs', + bundled: false, + envKeys: ['ELEVENLABS_API_KEY'], + }, +} + +/** Factory-name suffix per activity (the irregular `chat` -> `Chat`/text case included). */ +const ACTIVITY_SUFFIX: Record = { + chat: 'Chat', + image: 'Image', + video: 'Video', + audio: 'Audio', + speech: 'Speech', + transcription: 'Transcription', + summarize: 'Summarize', +} + +export interface ResolvedModel { + provider: string + /** Bare model id with the provider prefix stripped. */ + model: string + entry: ProviderEntry +} + +/** + * Parse a `--model` value into provider + model. Accepts the canonical + * `provider/model` slug; a bare model id falls back to the popular-model lookup. + */ +export function resolveModelSlug(rawModel: string): ResolvedModel { + const slashIndex = rawModel.indexOf('/') + if (slashIndex > 0) { + const provider = rawModel.slice(0, slashIndex) + const model = rawModel.slice(slashIndex + 1) + const entry = PROVIDERS[provider] + if (!entry) { + throw new CliError( + 'USAGE', + `Unknown provider "${provider}". Known providers: ${Object.keys(PROVIDERS).join(', ')}.`, + ) + } + if (!model) { + throw new CliError('USAGE', `Missing model after "${provider}/".`) + } + return { provider, model, entry } + } + + const inferred = POPULAR_MODEL_PROVIDERS[rawModel] + const inferredEntry = inferred ? PROVIDERS[inferred] : undefined + if (!inferred || !inferredEntry) { + throw new CliError( + 'USAGE', + `Cannot infer a provider from "${rawModel}". Use the "provider/model" form, e.g. "openai/${rawModel}".`, + ) + } + return { provider: inferred, model: rawModel, entry: inferredEntry } +} + +/** + * Resolve the API key: explicit `--apiKey` wins, otherwise the first matching + * conventional env var. + */ +export function resolveApiKey( + entry: ProviderEntry, + provider: string, + explicitKey: string | undefined, + env: NodeJS.ProcessEnv = process.env, +): string { + if (explicitKey) return explicitKey + for (const key of entry.envKeys) { + const value = env[key] + if (value) return value + } + throw new CliError( + 'USAGE', + `No API key for "${provider}". Pass --apiKey or set ${entry.envKeys.join(' / ')}.`, + { provider }, + ) +} + +/** + * Dynamically import a provider package and instantiate the adapter for the + * given activity. Throws a typed CliError if the package is missing + * (exit 4) or the provider does not support the activity (exit 2). + */ +export async function instantiateAdapter(params: { + resolved: ResolvedModel + activity: Activity + apiKey: string + /** Adapter config (baseURL, modelOptions, etc.). */ + config?: Record +}): Promise { + const { resolved, activity, apiKey, config } = params + const { entry, provider, model } = resolved + + const mod = await importProvider(entry, provider) + const moduleExports = mod as Record + + // Chat factory naming varies by provider (Chat vs Text vs ResponsesText); + // every other activity uses a single `create` name. + const alt = entry.altFactoryPrefix + const candidates = + activity === 'chat' + ? [ + `create${entry.factoryPrefix}Chat`, + `create${entry.factoryPrefix}Text`, + `create${entry.factoryPrefix}ResponsesText`, + ...(alt ? [`${alt}Chat`, `${alt}Text`] : []), + ] + : [ + `create${entry.factoryPrefix}${ACTIVITY_SUFFIX[activity]}`, + ...(alt ? [`${alt}${ACTIVITY_SUFFIX[activity]}`] : []), + ] + + for (const name of candidates) { + const factory = moduleExports[name] + if (typeof factory === 'function') { + const fn = factory as (...factoryArgs: Array) => unknown + return entry.configStyle === 'configObject' + ? fn(model, { apiKey, ...config }) + : fn(model, apiKey, config) + } + } + + throw new CliError( + 'USAGE', + `Provider "${provider}" does not support "${activity}" (tried: ${candidates.join(', ')}).`, + { provider, detail: { activity } }, + ) +} + +async function importProvider( + entry: ProviderEntry, + provider: string, +): Promise { + try { + return await import(entry.pkg) + } catch (cause) { + throw new CliError( + 'PROVIDER_NOT_INSTALLED', + `Provider "${provider}" requires ${entry.pkg}. Install it: pnpm add ${entry.pkg}`, + { provider, detail: { package: entry.pkg }, cause }, + ) + } +} + +export function bundledProviders(): Array { + return Object.entries(PROVIDERS) + .filter(([, entry]) => entry.bundled) + .map(([name]) => name) +} + +/** + * Minimal popular-model -> provider table for the bare `--model` convenience + * form. The documented form is always `provider/model`; this only covers a few + * unambiguous flagships so quick one-offs work without the prefix. + */ +const POPULAR_MODEL_PROVIDERS: Record = { + 'gpt-5.5': 'openai', + 'gpt-image-1': 'openai', +} diff --git a/packages/ai-cli/src/index.ts b/packages/ai-cli/src/index.ts new file mode 100644 index 000000000..1f6e5c34c --- /dev/null +++ b/packages/ai-cli/src/index.ts @@ -0,0 +1,19 @@ +/** + * Programmatic entry point for `@tanstack/ai-cli`. + * + * The executable lives in `src/cli` (built separately as the `ts-ai` bin). This + * module exports the declarative manifest and supporting types so tooling can + * introspect the CLI surface without spawning the binary. + */ +export { buildManifest, MANIFEST_VERSION, COMMANDS, COMMON_FLAGS, findCommand } from './manifest/manifest' +export type { + Activity, + CliManifest, + CommandSpec, + FlagSpec, + FlagType, +} from './manifest/types' +export { ExitCode, CliError } from './core/exit-codes' +export type { ExitCodeValue, CliErrorCode } from './core/exit-codes' +export { resolveModelSlug, bundledProviders } from './core/providers' +export type { OutputMode } from './core/output' diff --git a/packages/ai-cli/src/manifest/manifest.ts b/packages/ai-cli/src/manifest/manifest.ts new file mode 100644 index 000000000..3e79fda2a --- /dev/null +++ b/packages/ai-cli/src/manifest/manifest.ts @@ -0,0 +1,206 @@ +import { ExitCode } from '../core/exit-codes' +import { bundledProviders } from '../core/providers' +import type { CliManifest, CommandSpec, FlagSpec } from './types' + +/** Schema version of the introspect document; bump on breaking surface changes. */ +export const MANIFEST_VERSION = '1' + +/** Flags accepted by every command. */ +export const COMMON_FLAGS: Array = [ + { + name: 'model', + type: 'string', + description: 'Model as a "provider/model" slug, e.g. openai/gpt-5.5.', + }, + { name: 'apiKey', type: 'string', description: 'API key (overrides env vars).' }, + { + name: 'json', + type: 'boolean', + description: 'Emit a single buffered JSON result to stdout.', + }, + { + name: 'stream', + type: 'boolean', + description: 'Emit the AG-UI event stream as NDJSON to stdout.', + }, + { + name: 'output', + short: 'o', + type: 'string', + description: 'Write artifact to this path. "-" writes bytes to stdout.', + }, + { + name: 'preview', + type: 'boolean', + default: true, + description: 'Inline-preview artifacts in a capable terminal (use --no-preview to disable).', + }, + { + name: 'config', + type: 'json', + description: 'Options as a JSON file path or inline JSON string.', + }, + { name: 'verbose', type: 'boolean', description: 'Verbose debug logging to stderr.' }, + { name: 'quiet', type: 'boolean', description: 'Suppress non-error stderr output.' }, +] + +const ATTACHMENT_FLAG: FlagSpec = { + name: 'attachment', + type: 'string[]', + repeatable: true, + description: 'Attach a file (repeatable). "-" reads stdin.', +} + +export const COMMANDS: Array = [ + { + name: 'chat', + description: 'Chat / agentic text generation with optional tools and structured output.', + activity: 'chat', + acceptsPrompt: true, + producesArtifact: false, + flags: [ + ATTACHMENT_FLAG, + { name: 'system', type: 'string', description: 'System prompt (text or file path).' }, + { + name: 'messages', + type: 'json', + description: 'Full message history as a JSON array (stateless multi-turn).', + }, + { name: 'threadId', type: 'string', description: 'Correlation id passed through to telemetry/AG-UI.' }, + { + name: 'maxSteps', + type: 'number', + description: 'Max agent-loop iterations (tool-calling).', + }, + { + name: 'mcp', + type: 'string[]', + repeatable: true, + description: 'MCP server (command or URL) exposing tools (repeatable).', + }, + { name: 'codeMode', type: 'boolean', description: 'Enable the sandboxed execute_typescript tool.' }, + { + name: 'schema', + type: 'json', + description: 'JSON Schema for structured output (file path or inline). Result is under .data.', + }, + ], + }, + { + name: 'image', + description: 'Generate an image from a prompt.', + activity: 'image', + acceptsPrompt: true, + producesArtifact: true, + flags: [ + ATTACHMENT_FLAG, + { name: 'size', type: 'string', description: 'Output size, e.g. 1024x1024.' }, + { name: 'count', type: 'number', default: 1, description: 'Number of images to generate.' }, + ], + }, + { + name: 'video', + description: 'Generate a video from a prompt (async job; blocks until done by default).', + activity: 'video', + acceptsPrompt: true, + producesArtifact: true, + experimental: true, + flags: [ + ATTACHMENT_FLAG, + { + name: 'wait', + type: 'boolean', + default: true, + description: 'Poll until the job completes (use --no-wait to return the job id immediately).', + }, + { name: 'size', type: 'string', description: 'Output size / resolution.' }, + ], + }, + { + name: 'audio', + description: 'Generate audio (music / sound effects) from a prompt.', + activity: 'audio', + acceptsPrompt: true, + producesArtifact: true, + flags: [{ name: 'duration', type: 'number', description: 'Desired duration in seconds.' }], + }, + { + name: 'speech', + aliases: ['tts'], + description: 'Synthesize speech audio from text (text-to-speech).', + activity: 'speech', + acceptsPrompt: true, + producesArtifact: true, + flags: [ + { name: 'voice', type: 'string', description: 'Voice id.' }, + { name: 'format', type: 'string', description: 'Audio format: mp3, opus, aac, flac, wav, pcm.' }, + { name: 'speed', type: 'number', description: 'Playback speed 0.25–4.0.' }, + ], + }, + { + name: 'transcribe', + aliases: ['stt'], + description: 'Transcribe an audio file to text (speech-to-text).', + activity: 'transcription', + acceptsPrompt: false, + producesArtifact: false, + flags: [ + ATTACHMENT_FLAG, + { name: 'language', type: 'string', description: 'ISO-639-1 language hint, e.g. en.' }, + ], + }, + { + name: 'summarize', + description: 'Summarize input text.', + activity: 'summarize', + acceptsPrompt: true, + producesArtifact: false, + flags: [ + { name: 'maxLength', type: 'number', description: 'Maximum summary length.' }, + { + name: 'style', + type: 'string', + description: 'Summary style: bullet-points, paragraph, concise.', + }, + { name: 'focus', type: 'string[]', repeatable: true, description: 'Topic to focus on (repeatable).' }, + ], + }, +] + +/** Convert a camelCase flag identifier to its kebab-case CLI spelling. */ +export function toKebabFlag(name: string): string { + return name.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`) +} + +/** Annotate a flag with its exact CLI spelling for the introspect document. */ +function withFlagSpelling(flag: FlagSpec): FlagSpec { + const kebab = toKebabFlag(flag.name) + // Default-true booleans are negatable flags (`--no-x`). + const spelling = flag.type === 'boolean' && flag.default === true ? `--no-${kebab}` : `--${kebab}` + return { ...flag, flag: spelling } +} + +/** Build the full serializable manifest. */ +export function buildManifest(cliVersion: string): CliManifest { + return { + bin: 'ts-ai', + manifestVersion: MANIFEST_VERSION, + cliVersion, + bundledProviders: bundledProviders(), + commonFlags: COMMON_FLAGS.map(withFlagSpelling), + commands: COMMANDS.map((c) => ({ ...c, flags: c.flags.map(withFlagSpelling) })), + exitCodes: { + success: ExitCode.Success, + runtime: ExitCode.Runtime, + usage: ExitCode.Usage, + provider: ExitCode.Provider, + providerNotInstalled: ExitCode.ProviderNotInstalled, + }, + } +} + +export function findCommand(name: string): CommandSpec | undefined { + return COMMANDS.find( + (c) => c.name === name || (c.aliases?.includes(name) ?? false), + ) +} diff --git a/packages/ai-cli/src/manifest/types.ts b/packages/ai-cli/src/manifest/types.ts new file mode 100644 index 000000000..161f57eb8 --- /dev/null +++ b/packages/ai-cli/src/manifest/types.ts @@ -0,0 +1,73 @@ +/** + * Declarative command manifest types. + * + * The manifest is the single source of truth for the CLI surface. The commander + * program, the `introspect --json` document, and the `ts-ai mcp` tool list are + * all generated from it, so they can never drift apart. + */ + +/** A core `@tanstack/ai` activity a generation command maps onto. */ +export type Activity = + | 'chat' + | 'image' + | 'video' + | 'audio' + | 'speech' + | 'transcription' + | 'summarize' + +export type FlagType = 'string' | 'number' | 'boolean' | 'string[]' | 'json' + +/** A single command-line flag. */ +export interface FlagSpec { + /** Canonical camelCase identifier; also the key on the parsed options bag. */ + name: string + /** The exact CLI flag spelling (kebab-cased), populated in the introspect doc. */ + flag?: string + /** Single-char alias without dash, e.g. `o` for `-o`. */ + short?: string + type: FlagType + description: string + /** Default value, surfaced in `introspect` and `--help`. */ + default?: string | number | boolean + /** Repeatable flag (collected into an array). Implies `string[]`. */ + repeatable?: boolean + /** Hidden from `--help` (still parsed). */ + hidden?: boolean +} + +/** + * A command in the manifest. `activity` is present for the seven generation + * commands and absent for meta commands (`introspect`, `mcp`, `update`). + */ +export interface CommandSpec { + name: string + /** Hidden command aliases, e.g. `tts` -> `speech`. */ + aliases?: Array + description: string + activity?: Activity + /** Whether the command consumes a positional prompt / input text. */ + acceptsPrompt: boolean + /** Whether the activity writes a binary artifact (image/video/audio/speech). */ + producesArtifact: boolean + /** Marked experimental in core (currently `video`). */ + experimental?: boolean + /** Command-specific flags (common flags are added globally). */ + flags: Array +} + +/** The serialized `introspect` document. */ +export interface CliManifest { + /** CLI binary name. */ + bin: string + /** Manifest schema version (independent of package version). */ + manifestVersion: string + /** Package version this binary was built from. */ + cliVersion: string + /** Providers bundled for zero-install use. */ + bundledProviders: Array + /** Common flags accepted by every command. */ + commonFlags: Array + commands: Array + exitCodes: Record +} diff --git a/packages/ai-cli/src/render/ink.tsx b/packages/ai-cli/src/render/ink.tsx new file mode 100644 index 000000000..d667e2073 --- /dev/null +++ b/packages/ai-cli/src/render/ink.tsx @@ -0,0 +1,75 @@ +import { Box, Text, render } from 'ink' +import terminalImage from 'terminal-image' +import type { RenderedImage } from './lazy' + +/** + * Encode an image file into a terminal-renderable string (native iTerm2/Kitty + * graphics where supported, ANSI block-art otherwise). Returns null on failure + * so a missing preview never breaks the run. + */ +async function encodePreview(path: string): Promise { + try { + return await terminalImage.file(path, { height: 20 }) + } catch { + return null + } +} + +/** Render generated images with an inline preview + the saved path(s). */ +export async function renderImageResultInk(input: { + model: string + images: Array + preview: boolean +}): Promise { + const previews = input.preview + ? await Promise.all(input.images.map((image) => encodePreview(image.path))) + : input.images.map(() => null) + + const { waitUntilExit } = render( + + + ✓ Generated {input.images.length} image(s) with {input.model} + + {input.images.map((image, index) => ( + + {previews[index] ? {previews[index]} : null} + {image.path} + {image.revisedPrompt ? “{image.revisedPrompt}” : null} + + ))} + , + ) + await waitUntilExit() +} + +/** Render a block of finished text (e.g. chat one-shot, summary). */ +export async function renderTextInk(text: string): Promise { + const { waitUntilExit } = render( + + {text} + , + ) + await waitUntilExit() +} + +/** Render a saved-artifact confirmation with the path and metadata. */ +export async function renderArtifactPathInk(input: { + label: string + path: string + meta?: Record +}): Promise { + const { waitUntilExit } = render( + + ✓ {input.label} + {input.path} + {input.meta + ? Object.entries(input.meta).map(([key, value]) => ( + + {key}: {String(value)} + + )) + : null} + , + ) + await waitUntilExit() +} diff --git a/packages/ai-cli/src/render/lazy.ts b/packages/ai-cli/src/render/lazy.ts new file mode 100644 index 000000000..f39ca01a7 --- /dev/null +++ b/packages/ai-cli/src/render/lazy.ts @@ -0,0 +1,52 @@ +/** + * Lazy render boundary. + * + * Every function here dynamically imports the Ink implementation so that React, + * the Ink reconciler, and ink-picture are loaded ONLY on the interactive/pretty + * path. The machine path (`--json` / `--stream` / non-TTY) never touches them, + * keeping cold start fast for agent harnesses. + */ + +import type { MenuChoice } from './menu' +import type { ReplMessage } from './repl' + +export interface RenderedImage { + path: string + revisedPrompt?: string +} + +export async function renderImageResult(input: { + model: string + images: Array + preview: boolean +}): Promise { + const { renderImageResultInk } = await import('./ink') + await renderImageResultInk(input) +} + +export async function renderText(text: string): Promise { + const { renderTextInk } = await import('./ink') + await renderTextInk(text) +} + +export async function renderArtifactPath(input: { + label: string + path: string + meta?: Record +}): Promise { + const { renderArtifactPathInk } = await import('./ink') + await renderArtifactPathInk(input) +} + +export async function renderMenu(): Promise { + const { runMenuInk } = await import('./menu') + return runMenuInk() +} + +export async function renderChatRepl(input: { + model: string + respond: (messages: Array) => Promise +}): Promise { + const { runChatReplInk } = await import('./repl') + await runChatReplInk(input) +} diff --git a/packages/ai-cli/src/render/menu.tsx b/packages/ai-cli/src/render/menu.tsx new file mode 100644 index 000000000..eaf7bc807 --- /dev/null +++ b/packages/ai-cli/src/render/menu.tsx @@ -0,0 +1,146 @@ +import { useEffect, useState } from 'react' +import { Box, Text, render, useApp, useInput } from 'ink' + +/** A selectable action from the home menu. */ +export interface MenuChoice { + /** Command to run, or 'quit'. */ + command: string + /** Prompt text the user typed (for non-chat commands). */ + prompt?: string +} + +interface MenuItem { + command: string + label: string + hint: string + /** Whether this command needs a prompt typed before running. */ + needsPrompt: boolean +} + +const ITEMS: Array = [ + { command: 'chat', label: 'Chat', hint: 'Interactive agentic chat', needsPrompt: false }, + { command: 'image', label: 'Image', hint: 'Generate an image', needsPrompt: true }, + { command: 'video', label: 'Video', hint: 'Generate a video', needsPrompt: true }, + { command: 'audio', label: 'Audio', hint: 'Generate music / sfx', needsPrompt: true }, + { command: 'speech', label: 'Speech', hint: 'Text to speech', needsPrompt: true }, + { command: 'summarize', label: 'Summarize', hint: 'Summarize text', needsPrompt: true }, + { command: 'transcribe', label: 'Transcribe', hint: 'Audio file to text', needsPrompt: true }, + { command: 'quit', label: 'Quit', hint: 'Exit', needsPrompt: false }, +] + +const TITLE = 'TANSTACK AI' +const GRADIENT = ['cyan', 'cyanBright', 'blueBright', 'magenta', 'magentaBright', 'blueBright'] + +/** Animated wordmark: a gradient sweeps across the letters. */ +function Title() { + const [tick, setTick] = useState(0) + useEffect(() => { + const id = setInterval(() => setTick((t) => t + 1), 90) + return () => clearInterval(id) + }, []) + return ( + + + {TITLE.split('').map((ch, i) => ( + + {ch} + + ))} + + + ) +} + +function Menu({ onChoose }: { onChoose: (choice: MenuChoice) => void }) { + const { exit } = useApp() + const [index, setIndex] = useState(0) + const [promptFor, setPromptFor] = useState(null) + const [draft, setDraft] = useState('') + + useInput((input, key) => { + if (promptFor) { + if (key.return) { + onChoose({ command: promptFor.command, prompt: draft }) + exit() + return + } + if (key.escape) { + setPromptFor(null) + setDraft('') + return + } + if (key.backspace || key.delete) { + setDraft((d) => d.slice(0, -1)) + return + } + if (input && !key.ctrl && !key.meta) setDraft((d) => d + input) + return + } + + if (key.upArrow) setIndex((i) => (i - 1 + ITEMS.length) % ITEMS.length) + else if (key.downArrow) setIndex((i) => (i + 1) % ITEMS.length) + else if (key.return) { + const item = ITEMS[index] + if (!item) return + if (item.command === 'quit') { + onChoose({ command: 'quit' }) + exit() + } else if (item.needsPrompt) { + setPromptFor(item) + } else { + onChoose({ command: item.command }) + exit() + } + } else if (key.escape) { + onChoose({ command: 'quit' }) + exit() + } + }) + + if (promptFor) { + return ( + + + <Text> + {promptFor.label}: <Text color="cyan">{draft}</Text> + <Text color="gray">▌</Text> + </Text> + <Text dimColor>Enter to run · Esc to go back</Text> + </Box> + ) + } + + return ( + <Box flexDirection="column"> + <Title /> + <Text dimColor>What do you want to do?</Text> + <Box flexDirection="column" marginTop={1}> + {ITEMS.map((item, i) => ( + <Text key={item.command} color={i === index ? 'cyan' : undefined}> + {i === index ? '❯ ' : ' '} + <Text bold={i === index}>{item.label}</Text> + <Text dimColor> — {item.hint}</Text> + </Text> + ))} + </Box> + <Box marginTop={1}> + <Text dimColor>↑/↓ to move · Enter to select · Esc to quit</Text> + </Box> + </Box> + ) +} + +/** Render the home screen and resolve the user's choice. */ +export function runMenuInk(): Promise<MenuChoice> { + return new Promise((resolve) => { + let choice: MenuChoice = { command: 'quit' } + const { waitUntilExit } = render( + <Menu + onChoose={(c) => { + choice = c + }} + />, + ) + void waitUntilExit().then(() => resolve(choice)) + }) +} diff --git a/packages/ai-cli/src/render/repl.tsx b/packages/ai-cli/src/render/repl.tsx new file mode 100644 index 000000000..39fdb38ac --- /dev/null +++ b/packages/ai-cli/src/render/repl.tsx @@ -0,0 +1,105 @@ +import { useState } from 'react' +import { Box, Text, render, useApp, useInput } from 'ink' + +export interface ReplMessage { + role: 'user' | 'assistant' + content: string +} + +/** + * Interactive chat REPL. Stateless across turns from the CLI's perspective — + * the full message list is handed to `respond` each turn. `respond` returns the + * assistant's reply text. `/exit` quits, `/clear` resets the conversation. + */ +function Repl({ + model, + respond, +}: { + model: string + respond: (messages: Array<ReplMessage>) => Promise<string> +}) { + const { exit } = useApp() + const [messages, setMessages] = useState<Array<ReplMessage>>([]) + const [draft, setDraft] = useState('') + const [busy, setBusy] = useState(false) + const [error, setError] = useState<string | null>(null) + + useInput((input, key) => { + if (busy) return + if (key.return) { + const text = draft.trim() + setDraft('') + if (!text) return + if (text === '/exit' || text === '/quit') { + exit() + return + } + if (text === '/clear') { + setMessages([]) + setError(null) + return + } + const next = [...messages, { role: 'user' as const, content: text }] + setMessages(next) + setBusy(true) + setError(null) + respond(next) + .then((reply) => { + setMessages((m) => [...m, { role: 'assistant', content: reply }]) + }) + .catch((err: unknown) => { + setError(err instanceof Error ? err.message : String(err)) + }) + .finally(() => setBusy(false)) + return + } + if (key.escape) { + exit() + return + } + if (key.backspace || key.delete) { + setDraft((d) => d.slice(0, -1)) + return + } + if (input && !key.ctrl && !key.meta) setDraft((d) => d + input) + }) + + return ( + <Box flexDirection="column"> + <Text dimColor> + chat · {model} · /clear to reset · /exit to quit + </Text> + <Box flexDirection="column" marginTop={1}> + {messages.map((m, i) => ( + <Box key={i} marginBottom={1} flexDirection="column"> + <Text bold color={m.role === 'user' ? 'cyan' : 'green'}> + {m.role === 'user' ? 'you' : 'ai'} + </Text> + <Text>{m.content}</Text> + </Box> + ))} + </Box> + {error ? <Text color="red">error: {error}</Text> : null} + <Box> + {busy ? ( + <Text color="yellow">…thinking</Text> + ) : ( + <Text> + <Text color="cyan">❯ </Text> + {draft} + <Text color="gray">▌</Text> + </Text> + )} + </Box> + </Box> + ) +} + +/** Render the REPL and resolve when the user exits. */ +export async function runChatReplInk(input: { + model: string + respond: (messages: Array<ReplMessage>) => Promise<string> +}): Promise<void> { + const { waitUntilExit } = render(<Repl model={input.model} respond={input.respond} />) + await waitUntilExit() +} diff --git a/packages/ai-cli/tests/core.test.ts b/packages/ai-cli/tests/core.test.ts new file mode 100644 index 000000000..319692b9f --- /dev/null +++ b/packages/ai-cli/tests/core.test.ts @@ -0,0 +1,216 @@ +import { describe, expect, it } from 'vitest' +import { + bundledProviders, + instantiateAdapter, + resolveApiKey, + resolveModelSlug, +} from '../src/core/providers' +import { mergeOptions } from '../src/core/config' +import { resolveOutputMode } from '../src/core/output' +import { coerceFlags } from '../src/cli/options' +import { CliError, ExitCode, toCliError } from '../src/core/exit-codes' +import { inferMimeType, resolvePrompt } from '../src/core/io' +import { tokenizeCommand } from '../src/cli/mcp-clients' +import { findCommand } from '../src/manifest/manifest' +import type { CommandSpec } from '../src/manifest/types' + +describe('resolveModelSlug', () => { + it('parses a provider/model slug', () => { + const r = resolveModelSlug('openai/gpt-5.5') + expect(r.provider).toBe('openai') + expect(r.model).toBe('gpt-5.5') + }) + + it('infers provider for a known bare model', () => { + expect(resolveModelSlug('gpt-5.5').provider).toBe('openai') + }) + + it('rejects an unknown provider', () => { + expect(() => resolveModelSlug('bogus/x')).toThrowError(CliError) + }) + + it('rejects a bare model it cannot infer', () => { + expect(() => resolveModelSlug('mystery-model')).toThrowError(/provider\/model/) + }) + + it('rejects a slug with an empty model', () => { + expect(() => resolveModelSlug('openai/')).toThrowError(CliError) + }) + + it('splits only on the first slash (multi-segment model ids)', () => { + const or = resolveModelSlug('openrouter/openai/gpt-oss-120b') + expect(or.provider).toBe('openrouter') + expect(or.model).toBe('openai/gpt-oss-120b') + const fal = resolveModelSlug('fal/fal-ai/ltx-video') + expect(fal.provider).toBe('fal') + expect(fal.model).toBe('fal-ai/ltx-video') + }) +}) + +describe('instantiateAdapter factory resolution (no network — just constructs adapters)', () => { + const make = (slug: string, activity: Parameters<typeof instantiateAdapter>[0]['activity']) => + instantiateAdapter({ resolved: resolveModelSlug(slug), activity, apiKey: 'test-key' }) + + it('resolves openai chat via the create*Chat factory', async () => { + expect(await make('openai/gpt-5.5', 'chat')).toBeTruthy() + }) + + it('resolves openrouter chat via the *Text factory (not Chat)', async () => { + // Regression: OpenRouter exports createOpenRouterText, and the prefix is + // "OpenRouter" (capital R), not "Openrouter". + expect(await make('openrouter/openai/gpt-oss-120b', 'chat')).toBeTruthy() + }) + + it('resolves fal image via the falImage factory + config-object key style', async () => { + // Regression: fal uses `falImage(model, { apiKey })`, not createFalImage(model, key). + expect(await make('fal/fal-ai/flux/dev', 'image')).toBeTruthy() + }) + + it('throws PROVIDER_NOT_INSTALLED for a non-bundled provider', async () => { + await expect(make('groq/llama-3.3-70b-versatile', 'chat')).rejects.toMatchObject({ + code: 'PROVIDER_NOT_INSTALLED', + }) + }) + + it('throws USAGE when a provider does not support an activity', async () => { + await expect(make('fal/fal-ai/flux/dev', 'chat')).rejects.toMatchObject({ code: 'USAGE' }) + }) +}) + +describe('resolveApiKey', () => { + const { entry } = resolveModelSlug('openai/gpt-5.5') + + it('prefers the explicit key', () => { + expect(resolveApiKey(entry, 'openai', 'sk-explicit', {})).toBe('sk-explicit') + }) + + it('falls back to the conventional env var', () => { + expect(resolveApiKey(entry, 'openai', undefined, { OPENAI_API_KEY: 'sk-env' })).toBe('sk-env') + }) + + it('throws a USAGE error when no key is available', () => { + try { + resolveApiKey(entry, 'openai', undefined, {}) + expect.unreachable('should have thrown') + } catch (err) { + expect(err).toBeInstanceOf(CliError) + expect((err as CliError).exitCode).toBe(ExitCode.Usage) + } + }) +}) + +describe('bundledProviders', () => { + it('lists exactly the five zero-install providers', () => { + expect(bundledProviders().sort()).toEqual( + ['anthropic', 'fal', 'gemini', 'openai', 'openrouter'].sort(), + ) + }) +}) + +describe('mergeOptions', () => { + it('lets defined flags win over config but keeps config for undefined flags', () => { + const merged = mergeOptions( + { model: 'openai/gpt-5.5', size: undefined }, + { model: 'anthropic/x', size: '1024x1024', extra: true }, + ) + expect(merged).toEqual({ model: 'openai/gpt-5.5', size: '1024x1024', extra: true }) + }) +}) + +describe('resolveOutputMode', () => { + it('returns json when --json is set', () => { + expect(resolveOutputMode({ json: true })).toBe('json') + }) + it('returns stream when --stream is set', () => { + expect(resolveOutputMode({ stream: true })).toBe('stream') + }) + it('prefers stream when both are set', () => { + expect(resolveOutputMode({ json: true, stream: true })).toBe('stream') + }) + it('is pretty on a TTY and json otherwise', () => { + expect(resolveOutputMode({ isTTY: true })).toBe('pretty') + expect(resolveOutputMode({ isTTY: false })).toBe('json') + }) +}) + +describe('coerceFlags', () => { + const spec = findCommand('image') as CommandSpec + + it('coerces numbers and leaves config/strings alone', () => { + const out = coerceFlags(spec, { count: '3', model: 'openai/gpt-image-1', config: '{"a":1}' }) + expect(out.count).toBe(3) + expect(out.model).toBe('openai/gpt-image-1') + expect(out.config).toBe('{"a":1}') + }) + + it('throws on a non-numeric number flag', () => { + expect(() => coerceFlags(spec, { count: 'abc' })).toThrowError(/must be a number/) + }) + + it('parses json flags from the chat command', () => { + const chatSpec = findCommand('chat') as CommandSpec + const out = coerceFlags(chatSpec, { messages: '[{"role":"user","content":"hi"}]' }) + expect(Array.isArray(out.messages)).toBe(true) + }) +}) + +describe('exit codes', () => { + it('maps error codes to exit codes', () => { + expect(new CliError('USAGE', 'x').exitCode).toBe(ExitCode.Usage) + expect(new CliError('PROVIDER_NOT_INSTALLED', 'x').exitCode).toBe(ExitCode.ProviderNotInstalled) + expect(new CliError('PROVIDER', 'x').exitCode).toBe(ExitCode.Provider) + }) + + it('coerces unknown throws into a runtime CliError', () => { + const e = toCliError(new Error('boom')) + expect(e).toBeInstanceOf(CliError) + expect(e.code).toBe('RUNTIME') + }) + + it('serializes a structured error object', () => { + const obj = new CliError('PROVIDER', 'nope', { provider: 'openai' }).toErrorObject() + expect(obj).toMatchObject({ code: 'PROVIDER', message: 'nope', provider: 'openai' }) + }) +}) + +describe('io helpers', () => { + it('infers mime types from extensions', () => { + expect(inferMimeType('a.png')).toBe('image/png') + expect(inferMimeType('a.mp3')).toBe('audio/mpeg') + expect(inferMimeType('a.unknown')).toBe('application/octet-stream') + }) + + it('uses positional args as the prompt without touching stdin', async () => { + // No stdin read happens because positional args are present. + expect(await resolvePrompt(['hello', 'world'])).toBe('hello world') + }) + + it('rejects a stale find for aliases', () => { + expect(findCommand('tts')?.name).toBe('speech') + expect(findCommand('stt')?.name).toBe('transcribe') + }) +}) + +describe('tokenizeCommand (--mcp stdio spec)', () => { + it('splits a simple command on whitespace', () => { + expect(tokenizeCommand('npx -y @scope/server /tmp')).toEqual([ + 'npx', + '-y', + '@scope/server', + '/tmp', + ]) + }) + + it('keeps quoted paths with spaces intact', () => { + expect(tokenizeCommand('node "C:\\Program Files\\srv.js" --flag')).toEqual([ + 'node', + 'C:\\Program Files\\srv.js', + '--flag', + ]) + expect(tokenizeCommand("node '/opt/my srv/x.js'")).toEqual(['node', '/opt/my srv/x.js']) + }) + + it('collapses runs of whitespace', () => { + expect(tokenizeCommand(' node server.js ')).toEqual(['node', 'server.js']) + }) +}) diff --git a/packages/ai-cli/tsconfig.json b/packages/ai-cli/tsconfig.json new file mode 100644 index 000000000..773e16853 --- /dev/null +++ b/packages/ai-cli/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "jsx": "react-jsx", + "types": ["node"] + }, + "include": ["src", "tests"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/ai-cli/tsup.bin.config.ts b/packages/ai-cli/tsup.bin.config.ts new file mode 100644 index 000000000..8461835e6 --- /dev/null +++ b/packages/ai-cli/tsup.bin.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from 'tsup' + +/** + * The `ts-ai` executable. Our own source is bundled into a single `bin.js`; + * every package.json dependency (Ink, React, the MCP SDK, and all provider + * adapters) stays external and is resolved from node_modules at runtime. The + * provider packages are loaded via dynamic `import()` on demand, so they are + * intentionally kept external here too. + */ +export default defineConfig({ + entry: { bin: 'src/cli/bin.ts' }, + outDir: 'dist/bin', + format: ['esm'], + platform: 'node', + target: 'node18', + // tsup externalizes package.json deps by default; nothing extra to inline. + banner: { js: '#!/usr/bin/env node' }, +}) diff --git a/packages/ai-cli/vite.config.ts b/packages/ai-cli/vite.config.ts new file mode 100644 index 000000000..3ea9c7207 --- /dev/null +++ b/packages/ai-cli/vite.config.ts @@ -0,0 +1,30 @@ +import { defineConfig, mergeConfig } from 'vitest/config' +import { tanstackViteConfig } from '@tanstack/vite-config' +import packageJson from './package.json' + +const config = defineConfig({ + test: { + name: packageJson.name, + dir: './', + watch: false, + globals: true, + environment: 'node', + include: ['tests/**/*.test.ts'], + coverage: { + provider: 'v8', + include: ['src/**/*.ts'], + // The bin entry, Ink render layer, and pure type modules are exercised by + // the testing/cli subprocess suite, not unit-covered here. + exclude: ['src/cli/**', 'src/render/**', '**/*.test.ts', 'src/**/types.ts'], + }, + }, +}) + +export default mergeConfig( + config, + tanstackViteConfig({ + entry: ['./src/index.ts'], + srcDir: './src', + cjs: false, + }), +) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ab84c8703..446465aee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1035,6 +1035,73 @@ importers: specifier: ^4.2.0 version: 4.2.1 + packages/ai-cli: + dependencies: + '@modelcontextprotocol/sdk': + specifier: ^1.29.0 + version: 1.29.0(zod@4.3.6) + '@tanstack/ai': + specifier: workspace:* + version: link:../ai + '@tanstack/ai-anthropic': + specifier: workspace:* + version: link:../ai-anthropic + '@tanstack/ai-code-mode': + specifier: workspace:* + version: link:../ai-code-mode + '@tanstack/ai-fal': + specifier: workspace:* + version: link:../ai-fal + '@tanstack/ai-gemini': + specifier: workspace:* + version: link:../ai-gemini + '@tanstack/ai-isolate-node': + specifier: workspace:* + version: link:../ai-isolate-node + '@tanstack/ai-mcp': + specifier: workspace:* + version: link:../ai-mcp + '@tanstack/ai-openai': + specifier: workspace:* + version: link:../ai-openai + '@tanstack/ai-openrouter': + specifier: workspace:* + version: link:../ai-openrouter + commander: + specifier: ^13.1.0 + version: 13.1.0 + ink: + specifier: ^7.0.5 + version: 7.0.5(@types/react@19.2.7)(react-devtools-core@6.1.5)(react@19.2.3) + react: + specifier: ^19.2.3 + version: 19.2.3 + react-devtools-core: + specifier: ^6.1.5 + version: 6.1.5 + terminal-image: + specifier: ^4.3.0 + version: 4.3.0 + devDependencies: + '@types/node': + specifier: ^24.10.1 + version: 24.10.3 + '@types/react': + specifier: ^19.2.7 + version: 19.2.7 + '@vitest/coverage-v8': + specifier: 4.0.14 + version: 4.0.14(vitest@4.0.14(@opentelemetry/api@1.9.1)(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.15))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + tsup: + specifier: ^8.5.1 + version: 8.5.1(@microsoft/api-extractor@7.47.7(@types/node@24.10.3))(jiti@2.6.1)(postcss@8.5.15)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + vite: + specifier: ^7.3.3 + version: 7.3.3(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + zod: + specifier: ^4.2.0 + version: 4.3.6 + packages/ai-client: dependencies: '@tanstack/ai': @@ -1276,7 +1343,7 @@ importers: version: link:../openai-base openai: specifier: ^6.41.0 - version: 6.41.0(ws@8.19.0)(zod@4.3.6) + version: 6.41.0(ws@8.21.0)(zod@4.3.6) zod: specifier: ^4.0.0 version: 4.3.6 @@ -1307,7 +1374,7 @@ importers: version: link:../openai-base openai: specifier: ^6.41.0 - version: 6.41.0(ws@8.19.0)(zod@4.3.6) + version: 6.41.0(ws@8.21.0)(zod@4.3.6) zod: specifier: ^4.0.0 version: 4.3.6 @@ -1418,7 +1485,7 @@ importers: version: link:../openai-base openai: specifier: ^6.41.0 - version: 6.41.0(ws@8.19.0)(zod@4.3.6) + version: 6.41.0(ws@8.21.0)(zod@4.3.6) devDependencies: '@tanstack/ai': specifier: workspace:* @@ -1755,7 +1822,7 @@ importers: version: link:../ai-utils openai: specifier: ^6.41.0 - version: 6.41.0(ws@8.19.0)(zod@4.3.6) + version: 6.41.0(ws@8.21.0)(zod@4.3.6) devDependencies: '@tanstack/ai': specifier: workspace:* @@ -1833,6 +1900,18 @@ importers: specifier: ^2.11.10 version: 2.11.10(solid-js@1.9.10)(vite@7.3.3(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + testing/cli: + devDependencies: + '@modelcontextprotocol/sdk': + specifier: ^1.29.0 + version: 1.29.0(zod@4.3.6) + '@tanstack/ai-cli': + specifier: workspace:* + version: link:../../packages/ai-cli + vitest: + specifier: ^4.0.14 + version: 4.1.4(@opentelemetry/api@1.9.1)(@types/node@24.10.3)(happy-dom@20.0.11)(jsdom@27.3.0(postcss@8.5.15))(vite@7.3.3(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + testing/e2e: dependencies: '@copilotkit/aimock': @@ -2117,6 +2196,10 @@ packages: '@ag-ui/core@0.0.52': resolution: {integrity: sha512-Xo0bUaNV56EqylzcrAuhUkQX7et7+SZIrqZZtEByGwEq/I1EHny6ZMkWHLkKR7UNi0FJZwJyhKYmKJS3B2SEgA==} + '@alcalzone/ansi-tokenize@0.3.0': + resolution: {integrity: sha512-p+CMKJ93HFmLkjXKlXiVGlMQEuRb6H0MokBSwUsX+S6BRX8eV5naFZpQJFfJHjRZY0Hmnqy1/r6UWl3x+19zYA==} + engines: {node: '>=18'} + '@anthropic-ai/sdk@0.97.1': resolution: {integrity: sha512-wOf7AUeJPitcVpvKO4UMu63mWH5SaVipkGd7OOQJt/G6VYGlV8D2Gp9dLxOrttDJh/9gqPqdaBwDGcBevumeAg==} hasBin: true @@ -2666,6 +2749,9 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} + '@borewit/text-codec@0.2.2': + resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==} + '@bufbuild/protobuf@1.10.1': resolution: {integrity: sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ==} @@ -4132,6 +4218,118 @@ packages: resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jimp/core@1.6.1': + resolution: {integrity: sha512-+BoKC5G6hkrSy501zcJ2EpfnllP+avPevcBfRcZe/CW+EwEfY6X1EZ8QWyT7NpDIvEEJb1fdJnMMfUnFkxmw9A==} + engines: {node: '>=18'} + + '@jimp/diff@1.6.1': + resolution: {integrity: sha512-YkKDPdHjLgo1Api3+Bhc0GLAygldlpt97NfOKoNg1U6IUNXA6X2MgosCjPfSBiSvJvrrz1fsIR+/4cfYXBI/HQ==} + engines: {node: '>=18'} + + '@jimp/file-ops@1.6.1': + resolution: {integrity: sha512-T+gX6osHjprbDRad0/B71Evyre7ZdVY1z/gFGEG9Z8KOtZPKboWvPeP2UjbZYWQLy9UKCPQX1FNAnDiOPkJL7w==} + engines: {node: '>=18'} + + '@jimp/js-bmp@1.6.1': + resolution: {integrity: sha512-xzWzNT4/u5zGrTT3Tme9sGU7YzIKxi13+BCQwLqACbt5DXf9SAfdzRkopZQnmDko+6In5nqaT89Gjs43/WdnYQ==} + engines: {node: '>=18'} + + '@jimp/js-gif@1.6.1': + resolution: {integrity: sha512-YjY2W26rQa05XhanYhRZ7dingCiNN+T2Ymb1JiigIbABY0B28wHE3v3Cf1/HZPWGu0hOg36ylaKgV5KxF2M58w==} + engines: {node: '>=18'} + + '@jimp/js-jpeg@1.6.1': + resolution: {integrity: sha512-HT9H3yOmlOFzYmdI15IYdfy6ggQhSRIaHeA+OTJSEORXBqEo97sUZu/DsgHIcX5NJ7TkJBTgZ9BZXsV6UbsyMg==} + engines: {node: '>=18'} + + '@jimp/js-png@1.6.1': + resolution: {integrity: sha512-SZ/KVhI5UjcSzzlXsXdIi/LhJ7UShf2NkMOtVrbZQcGzsqNtynAelrOXeoTxcanfVqmNhAoVHg8yR2cYoqrYjA==} + engines: {node: '>=18'} + + '@jimp/js-tiff@1.6.1': + resolution: {integrity: sha512-jDG/eJquID1M4MBlKMmDRBmz2TpXMv7TUyu2nIRUxhlUc2ogC82T+VQUkca9GJH1BBJ9dx5sSE5dGkWNjIbZxw==} + engines: {node: '>=18'} + + '@jimp/plugin-blit@1.6.1': + resolution: {integrity: sha512-MwnI7C7K81uWddY9FLw1fCOIy6SsPIUftUz36Spt7jisCn8/40DhQMlSxpxTNelnZb/2SnloFimQfRZAmHLOqQ==} + engines: {node: '>=18'} + + '@jimp/plugin-blur@1.6.1': + resolution: {integrity: sha512-lIo7Tzp5jQu30EFFSK/phXANK3citKVEjepDjQ6ljHoIFtuMRrnybnmI2Md24ulvWlDaz+hh3n6qrMb8ydwhZQ==} + engines: {node: '>=18'} + + '@jimp/plugin-circle@1.6.1': + resolution: {integrity: sha512-kK1PavY6cKHNNKce37vdV4Tmpc1/zDKngGoeOV3j+EMatoHFZUinV3s6F9aWryPs3A0xhCLZgdJ6Zeea1d5LCQ==} + engines: {node: '>=18'} + + '@jimp/plugin-color@1.6.1': + resolution: {integrity: sha512-LtUN1vAP+LRlZAtTNVhDRSiXx+26Kbz3zJaG6a5k59gQ95jgT5mknnF8lxkHcqJthM4MEk3/tPxkdJpEybyF/A==} + engines: {node: '>=18'} + + '@jimp/plugin-contain@1.6.1': + resolution: {integrity: sha512-m0qhrfA8jkTqretGv4w+T/ADFR4GwBpE0sCOC2uJ0dzr44/ddOMsIdrpi89kabqYiPYIrxkgdCVCLm3zn1Vkkg==} + engines: {node: '>=18'} + + '@jimp/plugin-cover@1.6.1': + resolution: {integrity: sha512-hZytnsth0zoll6cPf434BrT+p/v569Wr5tyO6Dp0dH1IDPhzhB5F38sZGMLDo7bzQiN9JFVB3fxkcJ/WYCJ3Mg==} + engines: {node: '>=18'} + + '@jimp/plugin-crop@1.6.1': + resolution: {integrity: sha512-EerRSLlclXyKDnYc/H9w/1amZW7b7v3OGi/VlerPd2M/pAu5X8TkyYWtfqYCXnNp1Ixtd8oCo9zGfY9zoXT4rg==} + engines: {node: '>=18'} + + '@jimp/plugin-displace@1.6.1': + resolution: {integrity: sha512-K07QVl7xQwIfD6KfxRV/c3E9e7ZBXxUXdWuvoTWcKHL2qV48MOF5Nqbz/aJW4ThnQARIsxvYlZjPFiqkCjlU+g==} + engines: {node: '>=18'} + + '@jimp/plugin-dither@1.6.1': + resolution: {integrity: sha512-+2V+GCV2WycMoX1/z977TkZ8Zq/4MVSKElHYatgUqtwXMi2fDK2gKYU2g9V39IqFvTJsTIsK0+58VFz/ROBVew==} + engines: {node: '>=18'} + + '@jimp/plugin-fisheye@1.6.1': + resolution: {integrity: sha512-XtS5ZyoZ0vxZxJ6gkqI63SivhtI58vX95foMPM+cyzYkRsJXMOYCr8DScxF5bp4Xr003NjYm/P+7+08tibwzHA==} + engines: {node: '>=18'} + + '@jimp/plugin-flip@1.6.1': + resolution: {integrity: sha512-ws38W/sGj7LobNRayQ83garxiktOyWxM5vO/y4a/2cy9v65SLEUzVkrj+oeAaUSSObdz4HcCEla7XtGlnAGAaA==} + engines: {node: '>=18'} + + '@jimp/plugin-hash@1.6.1': + resolution: {integrity: sha512-sZt6ZcMX6i8vFWb4GYnw0pR/o9++ef0dTVcboTB5B/g7nrxCODIB4wfEkJ/YqZM5wUvol77K1qeS0/rVO6z21A==} + engines: {node: '>=18'} + + '@jimp/plugin-mask@1.6.1': + resolution: {integrity: sha512-SIG0/FcmEj3tkwFxc7fAGLO8o4uNzMpSOdQOhbCgxefQKq5wOVMk9BQx/sdMPBwtMLr9WLq0GzLA/rk6t2v20A==} + engines: {node: '>=18'} + + '@jimp/plugin-print@1.6.1': + resolution: {integrity: sha512-BYVz/X3Xzv8XYilVeDy11NOp0h7BTDjlOtu0BekIFHP1yHVd24AXNzbOy52XlzYZWQ0Dl36HOHEpl/nSNrzc6w==} + engines: {node: '>=18'} + + '@jimp/plugin-quantize@1.6.1': + resolution: {integrity: sha512-J2En9PLURfP+vwYDtuZ9T8yBW6BWYZBScydAjRiPBmJfEhTcNQqiiQODrZf7EqbbX/Sy5H6dAeRiqkgoV9N6Ww==} + engines: {node: '>=18'} + + '@jimp/plugin-resize@1.6.1': + resolution: {integrity: sha512-CLkrtJoIz2HdWnpYiN6p8KYcPc00rCH/SUu6o+lfZL05Q4uhecJlnvXuj9x+U6mDn3ldPmJj6aZqMHuUJzdVqg==} + engines: {node: '>=18'} + + '@jimp/plugin-rotate@1.6.1': + resolution: {integrity: sha512-nOjVjbbj705B02ksysKnh0POAwEBXZtJ9zQ5qC+X7Tavl3JNn+P3BzQovbBxLPSbUSld6XID9z5ijin4PtOAUg==} + engines: {node: '>=18'} + + '@jimp/plugin-threshold@1.6.1': + resolution: {integrity: sha512-JOKv9F8s6tnVLf4sB/2fF0F339EFnHvgEdFYugO6VhowKLsap0pEZmLyE/DlRnYtIj2RddHZVxVMp/eKJ04l2Q==} + engines: {node: '>=18'} + + '@jimp/types@1.6.1': + resolution: {integrity: sha512-leI7YbveTNi565m910XgIOwXyuu074H5qazAD1357HImJSv2hqxnWXpwxQbadGWZ7goZRYBDZy5lpqud0p7q5w==} + engines: {node: '>=18'} + + '@jimp/utils@1.6.1': + resolution: {integrity: sha512-veFPRd93FCnS7AgmCkPgARVGoDRrJ9cm1ujuNyA+UfQ5VKbED2002sm5XfFLFwTsKC8j04heTrwe+tU1dluXOw==} + engines: {node: '>=18'} + '@jitl/quickjs-ffi-types@0.31.0': resolution: {integrity: sha512-1yrgvXlmXH2oNj3eFTrkwacGJbmM0crwipA3ohCrjv52gBeDaD7PsTvFYinlAnqU8iPME3LGP437yk05a2oejw==} @@ -7124,6 +7322,13 @@ packages: '@types/react-dom': optional: true + '@tokenizer/inflate@0.4.1': + resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + '@tootallnate/quickjs-emscripten@0.23.0': resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} @@ -7241,6 +7446,9 @@ packages: '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + '@types/node@16.9.1': + resolution: {integrity: sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==} + '@types/node@20.19.26': resolution: {integrity: sha512-0l6cjgF0XnihUpndDhk+nyD3exio3iKaYROSgvh/qSevPXax3L8p5DBRFjbvalnwatGgHEQn2R88y2fA3g4irg==} @@ -7788,6 +7996,10 @@ packages: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} + ansi-regex@4.1.1: resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} engines: {node: '>=6'} @@ -7820,6 +8032,9 @@ packages: resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} engines: {node: '>=14'} + any-base@1.1.0: + resolution: {integrity: sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==} + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -7827,6 +8042,10 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + app-path@4.0.0: + resolution: {integrity: sha512-mgBO9PZJ3MpbKbwFTljTi36ZKBvG5X/fkVR1F85ANsVcVllEb+C0LGNdJfGUm84GpC4xxgN6HFkmkMU8VEO4mA==} + engines: {node: '>=12'} + archiver-utils@5.0.2: resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==} engines: {node: '>= 14'} @@ -7903,6 +8122,10 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + auto-bind@5.0.1: + resolution: {integrity: sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + autoprefixer@10.4.22: resolution: {integrity: sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==} engines: {node: ^10 || ^12 || >=14} @@ -7914,6 +8137,10 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} + await-to-js@3.0.0: + resolution: {integrity: sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==} + engines: {node: '>=6.0.0'} + axios@1.13.2: resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} @@ -8081,6 +8308,9 @@ packages: blake3-wasm@2.1.5: resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} + bmp-ts@1.0.9: + resolution: {integrity: sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==} + body-parser@2.2.1: resolution: {integrity: sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==} engines: {node: '>=18'} @@ -8298,6 +8528,10 @@ packages: resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} engines: {node: '>=10'} + cli-boxes@4.0.1: + resolution: {integrity: sha512-5IOn+jcCEHEraYolBPs/sT4BxYCe2nHg374OPiItB1O96KZFseS2gthU4twyYzeDcFew4DaUM/xwc5BQf08JJw==} + engines: {node: '>=18.20 <19 || >=20.10'} + cli-cursor@2.1.0: resolution: {integrity: sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==} engines: {node: '>=4'} @@ -8306,10 +8540,22 @@ packages: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} + cli-cursor@4.0.0: + resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + cli-spinners@2.6.1: resolution: {integrity: sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==} engines: {node: '>=6'} + cli-truncate@6.0.0: + resolution: {integrity: sha512-3+YKIUFsohD9MIoOFPFBldjAlnfCmCDcqe6aYGFqlDTRKg80p4wg35L+j83QQ63iOlKRccEkbn8IuM++HsgEjA==} + engines: {node: '>=22'} + clipboardy@4.0.0: resolution: {integrity: sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w==} engines: {node: '>=18'} @@ -8334,6 +8580,10 @@ packages: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} + code-excerpt@4.0.0: + resolution: {integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -8444,6 +8694,10 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + convert-to-spaces@2.0.1: + resolution: {integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + cookie-es@1.2.2: resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} @@ -8905,6 +9159,10 @@ packages: miniflare: optional: true + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} @@ -8939,6 +9197,9 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} + es-toolkit@1.47.0: + resolution: {integrity: sha512-n1GuoD0WEQZMBk5tttoZSqwgyLx01oqa5XsBmCHwPyNe1S9jPBEmtR2pSgp2kJuWE3ciFZ6yRHmY4pM4C3OOkw==} + esbuild-plugin-solid@0.5.0: resolution: {integrity: sha512-ITK6n+0ayGFeDVUZWNMxX+vLsasEN1ILrg4pISsNOQ+mq4ljlJJiuXotInd+HE0MzwTcA9wExT1yzDE2hsqPsg==} peerDependencies: @@ -8981,6 +9242,10 @@ packages: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -9160,10 +9425,17 @@ packages: resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} engines: {node: '>=18.0.0'} + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} + exif-parser@0.1.12: + resolution: {integrity: sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} @@ -9343,6 +9615,10 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + file-type@21.3.4: + resolution: {integrity: sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==} + engines: {node: '>=20'} + file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} @@ -9530,6 +9806,10 @@ packages: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} @@ -9545,6 +9825,9 @@ packages: resolution: {integrity: sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==} engines: {node: '>=6'} + gifwrap@0.10.1: + resolution: {integrity: sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==} + giget@2.0.0: resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} hasBin: true @@ -9814,6 +10097,10 @@ packages: resolution: {integrity: sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==} hasBin: true + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} @@ -9837,6 +10124,14 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} + image-dimensions@2.5.1: + resolution: {integrity: sha512-It7CkTNp7IYA8jhvGgz8K18OAbHF3/Juk8RNEyTyMFfolJOIU1Y2wGDnzDQPbGCjyxVF/++pwIZE0BKJUEOb1g==} + engines: {node: '>=18'} + hasBin: true + + image-q@4.0.0: + resolution: {integrity: sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==} + image-size@1.2.1: resolution: {integrity: sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==} engines: {node: '>=16.x'} @@ -9861,12 +10156,29 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + indent-string@5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + ink@7.0.5: + resolution: {integrity: sha512-zWNjGHQPxSeiSAmDUOq+QPQ6CfmMhmNi85vrJIuy4prafKKUSoZlXEy4wbM7LuLuF1pDURk7qvF4fxrQlLxv3w==} + engines: {node: '>=22'} + peerDependencies: + '@types/react': '>=19.2.0' + react: '>=19.2.0' + react-devtools-core: '>=6.1.2' + peerDependenciesMeta: + '@types/react': + optional: true + react-devtools-core: + optional: true + inline-style-parser@0.1.1: resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} @@ -9892,10 +10204,6 @@ packages: resolution: {integrity: sha512-tAAg/72/VxOUW7RQSX1pIxJVucYKcjFjfvj60L57jrZpYCHC3XN0WCQ3sNYL4Gmvv+7GPvTAjc+KSdeNuE8oWQ==} engines: {node: '>=12.22.0'} - ip-address@10.1.0: - resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} - engines: {node: '>= 12'} - ip-address@10.2.0: resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} engines: {node: '>= 12'} @@ -9969,6 +10277,10 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -9980,6 +10292,11 @@ packages: resolution: {integrity: sha512-S+OpgB5i7wzIue/YSE5hg0e5ZYfG3hhpNh9KGl6ayJ38p7ED6wxQLd1TV91xHpcTvw90KMJ9EwN3F/iNflHBVg==} engines: {node: '>=8'} + is-in-ci@2.0.0: + resolution: {integrity: sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==} + engines: {node: '>=20'} + hasBin: true + is-inside-container@1.0.0: resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} engines: {node: '>=14.16'} @@ -10133,6 +10450,10 @@ packages: resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} engines: {node: '>=8'} + iterm2-version@5.0.0: + resolution: {integrity: sha512-WdLXcMYvN3SXT6vEtuW78vnZs4pVWm2nBnb4VKjOPPXmdlR1xTHmBgqKacOzAe4RXOiY/V+0u/0zsU3LoGQoBg==} + engines: {node: '>=12'} + jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -10159,6 +10480,10 @@ packages: jimp-compact@0.16.1: resolution: {integrity: sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww==} + jimp@1.6.1: + resolution: {integrity: sha512-hNQh6rZtWfSVWSNVmvq87N5BPJsNH7k7I7qyrXf9DOma9xATQk3fsyHazCQe51nCjdkoWdTmh0vD7bjVSLoxxw==} + engines: {node: '>=18'} + jiti@1.21.7: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true @@ -10177,6 +10502,9 @@ packages: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} + jpeg-js@0.4.4: + resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==} + js-beautify@1.15.4: resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==} engines: {node: '>=14'} @@ -10495,6 +10823,10 @@ packages: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} + log-update@8.0.0: + resolution: {integrity: sha512-lddSgOt3bPASrylL54ZSpy8nBHns+vBVSoILlVOx+dei300pnLRN958rj/EdlVLKuWlSESU3qdnDZdAI7FXYGg==} + engines: {node: '>=22'} + loglevel@1.9.2: resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} engines: {node: '>= 0.6.0'} @@ -10883,6 +11215,10 @@ packages: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + miniflare@4.20260504.0: resolution: {integrity: sha512-HeI/HLx+rbeo/UB4qb6NsNcFdUVD7xDzyCexZJTVtFMlfpfexUKEDmdeTRRpzeHrJseZFGua+v9JO1kfPublUw==} engines: {node: '>=22.0.0'} @@ -11238,6 +11574,9 @@ packages: ollama@0.6.3: resolution: {integrity: sha512-KEWEhIqE5wtfzEIZbDCLH51VFZ6Z3ZSa6sIOg/E/tBV8S51flyqBOXi+bRxlOYKDf8i327zG9eSTb8IJxvm3Zg==} + omggif@1.0.10: + resolution: {integrity: sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==} + on-finished@2.3.0: resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} engines: {node: '>= 0.8'} @@ -11265,6 +11604,10 @@ packages: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + open@7.4.2: resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} engines: {node: '>=8'} @@ -11363,10 +11706,22 @@ packages: package-manager-detector@1.6.0: resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-bmfont-ascii@1.0.6: + resolution: {integrity: sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==} + + parse-bmfont-binary@1.0.6: + resolution: {integrity: sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==} + + parse-bmfont-xml@1.1.6: + resolution: {integrity: sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==} + parse-entities@4.0.2: resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} @@ -11397,6 +11752,10 @@ packages: partial-json@0.1.7: resolution: {integrity: sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==} + patch-console@2.0.0: + resolution: {integrity: sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} @@ -11476,6 +11835,10 @@ packages: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} + pixelmatch@5.3.0: + resolution: {integrity: sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==} + hasBin: true + pkce-challenge@5.0.1: resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} engines: {node: '>=16.20.0'} @@ -11508,6 +11871,14 @@ packages: resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==} engines: {node: '>=4.0.0'} + pngjs@6.0.0: + resolution: {integrity: sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==} + engines: {node: '>=12.13.0'} + + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -11762,6 +12133,12 @@ packages: '@types/react': optional: true + react-reconciler@0.33.0: + resolution: {integrity: sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==} + engines: {node: '>=0.10.0'} + peerDependencies: + react: ^19.2.0 + react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} @@ -11971,6 +12348,14 @@ packages: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} + restore-cursor@4.0.0: + resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + retry@0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} @@ -12287,6 +12672,10 @@ packages: simple-plist@1.3.1: resolution: {integrity: sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==} + simple-xml-to-json@1.2.7: + resolution: {integrity: sha512-mz9VXphOxQWX3eQ/uXCtm6upltoN0DLx8Zb5T4TFC4FHB7S9FDPGre8CfLWqPWQQH/GrQYd2AXhhVM5LDpYx6Q==} + engines: {node: '>=20.12.2'} + sirv@3.0.2: resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} engines: {node: '>=18'} @@ -12302,6 +12691,10 @@ packages: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} + slice-ansi@9.0.0: + resolution: {integrity: sha512-SO/3iYL5S3W57LLEniscOGPZgOqZUPCx6d3dB+52B80yJ0XstzsC/eV8gnA4tM3MHDrKz+OCFSLNjswdSC+/bA==} + engines: {node: '>=22'} + slugify@1.6.9: resolution: {integrity: sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg==} engines: {node: '>=8.0.0'} @@ -12394,6 +12787,10 @@ packages: resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} engines: {node: '>=12.0.0'} + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -12451,6 +12848,10 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string-width@8.2.1: + resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==} + engines: {node: '>=20'} + string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -12472,10 +12873,18 @@ packages: resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + strip-final-newline@3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} @@ -12491,6 +12900,10 @@ packages: strip-literal@3.1.0: resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + strtok3@10.3.5: + resolution: {integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==} + engines: {node: '>=18'} + structured-headers@0.4.1: resolution: {integrity: sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==} @@ -12532,6 +12945,10 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + supports-terminal-graphics@0.1.0: + resolution: {integrity: sha512-+KdfozhS0Fw8y5Sghw8kkZNGT8nWYzJ1EzcoIvVjxhl+26TJTs26y02yfBgvc1jh5AS/c8jcI3xtahhR95KRyQ==} + engines: {node: '>=20'} + svelte-check@4.3.4: resolution: {integrity: sha512-DVWvxhBrDsd+0hHWKfjP99lsSXASeOhHJYyuKOFYJcP7ThfSCKgjVarE8XfuMWpS5JV3AlDf+iK1YGGo2TACdw==} engines: {node: '>= 18.0.0'} @@ -12589,14 +13006,26 @@ packages: teex@1.0.1: resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} + term-img@7.1.0: + resolution: {integrity: sha512-au++khgSDly2KXNhC6BOU3mLi2v+Dk5mChYKDcpB5xYwhlwqYQtj0z59dIqFEmr+w7ndZaNqurHapkGc6/hprQ==} + engines: {node: '>=18'} + term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} + terminal-image@4.3.0: + resolution: {integrity: sha512-P4bCi7Ich17LgN/P2zM9sYbeleYNgu1Skk3q/uc15Ol9zWSiCldrCcIJ1Pd6dpgRGDmWhGVTgWHjV+ZMk1m/5g==} + engines: {node: '>=20'} + terminal-link@2.1.1: resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} engines: {node: '>=8'} + terminal-size@4.0.1: + resolution: {integrity: sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==} + engines: {node: '>=18'} + terser@5.44.1: resolution: {integrity: sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==} engines: {node: '>=10'} @@ -12624,6 +13053,9 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinycolor2@1.6.0: + resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} + tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} @@ -12673,6 +13105,10 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + token-types@6.1.2: + resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} + engines: {node: '>=14.16'} + toqr@0.1.1: resolution: {integrity: sha512-FWAPzCIHZHnrE/5/w9MPk0kK25hSQSH2IKhYh9PyjS3SG/+IEMvlwIHbhz+oF7xl54I+ueZlVnMjyzdSwLmAwA==} @@ -12822,6 +13258,10 @@ packages: resolution: {integrity: sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg==} engines: {node: '>=20'} + type-fest@5.7.0: + resolution: {integrity: sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==} + engines: {node: '>=20'} + type-is@2.0.1: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} @@ -12876,6 +13316,10 @@ packages: ufo@1.6.3: resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + uint8array-extras@1.5.0: + resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} + engines: {node: '>=18'} + ultrahtml@1.6.0: resolution: {integrity: sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==} @@ -13270,6 +13714,9 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + utif2@4.1.0: + resolution: {integrity: sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==} + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -13708,6 +14155,10 @@ packages: resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} engines: {node: '>=12'} + widest-line@6.0.0: + resolution: {integrity: sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA==} + engines: {node: '>=20'} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -13727,6 +14178,10 @@ packages: '@cloudflare/workers-types': optional: true + wrap-ansi@10.0.0: + resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==} + engines: {node: '>=20'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -13790,6 +14245,18 @@ packages: utf-8-validate: optional: true + 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 + xcode@3.0.1: resolution: {integrity: sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA==} engines: {node: '>=10.0.0'} @@ -13798,6 +14265,13 @@ packages: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} + xml-parse-from-string@1.0.1: + resolution: {integrity: sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==} + + xml2js@0.5.0: + resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} + engines: {node: '>=4.0.0'} + xml2js@0.6.0: resolution: {integrity: sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w==} engines: {node: '>=4.0.0'} @@ -13855,6 +14329,9 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yoga-layout@3.2.1: + resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} + youch-core@0.3.3: resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} @@ -13896,7 +14373,12 @@ snapshots: dependencies: zod: 3.25.76 - '@anthropic-ai/sdk@0.97.1(zod@4.2.1)': + '@alcalzone/ansi-tokenize@0.3.0': + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + '@anthropic-ai/sdk@0.97.1(zod@4.2.1)': dependencies: json-schema-to-ts: 3.1.1 standardwebhooks: 1.0.0 @@ -14007,7 +14489,7 @@ snapshots: '@babel/helper-annotate-as-pure@7.27.3': dependencies: - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 '@babel/helper-annotate-as-pure@7.29.7': dependencies: @@ -14093,7 +14575,7 @@ snapshots: '@babel/helper-member-expression-to-functions@7.28.5': dependencies: '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color @@ -14106,7 +14588,7 @@ snapshots: '@babel/helper-module-imports@7.18.6': dependencies: - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 '@babel/helper-module-imports@7.27.1': dependencies: @@ -14133,7 +14615,7 @@ snapshots: '@babel/helper-optimise-call-expression@7.27.1': dependencies: - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 '@babel/helper-optimise-call-expression@7.29.7': dependencies: @@ -14547,8 +15029,8 @@ snapshots: '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 '@babel/template@7.29.7': dependencies: @@ -14609,6 +15091,8 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} + '@borewit/text-codec@0.2.2': {} + '@bufbuild/protobuf@1.10.1': {} '@changesets/apply-release-plan@7.1.0': @@ -15935,6 +16419,229 @@ snapshots: '@types/yargs': 17.0.35 chalk: 4.1.2 + '@jimp/core@1.6.1': + dependencies: + '@jimp/file-ops': 1.6.1 + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + await-to-js: 3.0.0 + exif-parser: 0.1.12 + file-type: 21.3.4 + mime: 3.0.0 + transitivePeerDependencies: + - supports-color + + '@jimp/diff@1.6.1': + dependencies: + '@jimp/plugin-resize': 1.6.1 + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + pixelmatch: 5.3.0 + transitivePeerDependencies: + - supports-color + + '@jimp/file-ops@1.6.1': {} + + '@jimp/js-bmp@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + bmp-ts: 1.0.9 + transitivePeerDependencies: + - supports-color + + '@jimp/js-gif@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/types': 1.6.1 + gifwrap: 0.10.1 + omggif: 1.0.10 + transitivePeerDependencies: + - supports-color + + '@jimp/js-jpeg@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/types': 1.6.1 + jpeg-js: 0.4.4 + transitivePeerDependencies: + - supports-color + + '@jimp/js-png@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/types': 1.6.1 + pngjs: 7.0.0 + transitivePeerDependencies: + - supports-color + + '@jimp/js-tiff@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/types': 1.6.1 + utif2: 4.1.0 + transitivePeerDependencies: + - supports-color + + '@jimp/plugin-blit@1.6.1': + dependencies: + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + zod: 3.25.76 + + '@jimp/plugin-blur@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/utils': 1.6.1 + transitivePeerDependencies: + - supports-color + + '@jimp/plugin-circle@1.6.1': + dependencies: + '@jimp/types': 1.6.1 + zod: 3.25.76 + + '@jimp/plugin-color@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + tinycolor2: 1.6.0 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@jimp/plugin-contain@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/plugin-blit': 1.6.1 + '@jimp/plugin-resize': 1.6.1 + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@jimp/plugin-cover@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/plugin-crop': 1.6.1 + '@jimp/plugin-resize': 1.6.1 + '@jimp/types': 1.6.1 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@jimp/plugin-crop@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@jimp/plugin-displace@1.6.1': + dependencies: + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + zod: 3.25.76 + + '@jimp/plugin-dither@1.6.1': + dependencies: + '@jimp/types': 1.6.1 + + '@jimp/plugin-fisheye@1.6.1': + dependencies: + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + zod: 3.25.76 + + '@jimp/plugin-flip@1.6.1': + dependencies: + '@jimp/types': 1.6.1 + zod: 3.25.76 + + '@jimp/plugin-hash@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/js-bmp': 1.6.1 + '@jimp/js-jpeg': 1.6.1 + '@jimp/js-png': 1.6.1 + '@jimp/js-tiff': 1.6.1 + '@jimp/plugin-color': 1.6.1 + '@jimp/plugin-resize': 1.6.1 + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + any-base: 1.1.0 + transitivePeerDependencies: + - supports-color + + '@jimp/plugin-mask@1.6.1': + dependencies: + '@jimp/types': 1.6.1 + zod: 3.25.76 + + '@jimp/plugin-print@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/js-jpeg': 1.6.1 + '@jimp/js-png': 1.6.1 + '@jimp/plugin-blit': 1.6.1 + '@jimp/types': 1.6.1 + parse-bmfont-ascii: 1.0.6 + parse-bmfont-binary: 1.0.6 + parse-bmfont-xml: 1.1.6 + simple-xml-to-json: 1.2.7 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@jimp/plugin-quantize@1.6.1': + dependencies: + image-q: 4.0.0 + zod: 3.25.76 + + '@jimp/plugin-resize@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/types': 1.6.1 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@jimp/plugin-rotate@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/plugin-crop': 1.6.1 + '@jimp/plugin-resize': 1.6.1 + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@jimp/plugin-threshold@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/plugin-color': 1.6.1 + '@jimp/plugin-hash': 1.6.1 + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@jimp/types@1.6.1': + dependencies: + zod: 3.25.76 + + '@jimp/utils@1.6.1': + dependencies: + '@jimp/types': 1.6.1 + tinycolor2: 1.6.0 + '@jitl/quickjs-ffi-types@0.31.0': {} '@jitl/quickjs-wasmfile-debug-asyncify@0.31.0': @@ -18202,7 +18909,7 @@ snapshots: '@babel/code-frame': 7.27.1 '@babel/core': 7.28.5 '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 '@tanstack/router-utils': 1.131.2 babel-dead-code-elimination: 1.0.10 tiny-invariant: 1.3.3 @@ -18215,7 +18922,7 @@ snapshots: '@babel/code-frame': 7.27.1 '@babel/core': 7.28.5 '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 '@tanstack/router-utils': 1.141.0 babel-dead-code-elimination: 1.0.10 pathe: 2.0.3 @@ -18663,7 +19370,7 @@ snapshots: '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 '@tanstack/router-core': 1.131.50 '@tanstack/router-generator': 1.131.50 '@tanstack/router-utils': 1.131.2 @@ -18738,7 +19445,7 @@ snapshots: dependencies: '@babel/core': 7.28.5 '@babel/generator': 7.29.1 - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.7 '@babel/preset-typescript': 7.28.5(@babel/core@7.28.5) ansis: 4.2.0 diff: 8.0.2 @@ -19048,7 +19755,7 @@ snapshots: dependencies: '@babel/code-frame': 7.26.2 '@babel/core': 7.28.5 - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 '@tanstack/router-core': 1.131.50 '@tanstack/router-generator': 1.131.50 '@tanstack/router-plugin': 1.131.50(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.3.3(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(vite@7.3.3(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) @@ -19432,6 +20139,15 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@tokenizer/inflate@0.4.1': + dependencies: + debug: 4.4.3 + token-types: 6.1.2 + transitivePeerDependencies: + - supports-color + + '@tokenizer/token@0.3.0': {} + '@tootallnate/quickjs-emscripten@0.23.0': {} '@tybys/wasm-util@0.10.1': @@ -19554,6 +20270,8 @@ snapshots: '@types/node@12.20.55': {} + '@types/node@16.9.1': {} + '@types/node@20.19.26': dependencies: undici-types: 6.21.0 @@ -20259,6 +20977,10 @@ snapshots: dependencies: type-fest: 0.21.3 + ansi-escapes@7.3.0: + dependencies: + environment: 1.1.0 + ansi-regex@4.1.1: {} ansi-regex@5.0.1: {} @@ -20279,6 +21001,8 @@ snapshots: ansis@4.2.0: {} + any-base@1.1.0: {} + any-promise@1.3.0: {} anymatch@3.1.3: @@ -20286,6 +21010,10 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + app-path@4.0.0: + dependencies: + execa: 5.1.1 + archiver-utils@5.0.2: dependencies: glob: 10.5.0 @@ -20377,6 +21105,8 @@ snapshots: asynckit@0.4.0: {} + auto-bind@5.0.1: {} + autoprefixer@10.4.22(postcss@8.5.15): dependencies: browserslist: 4.28.1 @@ -20391,6 +21121,8 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 + await-to-js@3.0.0: {} + axios@1.13.2: dependencies: follow-redirects: 1.15.11 @@ -20607,6 +21339,8 @@ snapshots: blake3-wasm@2.1.5: {} + bmp-ts@1.0.9: {} + body-parser@2.2.1: dependencies: bytes: 3.1.2 @@ -20881,6 +21615,8 @@ snapshots: cli-boxes@3.0.0: {} + cli-boxes@4.0.1: {} + cli-cursor@2.1.0: dependencies: restore-cursor: 2.0.0 @@ -20889,8 +21625,21 @@ snapshots: dependencies: restore-cursor: 3.1.0 + cli-cursor@4.0.0: + dependencies: + restore-cursor: 4.0.0 + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + cli-spinners@2.6.1: {} + cli-truncate@6.0.0: + dependencies: + slice-ansi: 9.0.0 + string-width: 8.2.1 + clipboardy@4.0.0: dependencies: execa: 8.0.1 @@ -20915,6 +21664,10 @@ snapshots: cluster-key-slot@1.1.2: {} + code-excerpt@4.0.0: + dependencies: + convert-to-spaces: 2.0.1 + color-convert@1.9.3: dependencies: color-name: 1.1.3 @@ -21018,6 +21771,8 @@ snapshots: convert-source-map@2.0.0: {} + convert-to-spaces@2.0.1: {} + cookie-es@1.2.2: {} cookie-es@2.0.0: {} @@ -21395,6 +22150,8 @@ snapshots: optionalDependencies: miniflare: 4.20260504.0 + environment@1.1.0: {} + error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 @@ -21436,6 +22193,8 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + es-toolkit@1.47.0: {} + esbuild-plugin-solid@0.5.0(esbuild@0.27.7)(solid-js@1.9.10): dependencies: '@babel/core': 7.28.5 @@ -21594,6 +22353,8 @@ snapshots: escape-string-regexp@1.0.5: {} + escape-string-regexp@2.0.0: {} + escape-string-regexp@4.0.0: {} escape-string-regexp@5.0.0: {} @@ -21829,6 +22590,18 @@ snapshots: dependencies: eventsource-parser: 3.1.0 + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + execa@8.0.1: dependencies: cross-spawn: 7.0.6 @@ -21841,6 +22614,8 @@ snapshots: signal-exit: 4.1.0 strip-final-newline: 3.0.0 + exif-parser@0.1.12: {} + expect-type@1.3.0: {} expo-asset@56.0.15(expo@56.0.5)(react-native@0.85.3(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.2.3))(react@19.2.3)(typescript@5.9.3): @@ -22064,6 +22839,15 @@ snapshots: dependencies: flat-cache: 4.0.1 + file-type@21.3.4: + dependencies: + '@tokenizer/inflate': 0.4.1 + strtok3: 10.3.5 + token-types: 6.1.2 + uint8array-extras: 1.5.0 + transitivePeerDependencies: + - supports-color + file-uri-to-path@1.0.0: {} fill-range@7.1.1: @@ -22257,6 +23041,8 @@ snapshots: dependencies: pump: 3.0.4 + get-stream@6.0.1: {} + get-stream@8.0.1: {} get-tsconfig@4.13.0: @@ -22273,6 +23059,11 @@ snapshots: getenv@2.0.0: {} + gifwrap@0.10.1: + dependencies: + image-q: 4.0.0 + omggif: 1.0.10 + giget@2.0.0: dependencies: citty: 0.1.6 @@ -22643,6 +23434,8 @@ snapshots: human-id@4.1.3: {} + human-signals@2.1.0: {} + human-signals@5.0.0: {} iconv-lite@0.6.3: @@ -22659,6 +23452,12 @@ snapshots: ignore@7.0.5: {} + image-dimensions@2.5.1: {} + + image-q@4.0.0: + dependencies: + '@types/node': 16.9.1 + image-size@1.2.1: dependencies: queue: 6.0.2 @@ -22676,10 +23475,47 @@ snapshots: imurmurhash@0.1.4: {} + indent-string@5.0.0: {} + inherits@2.0.4: {} ini@1.3.8: {} + ink@7.0.5(@types/react@19.2.7)(react-devtools-core@6.1.5)(react@19.2.3): + dependencies: + '@alcalzone/ansi-tokenize': 0.3.0 + ansi-escapes: 7.3.0 + ansi-styles: 6.2.3 + auto-bind: 5.0.1 + chalk: 5.6.2 + cli-boxes: 4.0.1 + cli-cursor: 4.0.0 + cli-truncate: 6.0.0 + code-excerpt: 4.0.0 + es-toolkit: 1.47.0 + indent-string: 5.0.0 + is-in-ci: 2.0.0 + patch-console: 2.0.0 + react: 19.2.3 + react-reconciler: 0.33.0(react@19.2.3) + scheduler: 0.27.0 + signal-exit: 3.0.7 + slice-ansi: 9.0.0 + stack-utils: 2.0.6 + string-width: 8.2.1 + terminal-size: 4.0.1 + type-fest: 5.7.0 + widest-line: 6.0.0 + wrap-ansi: 10.0.0 + ws: 8.21.0 + yoga-layout: 3.2.1 + optionalDependencies: + '@types/react': 19.2.7 + react-devtools-core: 6.1.5 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + inline-style-parser@0.1.1: {} inline-style-parser@0.2.7: {} @@ -22724,8 +23560,6 @@ snapshots: transitivePeerDependencies: - supports-color - ip-address@10.1.0: {} - ip-address@10.2.0: {} ipaddr.js@1.9.1: {} @@ -22786,6 +23620,10 @@ snapshots: is-fullwidth-code-point@3.0.0: {} + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.6.0 + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 @@ -22796,6 +23634,8 @@ snapshots: dependencies: html-tags: 3.3.1 + is-in-ci@2.0.0: {} + is-inside-container@1.0.0: dependencies: is-docker: 3.0.0 @@ -22927,6 +23767,11 @@ snapshots: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 + iterm2-version@5.0.0: + dependencies: + app-path: 4.0.0 + plist: 3.1.1 + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -22969,6 +23814,38 @@ snapshots: jimp-compact@0.16.1: {} + jimp@1.6.1: + dependencies: + '@jimp/core': 1.6.1 + '@jimp/diff': 1.6.1 + '@jimp/js-bmp': 1.6.1 + '@jimp/js-gif': 1.6.1 + '@jimp/js-jpeg': 1.6.1 + '@jimp/js-png': 1.6.1 + '@jimp/js-tiff': 1.6.1 + '@jimp/plugin-blit': 1.6.1 + '@jimp/plugin-blur': 1.6.1 + '@jimp/plugin-circle': 1.6.1 + '@jimp/plugin-color': 1.6.1 + '@jimp/plugin-contain': 1.6.1 + '@jimp/plugin-cover': 1.6.1 + '@jimp/plugin-crop': 1.6.1 + '@jimp/plugin-displace': 1.6.1 + '@jimp/plugin-dither': 1.6.1 + '@jimp/plugin-fisheye': 1.6.1 + '@jimp/plugin-flip': 1.6.1 + '@jimp/plugin-hash': 1.6.1 + '@jimp/plugin-mask': 1.6.1 + '@jimp/plugin-print': 1.6.1 + '@jimp/plugin-quantize': 1.6.1 + '@jimp/plugin-resize': 1.6.1 + '@jimp/plugin-rotate': 1.6.1 + '@jimp/plugin-threshold': 1.6.1 + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + transitivePeerDependencies: + - supports-color + jiti@1.21.7: {} jiti@2.6.1: {} @@ -22979,6 +23856,8 @@ snapshots: joycon@3.1.1: {} + jpeg-js@0.4.4: {} + js-beautify@1.15.4: dependencies: config-chain: 1.1.13 @@ -23323,6 +24202,15 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 + log-update@8.0.0: + dependencies: + ansi-escapes: 7.3.0 + cli-cursor: 5.0.0 + slice-ansi: 9.0.0 + string-width: 8.2.1 + strip-ansi: 7.2.0 + wrap-ansi: 10.0.0 + loglevel@1.9.2: {} long@5.3.2: {} @@ -24004,6 +24892,8 @@ snapshots: mimic-fn@4.0.0: {} + mimic-function@5.0.1: {} + miniflare@4.20260504.0: dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -24610,6 +25500,8 @@ snapshots: dependencies: whatwg-fetch: 3.6.20 + omggif@1.0.10: {} + on-finished@2.3.0: dependencies: ee-first: 1.1.1 @@ -24636,6 +25528,10 @@ snapshots: dependencies: mimic-fn: 4.0.0 + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + open@7.4.2: dependencies: is-docker: 2.2.1 @@ -24647,9 +25543,9 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 - openai@6.41.0(ws@8.19.0)(zod@4.3.6): + openai@6.41.0(ws@8.21.0)(zod@4.3.6): optionalDependencies: - ws: 8.19.0 + ws: 8.21.0 zod: 4.3.6 optionator@0.9.4: @@ -24811,10 +25707,21 @@ snapshots: package-manager-detector@1.6.0: {} + pako@1.0.11: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 + parse-bmfont-ascii@1.0.6: {} + + parse-bmfont-binary@1.0.6: {} + + parse-bmfont-xml@1.1.6: + dependencies: + xml-parse-from-string: 1.0.1 + xml2js: 0.5.0 + parse-entities@4.0.2: dependencies: '@types/unist': 2.0.11 @@ -24857,6 +25764,8 @@ snapshots: partial-json@0.1.7: {} + patch-console@2.0.0: {} + path-browserify@1.0.1: {} path-exists@3.0.0: {} @@ -24907,6 +25816,10 @@ snapshots: pirates@4.0.7: {} + pixelmatch@5.3.0: + dependencies: + pngjs: 6.0.0 + pkce-challenge@5.0.1: {} pkg-dir@3.0.0: @@ -24941,6 +25854,10 @@ snapshots: pngjs@3.4.0: {} + pngjs@6.0.0: {} + + pngjs@7.0.0: {} + possible-typed-array-names@1.1.0: {} postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.15)(tsx@4.21.0)(yaml@2.8.2): @@ -25322,6 +26239,11 @@ snapshots: - supports-color - utf-8-validate + react-reconciler@0.33.0(react@19.2.3): + dependencies: + react: 19.2.3 + scheduler: 0.27.0 + react-refresh@0.14.2: {} react-refresh@0.17.0: {} @@ -25575,6 +26497,16 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 + restore-cursor@4.0.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + retry@0.13.1: {} reusify@1.1.0: {} @@ -26021,6 +26953,8 @@ snapshots: bplist-parser: 0.3.1 plist: 3.1.1 + simple-xml-to-json@1.2.7: {} + sirv@3.0.2: dependencies: '@polka/url': 1.0.0-next.29 @@ -26033,6 +26967,11 @@ snapshots: slash@5.1.0: {} + slice-ansi@9.0.0: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + slugify@1.6.9: {} smart-buffer@4.2.0: {} @@ -26051,7 +26990,7 @@ snapshots: socks@2.8.7: dependencies: - ip-address: 10.1.0 + ip-address: 10.2.0 smart-buffer: 4.2.0 solid-js@1.9.10: @@ -26118,6 +27057,10 @@ snapshots: stable-hash-x@0.2.0: {} + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + stackback@0.0.2: {} stackframe@1.3.4: {} @@ -26173,6 +27116,11 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.2 + string-width@8.2.1: + dependencies: + get-east-asian-width: 1.6.0 + strip-ansi: 7.1.2 + string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 @@ -26198,8 +27146,14 @@ snapshots: dependencies: ansi-regex: 6.2.2 + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + strip-bom@3.0.0: {} + strip-final-newline@2.0.0: {} + strip-final-newline@3.0.0: {} strip-json-comments@3.1.1: {} @@ -26210,6 +27164,10 @@ snapshots: dependencies: js-tokens: 9.0.1 + strtok3@10.3.5: + dependencies: + '@tokenizer/token': 0.3.0 + structured-headers@0.4.1: {} style-to-js@1.1.21: @@ -26255,6 +27213,8 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + supports-terminal-graphics@0.1.0: {} + svelte-check@4.3.4(picomatch@4.0.4)(svelte@5.45.10)(typescript@5.9.3): dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -26349,13 +27309,32 @@ snapshots: - react-native-b4a optional: true + term-img@7.1.0: + dependencies: + ansi-escapes: 7.3.0 + iterm2-version: 5.0.0 + term-size@2.2.1: {} + terminal-image@4.3.0: + dependencies: + chalk: 5.6.2 + image-dimensions: 2.5.1 + jimp: 1.6.1 + log-update: 8.0.0 + omggif: 1.0.10 + supports-terminal-graphics: 0.1.0 + term-img: 7.1.0 + transitivePeerDependencies: + - supports-color + terminal-link@2.1.1: dependencies: ansi-escapes: 4.3.2 supports-hyperlinks: 2.3.0 + terminal-size@4.0.1: {} + terser@5.44.1: dependencies: '@jridgewell/source-map': 0.3.11 @@ -26385,6 +27364,8 @@ snapshots: tinybench@2.9.0: {} + tinycolor2@1.6.0: {} + tinyexec@0.3.2: {} tinyexec@1.0.2: {} @@ -26421,6 +27402,12 @@ snapshots: toidentifier@1.0.1: {} + token-types@6.1.2: + dependencies: + '@borewit/text-codec': 0.2.2 + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + toqr@0.1.1: {} totalist@3.0.1: {} @@ -26560,6 +27547,10 @@ snapshots: dependencies: tagged-tag: 1.0.0 + type-fest@5.7.0: + dependencies: + tagged-tag: 1.0.0 + type-is@2.0.1: dependencies: content-type: 1.0.5 @@ -26611,6 +27602,8 @@ snapshots: ufo@1.6.3: {} + uint8array-extras@1.5.0: {} + ultrahtml@1.6.0: {} unconfig-core@7.4.2: @@ -26909,6 +27902,10 @@ snapshots: dependencies: react: 19.2.3 + utif2@4.1.0: + dependencies: + pako: 1.0.11 + util-deprecate@1.0.2: {} utils-merge@1.0.1: {} @@ -27379,6 +28376,10 @@ snapshots: dependencies: string-width: 5.1.2 + widest-line@6.0.0: + dependencies: + string-width: 8.2.1 + word-wrap@1.2.5: {} workerd@1.20260504.1: @@ -27406,6 +28407,12 @@ snapshots: - bufferutil - utf-8-validate + wrap-ansi@10.0.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 8.2.1 + strip-ansi: 7.1.2 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -27433,6 +28440,8 @@ snapshots: ws@8.19.0: {} + ws@8.21.0: {} + xcode@3.0.1: dependencies: simple-plist: 1.3.1 @@ -27440,6 +28449,13 @@ snapshots: xml-name-validator@5.0.0: {} + xml-parse-from-string@1.0.1: {} + + xml2js@0.5.0: + dependencies: + sax: 1.6.0 + xmlbuilder: 11.0.1 + xml2js@0.6.0: dependencies: sax: 1.6.0 @@ -27494,6 +28510,8 @@ snapshots: yocto-queue@0.1.0: {} + yoga-layout@3.2.1: {} + youch-core@0.3.3: dependencies: '@poppinss/exception': 1.2.3 diff --git a/testing/cli/README.md b/testing/cli/README.md new file mode 100644 index 000000000..078e21c16 --- /dev/null +++ b/testing/cli/README.md @@ -0,0 +1,23 @@ +# @tanstack/ai-cli-tests + +Subprocess E2E suite for the `ts-ai` binary (`@tanstack/ai-cli`). + +Each test spawns the **built** `ts-ai` binary as a real subprocess and asserts +the machine-facing contract: `--json` payload shape, `--stream` AG-UI events, +exit codes, written artifacts, the `introspect` manifest, and `ts-ai mcp`. This +mirrors exactly how an agent harness drives the CLI. + +## Running + +```bash +# Build the CLI first (the suite runs the compiled bin) +pnpm --filter @tanstack/ai-cli build + +# Run the suite +pnpm --filter @tanstack/ai-cli-tests test:e2e +``` + +The contract tests above need no API keys — they exercise version, introspect, +and the error/exit-code paths. Tests that perform real generations point a +provider `baseURL` at a local mock (aimock for chat/text; media-endpoint mock +routes are added here as those commands gain coverage) and supply a dummy key. diff --git a/testing/cli/package.json b/testing/cli/package.json new file mode 100644 index 000000000..0a25ec26e --- /dev/null +++ b/testing/cli/package.json @@ -0,0 +1,14 @@ +{ + "name": "@tanstack/ai-cli-tests", + "private": true, + "type": "module", + "scripts": { + "test:e2e": "vitest run", + "test:e2e:dev": "vitest" + }, + "devDependencies": { + "@modelcontextprotocol/sdk": "^1.29.0", + "@tanstack/ai-cli": "workspace:*", + "vitest": "^4.0.14" + } +} diff --git a/testing/cli/tests/cli.spec.ts b/testing/cli/tests/cli.spec.ts new file mode 100644 index 000000000..9ac652ba5 --- /dev/null +++ b/testing/cli/tests/cli.spec.ts @@ -0,0 +1,179 @@ +import { spawn } from 'node:child_process' +import { fileURLToPath } from 'node:url' +import { describe, expect, it } from 'vitest' + +const BIN = fileURLToPath( + new URL('../../../packages/ai-cli/dist/bin/bin.js', import.meta.url), +) + +interface RunResult { + code: number | null + stdout: string + stderr: string +} + +/** + * Run the built `ts-ai` binary with stdin closed (the harness shape) and a + * scrubbed env so no real provider key leaks in. Returns the captured streams + * and exit code. + */ +function runCli(args: Array<string>, env: Record<string, string> = {}): Promise<RunResult> { + return new Promise((resolve, reject) => { + const child = spawn(process.execPath, [BIN, ...args], { + stdio: ['ignore', 'pipe', 'pipe'], + env: { PATH: process.env.PATH ?? '', ...env }, + }) + let stdout = '' + let stderr = '' + child.stdout.on('data', (c) => (stdout += String(c))) + child.stderr.on('data', (c) => (stderr += String(c))) + child.on('error', reject) + child.on('close', (code) => resolve({ code, stdout, stderr })) + }) +} + +describe('ts-ai meta commands', () => { + it('reports its version', async () => { + const { code, stdout } = await runCli(['--version']) + expect(code).toBe(0) + expect(stdout.trim()).toMatch(/^\d+\.\d+\.\d+$/) + }) + + it('introspect emits a parseable manifest with all commands', async () => { + const { code, stdout } = await runCli(['introspect']) + expect(code).toBe(0) + const manifest = JSON.parse(stdout) + expect(manifest.bin).toBe('ts-ai') + expect(manifest.bundledProviders).toEqual( + expect.arrayContaining(['openai', 'anthropic', 'gemini', 'openrouter', 'fal']), + ) + const names = manifest.commands.map((c: { name: string }) => c.name) + expect(names).toEqual( + expect.arrayContaining([ + 'chat', + 'image', + 'video', + 'audio', + 'speech', + 'transcribe', + 'summarize', + ]), + ) + }) +}) + +describe('ts-ai machine-mode error contract', () => { + it('emits a structured USAGE error + exit 2 when --model is missing', async () => { + const { code, stdout } = await runCli(['image', 'a cat', '--json']) + expect(code).toBe(2) + expect(JSON.parse(stdout)).toMatchObject({ error: { code: 'USAGE' } }) + }) + + it('rejects an unknown provider with a USAGE error', async () => { + const { code, stdout } = await runCli(['chat', 'hi', '--model', 'bogus/x', '--json']) + expect(code).toBe(2) + expect(JSON.parse(stdout).error.code).toBe('USAGE') + }) + + it('reports a missing API key with the provider attached', async () => { + const { code, stdout } = await runCli([ + 'chat', + 'hi', + '--model', + 'openai/gpt-5.5', + '--json', + ]) + expect(code).toBe(2) + expect(JSON.parse(stdout).error).toMatchObject({ code: 'USAGE', provider: 'openai' }) + }) + + it('keeps stdout free of human chatter in --json mode', async () => { + const { stdout } = await runCli(['image', 'a cat', '--json']) + // Exactly one JSON line on stdout, nothing else. + expect(stdout.trim().split('\n')).toHaveLength(1) + expect(() => JSON.parse(stdout)).not.toThrow() + }) + + it('video status receives --model from the parent command (errors on key, not model)', async () => { + const { code, stdout } = await runCli([ + 'video', + 'status', + 'job_123', + '--model', + 'openai/sora-2', + '--json', + ]) + expect(code).toBe(2) + const err = JSON.parse(stdout).error + expect(err.code).toBe('USAGE') + // If --model weren't merged from the parent it would fail with "Missing + // --model" instead — assert it got far enough to need a key. + expect(err.message).toMatch(/api key/i) + }) + + it('parses kebab-case flags and coerces their values (--max-steps)', async () => { + const { code, stdout } = await runCli([ + 'chat', + 'hi', + '--model', + 'openai/gpt-5.5', + '--max-steps', + 'not-a-number', + '--json', + ]) + expect(code).toBe(2) + const err = JSON.parse(stdout).error + expect(err.code).toBe('USAGE') + expect(err.message).toMatch(/number/i) + }) +}) + +describe('ts-ai argv-injection guard', () => { + it('treats a flag passed as the prompt as text, not an option', async () => { + // Control: a real --version flag prints the version. + const version = await runCli(['chat', '--version']) + expect(version.code).toBe(0) + expect(version.stdout.trim()).toMatch(/^\d+\.\d+\.\d+$/) + + // After `--`, the same token is the prompt — never re-parsed as a flag, so + // it falls through to the missing-model error rather than printing version. + const injected = await runCli(['chat', '--json', '--', '--version']) + expect(injected.code).toBe(2) + const err = JSON.parse(injected.stdout).error + expect(err.code).toBe('USAGE') + expect(err.message).toMatch(/model/i) + }) + + it('does not let a prompt smuggle --api-key', async () => { + const { code, stdout } = await runCli(['chat', '--json', '--', '--api-key', 'LEAKED']) + expect(code).toBe(2) + expect(JSON.parse(stdout).error.code).toBe('USAGE') + }) +}) + +describe('ts-ai introspect flag spelling', () => { + it('emits kebab-cased CLI flag strings for multi-word options', async () => { + const { stdout } = await runCli(['introspect']) + const manifest = JSON.parse(stdout) + const apiKey = manifest.commonFlags.find((f: { name: string }) => f.name === 'apiKey') + expect(apiKey.flag).toBe('--api-key') + const chat = manifest.commands.find((c: { name: string }) => c.name === 'chat') + const maxSteps = chat.flags.find((f: { name: string }) => f.name === 'maxSteps') + expect(maxSteps.flag).toBe('--max-steps') + const video = manifest.commands.find((c: { name: string }) => c.name === 'video') + const wait = video.flags.find((f: { name: string }) => f.name === 'wait') + // default-true booleans render as negatable --no-x flags. + expect(wait.flag).toBe('--no-wait') + }) +}) + +describe('ts-ai no command (non-TTY)', () => { + it('prints help instead of launching the interactive menu', async () => { + // stdout is a pipe here (not a TTY), so the home menu must not start. + const { code, stdout } = await runCli([]) + expect(code).toBe(0) + expect(stdout).toContain('ts-ai') + expect(stdout).toContain('chat') + expect(stdout).toContain('image') + }) +}) diff --git a/testing/cli/tests/mcp.spec.ts b/testing/cli/tests/mcp.spec.ts new file mode 100644 index 000000000..4a63b3e86 --- /dev/null +++ b/testing/cli/tests/mcp.spec.ts @@ -0,0 +1,68 @@ +import { dirname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' +import { afterAll, beforeAll, describe, expect, it } from 'vitest' +import { Client } from '@modelcontextprotocol/sdk/client/index.js' +import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js' + +const BIN = fileURLToPath( + new URL('../../../packages/ai-cli/dist/bin/bin.js', import.meta.url), +) +// Run the server from a directory with no `.env`, so the shelled-out ts-ai has +// no API key and deterministically returns the structured no-key error rather +// than making a real provider call. +const NO_ENV_CWD = resolve(dirname(fileURLToPath(import.meta.url)), '..') + +let client: Client + +beforeAll(async () => { + client = new Client({ name: 'mcp-spec', version: '1.0.0' }) + await client.connect( + new StdioClientTransport({ + command: process.execPath, + args: [BIN, 'mcp'], + cwd: NO_ENV_CWD, + }), + ) +}) + +afterAll(async () => { + await client?.close() +}) + +function toolResultJson(res: { content?: Array<{ type: string; text?: string }> }): any { + const text = res.content?.find((c) => c.type === 'text')?.text ?? '' + return JSON.parse(text) +} + +describe('ts-ai mcp server', () => { + it('registers every generation command as a tool', async () => { + const { tools } = await client.listTools() + const names = tools.map((t) => t.name).sort() + expect(names).toEqual( + ['audio', 'chat', 'image', 'speech', 'summarize', 'transcribe', 'video'].sort(), + ) + }) + + it('round-trips a tool call: client -> server -> ts-ai -> structured JSON', async () => { + // options -> --config blob; with a model but no key, ts-ai resolves the + // model then fails on the missing key — proving the whole pipeline ran. + const res = await client.callTool({ + name: 'chat', + arguments: { prompt: 'hi', options: { model: 'openai/gpt-5.5' } }, + }) + const payload = toolResultJson(res as never) + expect(payload.error.code).toBe('USAGE') + expect(payload.error.message).toMatch(/api key/i) + }) + + it('does not let the prompt smuggle CLI flags through the tool call', async () => { + const res = await client.callTool({ + name: 'chat', + arguments: { prompt: '--version', options: { model: 'openai/gpt-5.5' } }, + }) + const payload = toolResultJson(res as never) + // If --version had been parsed as a flag, this would be a version string, + // not a structured key error. + expect(payload.error.code).toBe('USAGE') + }) +}) diff --git a/testing/cli/vitest.config.ts b/testing/cli/vitest.config.ts new file mode 100644 index 000000000..b674becc1 --- /dev/null +++ b/testing/cli/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + name: '@tanstack/ai-cli-tests', + environment: 'node', + include: ['tests/**/*.spec.ts'], + // Spawning a real subprocess per case is slower than a unit test; give the + // suite room and run files serially to keep stdout assertions clean. + testTimeout: 30_000, + fileParallelism: false, + }, +}) From 329f56a35089950bdde99b1e2ac7ec9bbb1cb1a6 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 7 Jun 2026 13:46:16 +0000 Subject: [PATCH 02/13] ci: apply automated fixes --- packages/ai-cli/src/cli/activities/audio.ts | 23 +++- packages/ai-cli/src/cli/activities/chat.ts | 9 +- packages/ai-cli/src/cli/activities/image.ts | 31 +++-- packages/ai-cli/src/cli/activities/speech.ts | 9 +- .../ai-cli/src/cli/activities/summarize.ts | 10 +- .../ai-cli/src/cli/activities/transcribe.ts | 19 ++- packages/ai-cli/src/cli/activities/video.ts | 53 +++++--- packages/ai-cli/src/cli/artifact.ts | 12 +- packages/ai-cli/src/cli/bin.ts | 4 +- packages/ai-cli/src/cli/code-mode.ts | 11 +- packages/ai-cli/src/cli/context.ts | 3 +- packages/ai-cli/src/cli/interactive.ts | 20 ++- packages/ai-cli/src/cli/mcp-clients.ts | 15 ++- packages/ai-cli/src/cli/mcp.ts | 4 +- packages/ai-cli/src/cli/options.ts | 5 +- packages/ai-cli/src/cli/program.ts | 40 ++++-- packages/ai-cli/src/cli/run.ts | 5 +- packages/ai-cli/src/cli/update.ts | 16 ++- packages/ai-cli/src/core/exit-codes.ts | 11 +- packages/ai-cli/src/core/io.ts | 12 +- packages/ai-cli/src/index.ts | 8 +- packages/ai-cli/src/manifest/manifest.ts | 122 ++++++++++++++---- packages/ai-cli/src/render/ink.tsx | 4 +- packages/ai-cli/src/render/menu.tsx | 58 +++++++-- packages/ai-cli/src/render/repl.tsx | 8 +- packages/ai-cli/tests/core.test.ts | 75 ++++++++--- packages/ai-cli/vite.config.ts | 7 +- testing/cli/tests/cli.spec.ts | 50 +++++-- testing/cli/tests/mcp.spec.ts | 14 +- 29 files changed, 504 insertions(+), 154 deletions(-) diff --git a/packages/ai-cli/src/cli/activities/audio.ts b/packages/ai-cli/src/cli/activities/audio.ts index 5d1e97d8a..ed716e942 100644 --- a/packages/ai-cli/src/cli/activities/audio.ts +++ b/packages/ai-cli/src/cli/activities/audio.ts @@ -9,7 +9,12 @@ import type { RunContext } from '../context' interface AudioResultLike { id: string model: string - audio: { url?: string; b64Json?: string; contentType?: string; duration?: number } + audio: { + url?: string + b64Json?: string + contentType?: string + duration?: number + } usage?: unknown } @@ -23,9 +28,8 @@ const EXT_BY_CONTENT_TYPE: Record<string, string> = { /** `ts-ai audio` (music / sfx) handler. */ export async function runAudio(ctx: RunContext, prompt: string): Promise<void> { - const { resolved, apiKey, adapterConfig, modelOptions } = resolveAdapterContext( - ctx.options, - ) + const { resolved, apiKey, adapterConfig, modelOptions } = + resolveAdapterContext(ctx.options) const adapter = await instantiateAdapter({ resolved, activity: 'audio', @@ -33,7 +37,9 @@ export async function runAudio(ctx: RunContext, prompt: string): Promise<void> { config: adapterConfig, }) - ctx.logger.info(`Generating audio with ${resolved.provider}/${resolved.model}…`) + ctx.logger.info( + `Generating audio with ${resolved.provider}/${resolved.model}…`, + ) const duration = typeof ctx.options.duration === 'number' @@ -52,7 +58,8 @@ export async function runAudio(ctx: RunContext, prompt: string): Promise<void> { const bytes = await mediaSourceToBytes(result.audio) const ext = EXT_BY_CONTENT_TYPE[result.audio.contentType ?? ''] ?? 'mp3' - const output = typeof ctx.options.output === 'string' ? ctx.options.output : undefined + const output = + typeof ctx.options.output === 'string' ? ctx.options.output : undefined const path = await writeArtifact( 'audio', { bytes, ext, mimeType: result.audio.contentType ?? `audio/${ext}` }, @@ -64,7 +71,9 @@ export async function runAudio(ctx: RunContext, prompt: string): Promise<void> { await renderArtifactPath({ label: `Audio generated with ${result.model}`, path: path ?? '(stdout)', - meta: result.audio.duration ? { duration: `${result.audio.duration}s` } : undefined, + meta: result.audio.duration + ? { duration: `${result.audio.duration}s` } + : undefined, }) return } diff --git a/packages/ai-cli/src/cli/activities/chat.ts b/packages/ai-cli/src/cli/activities/chat.ts index 9eacfcd10..7677e11aa 100644 --- a/packages/ai-cli/src/cli/activities/chat.ts +++ b/packages/ai-cli/src/cli/activities/chat.ts @@ -30,9 +30,8 @@ interface ModelMessageLike { * `execute_typescript` tool. */ export async function runChat(ctx: RunContext, prompt: string): Promise<void> { - const { resolved, apiKey, adapterConfig, modelOptions } = resolveAdapterContext( - ctx.options, - ) + const { resolved, apiKey, adapterConfig, modelOptions } = + resolveAdapterContext(ctx.options) const adapter = await instantiateAdapter({ resolved, activity: 'chat', @@ -59,7 +58,9 @@ export async function runChat(ctx: RunContext, prompt: string): Promise<void> { : undefined // Resolve tools from MCP servers, optionally wrapped in Code Mode. - const mcpSpecs = Array.isArray(ctx.options.mcp) ? (ctx.options.mcp as Array<string>) : [] + const mcpSpecs = Array.isArray(ctx.options.mcp) + ? (ctx.options.mcp as Array<string>) + : [] const useCodeMode = Boolean(ctx.options.codeMode) const clients = await buildMcpClients(mcpSpecs) diff --git a/packages/ai-cli/src/cli/activities/image.ts b/packages/ai-cli/src/cli/activities/image.ts index 91d0c064f..177c1a9bc 100644 --- a/packages/ai-cli/src/cli/activities/image.ts +++ b/packages/ai-cli/src/cli/activities/image.ts @@ -27,9 +27,8 @@ const EXT_BY_MIME: Record<string, string> = { /** `ts-ai image` handler. */ export async function runImage(ctx: RunContext, prompt: string): Promise<void> { - const { resolved, apiKey, adapterConfig, modelOptions } = resolveAdapterContext( - ctx.options, - ) + const { resolved, apiKey, adapterConfig, modelOptions } = + resolveAdapterContext(ctx.options) const adapter = await instantiateAdapter({ resolved, activity: 'image', @@ -37,7 +36,9 @@ export async function runImage(ctx: RunContext, prompt: string): Promise<void> { config: adapterConfig, }) - ctx.logger.info(`Generating image with ${resolved.provider}/${resolved.model}…`) + ctx.logger.info( + `Generating image with ${resolved.provider}/${resolved.model}…`, + ) const result = (await generateImage({ // The CLI resolves adapters at runtime; the static generic shape is erased. @@ -50,7 +51,11 @@ export async function runImage(ctx: RunContext, prompt: string): Promise<void> { })) as ImageResultLike const output = stringValue(ctx.options.output) - const written: Array<{ path: string | null; mimeType: string; revisedPrompt?: string }> = [] + const written: Array<{ + path: string | null + mimeType: string + revisedPrompt?: string + }> = [] for (const [index, image] of result.images.entries()) { const bytes = await mediaSourceToBytes(image) @@ -58,15 +63,25 @@ export async function runImage(ctx: RunContext, prompt: string): Promise<void> { const ext = EXT_BY_MIME[mimeType] ?? 'png' // Only the first image honors an explicit -o; subsequent ones get a suffix. const target = - index === 0 ? output : output && output !== '-' ? suffixPath(output, index) : undefined - const path = await writeArtifact('image', { bytes, ext, mimeType }, target, ctx.now + index) + index === 0 + ? output + : output && output !== '-' + ? suffixPath(output, index) + : undefined + const path = await writeArtifact( + 'image', + { bytes, ext, mimeType }, + target, + ctx.now + index, + ) written.push({ path, mimeType, revisedPrompt: image.revisedPrompt }) } if (ctx.mode === 'pretty') { const previewable: Array<{ path: string; revisedPrompt?: string }> = [] for (const w of written) { - if (w.path) previewable.push({ path: w.path, revisedPrompt: w.revisedPrompt }) + if (w.path) + previewable.push({ path: w.path, revisedPrompt: w.revisedPrompt }) } await renderImageResult({ model: result.model, diff --git a/packages/ai-cli/src/cli/activities/speech.ts b/packages/ai-cli/src/cli/activities/speech.ts index 0a9b9e659..a8af10379 100644 --- a/packages/ai-cli/src/cli/activities/speech.ts +++ b/packages/ai-cli/src/cli/activities/speech.ts @@ -18,9 +18,8 @@ type SpeechFormat = 'mp3' | 'opus' | 'aac' | 'flac' | 'wav' | 'pcm' /** `ts-ai speech` (text-to-speech) handler. */ export async function runSpeech(ctx: RunContext, text: string): Promise<void> { - const { resolved, apiKey, adapterConfig, modelOptions } = resolveAdapterContext( - ctx.options, - ) + const { resolved, apiKey, adapterConfig, modelOptions } = + resolveAdapterContext(ctx.options) const adapter = await instantiateAdapter({ resolved, activity: 'speech', @@ -28,7 +27,9 @@ export async function runSpeech(ctx: RunContext, text: string): Promise<void> { config: adapterConfig, }) - ctx.logger.info(`Synthesizing speech with ${resolved.provider}/${resolved.model}…`) + ctx.logger.info( + `Synthesizing speech with ${resolved.provider}/${resolved.model}…`, + ) const result = (await generateSpeech({ adapter: adapter as never, diff --git a/packages/ai-cli/src/cli/activities/summarize.ts b/packages/ai-cli/src/cli/activities/summarize.ts index 4cc732015..7634789a9 100644 --- a/packages/ai-cli/src/cli/activities/summarize.ts +++ b/packages/ai-cli/src/cli/activities/summarize.ts @@ -15,10 +15,12 @@ interface SummaryResultLike { type SummaryStyle = 'bullet-points' | 'paragraph' | 'concise' /** `ts-ai summarize` handler. */ -export async function runSummarize(ctx: RunContext, text: string): Promise<void> { - const { resolved, apiKey, adapterConfig, modelOptions } = resolveAdapterContext( - ctx.options, - ) +export async function runSummarize( + ctx: RunContext, + text: string, +): Promise<void> { + const { resolved, apiKey, adapterConfig, modelOptions } = + resolveAdapterContext(ctx.options) const adapter = await instantiateAdapter({ resolved, activity: 'summarize', diff --git a/packages/ai-cli/src/cli/activities/transcribe.ts b/packages/ai-cli/src/cli/activities/transcribe.ts index 9139634ae..754573984 100644 --- a/packages/ai-cli/src/cli/activities/transcribe.ts +++ b/packages/ai-cli/src/cli/activities/transcribe.ts @@ -28,12 +28,14 @@ export async function runTranscribe( : undefined const audioPath = positional[0] ?? attachment if (!audioPath) { - throw new CliError('USAGE', 'Provide an audio file: ts-ai transcribe ./audio.mp3') + throw new CliError( + 'USAGE', + 'Provide an audio file: ts-ai transcribe ./audio.mp3', + ) } - const { resolved, apiKey, adapterConfig, modelOptions } = resolveAdapterContext( - ctx.options, - ) + const { resolved, apiKey, adapterConfig, modelOptions } = + resolveAdapterContext(ctx.options) const adapter = await instantiateAdapter({ resolved, activity: 'transcription', @@ -47,7 +49,9 @@ export async function runTranscribe( // Buffer — so hand over base64. audio = (await readFile(audioPath)).toString('base64') } catch (cause) { - throw new CliError('USAGE', `Cannot read audio file "${audioPath}".`, { cause }) + throw new CliError('USAGE', `Cannot read audio file "${audioPath}".`, { + cause, + }) } ctx.logger.info(`Transcribing with ${resolved.provider}/${resolved.model}…`) @@ -55,7 +59,10 @@ export async function runTranscribe( const result = (await generateTranscription({ adapter: adapter as never, audio, - language: typeof ctx.options.language === 'string' ? ctx.options.language : undefined, + language: + typeof ctx.options.language === 'string' + ? ctx.options.language + : undefined, modelOptions: modelOptions as never, debug: false, })) as TranscriptionResultLike diff --git a/packages/ai-cli/src/cli/activities/video.ts b/packages/ai-cli/src/cli/activities/video.ts index 74999f20c..1e8413f08 100644 --- a/packages/ai-cli/src/cli/activities/video.ts +++ b/packages/ai-cli/src/cli/activities/video.ts @@ -15,9 +15,8 @@ const POLL_INTERVAL_MS = 3000 * `--no-wait` returns the job id immediately. */ export async function runVideo(ctx: RunContext, prompt: string): Promise<void> { - const { resolved, apiKey, adapterConfig, modelOptions } = resolveAdapterContext( - ctx.options, - ) + const { resolved, apiKey, adapterConfig, modelOptions } = + resolveAdapterContext(ctx.options) const adapter = await instantiateAdapter({ resolved, activity: 'video', @@ -25,19 +24,26 @@ export async function runVideo(ctx: RunContext, prompt: string): Promise<void> { config: adapterConfig, }) - ctx.logger.info(`Creating video job with ${resolved.provider}/${resolved.model}…`) + ctx.logger.info( + `Creating video job with ${resolved.provider}/${resolved.model}…`, + ) const job = await generateVideo({ adapter: adapter as never, prompt, - size: (typeof ctx.options.size === 'string' ? ctx.options.size : undefined) as never, + size: (typeof ctx.options.size === 'string' + ? ctx.options.size + : undefined) as never, modelOptions: modelOptions as never, debug: false, }) if (ctx.options.wait === false) { if (ctx.mode === 'pretty') { - await renderArtifactPath({ label: `Video job created (${job.model})`, path: job.jobId }) + await renderArtifactPath({ + label: `Video job created (${job.model})`, + path: job.jobId, + }) return } emitJson({ jobId: job.jobId, model: job.model, status: 'pending' }) @@ -46,25 +52,41 @@ export async function runVideo(ctx: RunContext, prompt: string): Promise<void> { const final = await pollToCompletion(ctx, adapter, job.jobId) if (final.status === 'failed' || !final.url) { - throw new CliError('PROVIDER', `Video job failed: ${final.error ?? 'no URL returned'}.`, { - provider: resolved.provider, - detail: { jobId: job.jobId }, - }) + throw new CliError( + 'PROVIDER', + `Video job failed: ${final.error ?? 'no URL returned'}.`, + { + provider: resolved.provider, + detail: { jobId: job.jobId }, + }, + ) } const bytes = await fetchBytes(final.url) - const output = typeof ctx.options.output === 'string' ? ctx.options.output : undefined - const path = await writeArtifact('video', { bytes, ext: 'mp4', mimeType: 'video/mp4' }, output, ctx.now) + const output = + typeof ctx.options.output === 'string' ? ctx.options.output : undefined + const path = await writeArtifact( + 'video', + { bytes, ext: 'mp4', mimeType: 'video/mp4' }, + output, + ctx.now, + ) if (ctx.mode === 'pretty') { - await renderArtifactPath({ label: `Video generated with ${job.model}`, path: path ?? '(stdout)' }) + await renderArtifactPath({ + label: `Video generated with ${job.model}`, + path: path ?? '(stdout)', + }) return } emitJson({ jobId: job.jobId, model: job.model, path, mimeType: 'video/mp4' }) } /** `ts-ai video status <jobId>` — one-shot status check for an existing job. */ -export async function runVideoStatus(ctx: RunContext, jobId: string): Promise<void> { +export async function runVideoStatus( + ctx: RunContext, + jobId: string, +): Promise<void> { const { resolved, apiKey, adapterConfig } = resolveAdapterContext(ctx.options) const adapter = await instantiateAdapter({ resolved, @@ -102,7 +124,8 @@ async function pollToCompletion( ctx.logger.info( `job ${jobId}: ${status.status}${status.progress != null ? ` (${status.progress}%)` : ''}`, ) - if (status.status === 'completed' || status.status === 'failed') return status + if (status.status === 'completed' || status.status === 'failed') + return status await sleep(POLL_INTERVAL_MS) } } diff --git a/packages/ai-cli/src/cli/artifact.ts b/packages/ai-cli/src/cli/artifact.ts index ffa040f12..268208231 100644 --- a/packages/ai-cli/src/cli/artifact.ts +++ b/packages/ai-cli/src/cli/artifact.ts @@ -41,7 +41,9 @@ export async function writeArtifact( try { await writeFile(path, artifact.bytes) } catch (cause) { - throw new CliError('RUNTIME', `Failed to write artifact to "${path}".`, { cause }) + throw new CliError('RUNTIME', `Failed to write artifact to "${path}".`, { + cause, + }) } return path } @@ -50,7 +52,10 @@ export async function writeArtifact( export async function fetchBytes(url: string): Promise<Uint8Array> { const res = await fetch(url) if (!res.ok) { - throw new CliError('PROVIDER', `Failed to download artifact (${res.status}) from ${url}.`) + throw new CliError( + 'PROVIDER', + `Failed to download artifact (${res.status}) from ${url}.`, + ) } return new Uint8Array(await res.arrayBuffer()) } @@ -60,7 +65,8 @@ export async function mediaSourceToBytes(source: { url?: string b64Json?: string }): Promise<Uint8Array> { - if (source.b64Json) return new Uint8Array(Buffer.from(source.b64Json, 'base64')) + if (source.b64Json) + return new Uint8Array(Buffer.from(source.b64Json, 'base64')) if (source.url) return fetchBytes(source.url) throw new CliError('PROVIDER', 'Generated media has neither url nor b64Json.') } diff --git a/packages/ai-cli/src/cli/bin.ts b/packages/ai-cli/src/cli/bin.ts index 734061f63..64cc96147 100644 --- a/packages/ai-cli/src/cli/bin.ts +++ b/packages/ai-cli/src/cli/bin.ts @@ -15,6 +15,8 @@ run(argv, pkg.version) }) .catch((err: unknown) => { // Last-resort guard; run() is expected to handle everything itself. - process.stderr.write(`error: ${err instanceof Error ? err.message : String(err)}\n`) + process.stderr.write( + `error: ${err instanceof Error ? err.message : String(err)}\n`, + ) process.exitCode = 1 }) diff --git a/packages/ai-cli/src/cli/code-mode.ts b/packages/ai-cli/src/cli/code-mode.ts index 941b97154..e2b7a2e2a 100644 --- a/packages/ai-cli/src/cli/code-mode.ts +++ b/packages/ai-cli/src/cli/code-mode.ts @@ -15,7 +15,9 @@ export interface CodeModeWiring { * * `@tanstack/ai-code-mode` and `@tanstack/ai-isolate-node` are imported lazily. */ -export async function buildCodeMode(tools: Array<unknown>): Promise<CodeModeWiring> { +export async function buildCodeMode( + tools: Array<unknown>, +): Promise<CodeModeWiring> { if (tools.length === 0) { throw new CliError( 'USAGE', @@ -32,7 +34,12 @@ export async function buildCodeMode(tools: Array<unknown>): Promise<CodeModeWiri throw new CliError( 'PROVIDER_NOT_INSTALLED', '--code-mode requires @tanstack/ai-code-mode and @tanstack/ai-isolate-node.', - { detail: { packages: ['@tanstack/ai-code-mode', '@tanstack/ai-isolate-node'] }, cause }, + { + detail: { + packages: ['@tanstack/ai-code-mode', '@tanstack/ai-isolate-node'], + }, + cause, + }, ) } diff --git a/packages/ai-cli/src/cli/context.ts b/packages/ai-cli/src/cli/context.ts index 4d99b168a..9297f553d 100644 --- a/packages/ai-cli/src/cli/context.ts +++ b/packages/ai-cli/src/cli/context.ts @@ -60,7 +60,8 @@ export function resolveAdapterContext( typeof options.modelOptions === 'object' && options.modelOptions !== null ? (options.modelOptions as Record<string, unknown>) : undefined - const baseURL = typeof options.baseURL === 'string' ? { baseURL: options.baseURL } : {} + const baseURL = + typeof options.baseURL === 'string' ? { baseURL: options.baseURL } : {} return { resolved, apiKey, diff --git a/packages/ai-cli/src/cli/interactive.ts b/packages/ai-cli/src/cli/interactive.ts index 9bed437f1..19004c1f9 100644 --- a/packages/ai-cli/src/cli/interactive.ts +++ b/packages/ai-cli/src/cli/interactive.ts @@ -1,5 +1,9 @@ import { StreamProcessor, chat } from '@tanstack/ai' -import { instantiateAdapter, resolveApiKey, resolveModelSlug } from '../core/providers' +import { + instantiateAdapter, + resolveApiKey, + resolveModelSlug, +} from '../core/providers' import { findCommand } from '../manifest/manifest' import { renderChatRepl, renderMenu } from '../render/lazy' import { dispatchCommand } from './dispatch' @@ -23,7 +27,9 @@ export async function runHome(modelOverride?: string): Promise<number> { if (choice.command === 'quit') return 0 if (choice.command === 'chat') { - return runChatRepl(modelOverride ?? DEFAULT_MODELS['chat'] ?? 'openai/gpt-5.5') + return runChatRepl( + modelOverride ?? DEFAULT_MODELS['chat'] ?? 'openai/gpt-5.5', + ) } const model = modelOverride ?? DEFAULT_MODELS[choice.command] @@ -36,7 +42,10 @@ export async function runHome(modelOverride?: string): Promise<number> { const spec = findCommand(choice.command) if (!spec) return 0 - await dispatchCommand(spec, choice.prompt ? [choice.prompt] : [], { model, preview: true }) + await dispatchCommand(spec, choice.prompt ? [choice.prompt] : [], { + model, + preview: true, + }) return 0 } @@ -63,6 +72,9 @@ export async function runChatRepl(modelSlug: string): Promise<number> { return result.content || '(no response)' } - await renderChatRepl({ model: `${resolved.provider}/${resolved.model}`, respond }) + await renderChatRepl({ + model: `${resolved.provider}/${resolved.model}`, + respond, + }) return 0 } diff --git a/packages/ai-cli/src/cli/mcp-clients.ts b/packages/ai-cli/src/cli/mcp-clients.ts index da0a103c7..720c09bff 100644 --- a/packages/ai-cli/src/cli/mcp-clients.ts +++ b/packages/ai-cli/src/cli/mcp-clients.ts @@ -17,7 +17,9 @@ export interface McpClientLike { * `@tanstack/ai-mcp` is imported lazily so the machine path that doesn't use * tools never loads it. */ -export async function buildMcpClients(specs: Array<string>): Promise<Array<McpClientLike>> { +export async function buildMcpClients( + specs: Array<string>, +): Promise<Array<McpClientLike>> { if (specs.length === 0) return [] let mcp: typeof McpModule @@ -35,7 +37,10 @@ export async function buildMcpClients(specs: Array<string>): Promise<Array<McpCl const clients: Array<McpClientLike> = [] for (const spec of specs) { - const httpTransport: { type: 'http'; url: string } = { type: 'http', url: spec } + const httpTransport: { type: 'http'; url: string } = { + type: 'http', + url: spec, + } const transport = isUrl(spec) ? httpTransport : stdio.stdioTransport(parseCommand(spec)) @@ -43,7 +48,11 @@ export async function buildMcpClients(specs: Array<string>): Promise<Array<McpCl const client = await mcp.createMCPClient({ transport }) clients.push(client) } catch (cause) { - throw new CliError('RUNTIME', `Failed to connect to MCP server "${spec}".`, { cause }) + throw new CliError( + 'RUNTIME', + `Failed to connect to MCP server "${spec}".`, + { cause }, + ) } } return clients diff --git a/packages/ai-cli/src/cli/mcp.ts b/packages/ai-cli/src/cli/mcp.ts index 3c18eff80..6a6f29304 100644 --- a/packages/ai-cli/src/cli/mcp.ts +++ b/packages/ai-cli/src/cli/mcp.ts @@ -62,7 +62,9 @@ function invokeSelf( if (args.prompt) argv.push('--', args.prompt) return new Promise((resolve, reject) => { - const child = spawn(process.execPath, argv, { stdio: ['ignore', 'pipe', 'pipe'] }) + const child = spawn(process.execPath, argv, { + stdio: ['ignore', 'pipe', 'pipe'], + }) let stdout = '' let stderr = '' child.stdout.on('data', (chunk) => (stdout += String(chunk))) diff --git a/packages/ai-cli/src/cli/options.ts b/packages/ai-cli/src/cli/options.ts index ed11b3295..305dfd164 100644 --- a/packages/ai-cli/src/cli/options.ts +++ b/packages/ai-cli/src/cli/options.ts @@ -36,7 +36,10 @@ function coerceValue(flag: FlagSpec, value: unknown): unknown { case 'number': { const n = Number(value) if (Number.isNaN(n)) { - throw new CliError('USAGE', `--${flag.name} must be a number, got "${String(value)}".`) + throw new CliError( + 'USAGE', + `--${flag.name} must be a number, got "${String(value)}".`, + ) } return n } diff --git a/packages/ai-cli/src/cli/program.ts b/packages/ai-cli/src/cli/program.ts index 8d3b3fd21..eb7637b47 100644 --- a/packages/ai-cli/src/cli/program.ts +++ b/packages/ai-cli/src/cli/program.ts @@ -10,7 +10,9 @@ export function buildProgram(cliVersion: string): Command { const program = new Command() program .name('ts-ai') - .description('Type-safe CLI over TanStack AI — chat, image, video, audio, speech, transcribe, summarize.') + .description( + 'Type-safe CLI over TanStack AI — chat, image, video, audio, speech, transcribe, summarize.', + ) .version(cliVersion, '--version') .showHelpAfterError() @@ -45,15 +47,21 @@ function registerGenerationCommand(program: Command, spec: CommandSpec): void { for (const flag of [...COMMON_FLAGS, ...spec.flags]) applyFlag(cmd, flag) - cmd.action(async (positional: Array<string> | string | undefined, _opts, command: Command) => { - const raw = coerceFlags(spec, command.opts()) - const args = Array.isArray(positional) - ? positional - : positional - ? [positional] - : [] - await dispatchCommand(spec, args, raw) - }) + cmd.action( + async ( + positional: Array<string> | string | undefined, + _opts, + command: Command, + ) => { + const raw = coerceFlags(spec, command.opts()) + const args = Array.isArray(positional) + ? positional + : positional + ? [positional] + : [] + await dispatchCommand(spec, args, raw) + }, + ) // `ts-ai video status <jobId>` — poll an existing job. if (spec.name === 'video') { @@ -76,7 +84,9 @@ function registerGenerationCommand(program: Command, spec: CommandSpec): void { function registerIntrospect(program: Command, cliVersion: string): void { program .command('introspect') - .description('Print a machine-readable manifest of the entire CLI surface as JSON.') + .description( + 'Print a machine-readable manifest of the entire CLI surface as JSON.', + ) .action(() => runIntrospect(cliVersion)) } @@ -111,13 +121,15 @@ function applyFlag(cmd: Command, flag: FlagSpec): void { const long = `--${kebab}` const namePart = flag.short ? `-${flag.short}, ${long}` : long - const flagStr = - flag.type === 'boolean' ? namePart : `${namePart} <value>` + const flagStr = flag.type === 'boolean' ? namePart : `${namePart} <value>` const option = new Option(flagStr, flag.description) if (flag.hidden) option.hideHelp() if (flag.repeatable || flag.type === 'string[]') { - option.argParser((value: string, previous: Array<string> = []) => [...previous, value]) + option.argParser((value: string, previous: Array<string> = []) => [ + ...previous, + value, + ]) option.default([]) } cmd.addOption(option) diff --git a/packages/ai-cli/src/cli/run.ts b/packages/ai-cli/src/cli/run.ts index 5ee0236aa..da63e4c73 100644 --- a/packages/ai-cli/src/cli/run.ts +++ b/packages/ai-cli/src/cli/run.ts @@ -10,7 +10,10 @@ import type { ExitCodeValue } from '../core/exit-codes' * errors are funneled through CliError so the exit code and (in machine mode) * the structured stdout error object are consistent. */ -export async function run(argv: Array<string>, cliVersion: string): Promise<ExitCodeValue> { +export async function run( + argv: Array<string>, + cliVersion: string, +): Promise<ExitCodeValue> { const program = buildProgram(cliVersion) // Take control of exits so commander's own usage/help/version paths don't // call process.exit out from under us. diff --git a/packages/ai-cli/src/cli/update.ts b/packages/ai-cli/src/cli/update.ts index 165fc5e00..c8a2b9c8a 100644 --- a/packages/ai-cli/src/cli/update.ts +++ b/packages/ai-cli/src/cli/update.ts @@ -22,7 +22,10 @@ export async function runUpdate(): Promise<void> { process.stderr.write(`Updating ${PKG} via: ${cmd} ${args.join(' ')}\n`) const status = await runProcess(cmd, args) if (status !== 0) { - throw new CliError('RUNTIME', `Update failed (${cmd} exited with ${status ?? 'signal'}).`) + throw new CliError( + 'RUNTIME', + `Update failed (${cmd} exited with ${status ?? 'signal'}).`, + ) } } @@ -39,13 +42,16 @@ function runProcess(cmd: string, args: Array<string>): Promise<number | null> { function isOnDemand(): boolean { const execPath = process.env.npm_execpath ?? '' - return execPath.includes('_npx') || (process.env.npm_command === 'exec') + return execPath.includes('_npx') || process.env.npm_command === 'exec' } function upgradeCommand(agent: string): { cmd: string; args: Array<string> } { const target = `${PKG}@latest` - if (agent.startsWith('pnpm')) return { cmd: 'pnpm', args: ['add', '-g', target] } - if (agent.startsWith('yarn')) return { cmd: 'yarn', args: ['global', 'add', target] } - if (agent.startsWith('bun')) return { cmd: 'bun', args: ['add', '-g', target] } + if (agent.startsWith('pnpm')) + return { cmd: 'pnpm', args: ['add', '-g', target] } + if (agent.startsWith('yarn')) + return { cmd: 'yarn', args: ['global', 'add', target] } + if (agent.startsWith('bun')) + return { cmd: 'bun', args: ['add', '-g', target] } return { cmd: 'npm', args: ['install', '-g', target] } } diff --git a/packages/ai-cli/src/core/exit-codes.ts b/packages/ai-cli/src/core/exit-codes.ts index 584bccf96..2f668e695 100644 --- a/packages/ai-cli/src/core/exit-codes.ts +++ b/packages/ai-cli/src/core/exit-codes.ts @@ -46,9 +46,16 @@ export class CliError extends Error { constructor( code: CliErrorCode, message: string, - options?: { provider?: string; detail?: Record<string, unknown>; cause?: unknown }, + options?: { + provider?: string + detail?: Record<string, unknown> + cause?: unknown + }, ) { - super(message, options?.cause === undefined ? undefined : { cause: options.cause }) + super( + message, + options?.cause === undefined ? undefined : { cause: options.cause }, + ) this.name = 'CliError' this.code = code this.provider = options?.provider diff --git a/packages/ai-cli/src/core/io.ts b/packages/ai-cli/src/core/io.ts index b4d21079a..f02627c16 100644 --- a/packages/ai-cli/src/core/io.ts +++ b/packages/ai-cli/src/core/io.ts @@ -37,7 +37,10 @@ export async function resolvePrompt( const fromStdin = (await readStdin()).trim() if (!fromStdin && options.required) { - throw new CliError('USAGE', 'No prompt provided. Pass it as arguments or pipe via stdin.') + throw new CliError( + 'USAGE', + 'No prompt provided. Pass it as arguments or pipe via stdin.', + ) } return fromStdin } @@ -85,11 +88,14 @@ export async function loadAttachments( : await readFile(path) out.push({ path, - mimeType: path === '-' ? 'application/octet-stream' : inferMimeType(path), + mimeType: + path === '-' ? 'application/octet-stream' : inferMimeType(path), data: buffer.toString('base64'), }) } catch (cause) { - throw new CliError('USAGE', `Cannot read attachment "${path}".`, { cause }) + throw new CliError('USAGE', `Cannot read attachment "${path}".`, { + cause, + }) } } return out diff --git a/packages/ai-cli/src/index.ts b/packages/ai-cli/src/index.ts index 1f6e5c34c..b39b83be8 100644 --- a/packages/ai-cli/src/index.ts +++ b/packages/ai-cli/src/index.ts @@ -5,7 +5,13 @@ * module exports the declarative manifest and supporting types so tooling can * introspect the CLI surface without spawning the binary. */ -export { buildManifest, MANIFEST_VERSION, COMMANDS, COMMON_FLAGS, findCommand } from './manifest/manifest' +export { + buildManifest, + MANIFEST_VERSION, + COMMANDS, + COMMON_FLAGS, + findCommand, +} from './manifest/manifest' export type { Activity, CliManifest, diff --git a/packages/ai-cli/src/manifest/manifest.ts b/packages/ai-cli/src/manifest/manifest.ts index 3e79fda2a..9d858f15f 100644 --- a/packages/ai-cli/src/manifest/manifest.ts +++ b/packages/ai-cli/src/manifest/manifest.ts @@ -12,7 +12,11 @@ export const COMMON_FLAGS: Array<FlagSpec> = [ type: 'string', description: 'Model as a "provider/model" slug, e.g. openai/gpt-5.5.', }, - { name: 'apiKey', type: 'string', description: 'API key (overrides env vars).' }, + { + name: 'apiKey', + type: 'string', + description: 'API key (overrides env vars).', + }, { name: 'json', type: 'boolean', @@ -33,15 +37,24 @@ export const COMMON_FLAGS: Array<FlagSpec> = [ name: 'preview', type: 'boolean', default: true, - description: 'Inline-preview artifacts in a capable terminal (use --no-preview to disable).', + description: + 'Inline-preview artifacts in a capable terminal (use --no-preview to disable).', }, { name: 'config', type: 'json', description: 'Options as a JSON file path or inline JSON string.', }, - { name: 'verbose', type: 'boolean', description: 'Verbose debug logging to stderr.' }, - { name: 'quiet', type: 'boolean', description: 'Suppress non-error stderr output.' }, + { + name: 'verbose', + type: 'boolean', + description: 'Verbose debug logging to stderr.', + }, + { + name: 'quiet', + type: 'boolean', + description: 'Suppress non-error stderr output.', + }, ] const ATTACHMENT_FLAG: FlagSpec = { @@ -54,19 +67,29 @@ const ATTACHMENT_FLAG: FlagSpec = { export const COMMANDS: Array<CommandSpec> = [ { name: 'chat', - description: 'Chat / agentic text generation with optional tools and structured output.', + description: + 'Chat / agentic text generation with optional tools and structured output.', activity: 'chat', acceptsPrompt: true, producesArtifact: false, flags: [ ATTACHMENT_FLAG, - { name: 'system', type: 'string', description: 'System prompt (text or file path).' }, + { + name: 'system', + type: 'string', + description: 'System prompt (text or file path).', + }, { name: 'messages', type: 'json', - description: 'Full message history as a JSON array (stateless multi-turn).', + description: + 'Full message history as a JSON array (stateless multi-turn).', + }, + { + name: 'threadId', + type: 'string', + description: 'Correlation id passed through to telemetry/AG-UI.', }, - { name: 'threadId', type: 'string', description: 'Correlation id passed through to telemetry/AG-UI.' }, { name: 'maxSteps', type: 'number', @@ -78,11 +101,16 @@ export const COMMANDS: Array<CommandSpec> = [ repeatable: true, description: 'MCP server (command or URL) exposing tools (repeatable).', }, - { name: 'codeMode', type: 'boolean', description: 'Enable the sandboxed execute_typescript tool.' }, + { + name: 'codeMode', + type: 'boolean', + description: 'Enable the sandboxed execute_typescript tool.', + }, { name: 'schema', type: 'json', - description: 'JSON Schema for structured output (file path or inline). Result is under .data.', + description: + 'JSON Schema for structured output (file path or inline). Result is under .data.', }, ], }, @@ -94,13 +122,23 @@ export const COMMANDS: Array<CommandSpec> = [ producesArtifact: true, flags: [ ATTACHMENT_FLAG, - { name: 'size', type: 'string', description: 'Output size, e.g. 1024x1024.' }, - { name: 'count', type: 'number', default: 1, description: 'Number of images to generate.' }, + { + name: 'size', + type: 'string', + description: 'Output size, e.g. 1024x1024.', + }, + { + name: 'count', + type: 'number', + default: 1, + description: 'Number of images to generate.', + }, ], }, { name: 'video', - description: 'Generate a video from a prompt (async job; blocks until done by default).', + description: + 'Generate a video from a prompt (async job; blocks until done by default).', activity: 'video', acceptsPrompt: true, producesArtifact: true, @@ -111,9 +149,14 @@ export const COMMANDS: Array<CommandSpec> = [ name: 'wait', type: 'boolean', default: true, - description: 'Poll until the job completes (use --no-wait to return the job id immediately).', + description: + 'Poll until the job completes (use --no-wait to return the job id immediately).', + }, + { + name: 'size', + type: 'string', + description: 'Output size / resolution.', }, - { name: 'size', type: 'string', description: 'Output size / resolution.' }, ], }, { @@ -122,7 +165,13 @@ export const COMMANDS: Array<CommandSpec> = [ activity: 'audio', acceptsPrompt: true, producesArtifact: true, - flags: [{ name: 'duration', type: 'number', description: 'Desired duration in seconds.' }], + flags: [ + { + name: 'duration', + type: 'number', + description: 'Desired duration in seconds.', + }, + ], }, { name: 'speech', @@ -133,8 +182,16 @@ export const COMMANDS: Array<CommandSpec> = [ producesArtifact: true, flags: [ { name: 'voice', type: 'string', description: 'Voice id.' }, - { name: 'format', type: 'string', description: 'Audio format: mp3, opus, aac, flac, wav, pcm.' }, - { name: 'speed', type: 'number', description: 'Playback speed 0.25–4.0.' }, + { + name: 'format', + type: 'string', + description: 'Audio format: mp3, opus, aac, flac, wav, pcm.', + }, + { + name: 'speed', + type: 'number', + description: 'Playback speed 0.25–4.0.', + }, ], }, { @@ -146,7 +203,11 @@ export const COMMANDS: Array<CommandSpec> = [ producesArtifact: false, flags: [ ATTACHMENT_FLAG, - { name: 'language', type: 'string', description: 'ISO-639-1 language hint, e.g. en.' }, + { + name: 'language', + type: 'string', + description: 'ISO-639-1 language hint, e.g. en.', + }, ], }, { @@ -156,13 +217,22 @@ export const COMMANDS: Array<CommandSpec> = [ acceptsPrompt: true, producesArtifact: false, flags: [ - { name: 'maxLength', type: 'number', description: 'Maximum summary length.' }, + { + name: 'maxLength', + type: 'number', + description: 'Maximum summary length.', + }, { name: 'style', type: 'string', description: 'Summary style: bullet-points, paragraph, concise.', }, - { name: 'focus', type: 'string[]', repeatable: true, description: 'Topic to focus on (repeatable).' }, + { + name: 'focus', + type: 'string[]', + repeatable: true, + description: 'Topic to focus on (repeatable).', + }, ], }, ] @@ -176,7 +246,10 @@ export function toKebabFlag(name: string): string { function withFlagSpelling(flag: FlagSpec): FlagSpec { const kebab = toKebabFlag(flag.name) // Default-true booleans are negatable flags (`--no-x`). - const spelling = flag.type === 'boolean' && flag.default === true ? `--no-${kebab}` : `--${kebab}` + const spelling = + flag.type === 'boolean' && flag.default === true + ? `--no-${kebab}` + : `--${kebab}` return { ...flag, flag: spelling } } @@ -188,7 +261,10 @@ export function buildManifest(cliVersion: string): CliManifest { cliVersion, bundledProviders: bundledProviders(), commonFlags: COMMON_FLAGS.map(withFlagSpelling), - commands: COMMANDS.map((c) => ({ ...c, flags: c.flags.map(withFlagSpelling) })), + commands: COMMANDS.map((c) => ({ + ...c, + flags: c.flags.map(withFlagSpelling), + })), exitCodes: { success: ExitCode.Success, runtime: ExitCode.Runtime, diff --git a/packages/ai-cli/src/render/ink.tsx b/packages/ai-cli/src/render/ink.tsx index d667e2073..c1dca6d21 100644 --- a/packages/ai-cli/src/render/ink.tsx +++ b/packages/ai-cli/src/render/ink.tsx @@ -34,7 +34,9 @@ export async function renderImageResultInk(input: { <Box key={image.path} flexDirection="column"> {previews[index] ? <Text>{previews[index]}</Text> : null} <Text dimColor>{image.path}</Text> - {image.revisedPrompt ? <Text dimColor>“{image.revisedPrompt}”</Text> : null} + {image.revisedPrompt ? ( + <Text dimColor>“{image.revisedPrompt}”</Text> + ) : null} </Box> ))} </Box>, diff --git a/packages/ai-cli/src/render/menu.tsx b/packages/ai-cli/src/render/menu.tsx index eaf7bc807..7c7972ecd 100644 --- a/packages/ai-cli/src/render/menu.tsx +++ b/packages/ai-cli/src/render/menu.tsx @@ -18,18 +18,60 @@ interface MenuItem { } const ITEMS: Array<MenuItem> = [ - { command: 'chat', label: 'Chat', hint: 'Interactive agentic chat', needsPrompt: false }, - { command: 'image', label: 'Image', hint: 'Generate an image', needsPrompt: true }, - { command: 'video', label: 'Video', hint: 'Generate a video', needsPrompt: true }, - { command: 'audio', label: 'Audio', hint: 'Generate music / sfx', needsPrompt: true }, - { command: 'speech', label: 'Speech', hint: 'Text to speech', needsPrompt: true }, - { command: 'summarize', label: 'Summarize', hint: 'Summarize text', needsPrompt: true }, - { command: 'transcribe', label: 'Transcribe', hint: 'Audio file to text', needsPrompt: true }, + { + command: 'chat', + label: 'Chat', + hint: 'Interactive agentic chat', + needsPrompt: false, + }, + { + command: 'image', + label: 'Image', + hint: 'Generate an image', + needsPrompt: true, + }, + { + command: 'video', + label: 'Video', + hint: 'Generate a video', + needsPrompt: true, + }, + { + command: 'audio', + label: 'Audio', + hint: 'Generate music / sfx', + needsPrompt: true, + }, + { + command: 'speech', + label: 'Speech', + hint: 'Text to speech', + needsPrompt: true, + }, + { + command: 'summarize', + label: 'Summarize', + hint: 'Summarize text', + needsPrompt: true, + }, + { + command: 'transcribe', + label: 'Transcribe', + hint: 'Audio file to text', + needsPrompt: true, + }, { command: 'quit', label: 'Quit', hint: 'Exit', needsPrompt: false }, ] const TITLE = 'TANSTACK AI' -const GRADIENT = ['cyan', 'cyanBright', 'blueBright', 'magenta', 'magentaBright', 'blueBright'] +const GRADIENT = [ + 'cyan', + 'cyanBright', + 'blueBright', + 'magenta', + 'magentaBright', + 'blueBright', +] /** Animated wordmark: a gradient sweeps across the letters. */ function Title() { diff --git a/packages/ai-cli/src/render/repl.tsx b/packages/ai-cli/src/render/repl.tsx index 39fdb38ac..5e42c8fcc 100644 --- a/packages/ai-cli/src/render/repl.tsx +++ b/packages/ai-cli/src/render/repl.tsx @@ -66,9 +66,7 @@ function Repl({ return ( <Box flexDirection="column"> - <Text dimColor> - chat · {model} · /clear to reset · /exit to quit - </Text> + <Text dimColor>chat · {model} · /clear to reset · /exit to quit</Text> <Box flexDirection="column" marginTop={1}> {messages.map((m, i) => ( <Box key={i} marginBottom={1} flexDirection="column"> @@ -100,6 +98,8 @@ export async function runChatReplInk(input: { model: string respond: (messages: Array<ReplMessage>) => Promise<string> }): Promise<void> { - const { waitUntilExit } = render(<Repl model={input.model} respond={input.respond} />) + const { waitUntilExit } = render( + <Repl model={input.model} respond={input.respond} />, + ) await waitUntilExit() } diff --git a/packages/ai-cli/tests/core.test.ts b/packages/ai-cli/tests/core.test.ts index 319692b9f..bc2b3bd9b 100644 --- a/packages/ai-cli/tests/core.test.ts +++ b/packages/ai-cli/tests/core.test.ts @@ -30,7 +30,9 @@ describe('resolveModelSlug', () => { }) it('rejects a bare model it cannot infer', () => { - expect(() => resolveModelSlug('mystery-model')).toThrowError(/provider\/model/) + expect(() => resolveModelSlug('mystery-model')).toThrowError( + /provider\/model/, + ) }) it('rejects a slug with an empty model', () => { @@ -48,8 +50,15 @@ describe('resolveModelSlug', () => { }) describe('instantiateAdapter factory resolution (no network — just constructs adapters)', () => { - const make = (slug: string, activity: Parameters<typeof instantiateAdapter>[0]['activity']) => - instantiateAdapter({ resolved: resolveModelSlug(slug), activity, apiKey: 'test-key' }) + const make = ( + slug: string, + activity: Parameters<typeof instantiateAdapter>[0]['activity'], + ) => + instantiateAdapter({ + resolved: resolveModelSlug(slug), + activity, + apiKey: 'test-key', + }) it('resolves openai chat via the create*Chat factory', async () => { expect(await make('openai/gpt-5.5', 'chat')).toBeTruthy() @@ -67,13 +76,17 @@ describe('instantiateAdapter factory resolution (no network — just constructs }) it('throws PROVIDER_NOT_INSTALLED for a non-bundled provider', async () => { - await expect(make('groq/llama-3.3-70b-versatile', 'chat')).rejects.toMatchObject({ + await expect( + make('groq/llama-3.3-70b-versatile', 'chat'), + ).rejects.toMatchObject({ code: 'PROVIDER_NOT_INSTALLED', }) }) it('throws USAGE when a provider does not support an activity', async () => { - await expect(make('fal/fal-ai/flux/dev', 'chat')).rejects.toMatchObject({ code: 'USAGE' }) + await expect(make('fal/fal-ai/flux/dev', 'chat')).rejects.toMatchObject({ + code: 'USAGE', + }) }) }) @@ -81,11 +94,15 @@ describe('resolveApiKey', () => { const { entry } = resolveModelSlug('openai/gpt-5.5') it('prefers the explicit key', () => { - expect(resolveApiKey(entry, 'openai', 'sk-explicit', {})).toBe('sk-explicit') + expect(resolveApiKey(entry, 'openai', 'sk-explicit', {})).toBe( + 'sk-explicit', + ) }) it('falls back to the conventional env var', () => { - expect(resolveApiKey(entry, 'openai', undefined, { OPENAI_API_KEY: 'sk-env' })).toBe('sk-env') + expect( + resolveApiKey(entry, 'openai', undefined, { OPENAI_API_KEY: 'sk-env' }), + ).toBe('sk-env') }) it('throws a USAGE error when no key is available', () => { @@ -113,7 +130,11 @@ describe('mergeOptions', () => { { model: 'openai/gpt-5.5', size: undefined }, { model: 'anthropic/x', size: '1024x1024', extra: true }, ) - expect(merged).toEqual({ model: 'openai/gpt-5.5', size: '1024x1024', extra: true }) + expect(merged).toEqual({ + model: 'openai/gpt-5.5', + size: '1024x1024', + extra: true, + }) }) }) @@ -137,19 +158,27 @@ describe('coerceFlags', () => { const spec = findCommand('image') as CommandSpec it('coerces numbers and leaves config/strings alone', () => { - const out = coerceFlags(spec, { count: '3', model: 'openai/gpt-image-1', config: '{"a":1}' }) + const out = coerceFlags(spec, { + count: '3', + model: 'openai/gpt-image-1', + config: '{"a":1}', + }) expect(out.count).toBe(3) expect(out.model).toBe('openai/gpt-image-1') expect(out.config).toBe('{"a":1}') }) it('throws on a non-numeric number flag', () => { - expect(() => coerceFlags(spec, { count: 'abc' })).toThrowError(/must be a number/) + expect(() => coerceFlags(spec, { count: 'abc' })).toThrowError( + /must be a number/, + ) }) it('parses json flags from the chat command', () => { const chatSpec = findCommand('chat') as CommandSpec - const out = coerceFlags(chatSpec, { messages: '[{"role":"user","content":"hi"}]' }) + const out = coerceFlags(chatSpec, { + messages: '[{"role":"user","content":"hi"}]', + }) expect(Array.isArray(out.messages)).toBe(true) }) }) @@ -157,7 +186,9 @@ describe('coerceFlags', () => { describe('exit codes', () => { it('maps error codes to exit codes', () => { expect(new CliError('USAGE', 'x').exitCode).toBe(ExitCode.Usage) - expect(new CliError('PROVIDER_NOT_INSTALLED', 'x').exitCode).toBe(ExitCode.ProviderNotInstalled) + expect(new CliError('PROVIDER_NOT_INSTALLED', 'x').exitCode).toBe( + ExitCode.ProviderNotInstalled, + ) expect(new CliError('PROVIDER', 'x').exitCode).toBe(ExitCode.Provider) }) @@ -168,8 +199,14 @@ describe('exit codes', () => { }) it('serializes a structured error object', () => { - const obj = new CliError('PROVIDER', 'nope', { provider: 'openai' }).toErrorObject() - expect(obj).toMatchObject({ code: 'PROVIDER', message: 'nope', provider: 'openai' }) + const obj = new CliError('PROVIDER', 'nope', { + provider: 'openai', + }).toErrorObject() + expect(obj).toMatchObject({ + code: 'PROVIDER', + message: 'nope', + provider: 'openai', + }) }) }) @@ -207,10 +244,16 @@ describe('tokenizeCommand (--mcp stdio spec)', () => { 'C:\\Program Files\\srv.js', '--flag', ]) - expect(tokenizeCommand("node '/opt/my srv/x.js'")).toEqual(['node', '/opt/my srv/x.js']) + expect(tokenizeCommand("node '/opt/my srv/x.js'")).toEqual([ + 'node', + '/opt/my srv/x.js', + ]) }) it('collapses runs of whitespace', () => { - expect(tokenizeCommand(' node server.js ')).toEqual(['node', 'server.js']) + expect(tokenizeCommand(' node server.js ')).toEqual([ + 'node', + 'server.js', + ]) }) }) diff --git a/packages/ai-cli/vite.config.ts b/packages/ai-cli/vite.config.ts index 3ea9c7207..7c1cbf317 100644 --- a/packages/ai-cli/vite.config.ts +++ b/packages/ai-cli/vite.config.ts @@ -15,7 +15,12 @@ const config = defineConfig({ include: ['src/**/*.ts'], // The bin entry, Ink render layer, and pure type modules are exercised by // the testing/cli subprocess suite, not unit-covered here. - exclude: ['src/cli/**', 'src/render/**', '**/*.test.ts', 'src/**/types.ts'], + exclude: [ + 'src/cli/**', + 'src/render/**', + '**/*.test.ts', + 'src/**/types.ts', + ], }, }, }) diff --git a/testing/cli/tests/cli.spec.ts b/testing/cli/tests/cli.spec.ts index 9ac652ba5..a17d92321 100644 --- a/testing/cli/tests/cli.spec.ts +++ b/testing/cli/tests/cli.spec.ts @@ -17,7 +17,10 @@ interface RunResult { * scrubbed env so no real provider key leaks in. Returns the captured streams * and exit code. */ -function runCli(args: Array<string>, env: Record<string, string> = {}): Promise<RunResult> { +function runCli( + args: Array<string>, + env: Record<string, string> = {}, +): Promise<RunResult> { return new Promise((resolve, reject) => { const child = spawn(process.execPath, [BIN, ...args], { stdio: ['ignore', 'pipe', 'pipe'], @@ -45,7 +48,13 @@ describe('ts-ai meta commands', () => { const manifest = JSON.parse(stdout) expect(manifest.bin).toBe('ts-ai') expect(manifest.bundledProviders).toEqual( - expect.arrayContaining(['openai', 'anthropic', 'gemini', 'openrouter', 'fal']), + expect.arrayContaining([ + 'openai', + 'anthropic', + 'gemini', + 'openrouter', + 'fal', + ]), ) const names = manifest.commands.map((c: { name: string }) => c.name) expect(names).toEqual( @@ -70,7 +79,13 @@ describe('ts-ai machine-mode error contract', () => { }) it('rejects an unknown provider with a USAGE error', async () => { - const { code, stdout } = await runCli(['chat', 'hi', '--model', 'bogus/x', '--json']) + const { code, stdout } = await runCli([ + 'chat', + 'hi', + '--model', + 'bogus/x', + '--json', + ]) expect(code).toBe(2) expect(JSON.parse(stdout).error.code).toBe('USAGE') }) @@ -84,7 +99,10 @@ describe('ts-ai machine-mode error contract', () => { '--json', ]) expect(code).toBe(2) - expect(JSON.parse(stdout).error).toMatchObject({ code: 'USAGE', provider: 'openai' }) + expect(JSON.parse(stdout).error).toMatchObject({ + code: 'USAGE', + provider: 'openai', + }) }) it('keeps stdout free of human chatter in --json mode', async () => { @@ -145,7 +163,13 @@ describe('ts-ai argv-injection guard', () => { }) it('does not let a prompt smuggle --api-key', async () => { - const { code, stdout } = await runCli(['chat', '--json', '--', '--api-key', 'LEAKED']) + const { code, stdout } = await runCli([ + 'chat', + '--json', + '--', + '--api-key', + 'LEAKED', + ]) expect(code).toBe(2) expect(JSON.parse(stdout).error.code).toBe('USAGE') }) @@ -155,12 +179,20 @@ describe('ts-ai introspect flag spelling', () => { it('emits kebab-cased CLI flag strings for multi-word options', async () => { const { stdout } = await runCli(['introspect']) const manifest = JSON.parse(stdout) - const apiKey = manifest.commonFlags.find((f: { name: string }) => f.name === 'apiKey') + const apiKey = manifest.commonFlags.find( + (f: { name: string }) => f.name === 'apiKey', + ) expect(apiKey.flag).toBe('--api-key') - const chat = manifest.commands.find((c: { name: string }) => c.name === 'chat') - const maxSteps = chat.flags.find((f: { name: string }) => f.name === 'maxSteps') + const chat = manifest.commands.find( + (c: { name: string }) => c.name === 'chat', + ) + const maxSteps = chat.flags.find( + (f: { name: string }) => f.name === 'maxSteps', + ) expect(maxSteps.flag).toBe('--max-steps') - const video = manifest.commands.find((c: { name: string }) => c.name === 'video') + const video = manifest.commands.find( + (c: { name: string }) => c.name === 'video', + ) const wait = video.flags.find((f: { name: string }) => f.name === 'wait') // default-true booleans render as negatable --no-x flags. expect(wait.flag).toBe('--no-wait') diff --git a/testing/cli/tests/mcp.spec.ts b/testing/cli/tests/mcp.spec.ts index 4a63b3e86..d49d55894 100644 --- a/testing/cli/tests/mcp.spec.ts +++ b/testing/cli/tests/mcp.spec.ts @@ -29,7 +29,9 @@ afterAll(async () => { await client?.close() }) -function toolResultJson(res: { content?: Array<{ type: string; text?: string }> }): any { +function toolResultJson(res: { + content?: Array<{ type: string; text?: string }> +}): any { const text = res.content?.find((c) => c.type === 'text')?.text ?? '' return JSON.parse(text) } @@ -39,7 +41,15 @@ describe('ts-ai mcp server', () => { const { tools } = await client.listTools() const names = tools.map((t) => t.name).sort() expect(names).toEqual( - ['audio', 'chat', 'image', 'speech', 'summarize', 'transcribe', 'video'].sort(), + [ + 'audio', + 'chat', + 'image', + 'speech', + 'summarize', + 'transcribe', + 'video', + ].sort(), ) }) From efb4c54bcd36af385f2f5f0f164586eb6065083a Mon Sep 17 00:00:00 2001 From: Alem Tuzlak <t.zlak@hotmail.com> Date: Sun, 7 Jun 2026 16:00:59 +0200 Subject: [PATCH 03/13] fix(ai-cli): make provider factory-resolution test deterministic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The adapter-construction unit tests dynamically imported provider packages and depended on the pnpm node_modules layout (e.g. whether a non-bundled provider was resolvable), which differs between platforms and broke on CI. Extract the pure factory-candidate-name derivation into `factoryCandidatesForProvider` and unit-test that instead — it still guards the OpenRouter casing/Text and fal alt-prefix regressions, with no module resolution or SDK construction, so it's platform-independent. --- packages/ai-cli/src/core/providers.ts | 46 +++++++++++++++--------- packages/ai-cli/tests/core.test.ts | 51 ++++++++++----------------- 2 files changed, 48 insertions(+), 49 deletions(-) diff --git a/packages/ai-cli/src/core/providers.ts b/packages/ai-cli/src/core/providers.ts index c11ccb590..c808a5b9e 100644 --- a/packages/ai-cli/src/core/providers.ts +++ b/packages/ai-cli/src/core/providers.ts @@ -94,6 +94,35 @@ const PROVIDERS: Record<string, ProviderEntry> = { }, } +/** + * The factory export names to try, in order, for a provider + activity. + * + * Pure and deterministic — no module resolution — so it can be unit-tested. + * Chat factory naming varies by provider (`Chat` vs `Text` vs `ResponsesText`); + * every other activity uses a single `create<Prefix><Activity>` name, with an + * optional alternate prefix (e.g. fal's `falImage`). + */ +export function factoryCandidatesForProvider( + provider: string, + activity: Activity, +): Array<string> { + const entry = PROVIDERS[provider] + if (!entry) return [] + const alt = entry.altFactoryPrefix + if (activity === 'chat') { + return [ + `create${entry.factoryPrefix}Chat`, + `create${entry.factoryPrefix}Text`, + `create${entry.factoryPrefix}ResponsesText`, + ...(alt ? [`${alt}Chat`, `${alt}Text`] : []), + ] + } + return [ + `create${entry.factoryPrefix}${ACTIVITY_SUFFIX[activity]}`, + ...(alt ? [`${alt}${ACTIVITY_SUFFIX[activity]}`] : []), + ] +} + /** Factory-name suffix per activity (the irregular `chat` -> `Chat`/text case included). */ const ACTIVITY_SUFFIX: Record<Activity, string> = { chat: 'Chat', @@ -184,22 +213,7 @@ export async function instantiateAdapter(params: { const mod = await importProvider(entry, provider) const moduleExports = mod as Record<string, unknown> - - // Chat factory naming varies by provider (Chat vs Text vs ResponsesText); - // every other activity uses a single `create<Prefix><Activity>` name. - const alt = entry.altFactoryPrefix - const candidates = - activity === 'chat' - ? [ - `create${entry.factoryPrefix}Chat`, - `create${entry.factoryPrefix}Text`, - `create${entry.factoryPrefix}ResponsesText`, - ...(alt ? [`${alt}Chat`, `${alt}Text`] : []), - ] - : [ - `create${entry.factoryPrefix}${ACTIVITY_SUFFIX[activity]}`, - ...(alt ? [`${alt}${ACTIVITY_SUFFIX[activity]}`] : []), - ] + const candidates = factoryCandidatesForProvider(provider, activity) for (const name of candidates) { const factory = moduleExports[name] diff --git a/packages/ai-cli/tests/core.test.ts b/packages/ai-cli/tests/core.test.ts index bc2b3bd9b..af50f83a4 100644 --- a/packages/ai-cli/tests/core.test.ts +++ b/packages/ai-cli/tests/core.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest' import { bundledProviders, - instantiateAdapter, + factoryCandidatesForProvider, resolveApiKey, resolveModelSlug, } from '../src/core/providers' @@ -49,44 +49,29 @@ describe('resolveModelSlug', () => { }) }) -describe('instantiateAdapter factory resolution (no network — just constructs adapters)', () => { - const make = ( - slug: string, - activity: Parameters<typeof instantiateAdapter>[0]['activity'], - ) => - instantiateAdapter({ - resolved: resolveModelSlug(slug), - activity, - apiKey: 'test-key', - }) - - it('resolves openai chat via the create*Chat factory', async () => { - expect(await make('openai/gpt-5.5', 'chat')).toBeTruthy() - }) - - it('resolves openrouter chat via the *Text factory (not Chat)', async () => { - // Regression: OpenRouter exports createOpenRouterText, and the prefix is - // "OpenRouter" (capital R), not "Openrouter". - expect(await make('openrouter/openai/gpt-oss-120b', 'chat')).toBeTruthy() +describe('factoryCandidatesForProvider (pure factory-name derivation)', () => { + it('tries create<Prefix>Chat first for chat', () => { + expect(factoryCandidatesForProvider('openai', 'chat')[0]).toBe( + 'createOpenaiChat', + ) }) - it('resolves fal image via the falImage factory + config-object key style', async () => { - // Regression: fal uses `falImage(model, { apiKey })`, not createFalImage(model, key). - expect(await make('fal/fal-ai/flux/dev', 'image')).toBeTruthy() + it('uses the OpenRouter *Text factory with correct casing (regression)', () => { + // OpenRouter exports createOpenRouterText (capital R, "Text" not "Chat"). + const candidates = factoryCandidatesForProvider('openrouter', 'chat') + expect(candidates).toContain('createOpenRouterText') + expect(candidates).not.toContain('createOpenrouterChat') }) - it('throws PROVIDER_NOT_INSTALLED for a non-bundled provider', async () => { - await expect( - make('groq/llama-3.3-70b-versatile', 'chat'), - ).rejects.toMatchObject({ - code: 'PROVIDER_NOT_INSTALLED', - }) + it('tries createFalImage then falImage for fal (alt prefix regression)', () => { + expect(factoryCandidatesForProvider('fal', 'image')).toEqual([ + 'createFalImage', + 'falImage', + ]) }) - it('throws USAGE when a provider does not support an activity', async () => { - await expect(make('fal/fal-ai/flux/dev', 'chat')).rejects.toMatchObject({ - code: 'USAGE', - }) + it('returns no candidates for an unknown provider', () => { + expect(factoryCandidatesForProvider('nope', 'chat')).toEqual([]) }) }) From 5e4af8b750698a6a90f39250b149a4223efd51c3 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak <t.zlak@hotmail.com> Date: Mon, 8 Jun 2026 11:45:23 +0200 Subject: [PATCH 04/13] feat(ai-cli): brand the terminal UI, mcp connection info, --output-dir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Full-width welcome screen: island logo (graphics-capable terminals) above a two-color ANSI wordmark — TANSTACK white, AI in the package pink (#EC4899) — with a sunset gradient rule and tagline. New shared theme module so the menu, chat REPL, and artifact/error renderers all use consistent brand colors. - `ts-ai mcp` now logs connection info to stderr before listening: a ready-to-paste MCP client config, transport, and tool list (stdout stays the clean JSON-RPC channel). - `--output-dir <dir>` for generations (image/video/audio/speech): default is the current directory; --output-dir sets the directory (created if missing, cross-platform via node:path); -o/--output sets an exact path and wins. - Tests: resolveOutputPath precedence, describeMcpServer content, and an introspect assertion for the --output-dir flag. Docs + changeset updated. --- .changeset/ai-cli-initial.md | 18 ++- docs/cli/overview.md | 36 ++++- packages/ai-cli/assets/logo.png | Bin 0 -> 265387 bytes packages/ai-cli/package.json | 1 + packages/ai-cli/src/cli/activities/audio.ts | 6 +- packages/ai-cli/src/cli/activities/image.ts | 3 +- packages/ai-cli/src/cli/activities/speech.ts | 2 +- packages/ai-cli/src/cli/activities/video.ts | 6 +- packages/ai-cli/src/cli/artifact.ts | 36 +++-- packages/ai-cli/src/cli/mcp.ts | 42 ++++++ packages/ai-cli/src/cli/run.ts | 5 +- packages/ai-cli/src/manifest/manifest.ts | 12 ++ packages/ai-cli/src/render/ink.tsx | 20 ++- packages/ai-cli/src/render/menu.tsx | 81 +++++------ packages/ai-cli/src/render/repl.tsx | 18 ++- packages/ai-cli/src/render/theme.ts | 66 +++++++++ packages/ai-cli/src/render/welcome.tsx | 135 +++++++++++++++++++ packages/ai-cli/tests/core.test.ts | 39 ++++++ testing/cli/tests/cli.spec.ts | 8 ++ 19 files changed, 444 insertions(+), 90 deletions(-) create mode 100644 packages/ai-cli/assets/logo.png create mode 100644 packages/ai-cli/src/render/theme.ts create mode 100644 packages/ai-cli/src/render/welcome.tsx diff --git a/.changeset/ai-cli-initial.md b/.changeset/ai-cli-initial.md index e4efcc6bb..a1803bc87 100644 --- a/.changeset/ai-cli-initial.md +++ b/.changeset/ai-cli-initial.md @@ -15,9 +15,15 @@ anthropic, gemini, openrouter, and fal bundled for zero-install) with keys from `--apiKey`, a conventional `.env`, or environment variables, and all options are expressible via `--config` (file or inline JSON). -For humans there's a lazily-loaded Ink layer: running `ts-ai` with no command on -a TTY opens an animated home screen and menu, `ts-ai chat` with no prompt drops -into an interactive REPL, and image results preview inline. `chat` supports tools -via `--mcp` servers, sandboxed `--code-mode` execution, and `--schema` structured -output, plus `ts-ai introspect` (machine-readable manifest), `ts-ai mcp` (expose -commands as MCP tools), and `ts-ai update`. +For humans there's a lazily-loaded, TanStack-branded Ink layer: running `ts-ai` +with no command on a TTY opens a full-width welcome screen (island logo on +graphics-capable terminals + a two-color `TANSTACK` / pink `AI` wordmark) and +menu, `ts-ai chat` with no prompt drops into an interactive REPL, and image +results preview inline. `chat` supports tools via `--mcp` servers, sandboxed +`--code-mode` execution, and `--schema` structured output, plus +`ts-ai introspect` (machine-readable manifest), `ts-ai mcp` (expose commands as +MCP tools — prints a ready-to-paste client config to stderr), and `ts-ai update`. + +Generations (`image`, `video`, `audio`, `speech`) write to the current directory +by default; `--output-dir <dir>` sets the target directory (created if missing, +cross-platform) and `-o/--output <path>` sets an exact path. diff --git a/docs/cli/overview.md b/docs/cli/overview.md index 3af412571..80c0b5ba0 100644 --- a/docs/cli/overview.md +++ b/docs/cli/overview.md @@ -51,10 +51,11 @@ and `--apiKey` always take precedence over `.env`. ## Interactive mode -Run `ts-ai` with no command on a terminal and you get an animated home screen -that asks what you want to do, with a menu (Chat, Image, Video, …). Pick **Chat** -to drop into a live REPL (`/clear` to reset, `/exit` to quit); pick a generation -command to type a prompt and run it inline. +Run `ts-ai` with no command on a terminal and you get a full-width TanStack AI +welcome screen — the island logo (on graphics-capable terminals) above the +wordmark — and a menu (Chat, Image, Video, …). Pick **Chat** to drop into a live +REPL (`/clear` to reset, `/exit` to quit); pick a generation command to type a +prompt and run it inline. ```bash ts-ai # animated menu → pick an action @@ -109,8 +110,20 @@ ts-ai image "a red bicycle" --model openai/gpt-image-1 --json # {"id":"...","model":"gpt-image-1","images":[{"path":"./ts-ai-image-<ts>.png","mimeType":"image/png"}],"usage":{...}} ``` -Media commands always write the artifact to a file (override with `-o`, or -`-o -` to stream raw bytes to stdout) and report the path in the JSON. +Media commands (`image`, `video`, `audio`, `speech`) always write the artifact +to a file and report the path in the JSON. By default the file lands in the +**current directory** with an auto-generated name. Control where it goes: + +- `--output-dir <dir>` — write the auto-named file into `<dir>` (created if + missing). Works the same on Windows and macOS. +- `-o/--output <path>` — set the exact file path (wins over `--output-dir`). +- `-o -` — stream the raw bytes to stdout (for piping). + +```bash +ts-ai image "a red bicycle" --model openai/gpt-image-1 # ./ts-ai-image-<ts>.png +ts-ai image "a red bicycle" --model openai/gpt-image-1 --output-dir ./out +ts-ai image "a red bicycle" --model openai/gpt-image-1 -o ./pics/bike.png +``` ### Streaming the AG-UI event stream @@ -167,7 +180,16 @@ Two patterns make `ts-ai` easy to drive programmatically: 1. **`ts-ai introspect`** prints a versioned JSON manifest of every command, flag, type, and exit code — read it once and auto-generate tool definitions. 2. **`ts-ai mcp`** starts an MCP server (stdio) that exposes each command as a - tool, so any MCP-capable agent can register `ts-ai` directly. + tool, so any MCP-capable agent can register `ts-ai` directly. On startup it + prints the connection details to **stderr** (stdout is the JSON-RPC channel) + — a ready-to-paste client config, the transport, and the tool list: + + ```jsonc + // add to Claude Desktop / Cursor + "mcpServers": { + "tanstack-ai": { "command": "ts-ai", "args": ["mcp"] } + } + ``` ### Exit codes diff --git a/packages/ai-cli/assets/logo.png b/packages/ai-cli/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9db3e67bade15d9ced50bdfc2393359c5f6f5712 GIT binary patch literal 265387 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ϟKoY=Snqfy1)i`l$#Yvv>#w{lM68ok2%j-k9O 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%0A(FZ&@S*K}LKE%X8U=5NC^*)q#N-fWN^U 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 literal 0 HcmV?d00001 diff --git a/packages/ai-cli/package.json b/packages/ai-cli/package.json index c73d8cba1..358723a9d 100644 --- a/packages/ai-cli/package.json +++ b/packages/ai-cli/package.json @@ -38,6 +38,7 @@ "node": ">=18" }, "files": [ + "assets", "dist", "src" ], diff --git a/packages/ai-cli/src/cli/activities/audio.ts b/packages/ai-cli/src/cli/activities/audio.ts index ed716e942..aa2dd59e6 100644 --- a/packages/ai-cli/src/cli/activities/audio.ts +++ b/packages/ai-cli/src/cli/activities/audio.ts @@ -60,10 +60,14 @@ export async function runAudio(ctx: RunContext, prompt: string): Promise<void> { const ext = EXT_BY_CONTENT_TYPE[result.audio.contentType ?? ''] ?? 'mp3' const output = typeof ctx.options.output === 'string' ? ctx.options.output : undefined + const outputDir = + typeof ctx.options.outputDir === 'string' + ? ctx.options.outputDir + : undefined const path = await writeArtifact( 'audio', { bytes, ext, mimeType: result.audio.contentType ?? `audio/${ext}` }, - output, + { output, outputDir }, ctx.now, ) diff --git a/packages/ai-cli/src/cli/activities/image.ts b/packages/ai-cli/src/cli/activities/image.ts index 177c1a9bc..f29aa0e74 100644 --- a/packages/ai-cli/src/cli/activities/image.ts +++ b/packages/ai-cli/src/cli/activities/image.ts @@ -51,6 +51,7 @@ export async function runImage(ctx: RunContext, prompt: string): Promise<void> { })) as ImageResultLike const output = stringValue(ctx.options.output) + const outputDir = stringValue(ctx.options.outputDir) const written: Array<{ path: string | null mimeType: string @@ -71,7 +72,7 @@ export async function runImage(ctx: RunContext, prompt: string): Promise<void> { const path = await writeArtifact( 'image', { bytes, ext, mimeType }, - target, + { output: target, outputDir }, ctx.now + index, ) written.push({ path, mimeType, revisedPrompt: image.revisedPrompt }) diff --git a/packages/ai-cli/src/cli/activities/speech.ts b/packages/ai-cli/src/cli/activities/speech.ts index a8af10379..aa2ff88e1 100644 --- a/packages/ai-cli/src/cli/activities/speech.ts +++ b/packages/ai-cli/src/cli/activities/speech.ts @@ -46,7 +46,7 @@ export async function runSpeech(ctx: RunContext, text: string): Promise<void> { const path = await writeArtifact( 'speech', { bytes, ext, mimeType: `audio/${ext}` }, - str(ctx.options.output), + { output: str(ctx.options.output), outputDir: str(ctx.options.outputDir) }, ctx.now, ) diff --git a/packages/ai-cli/src/cli/activities/video.ts b/packages/ai-cli/src/cli/activities/video.ts index 1e8413f08..33af285ec 100644 --- a/packages/ai-cli/src/cli/activities/video.ts +++ b/packages/ai-cli/src/cli/activities/video.ts @@ -65,10 +65,14 @@ export async function runVideo(ctx: RunContext, prompt: string): Promise<void> { const bytes = await fetchBytes(final.url) const output = typeof ctx.options.output === 'string' ? ctx.options.output : undefined + const outputDir = + typeof ctx.options.outputDir === 'string' + ? ctx.options.outputDir + : undefined const path = await writeArtifact( 'video', { bytes, ext: 'mp4', mimeType: 'video/mp4' }, - output, + { output, outputDir }, ctx.now, ) diff --git a/packages/ai-cli/src/cli/artifact.ts b/packages/ai-cli/src/cli/artifact.ts index 268208231..8e3a62840 100644 --- a/packages/ai-cli/src/cli/artifact.ts +++ b/packages/ai-cli/src/cli/artifact.ts @@ -1,4 +1,5 @@ -import { writeFile } from 'node:fs/promises' +import { mkdir, writeFile } from 'node:fs/promises' +import { dirname, join } from 'node:path' import { CliError } from '../core/exit-codes' import { emitBytes } from '../core/emit' @@ -9,36 +10,49 @@ export interface Artifact { mimeType: string } +/** Where to write an artifact: an explicit full path and/or a target directory. */ +export interface OutputTarget { + /** `-o/--output`: explicit path ("-" = stdout). Wins over `outputDir`. */ + output?: string + /** `--outputDir`: directory for the auto-generated filename. Defaults to cwd. */ + outputDir?: string +} + /** - * Resolve where an artifact should be written. Explicit `-o` wins; otherwise a - * timestamped name in the cwd. `-o -` is handled by the caller (stdout). + * Resolve where an artifact should be written. Precedence: an explicit + * `--output` path wins; otherwise an auto-generated filename inside + * `--outputDir` (default: the current directory). Cross-platform via node:path. + * `-o -` is handled by the caller (stdout). */ export function resolveOutputPath( command: string, ext: string, - output: string | undefined, + target: OutputTarget, now: number, ): string { - if (output && output !== '-') return output - return `./ts-ai-${command}-${now}.${ext}` + if (target.output && target.output !== '-') return target.output + const dir = target.outputDir ?? '.' + return join(dir, `ts-ai-${command}-${now}.${ext}`) } /** - * Persist an artifact. Returns the path written, or null when bytes were sent - * to stdout (`-o -`). + * Persist an artifact, creating the target directory if needed. Returns the + * path written, or null when bytes were sent to stdout (`-o -`). */ export async function writeArtifact( command: string, artifact: Artifact, - output: string | undefined, + target: OutputTarget, now: number, ): Promise<string | null> { - if (output === '-') { + if (target.output === '-') { await emitBytes(artifact.bytes) return null } - const path = resolveOutputPath(command, artifact.ext, output, now) + const path = resolveOutputPath(command, artifact.ext, target, now) try { + const dir = dirname(path) + if (dir && dir !== '.') await mkdir(dir, { recursive: true }) await writeFile(path, artifact.bytes) } catch (cause) { throw new CliError('RUNTIME', `Failed to write artifact to "${path}".`, { diff --git a/packages/ai-cli/src/cli/mcp.ts b/packages/ai-cli/src/cli/mcp.ts index 6a6f29304..7106e4dc2 100644 --- a/packages/ai-cli/src/cli/mcp.ts +++ b/packages/ai-cli/src/cli/mcp.ts @@ -4,6 +4,44 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' import { z } from 'zod' import { COMMANDS } from '../manifest/manifest' +const PINK = '' +const DIM = '' +const RESET = '' + +/** + * Human-facing connection info for `ts-ai mcp`, returned for writing to STDERR + * (stdout is the JSON-RPC channel and must stay clean). Includes a ready-to-paste + * MCP client config, transport, server identity, and the tool list. + */ +export function describeMcpServer(cliVersion: string): string { + const tty = Boolean(process.stderr.isTTY) + const pink = (s: string) => (tty ? `${PINK}${s}${RESET}` : s) + const dim = (s: string) => (tty ? `${DIM}${s}${RESET}` : s) + const tools = COMMANDS.map((c) => c.name).join(', ') + const config = JSON.stringify( + { mcpServers: { 'tanstack-ai': { command: 'ts-ai', args: ['mcp'] } } }, + null, + 2, + ) + .split('\n') + .map((line) => ` ${line}`) + .join('\n') + + return [ + '', + `${pink('TanStack AI')} ${dim(`· MCP server (stdio) · v${cliVersion}`)}`, + '', + dim('Add to your MCP client (e.g. Claude Desktop / Cursor):'), + config, + '', + `${dim('Transport :')} stdio`, + `${dim('Tools :')} ${tools}`, + '', + `${pink('●')} listening on stdio — connect a client…`, + '', + ].join('\n') +} + /** * `ts-ai mcp` — expose each generation command as an MCP tool over stdio. * @@ -42,6 +80,10 @@ export async function runMcpServer(cliVersion: string): Promise<void> { ) } + // Log connection info to stderr BEFORE listening — stdout is the JSON-RPC + // channel, stderr is free and is surfaced in MCP client logs. + process.stderr.write(describeMcpServer(cliVersion)) + const transport = new StdioServerTransport() await server.connect(transport) } diff --git a/packages/ai-cli/src/cli/run.ts b/packages/ai-cli/src/cli/run.ts index da63e4c73..bf6647f62 100644 --- a/packages/ai-cli/src/cli/run.ts +++ b/packages/ai-cli/src/cli/run.ts @@ -44,7 +44,10 @@ export async function run( if (isMachine(mode)) { emitError(cliError) } else { - process.stderr.write(`error: ${cliError.message}\n`) + // Pretty (TTY) mode: a branded red ✗ instead of a bare "error:" line. + const red = process.stderr.isTTY ? '' : '' + const reset = process.stderr.isTTY ? '' : '' + process.stderr.write(`${red}✗${reset} ${cliError.message}\n`) } return cliError.exitCode } diff --git a/packages/ai-cli/src/manifest/manifest.ts b/packages/ai-cli/src/manifest/manifest.ts index 9d858f15f..ee07cf179 100644 --- a/packages/ai-cli/src/manifest/manifest.ts +++ b/packages/ai-cli/src/manifest/manifest.ts @@ -64,6 +64,14 @@ const ATTACHMENT_FLAG: FlagSpec = { description: 'Attach a file (repeatable). "-" reads stdin.', } +/** Directory for generated artifacts (image/video/audio/speech). */ +const OUTPUT_DIR_FLAG: FlagSpec = { + name: 'outputDir', + type: 'string', + description: + 'Directory for generated files (default: current directory; created if missing). -o sets a full path and wins.', +} + export const COMMANDS: Array<CommandSpec> = [ { name: 'chat', @@ -122,6 +130,7 @@ export const COMMANDS: Array<CommandSpec> = [ producesArtifact: true, flags: [ ATTACHMENT_FLAG, + OUTPUT_DIR_FLAG, { name: 'size', type: 'string', @@ -145,6 +154,7 @@ export const COMMANDS: Array<CommandSpec> = [ experimental: true, flags: [ ATTACHMENT_FLAG, + OUTPUT_DIR_FLAG, { name: 'wait', type: 'boolean', @@ -166,6 +176,7 @@ export const COMMANDS: Array<CommandSpec> = [ acceptsPrompt: true, producesArtifact: true, flags: [ + OUTPUT_DIR_FLAG, { name: 'duration', type: 'number', @@ -181,6 +192,7 @@ export const COMMANDS: Array<CommandSpec> = [ acceptsPrompt: true, producesArtifact: true, flags: [ + OUTPUT_DIR_FLAG, { name: 'voice', type: 'string', description: 'Voice id.' }, { name: 'format', diff --git a/packages/ai-cli/src/render/ink.tsx b/packages/ai-cli/src/render/ink.tsx index c1dca6d21..35f507f0e 100644 --- a/packages/ai-cli/src/render/ink.tsx +++ b/packages/ai-cli/src/render/ink.tsx @@ -1,5 +1,6 @@ import { Box, Text, render } from 'ink' import terminalImage from 'terminal-image' +import { DIM, PINK, SUCCESS } from './theme' import type { RenderedImage } from './lazy' /** @@ -27,15 +28,17 @@ export async function renderImageResultInk(input: { const { waitUntilExit } = render( <Box flexDirection="column" gap={1}> - <Text color="green"> - ✓ Generated {input.images.length} image(s) with {input.model} + <Text> + <Text color={SUCCESS}>✓ </Text> + Generated {input.images.length} image(s) with{' '} + <Text color={PINK}>{input.model}</Text> </Text> {input.images.map((image, index) => ( <Box key={image.path} flexDirection="column"> {previews[index] ? <Text>{previews[index]}</Text> : null} - <Text dimColor>{image.path}</Text> + <Text color={DIM}>{image.path}</Text> {image.revisedPrompt ? ( - <Text dimColor>“{image.revisedPrompt}”</Text> + <Text color={DIM}>“{image.revisedPrompt}”</Text> ) : null} </Box> ))} @@ -62,11 +65,14 @@ export async function renderArtifactPathInk(input: { }): Promise<void> { const { waitUntilExit } = render( <Box flexDirection="column"> - <Text color="green">✓ {input.label}</Text> - <Text dimColor>{input.path}</Text> + <Text> + <Text color={SUCCESS}>✓ </Text> + {input.label} + </Text> + <Text color={DIM}>{input.path}</Text> {input.meta ? Object.entries(input.meta).map(([key, value]) => ( - <Text key={key} dimColor> + <Text key={key} color={DIM}> {key}: {String(value)} </Text> )) diff --git a/packages/ai-cli/src/render/menu.tsx b/packages/ai-cli/src/render/menu.tsx index 7c7972ecd..4a0f173b8 100644 --- a/packages/ai-cli/src/render/menu.tsx +++ b/packages/ai-cli/src/render/menu.tsx @@ -1,5 +1,7 @@ -import { useEffect, useState } from 'react' +import { useState } from 'react' import { Box, Text, render, useApp, useInput } from 'ink' +import { DIM, PINK } from './theme' +import { WelcomeHeader, loadLogo } from './welcome' /** A selectable action from the home menu. */ export interface MenuChoice { @@ -63,37 +65,13 @@ const ITEMS: Array<MenuItem> = [ { command: 'quit', label: 'Quit', hint: 'Exit', needsPrompt: false }, ] -const TITLE = 'TANSTACK AI' -const GRADIENT = [ - 'cyan', - 'cyanBright', - 'blueBright', - 'magenta', - 'magentaBright', - 'blueBright', -] - -/** Animated wordmark: a gradient sweeps across the letters. */ -function Title() { - const [tick, setTick] = useState(0) - useEffect(() => { - const id = setInterval(() => setTick((t) => t + 1), 90) - return () => clearInterval(id) - }, []) - return ( - <Box marginBottom={1}> - <Text bold> - {TITLE.split('').map((ch, i) => ( - <Text key={i} color={GRADIENT[(i + tick) % GRADIENT.length]}> - {ch} - </Text> - ))} - </Text> - </Box> - ) -} - -function Menu({ onChoose }: { onChoose: (choice: MenuChoice) => void }) { +function Menu({ + onChoose, + logo, +}: { + onChoose: (choice: MenuChoice) => void + logo: string | null +}) { const { exit } = useApp() const [index, setIndex] = useState(0) const [promptFor, setPromptFor] = useState<MenuItem | null>(null) @@ -142,42 +120,51 @@ function Menu({ onChoose }: { onChoose: (choice: MenuChoice) => void }) { if (promptFor) { return ( <Box flexDirection="column"> - <Title /> + <WelcomeHeader logo={logo} /> <Text> - {promptFor.label}: <Text color="cyan">{draft}</Text> - <Text color="gray">▌</Text> + <Text color={PINK}>{promptFor.label}</Text> + <Text color={DIM}> › </Text> + <Text>{draft}</Text> + <Text color={DIM}>▌</Text> </Text> - <Text dimColor>Enter to run · Esc to go back</Text> + <Text color={DIM}>Enter to run · Esc to go back</Text> </Box> ) } return ( <Box flexDirection="column"> - <Title /> - <Text dimColor>What do you want to do?</Text> + <WelcomeHeader logo={logo} /> + <Text color={DIM}>What do you want to do?</Text> <Box flexDirection="column" marginTop={1}> - {ITEMS.map((item, i) => ( - <Text key={item.command} color={i === index ? 'cyan' : undefined}> - {i === index ? '❯ ' : ' '} - <Text bold={i === index}>{item.label}</Text> - <Text dimColor> — {item.hint}</Text> - </Text> - ))} + {ITEMS.map((item, i) => { + const active = i === index + return ( + <Text key={item.command}> + <Text color={PINK}>{active ? '❯ ' : ' '}</Text> + <Text color={active ? PINK : undefined} bold={active}> + {item.label} + </Text> + <Text color={DIM}> — {item.hint}</Text> + </Text> + ) + })} </Box> <Box marginTop={1}> - <Text dimColor>↑/↓ to move · Enter to select · Esc to quit</Text> + <Text color={DIM}>↑/↓ to move · Enter to select · Esc to quit</Text> </Box> </Box> ) } /** Render the home screen and resolve the user's choice. */ -export function runMenuInk(): Promise<MenuChoice> { +export async function runMenuInk(): Promise<MenuChoice> { + const logo = await loadLogo() return new Promise((resolve) => { let choice: MenuChoice = { command: 'quit' } const { waitUntilExit } = render( <Menu + logo={logo} onChoose={(c) => { choice = c }} diff --git a/packages/ai-cli/src/render/repl.tsx b/packages/ai-cli/src/render/repl.tsx index 5e42c8fcc..05ea55344 100644 --- a/packages/ai-cli/src/render/repl.tsx +++ b/packages/ai-cli/src/render/repl.tsx @@ -1,5 +1,7 @@ import { useState } from 'react' import { Box, Text, render, useApp, useInput } from 'ink' +import { DIM, ERROR_RED, PINK } from './theme' +import { BrandMark } from './welcome' export interface ReplMessage { role: 'user' | 'assistant' @@ -66,26 +68,28 @@ function Repl({ return ( <Box flexDirection="column"> - <Text dimColor>chat · {model} · /clear to reset · /exit to quit</Text> - <Box flexDirection="column" marginTop={1}> + <Box marginBottom={1}> + <BrandMark suffix={`chat · ${model} · /clear · /exit`} /> + </Box> + <Box flexDirection="column"> {messages.map((m, i) => ( <Box key={i} marginBottom={1} flexDirection="column"> - <Text bold color={m.role === 'user' ? 'cyan' : 'green'}> + <Text bold color={m.role === 'user' ? DIM : PINK}> {m.role === 'user' ? 'you' : 'ai'} </Text> <Text>{m.content}</Text> </Box> ))} </Box> - {error ? <Text color="red">error: {error}</Text> : null} + {error ? <Text color={ERROR_RED}>✗ {error}</Text> : null} <Box> {busy ? ( - <Text color="yellow">…thinking</Text> + <Text color={PINK}>● thinking…</Text> ) : ( <Text> - <Text color="cyan">❯ </Text> + <Text color={PINK}>❯ </Text> {draft} - <Text color="gray">▌</Text> + <Text color={DIM}>▌</Text> </Text> )} </Box> diff --git a/packages/ai-cli/src/render/theme.ts b/packages/ai-cli/src/render/theme.ts new file mode 100644 index 000000000..e831d9a91 --- /dev/null +++ b/packages/ai-cli/src/render/theme.ts @@ -0,0 +1,66 @@ +/** + * TanStack AI brand theme for the terminal UI. Single source of truth for + * colors so every Ink component stays consistent. + */ + +/** The TanStack AI package pink (Tailwind pink-500). */ +export const PINK = '#EC4899' +export const WHITE = '#FFFFFF' +/** Dim/muted gray for secondary text. */ +export const DIM = '#94A3B8' +export const SUCCESS = '#22C55E' +export const ERROR_RED = '#F43F5E' + +/** + * Sunset → sea palette sampled from the TanStack island logo, used for + * gradient divider rules (warm amber/orange into pink into teal). + */ +export const SUNSET = [ + '#FBBF24', + '#FB923C', + '#F97316', + '#F472B6', + '#EC4899', + '#22D3EE', + '#2DD4BF', +] as const + +/** Pick a gradient color for position `index` of `total` (banded, no interpolation). */ +export function gradientColorAt(index: number, total: number): string { + if (total <= 1) return SUNSET[0] + const ratio = index / (total - 1) + const slot = Math.min( + SUNSET.length - 1, + Math.round(ratio * (SUNSET.length - 1)), + ) + return SUNSET[slot] ?? SUNSET[0] +} + +/** Build a full-width gradient rule string of the given character. */ +export function gradientRuleSegments( + width: number, + char = '─', +): Array<{ char: string; color: string }> { + const w = Math.max(1, width) + return Array.from({ length: w }, (_, i) => ({ + char, + color: gradientColorAt(i, w), + })) +} + +/** + * Detect whether the terminal can render inline raster graphics (so the logo + * is shown only where it looks good — iTerm2, Kitty, WezTerm, Konsole/Sixel — + * and omitted elsewhere rather than rendered as muddy block-art). + */ +export function supportsInlineGraphics( + env: NodeJS.ProcessEnv = process.env, +): boolean { + if (!process.stdout.isTTY) return false + if (env.TERM_PROGRAM === 'iTerm.app' || env.TERM_PROGRAM === 'WezTerm') + return true + if (env.KITTY_WINDOW_ID || env.WEZTERM_PANE || env.KONSOLE_VERSION) + return true + if (env.TERM?.includes('kitty')) return true + return false +} diff --git a/packages/ai-cli/src/render/welcome.tsx b/packages/ai-cli/src/render/welcome.tsx new file mode 100644 index 000000000..da9a16ae4 --- /dev/null +++ b/packages/ai-cli/src/render/welcome.tsx @@ -0,0 +1,135 @@ +import { dirname, resolve } from 'node:path' +import { Box, Text } from 'ink' +import pkg from '../../package.json' +import { + DIM, + PINK, + WHITE, + gradientRuleSegments, + supportsInlineGraphics, +} from './theme' + +// Pre-rendered figlet ("ANSI Shadow") wordmark, embedded as constants so there +// is no runtime figlet dependency. TANSTACK renders white, AI renders pink. +const TANSTACK_LINES = [ + '████████╗ █████╗ ███╗ ██╗███████╗████████╗ █████╗ ██████╗██╗ ██╗', + '╚══██╔══╝██╔══██╗████╗ ██║██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝', + ' ██║ ███████║██╔██╗ ██║███████╗ ██║ ███████║██║ █████╔╝ ', + ' ██║ ██╔══██║██║╚██╗██║╚════██║ ██║ ██╔══██║██║ ██╔═██╗ ', + ' ██║ ██║ ██║██║ ╚████║███████║ ██║ ██║ ██║╚██████╗██║ ██╗', + ' ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝', +] +const AI_LINES = [ + ' █████╗ ██╗', + '██╔══██╗██║', + '███████║██║', + '██╔══██║██║', + '██║ ██║██║', + '╚═╝ ╚═╝╚═╝', +] +const WORDMARK_WIDTH = + (TANSTACK_LINES[0]?.length ?? 0) + 1 + (AI_LINES[0]?.length ?? 0) + +const TAGLINE = 'Type-safe AI in your terminal' + +/** + * Load the island logo as a terminal-renderable string, but only on terminals + * that support inline raster graphics — elsewhere it returns null and the + * wordmark stands alone (no muddy block-art). + */ +export async function loadLogo(): Promise<string | null> { + if (!supportsInlineGraphics()) return null + const binPath = process.argv[1] + if (!binPath) return null + try { + const { default: terminalImage } = await import('terminal-image') + const logoPath = resolve(dirname(binPath), '../../assets/logo.png') + return await terminalImage.file(logoPath, { height: 12 }) + } catch { + return null + } +} + +function columns(): number { + return process.stdout.columns && process.stdout.columns > 0 + ? process.stdout.columns + : 80 +} + +function GradientRule({ width }: { width: number }) { + return ( + <Text> + {gradientRuleSegments(width).map((seg, i) => ( + <Text key={i} color={seg.color}> + {seg.char} + </Text> + ))} + </Text> + ) +} + +/** + * The full welcome header: optional logo, the big two-color wordmark (or a + * compact fallback on narrow terminals), a sunset gradient rule, and tagline. + */ +export function WelcomeHeader({ logo }: { logo: string | null }) { + const cols = columns() + const ruleWidth = Math.min(cols, WORDMARK_WIDTH) + const wide = cols >= WORDMARK_WIDTH + + return ( + <Box flexDirection="column" marginBottom={1}> + {logo ? ( + <Box marginBottom={1} flexDirection="column"> + <Text>{logo}</Text> + </Box> + ) : null} + + {wide ? ( + <Box flexDirection="column"> + {TANSTACK_LINES.map((line, i) => ( + <Text key={i}> + <Text color={WHITE}>{line}</Text> + <Text> </Text> + <Text color={PINK} bold> + {AI_LINES[i]} + </Text> + </Text> + ))} + </Box> + ) : ( + <Text> + <Text color={WHITE} bold> + TanStack{' '} + </Text> + <Text color={PINK} bold> + AI + </Text> + </Text> + )} + + <Box marginTop={1} flexDirection="column"> + <GradientRule width={ruleWidth} /> + <Text> + <Text color={DIM}>{TAGLINE} · </Text> + <Text color={PINK}>v{pkg.version}</Text> + </Text> + </Box> + </Box> + ) +} + +/** Compact one-line brand mark for sub-screens (chat REPL header, etc.). */ +export function BrandMark({ suffix }: { suffix?: string }) { + return ( + <Text> + <Text color={WHITE} bold> + TanStack{' '} + </Text> + <Text color={PINK} bold> + AI + </Text> + {suffix ? <Text color={DIM}> · {suffix}</Text> : null} + </Text> + ) +} diff --git a/packages/ai-cli/tests/core.test.ts b/packages/ai-cli/tests/core.test.ts index af50f83a4..c6ae0aa93 100644 --- a/packages/ai-cli/tests/core.test.ts +++ b/packages/ai-cli/tests/core.test.ts @@ -10,7 +10,10 @@ import { resolveOutputMode } from '../src/core/output' import { coerceFlags } from '../src/cli/options' import { CliError, ExitCode, toCliError } from '../src/core/exit-codes' import { inferMimeType, resolvePrompt } from '../src/core/io' +import { join } from 'node:path' import { tokenizeCommand } from '../src/cli/mcp-clients' +import { resolveOutputPath } from '../src/cli/artifact' +import { describeMcpServer } from '../src/cli/mcp' import { findCommand } from '../src/manifest/manifest' import type { CommandSpec } from '../src/manifest/types' @@ -213,6 +216,42 @@ describe('io helpers', () => { }) }) +describe('resolveOutputPath (--outputDir / -o precedence)', () => { + it('defaults to an auto filename in the current directory', () => { + expect(resolveOutputPath('image', 'png', {}, 123)).toBe( + join('.', 'ts-ai-image-123.png'), + ) + }) + + it('writes the auto filename into --outputDir', () => { + expect( + resolveOutputPath('audio', 'mp3', { outputDir: 'out/clips' }, 9), + ).toBe(join('out/clips', 'ts-ai-audio-9.mp3')) + }) + + it('lets an explicit --output path win over --outputDir', () => { + expect( + resolveOutputPath( + 'image', + 'png', + { output: './pics/fox.png', outputDir: 'out' }, + 1, + ), + ).toBe('./pics/fox.png') + }) +}) + +describe('describeMcpServer', () => { + it('includes a paste-ready client config, transport, and tools', () => { + const info = describeMcpServer('1.2.3') + expect(info).toContain('"mcpServers"') + expect(info).toContain('"command": "ts-ai"') + expect(info).toContain('stdio') + expect(info).toContain('chat') + expect(info).toContain('v1.2.3') + }) +}) + describe('tokenizeCommand (--mcp stdio spec)', () => { it('splits a simple command on whitespace', () => { expect(tokenizeCommand('npx -y @scope/server /tmp')).toEqual([ diff --git a/testing/cli/tests/cli.spec.ts b/testing/cli/tests/cli.spec.ts index a17d92321..98e5e31c7 100644 --- a/testing/cli/tests/cli.spec.ts +++ b/testing/cli/tests/cli.spec.ts @@ -196,6 +196,14 @@ describe('ts-ai introspect flag spelling', () => { const wait = video.flags.find((f: { name: string }) => f.name === 'wait') // default-true booleans render as negatable --no-x flags. expect(wait.flag).toBe('--no-wait') + // generation commands expose --output-dir. + const image = manifest.commands.find( + (c: { name: string }) => c.name === 'image', + ) + const outputDir = image.flags.find( + (f: { name: string }) => f.name === 'outputDir', + ) + expect(outputDir.flag).toBe('--output-dir') }) }) From cffa5417e1b30e13d3f99a3d05c4e5b57386e4fb Mon Sep 17 00:00:00 2001 From: Alem Tuzlak <t.zlak@hotmail.com> Date: Mon, 8 Jun 2026 12:08:58 +0200 Subject: [PATCH 05/13] feat(ai-cli): animate the welcome wordmark and clear the screen on launch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ts-ai` (no command) now clears the terminal first for a clean splash, then a pink band sweeps left→right across the wordmark — starting at TANSTACK and landing on AI, which stays pink while TANSTACK settles to white. Run-length segmented per line so the per-frame node count stays small; the sweep runs once then stops. Narrow terminals and non-TTY output skip the animation. --- packages/ai-cli/src/render/menu.tsx | 4 +- packages/ai-cli/src/render/welcome.tsx | 135 ++++++++++++++++++++----- 2 files changed, 110 insertions(+), 29 deletions(-) diff --git a/packages/ai-cli/src/render/menu.tsx b/packages/ai-cli/src/render/menu.tsx index 4a0f173b8..ddc2f8474 100644 --- a/packages/ai-cli/src/render/menu.tsx +++ b/packages/ai-cli/src/render/menu.tsx @@ -134,7 +134,7 @@ function Menu({ return ( <Box flexDirection="column"> - <WelcomeHeader logo={logo} /> + <WelcomeHeader logo={logo} animate /> <Text color={DIM}>What do you want to do?</Text> <Box flexDirection="column" marginTop={1}> {ITEMS.map((item, i) => { @@ -160,6 +160,8 @@ function Menu({ /** Render the home screen and resolve the user's choice. */ export async function runMenuInk(): Promise<MenuChoice> { const logo = await loadLogo() + // Clear the screen (and scrollback) so the welcome splash starts clean. + if (process.stdout.isTTY) process.stdout.write('') return new Promise((resolve) => { let choice: MenuChoice = { command: 'quit' } const { waitUntilExit } = render( diff --git a/packages/ai-cli/src/render/welcome.tsx b/packages/ai-cli/src/render/welcome.tsx index da9a16ae4..6b25da2a0 100644 --- a/packages/ai-cli/src/render/welcome.tsx +++ b/packages/ai-cli/src/render/welcome.tsx @@ -1,4 +1,5 @@ import { dirname, resolve } from 'node:path' +import { useEffect, useState } from 'react' import { Box, Text } from 'ink' import pkg from '../../package.json' import { @@ -68,14 +69,113 @@ function GradientRule({ width }: { width: number }) { ) } +/** Width of the moving pink sweep band, in columns. */ +const SWEEP_BAND = 7 +const DONE_FRONT = WORDMARK_WIDTH + SWEEP_BAND + +/** + * Color for a single wordmark column given the sweep front position. Behind the + * band, columns settle to their final color (white for TANSTACK, pink for AI); + * the band itself is pink; columns ahead of it are still white. + */ +function colorForColumn(col: number, aiStart: number, front: number): string { + if (col <= front - SWEEP_BAND) return col >= aiStart ? PINK : WHITE + if (col <= front) return PINK + return WHITE +} + +/** Run-length group a line's columns into colored segments to minimize nodes. */ +function lineSegments( + line: string, + aiStart: number, + front: number, +): Array<{ text: string; color: string }> { + const segs: Array<{ text: string; color: string }> = [] + let color = '' + let buf = '' + for (let c = 0; c < line.length; c++) { + const next = colorForColumn(c, aiStart, front) + if (next !== color) { + if (buf) segs.push({ text: buf, color }) + buf = line[c] ?? '' + color = next + } else { + buf += line[c] ?? '' + } + } + if (buf) segs.push({ text: buf, color }) + return segs +} + +/** + * The big two-color wordmark. When `animate` is set (and the terminal is wide + * enough), a pink band sweeps left→right across the letters, leaving TANSTACK + * white and AI pink. Otherwise it renders the settled final state. + */ +function Wordmark({ animate }: { animate: boolean }) { + const wide = columns() >= WORDMARK_WIDTH + const [front, setFront] = useState(animate && wide ? 0 : DONE_FRONT) + + useEffect(() => { + if (!animate || !wide) return + const id = setInterval(() => { + setFront((f) => { + const n = f + 2 + if (n >= DONE_FRONT) { + clearInterval(id) + return DONE_FRONT + } + return n + }) + }, 35) + return () => clearInterval(id) + }, [animate, wide]) + + if (!wide) { + return ( + <Text> + <Text color={WHITE} bold> + TanStack{' '} + </Text> + <Text color={PINK} bold> + AI + </Text> + </Text> + ) + } + + const aiStart = (TANSTACK_LINES[0]?.length ?? 0) + 1 + return ( + <Box flexDirection="column"> + {TANSTACK_LINES.map((tan, i) => { + const line = `${tan} ${AI_LINES[i] ?? ''}` + return ( + <Text key={i}> + {lineSegments(line, aiStart, front).map((seg, j) => ( + <Text key={j} color={seg.color} bold={seg.color === PINK}> + {seg.text} + </Text> + ))} + </Text> + ) + })} + </Box> + ) +} + /** - * The full welcome header: optional logo, the big two-color wordmark (or a - * compact fallback on narrow terminals), a sunset gradient rule, and tagline. + * The full welcome header: optional logo, the big two-color wordmark (animated + * sweep when `animate` is set, compact fallback on narrow terminals), a sunset + * gradient rule, and tagline. */ -export function WelcomeHeader({ logo }: { logo: string | null }) { - const cols = columns() - const ruleWidth = Math.min(cols, WORDMARK_WIDTH) - const wide = cols >= WORDMARK_WIDTH +export function WelcomeHeader({ + logo, + animate = false, +}: { + logo: string | null + animate?: boolean +}) { + const ruleWidth = Math.min(columns(), WORDMARK_WIDTH) return ( <Box flexDirection="column" marginBottom={1}> @@ -85,28 +185,7 @@ export function WelcomeHeader({ logo }: { logo: string | null }) { </Box> ) : null} - {wide ? ( - <Box flexDirection="column"> - {TANSTACK_LINES.map((line, i) => ( - <Text key={i}> - <Text color={WHITE}>{line}</Text> - <Text> </Text> - <Text color={PINK} bold> - {AI_LINES[i]} - </Text> - </Text> - ))} - </Box> - ) : ( - <Text> - <Text color={WHITE} bold> - TanStack{' '} - </Text> - <Text color={PINK} bold> - AI - </Text> - </Text> - )} + <Wordmark animate={animate} /> <Box marginTop={1} flexDirection="column"> <GradientRule width={ruleWidth} /> From 5317fed7b1634823e9e5a1b3046f66203254acfe Mon Sep 17 00:00:00 2001 From: Alem Tuzlak <t.zlak@hotmail.com> Date: Mon, 8 Jun 2026 12:30:31 +0200 Subject: [PATCH 06/13] docs(ai-cli): add a TanStack Intent agent skill for the ts-ai CLI Ships skills/ai-cli/SKILL.md so coding agents (Claude Code, Cursor, Copilot) learn to drive ts-ai correctly: the machine-mode contract (--json/--stream, stdout-is-payload, exit codes, structured errors), provider/model slugs, --config + modelOptions, --output-dir, stateless chat via --messages, --mcp / --code-mode, and introspect / mcp. Adds the `tanstack-intent` keyword and `skills` to files[] for discovery, and lists the skill in the Agent Skills doc. --- .changeset/ai-cli-initial.md | 4 + docs/config.json | 3 +- docs/getting-started/agent-skills.md | 1 + packages/ai-cli/package.json | 4 +- packages/ai-cli/skills/ai-cli/SKILL.md | 238 +++++++++++++++++++++++++ 5 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 packages/ai-cli/skills/ai-cli/SKILL.md diff --git a/.changeset/ai-cli-initial.md b/.changeset/ai-cli-initial.md index a1803bc87..52c8ca765 100644 --- a/.changeset/ai-cli-initial.md +++ b/.changeset/ai-cli-initial.md @@ -27,3 +27,7 @@ MCP tools — prints a ready-to-paste client config to stderr), and `ts-ai updat Generations (`image`, `video`, `audio`, `speech`) write to the current directory by default; `--output-dir <dir>` sets the target directory (created if missing, cross-platform) and `-o/--output <path>` sets an exact path. + +Ships a TanStack Intent agent skill (`ai-cli`) so coding agents learn how to +drive `ts-ai` correctly (machine-mode contract, exit codes, `--config`, +`--output-dir`, `introspect`, `mcp`). diff --git a/docs/config.json b/docs/config.json index 55fb526f3..3b56667fe 100644 --- a/docs/config.json +++ b/docs/config.json @@ -47,7 +47,8 @@ { "label": "Agent Skills (TanStack Intent)", "to": "getting-started/agent-skills", - "addedAt": "2026-04-17" + "addedAt": "2026-04-17", + "updatedAt": "2026-06-07" } ] }, diff --git a/docs/getting-started/agent-skills.md b/docs/getting-started/agent-skills.md index a3441133d..b3e98eb45 100644 --- a/docs/getting-started/agent-skills.md +++ b/docs/getting-started/agent-skills.md @@ -31,6 +31,7 @@ TanStack AI publishes skills inside its packages so the guidance travels with `n |---------|-------|-----------------| | `@tanstack/ai` | `ai-core` | Chat experience, tool calling, adapters, middleware, structured outputs, media generation, AG-UI protocol, custom backends | | `@tanstack/ai-code-mode` | `ai-code-mode` | Setting up Code Mode with a sandbox driver and registering server tools | +| `@tanstack/ai-cli` | `ai-cli` | Driving the `ts-ai` CLI from a terminal or agent harness — JSON/stream output, exit codes, `--config`, `--output-dir`, `introspect`, and `mcp` | Each skill lives under `node_modules/<package>/skills/<skill-name>/SKILL.md` once the package is installed. diff --git a/packages/ai-cli/package.json b/packages/ai-cli/package.json index 358723a9d..da11277f9 100644 --- a/packages/ai-cli/package.json +++ b/packages/ai-cli/package.json @@ -19,7 +19,8 @@ "chat", "image-generation", "tts", - "transcription" + "transcription", + "tanstack-intent" ], "type": "module", "module": "./dist/esm/index.js", @@ -40,6 +41,7 @@ "files": [ "assets", "dist", + "skills", "src" ], "scripts": { diff --git a/packages/ai-cli/skills/ai-cli/SKILL.md b/packages/ai-cli/skills/ai-cli/SKILL.md new file mode 100644 index 000000000..52edabf9b --- /dev/null +++ b/packages/ai-cli/skills/ai-cli/SKILL.md @@ -0,0 +1,238 @@ +--- +name: ai-cli +description: > + Drive TanStack AI from the terminal or an agent harness with the `ts-ai` + binary: chat, image, video, audio, speech, transcribe, summarize, plus + introspect (machine-readable manifest), mcp (expose commands as MCP tools), + and update. Machine-first design — `--json` buffered output, `--stream` AG-UI + events, strict stdout-is-payload, typed exit codes, structured error objects. + Providers resolve from a `provider/model` slug; options via flags or `--config`; + generations write to the cwd or `--output-dir`. +type: core +library: tanstack-ai +library_version: '0.1.0' +sources: + - 'TanStack/ai:docs/cli/overview.md' + - 'TanStack/ai:packages/ai-cli/src/manifest/manifest.ts' +--- + +# `@tanstack/ai-cli` (`ts-ai`) + +`ts-ai` is a thin, type-safe CLI over the core TanStack AI activities, built so +the **same binary** serves one-off human use and agent harnesses. For +programmatic use, always drive the machine path: pass `--json`, parse stdout, +branch on the exit code. + +## Install / invoke + +```bash +# zero-install one-off +npx @tanstack/ai-cli image "a watercolor fox" --output-dir ./out + +# or globally +pnpm add -g @tanstack/ai-cli +ts-ai --version +``` + +OpenAI, Anthropic, Gemini, OpenRouter, and Fal are bundled (zero-install). Other +providers (ollama, grok, groq, elevenlabs) are loaded on demand; if one isn't +installed, `ts-ai` exits with code `4` telling you which package to add. + +## Model + key + +Select a model with a `provider/model` slug. The key comes from `--api-key`, a +conventional `.env` in the working directory, or the provider's env var +(`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, `OPENROUTER_API_KEY`, +`FAL_KEY`). + +```bash +ts-ai chat "Explain MCP in one sentence" --model openai/gpt-5.5 --json +ts-ai chat "Summarize this" --model anthropic/claude-haiku-4-5 --api-key sk-... +``` + +The model after the first `/` may itself contain slashes (e.g. +`openrouter/openai/gpt-oss-120b`, `fal/fal-ai/ltx-video`). + +## The machine-mode contract + +This is what makes `ts-ai` safe to script: + +- **`--json`** prints a single buffered JSON object to stdout, nothing else. +- **`--stream`** prints the AG-UI event stream as newline-delimited JSON (one + event per line) for incremental consumption. +- **stdout carries only the payload.** All progress, warnings, and logs go to + **stderr**. Capture stdout and `JSON.parse` it directly. +- **Exit codes:** `0` success · `1` runtime · `2` usage/validation · `3` + provider/API or output-schema validation · `4` provider package not installed. +- On any non-zero exit in `--json` mode, a structured error object is printed to + **stdout**: `{ "error": { "code": "...", "message": "...", "provider": "..." } }`. + +```bash +result=$(ts-ai chat "classify: 'app crashes on launch'" \ + --model openai/gpt-5.5 --schema ./ticket.schema.json --json) +echo "$result" | jq '.data' +``` + +## Commands + +| Command | Purpose | +| --- | --- | +| `ts-ai chat <prompt>` | Chat / agentic text (tools, code-mode, structured output) | +| `ts-ai image <prompt>` | Generate an image | +| `ts-ai video <prompt>` | Generate a video (async job; blocks until done) | +| `ts-ai audio <prompt>` | Generate audio (music / sfx) | +| `ts-ai speech <text>` | Text-to-speech (alias `tts`) | +| `ts-ai transcribe <file>` | Speech-to-text (alias `stt`) | +| `ts-ai summarize <text>` | Summarize text | +| `ts-ai introspect` | Print the full CLI manifest as JSON | +| `ts-ai mcp` | Expose every command as an MCP tool over stdio | +| `ts-ai update` | Update to the latest version | + +The prompt is every non-flag argument after the command. With no positional +prompt, input is read from stdin (`cat doc.txt | ts-ai summarize ...`). Media +inputs use the repeatable `--attachment <file>` flag (`-` reads stdin). + +## chat specifics + +`chat` is 100% stateless — pass the full history in and thread the returned +messages back yourself: + +```bash +ts-ai chat --model openai/gpt-5.5 --json \ + --messages '[{"role":"user","content":"hi"},{"role":"assistant","content":"hello!"},{"role":"user","content":"what did I say?"}]' +``` + +- `--system <text|file>` — system prompt. +- `--max-steps <n>` — bound the agent loop (tool-calling iterations). +- `--mcp <cmd-or-url>` (repeatable) — give the chat tools from an MCP server. A + spec is an HTTP(S) URL or a stdio command (`--mcp "npx -y @scope/server /tmp"`). +- `--code-mode` — wrap the MCP tools in a sandboxed `execute_typescript` tool + (requires at least one `--mcp` server to orchestrate). +- `--schema <file|inline>` — JSON Schema for structured output; the validated + object is returned under `.data` in the JSON envelope. +- `--thread-id <id>` — passthrough correlation id (telemetry / AG-UI); never + causes persistence. + +On a TTY with no prompt, `ts-ai chat` opens an interactive REPL instead. + +## Output of generations + +`image`, `video`, `audio`, `speech` always write a file and report the path in +the JSON. Default: the **current directory** with an auto-generated name. + +- `--output-dir <dir>` — auto-named file into `<dir>` (created if missing; + cross-platform). +- `-o/--output <path>` — exact file path (wins over `--output-dir`). +- `-o -` — stream raw bytes to stdout (for piping). + +```bash +ts-ai image "a red bicycle" --model openai/gpt-image-1 --output-dir ./assets --json +ts-ai speech "hello there" --model openai/gpt-4o-mini-tts -o ./hi.mp3 --json +ts-ai transcribe ./talk.mp3 --model openai/gpt-4o-mini-transcribe --json +``` + +## Options & --config + +Every option is a flag, but nested, provider-specific options live under +`--config`, which accepts a JSON file path **or** an inline JSON string mirroring +the command's options. Precedence: **flags > `--config` > env > defaults**. + +```bash +ts-ai image "a logo" --model openai/gpt-image-1 \ + --config '{"size":"1024x1024","modelOptions":{"background":"transparent"}}' +``` + +`modelOptions` (the unbounded provider-specific bag) is only settable via +`--config`. + +## Self-description for harnesses + +- `ts-ai introspect` prints a versioned JSON manifest of every command, flag + (with its exact CLI spelling), type, default, and exit code — read it once to + auto-generate tool/function definitions. +- `ts-ai mcp` starts an MCP server (stdio) exposing each command as a tool, so an + MCP-capable agent can register `ts-ai` directly. On startup it prints a + ready-to-paste client config to **stderr** (stdout is the JSON-RPC channel). + +## Common Mistakes + +### CRITICAL: Parsing stdout without `--json` + +On a TTY, `ts-ai` renders a pretty Ink UI (colors, image previews, spinners) to +stdout. A harness that scrapes that output gets ANSI/terminal garbage. Always +pass `--json` (or `--stream`) in programmatic use; then stdout is exactly one +JSON object (or one event per line) and nothing else. + +Wrong: + +```bash +answer=$(ts-ai chat "hi" --model openai/gpt-5.5) # pretty UI if stdout is a TTY +``` + +Right: + +```bash +answer=$(ts-ai chat "hi" --model openai/gpt-5.5 --json | jq -r '.text') +``` + +Source: docs/cli/overview.md + +### HIGH: Ignoring the exit code / not reading the error envelope + +Failures are reported by exit code AND, in `--json` mode, a structured error +object on stdout. Branch on the exit code; on non-zero, parse `.error.code` +(`USAGE`, `PROVIDER`, `PROVIDER_NOT_INSTALLED`, `OUTPUT_VALIDATION`, `RUNTIME`). + +```bash +out=$(ts-ai image "x" --model openai/gpt-image-1 --json) || { + code=$(echo "$out" | jq -r '.error.code') + echo "ts-ai failed: $code" >&2 + exit 1 +} +``` + +Source: packages/ai-cli/src/core/exit-codes.ts + +### HIGH: Passing provider-specific options as flags + +Only the bounded, documented options are flags (`--size`, `--voice`, etc.). +Anything provider-specific (reasoning effort, background, moderation, …) must go +under `--config`'s `modelOptions` — there is no generic `--model-options` flag. + +Wrong: + +```bash +ts-ai image "x" --model openai/gpt-image-1 --background transparent # unknown flag +``` + +Right: + +```bash +ts-ai image "x" --model openai/gpt-image-1 --config '{"modelOptions":{"background":"transparent"}}' +``` + +Source: packages/ai-cli/src/manifest/manifest.ts + +### MEDIUM: Expecting `ts-ai mcp` to stream, or printing info to stdout + +MCP tool calls are request/response — they return the command's buffered `--json` +result, never the AG-UI stream. And `ts-ai mcp`'s human-facing connection info is +written to **stderr** on purpose; stdout is reserved for JSON-RPC. Don't grep the +server's stdout for the config — read stderr (MCP clients surface it in logs). + +Source: docs/cli/overview.md + +### MEDIUM: Assuming every provider supports every command + +Resolution maps `provider/model` + activity to an adapter factory. A provider +that lacks a factory for an activity exits `2` (`USAGE`); a non-bundled provider +that isn't installed exits `4` (`PROVIDER_NOT_INSTALLED`). Only openai, +anthropic, gemini, openrouter, and fal are bundled. + +Source: packages/ai-cli/src/core/providers.ts + +## Cross-References + +- See also: ai-core/SKILL.md — the underlying `chat`/`generateImage`/… activities the CLI wraps. +- See also: ai-mcp/SKILL.md — the MCP client that powers `--mcp`. +- See also: ai-code-mode/SKILL.md — the sandbox behind `--code-mode`. From 9ccc029196a883d49b1de6f90f6582d28b8b048f Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2026 10:31:48 +0000 Subject: [PATCH 07/13] ci: apply automated fixes --- packages/ai-cli/skills/ai-cli/SKILL.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/ai-cli/skills/ai-cli/SKILL.md b/packages/ai-cli/skills/ai-cli/SKILL.md index 52edabf9b..44f18aa74 100644 --- a/packages/ai-cli/skills/ai-cli/SKILL.md +++ b/packages/ai-cli/skills/ai-cli/SKILL.md @@ -75,18 +75,18 @@ echo "$result" | jq '.data' ## Commands -| Command | Purpose | -| --- | --- | -| `ts-ai chat <prompt>` | Chat / agentic text (tools, code-mode, structured output) | -| `ts-ai image <prompt>` | Generate an image | -| `ts-ai video <prompt>` | Generate a video (async job; blocks until done) | -| `ts-ai audio <prompt>` | Generate audio (music / sfx) | -| `ts-ai speech <text>` | Text-to-speech (alias `tts`) | -| `ts-ai transcribe <file>` | Speech-to-text (alias `stt`) | -| `ts-ai summarize <text>` | Summarize text | -| `ts-ai introspect` | Print the full CLI manifest as JSON | -| `ts-ai mcp` | Expose every command as an MCP tool over stdio | -| `ts-ai update` | Update to the latest version | +| Command | Purpose | +| ------------------------- | --------------------------------------------------------- | +| `ts-ai chat <prompt>` | Chat / agentic text (tools, code-mode, structured output) | +| `ts-ai image <prompt>` | Generate an image | +| `ts-ai video <prompt>` | Generate a video (async job; blocks until done) | +| `ts-ai audio <prompt>` | Generate audio (music / sfx) | +| `ts-ai speech <text>` | Text-to-speech (alias `tts`) | +| `ts-ai transcribe <file>` | Speech-to-text (alias `stt`) | +| `ts-ai summarize <text>` | Summarize text | +| `ts-ai introspect` | Print the full CLI manifest as JSON | +| `ts-ai mcp` | Expose every command as an MCP tool over stdio | +| `ts-ai update` | Update to the latest version | The prompt is every non-flag argument after the command. With no positional prompt, input is read from stdin (`cat doc.txt | ts-ai summarize ...`). Media From 1e3d4e10e51eb7fdd3259dadddf9c6883ee12025 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak <t.zlak@hotmail.com> Date: Mon, 8 Jun 2026 12:40:58 +0200 Subject: [PATCH 08/13] chore: pin all packages/ libraries to workspace:* via pnpm overrides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a workspace:* override in pnpm-workspace.yaml for every library under packages/ so any transitive or example dependency that references a published @tanstack/ai-* version resolves to the local workspace copy — the monorepo never builds or tests against an npm-published version. Document the convention (add an override for each new packages/ library) in CLAUDE.md and AGENTS.md. --- AGENTS.md | 9 +++++++++ CLAUDE.md | 6 ++++++ pnpm-lock.yaml | 38 ++++++++++++++++++++++++++++++++++++-- pnpm-workspace.yaml | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 13d918e3d..b276add68 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,6 +9,15 @@ apply to every coding agent regardless of tool. Run `pnpm install` before starting any task and again after every merge with `main`. +## Adding a New Library + +When you add a new library under `packages/`, add a `workspace:*` override for +it in `pnpm-workspace.yaml` under `overrides:` (e.g. +`'@tanstack/ai-foo': workspace:*`) and run `pnpm install`. Every `packages/` +library must have an entry — this forces any transitive or example dependency +that references a published version onto the local workspace copy. Use +`workspace:*` for internal deps in `package.json` as usual. + ## Pre-PR Quality Gate (MANDATORY) **Before committing, run the narrowest meaningful quality checks for your diff --git a/CLAUDE.md b/CLAUDE.md index f1fa41281..89f65e194 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -269,6 +269,12 @@ pnpm dev # start dev server - Use `workspace:*` protocol for internal package dependencies in `package.json` - Example: `"@tanstack/ai": "workspace:*"` +- **When adding a new library under `packages/`, add a `workspace:*` override for + it in `pnpm-workspace.yaml` under `overrides:`** (e.g. + `'@tanstack/ai-foo': workspace:*`), then run `pnpm install`. Every `packages/` + library must have an entry. This forces any transitive or example dependency + that references a published version onto the local workspace copy, so the + monorepo never accidentally builds/tests against an npm-published version. ### Tree-Shakeable Exports diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 446465aee..591ba10e5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,40 @@ settings: overrides: abbrev: ^3.0.0 + '@tanstack/ai': workspace:* + '@tanstack/ai-anthropic': workspace:* + '@tanstack/ai-cli': workspace:* + '@tanstack/ai-client': workspace:* + '@tanstack/ai-code-mode': workspace:* + '@tanstack/ai-code-mode-models-eval': workspace:* + '@tanstack/ai-code-mode-skills': workspace:* + '@tanstack/ai-devtools-core': workspace:* + '@tanstack/ai-elevenlabs': workspace:* + '@tanstack/ai-event-client': workspace:* + '@tanstack/ai-fal': workspace:* + '@tanstack/ai-gemini': workspace:* + '@tanstack/ai-grok': workspace:* + '@tanstack/ai-groq': workspace:* + '@tanstack/ai-isolate-cloudflare': workspace:* + '@tanstack/ai-isolate-node': workspace:* + '@tanstack/ai-isolate-quickjs': workspace:* + '@tanstack/ai-mcp': workspace:* + '@tanstack/ai-ollama': workspace:* + '@tanstack/ai-openai': workspace:* + '@tanstack/ai-openrouter': workspace:* + '@tanstack/ai-preact': workspace:* + '@tanstack/ai-react': workspace:* + '@tanstack/ai-react-ui': workspace:* + '@tanstack/ai-solid': workspace:* + '@tanstack/ai-solid-ui': workspace:* + '@tanstack/ai-svelte': workspace:* + '@tanstack/ai-utils': workspace:* + '@tanstack/ai-vue': workspace:* + '@tanstack/ai-vue-ui': workspace:* + '@tanstack/openai-base': workspace:* + '@tanstack/preact-ai-devtools': workspace:* + '@tanstack/react-ai-devtools': workspace:* + '@tanstack/solid-ai-devtools': workspace:* patchedDependencies: '@changesets/assemble-release-plan@6.0.9': 1bc53c741da20baad9cdeb674c599225dcf9fe6aa7b0de16ff87e63f12a97b24 @@ -1364,7 +1398,7 @@ importers: packages/ai-groq: dependencies: '@tanstack/ai': - specifier: workspace:^ + specifier: workspace:* version: link:../ai '@tanstack/ai-utils': specifier: workspace:* @@ -1528,7 +1562,7 @@ importers: packages/ai-preact: dependencies: '@tanstack/ai': - specifier: workspace:^ + specifier: workspace:* version: link:../ai '@tanstack/ai-client': specifier: workspace:* diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index dc784eb75..f808f240f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -8,6 +8,43 @@ trustPolicy: 'no-downgrade' # here. See https://github.com/pnpm/pnpm/releases/tag/v11.0.0. overrides: abbrev: ^3.0.0 + # Force every workspace library under packages/ to resolve to its local + # workspace copy, even when a transitive/example dependency references a + # published version. When you add a new package under packages/, add it here. + '@tanstack/ai': workspace:* + '@tanstack/ai-anthropic': workspace:* + '@tanstack/ai-cli': workspace:* + '@tanstack/ai-client': workspace:* + '@tanstack/ai-code-mode': workspace:* + '@tanstack/ai-code-mode-models-eval': workspace:* + '@tanstack/ai-code-mode-skills': workspace:* + '@tanstack/ai-devtools-core': workspace:* + '@tanstack/ai-elevenlabs': workspace:* + '@tanstack/ai-event-client': workspace:* + '@tanstack/ai-fal': workspace:* + '@tanstack/ai-gemini': workspace:* + '@tanstack/ai-grok': workspace:* + '@tanstack/ai-groq': workspace:* + '@tanstack/ai-isolate-cloudflare': workspace:* + '@tanstack/ai-isolate-node': workspace:* + '@tanstack/ai-isolate-quickjs': workspace:* + '@tanstack/ai-mcp': workspace:* + '@tanstack/ai-ollama': workspace:* + '@tanstack/ai-openai': workspace:* + '@tanstack/ai-openrouter': workspace:* + '@tanstack/ai-preact': workspace:* + '@tanstack/ai-react': workspace:* + '@tanstack/ai-react-ui': workspace:* + '@tanstack/ai-solid': workspace:* + '@tanstack/ai-solid-ui': workspace:* + '@tanstack/ai-svelte': workspace:* + '@tanstack/ai-utils': workspace:* + '@tanstack/ai-vue': workspace:* + '@tanstack/ai-vue-ui': workspace:* + '@tanstack/openai-base': workspace:* + '@tanstack/preact-ai-devtools': workspace:* + '@tanstack/react-ai-devtools': workspace:* + '@tanstack/solid-ai-devtools': workspace:* patchedDependencies: '@changesets/assemble-release-plan@6.0.9': patches/@changesets__assemble-release-plan@6.0.9.patch From cd555067af911f3c02eb2f29dc576fd2ee2f2643 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak <t.zlak@hotmail.com> Date: Mon, 8 Jun 2026 13:04:42 +0200 Subject: [PATCH 09/13] fix(ai-cli): address review feedback, pixelated preview, interactive hub Image preview / pixelation: - Only render inline image previews on graphics-capable terminals (iTerm2/ Kitty/WezTerm); elsewhere show just the saved path instead of muddy ANSI block-art. Detect the real image format from magic bytes instead of assuming PNG, and fail fast on `-o -` with multiple images. Interactive menu is now a hub: - `ts-ai` (no command) loops back to the menu after each action; Esc inside a sub-flow (prompt input or chat REPL) returns to the menu, while `ts-ai chat` run directly still exits on Esc. The splash only animates on first show. CodeRabbit fixes: - Validate --max-steps / --max-length as positive integers; validate --messages item shapes. - Bound the video poll loop with a timeout; add a fetch timeout + normalized CliError for artifact downloads. - Close already-opened MCP clients when a later --mcp spec fails. - Only classify a genuinely-missing package as PROVIDER_NOT_INSTALLED; surface real load errors as RUNTIME. - Read --attachment - as raw bytes (no UTF-8 corruption of binary stdin). - Prevent CliError detail from overriding canonical error fields. - Broaden on-demand (npx/dlx) detection in `update`. - docs/config.json: add updatedAt to the cli/overview entry; tidy import order. Unit tests cover resolveOutputPath, describeMcpServer, factory candidates, and tokenizeCommand; all green (35 unit, 15 e2e). --- docs/config.json | 3 +- packages/ai-cli/src/cli/activities/chat.ts | 23 +++++++- packages/ai-cli/src/cli/activities/image.ts | 17 +++++- .../ai-cli/src/cli/activities/summarize.ts | 7 +++ packages/ai-cli/src/cli/activities/video.ts | 10 ++++ packages/ai-cli/src/cli/artifact.ts | 14 ++++- packages/ai-cli/src/cli/interactive.ts | 53 +++++++++++-------- packages/ai-cli/src/cli/mcp-clients.ts | 2 + packages/ai-cli/src/cli/update.ts | 10 +++- packages/ai-cli/src/core/exit-codes.ts | 3 +- packages/ai-cli/src/core/io.ts | 33 +++++++----- packages/ai-cli/src/core/providers.ts | 19 ++++++- packages/ai-cli/src/render/ink.tsx | 12 +++-- packages/ai-cli/src/render/lazy.ts | 4 +- packages/ai-cli/src/render/menu.tsx | 7 ++- packages/ai-cli/tests/core.test.ts | 2 +- 16 files changed, 164 insertions(+), 55 deletions(-) diff --git a/docs/config.json b/docs/config.json index 3b56667fe..6aba85cd1 100644 --- a/docs/config.json +++ b/docs/config.json @@ -223,7 +223,8 @@ { "label": "ts-ai CLI", "to": "cli/overview", - "addedAt": "2026-06-07" + "addedAt": "2026-06-07", + "updatedAt": "2026-06-07" } ] }, diff --git a/packages/ai-cli/src/cli/activities/chat.ts b/packages/ai-cli/src/cli/activities/chat.ts index 7677e11aa..0003127aa 100644 --- a/packages/ai-cli/src/cli/activities/chat.ts +++ b/packages/ai-cli/src/cli/activities/chat.ts @@ -56,6 +56,12 @@ export async function runChat(ctx: RunContext, prompt: string): Promise<void> { : typeof ctx.options.maxSteps === 'string' ? Number(ctx.options.maxSteps) : undefined + if ( + maxSteps !== undefined && + (!Number.isInteger(maxSteps) || maxSteps < 1) + ) { + throw new CliError('USAGE', '--max-steps must be a positive integer.') + } // Resolve tools from MCP servers, optionally wrapped in Code Mode. const mcpSpecs = Array.isArray(ctx.options.mcp) @@ -90,7 +96,9 @@ export async function runChat(ctx: RunContext, prompt: string): Promise<void> { debug: false as never, ...(tools ? { tools: tools as never } : {}), ...(mcp ? { mcp: mcp as never } : {}), - ...(maxSteps ? { agentLoopStrategy: maxIterations(maxSteps) } : {}), + ...(maxSteps !== undefined + ? { agentLoopStrategy: maxIterations(maxSteps) } + : {}), } // Structured output: schema-bearing call resolves to the validated object. @@ -175,6 +183,19 @@ function parseMessages(value: unknown): Array<ModelMessageLike> { if (!Array.isArray(value)) { throw new CliError('USAGE', '--messages must be a JSON array of messages.') } + for (const m of value) { + if ( + typeof m !== 'object' || + m === null || + typeof (m as { role?: unknown }).role !== 'string' || + !('content' in (m as Record<string, unknown>)) + ) { + throw new CliError( + 'USAGE', + '--messages entries must be objects with a string "role" and a "content" field.', + ) + } + } return value as Array<ModelMessageLike> } diff --git a/packages/ai-cli/src/cli/activities/image.ts b/packages/ai-cli/src/cli/activities/image.ts index f29aa0e74..ee2c53a2f 100644 --- a/packages/ai-cli/src/cli/activities/image.ts +++ b/packages/ai-cli/src/cli/activities/image.ts @@ -1,6 +1,7 @@ -import { generateImage } from '@tanstack/ai' +import { detectImageMimeType, generateImage } from '@tanstack/ai' import { instantiateAdapter } from '../../core/providers' import { emitJson } from '../../core/emit' +import { CliError } from '../../core/exit-codes' import { mediaSourceToBytes, writeArtifact } from '../artifact' import { resolveAdapterContext } from '../context' import { renderImageResult } from '../../render/lazy' @@ -52,6 +53,13 @@ export async function runImage(ctx: RunContext, prompt: string): Promise<void> { const output = stringValue(ctx.options.output) const outputDir = stringValue(ctx.options.outputDir) + // Streaming bytes to stdout only makes sense for a single image. + if (output === '-' && result.images.length > 1) { + throw new CliError( + 'USAGE', + `Cannot stream ${result.images.length} images to stdout with "-o -". Use --output-dir or --count 1.`, + ) + } const written: Array<{ path: string | null mimeType: string @@ -60,7 +68,12 @@ export async function runImage(ctx: RunContext, prompt: string): Promise<void> { for (const [index, image] of result.images.entries()) { const bytes = await mediaSourceToBytes(image) - const mimeType = 'image/png' + // Detect the real format from the magic bytes rather than assuming PNG. + // detectImageMimeType reads only the leading base64 chars, so encoding a + // small prefix is enough. + const b64Prefix = + image.b64Json ?? Buffer.from(bytes.subarray(0, 16)).toString('base64') + const mimeType = detectImageMimeType(b64Prefix) ?? 'image/png' const ext = EXT_BY_MIME[mimeType] ?? 'png' // Only the first image honors an explicit -o; subsequent ones get a suffix. const target = diff --git a/packages/ai-cli/src/cli/activities/summarize.ts b/packages/ai-cli/src/cli/activities/summarize.ts index 7634789a9..0e3a8a1a9 100644 --- a/packages/ai-cli/src/cli/activities/summarize.ts +++ b/packages/ai-cli/src/cli/activities/summarize.ts @@ -1,6 +1,7 @@ import { summarize } from '@tanstack/ai' import { instantiateAdapter } from '../../core/providers' import { emitJson } from '../../core/emit' +import { CliError } from '../../core/exit-codes' import { resolveAdapterContext } from '../context' import { renderText } from '../../render/lazy' import type { RunContext } from '../context' @@ -39,6 +40,12 @@ export async function runSummarize( : typeof ctx.options.maxLength === 'string' ? Number(ctx.options.maxLength) : undefined + if ( + maxLength !== undefined && + (!Number.isInteger(maxLength) || maxLength < 1) + ) { + throw new CliError('USAGE', '--max-length must be a positive integer.') + } const result = (await summarize({ adapter: adapter as never, diff --git a/packages/ai-cli/src/cli/activities/video.ts b/packages/ai-cli/src/cli/activities/video.ts index 33af285ec..3f4bac542 100644 --- a/packages/ai-cli/src/cli/activities/video.ts +++ b/packages/ai-cli/src/cli/activities/video.ts @@ -8,6 +8,8 @@ import { renderArtifactPath } from '../../render/lazy' import type { RunContext } from '../context' const POLL_INTERVAL_MS = 3000 +/** Stop polling after this long so a stuck job can't hang the CLI forever. */ +const MAX_POLL_MS = 15 * 60 * 1000 /** * `ts-ai video` handler (experimental). Creates a job and, by default, blocks @@ -120,6 +122,7 @@ async function pollToCompletion( adapter: unknown, jobId: string, ) { + const deadline = Date.now() + MAX_POLL_MS for (;;) { const status = await getVideoJobStatus({ adapter: adapter as never, @@ -130,6 +133,13 @@ async function pollToCompletion( ) if (status.status === 'completed' || status.status === 'failed') return status + if (Date.now() >= deadline) { + throw new CliError( + 'PROVIDER', + `Timed out after ${Math.round(MAX_POLL_MS / 60000)}m waiting for video job ${jobId}. Re-check with: ts-ai video status ${jobId}`, + { detail: { jobId } }, + ) + } await sleep(POLL_INTERVAL_MS) } } diff --git a/packages/ai-cli/src/cli/artifact.ts b/packages/ai-cli/src/cli/artifact.ts index 8e3a62840..3a7a9c471 100644 --- a/packages/ai-cli/src/cli/artifact.ts +++ b/packages/ai-cli/src/cli/artifact.ts @@ -62,9 +62,19 @@ export async function writeArtifact( return path } -/** Fetch bytes from a generated-media URL. */ +/** Fetch bytes from a generated-media URL, with a timeout and normalized errors. */ export async function fetchBytes(url: string): Promise<Uint8Array> { - const res = await fetch(url) + let res: Response + try { + res = await fetch(url, { signal: AbortSignal.timeout(120_000) }) + } catch (cause) { + const timedOut = cause instanceof Error && cause.name === 'TimeoutError' + throw new CliError( + 'PROVIDER', + `Failed to download artifact from ${url}${timedOut ? ' (timed out)' : ''}.`, + { cause }, + ) + } if (!res.ok) { throw new CliError( 'PROVIDER', diff --git a/packages/ai-cli/src/cli/interactive.ts b/packages/ai-cli/src/cli/interactive.ts index 19004c1f9..ee8b59972 100644 --- a/packages/ai-cli/src/cli/interactive.ts +++ b/packages/ai-cli/src/cli/interactive.ts @@ -20,33 +20,42 @@ const DEFAULT_MODELS: Record<string, string> = { /** * The home screen shown when `ts-ai` is run with no command on a TTY: an - * animated wordmark + menu. Resolves the chosen action and runs it. + * animated wordmark + menu. Acts as a hub — after each action (or pressing Esc + * inside a sub-flow) it returns to the menu, until the user quits. */ export async function runHome(modelOverride?: string): Promise<number> { - const choice = await renderMenu() - if (choice.command === 'quit') return 0 + let first = true + for (;;) { + const choice = await renderMenu(first) + first = false + if (choice.command === 'quit') return 0 - if (choice.command === 'chat') { - return runChatRepl( - modelOverride ?? DEFAULT_MODELS['chat'] ?? 'openai/gpt-5.5', - ) - } + try { + if (choice.command === 'chat') { + // Esc in the REPL unmounts it and returns here → back to the menu. + await runChatRepl(modelOverride ?? DEFAULT_MODELS['chat'] ?? 'openai/gpt-5.5') + continue + } - const model = modelOverride ?? DEFAULT_MODELS[choice.command] - if (!model) { - process.stderr.write( - `Run it with a model, e.g.:\n ts-ai ${choice.command} "${choice.prompt ?? '<prompt>'}" --model <provider/model>\n`, - ) - return 0 + const model = modelOverride ?? DEFAULT_MODELS[choice.command] + const spec = findCommand(choice.command) + if (!model || !spec) { + process.stderr.write( + `Run it with a model, e.g.:\n ts-ai ${choice.command} "${choice.prompt ?? '<prompt>'}" --model <provider/model>\n`, + ) + continue + } + await dispatchCommand(spec, choice.prompt ? [choice.prompt] : [], { + model, + preview: true, + }) + } catch (err) { + // A failed action shouldn't crash the hub — report and return to the menu. + process.stderr.write( + `error: ${err instanceof Error ? err.message : String(err)}\n`, + ) + } } - - const spec = findCommand(choice.command) - if (!spec) return 0 - await dispatchCommand(spec, choice.prompt ? [choice.prompt] : [], { - model, - preview: true, - }) - return 0 } /** Launch the interactive chat REPL (also used by `ts-ai chat` with no prompt on a TTY). */ diff --git a/packages/ai-cli/src/cli/mcp-clients.ts b/packages/ai-cli/src/cli/mcp-clients.ts index 720c09bff..941d4f217 100644 --- a/packages/ai-cli/src/cli/mcp-clients.ts +++ b/packages/ai-cli/src/cli/mcp-clients.ts @@ -48,6 +48,8 @@ export async function buildMcpClients( const client = await mcp.createMCPClient({ transport }) clients.push(client) } catch (cause) { + // Don't leak the connections opened so far if a later one fails. + await Promise.all(clients.map((c) => c.close().catch(() => undefined))) throw new CliError( 'RUNTIME', `Failed to connect to MCP server "${spec}".`, diff --git a/packages/ai-cli/src/cli/update.ts b/packages/ai-cli/src/cli/update.ts index c8a2b9c8a..a735d0759 100644 --- a/packages/ai-cli/src/cli/update.ts +++ b/packages/ai-cli/src/cli/update.ts @@ -41,8 +41,16 @@ function runProcess(cmd: string, args: Array<string>): Promise<number | null> { } function isOnDemand(): boolean { + // Detect one-shot runners (npx, pnpm dlx, yarn dlx, bunx) — there's nothing + // to update, since each invocation already fetches the latest. const execPath = process.env.npm_execpath ?? '' - return execPath.includes('_npx') || process.env.npm_command === 'exec' + const userAgent = process.env.npm_config_user_agent ?? '' + return ( + execPath.includes('_npx') || + execPath.includes('dlx') || + process.env.npm_command === 'exec' || + /\bdlx\b/.test(userAgent) + ) } function upgradeCommand(agent: string): { cmd: string; args: Array<string> } { diff --git a/packages/ai-cli/src/core/exit-codes.ts b/packages/ai-cli/src/core/exit-codes.ts index 2f668e695..97326116a 100644 --- a/packages/ai-cli/src/core/exit-codes.ts +++ b/packages/ai-cli/src/core/exit-codes.ts @@ -72,10 +72,11 @@ export class CliError extends Error { provider?: string } & Record<string, unknown> { return { + // Spread detail first so it can never override the canonical fields. + ...(this.detail ?? {}), code: this.code, message: this.message, ...(this.provider ? { provider: this.provider } : {}), - ...(this.detail ?? {}), } } } diff --git a/packages/ai-cli/src/core/io.ts b/packages/ai-cli/src/core/io.ts index f02627c16..511e9ea7d 100644 --- a/packages/ai-cli/src/core/io.ts +++ b/packages/ai-cli/src/core/io.ts @@ -2,23 +2,32 @@ import { readFile } from 'node:fs/promises' import { extname } from 'node:path' import { CliError } from './exit-codes' -let stdinCache: string | undefined +let stdinBufferCache: Buffer | undefined /** - * Read all of stdin as a string. Returns '' if stdin is an interactive TTY. - * The result is memoized: stdin can only be drained once, so a later consumer - * (e.g. `--attachment -` after the prompt was read from stdin) gets the same - * content instead of an empty buffer. + * Read all of stdin as raw bytes. Returns an empty buffer on an interactive TTY. + * Memoized: stdin can only be drained once, so a later consumer (e.g. + * `--attachment -` after the prompt was read from stdin) gets the same bytes. */ -export async function readStdin(): Promise<string> { - if (process.stdin.isTTY) return '' - if (stdinCache !== undefined) return stdinCache +async function readStdinBuffer(): Promise<Buffer> { + if (process.stdin.isTTY) return Buffer.alloc(0) + if (stdinBufferCache !== undefined) return stdinBufferCache const chunks: Array<Buffer> = [] for await (const chunk of process.stdin) { chunks.push(chunk as Buffer) } - stdinCache = Buffer.concat(chunks).toString('utf8') - return stdinCache + stdinBufferCache = Buffer.concat(chunks) + return stdinBufferCache +} + +/** Read all of stdin as a UTF-8 string (for text prompts). */ +export async function readStdin(): Promise<string> { + return (await readStdinBuffer()).toString('utf8') +} + +/** Read all of stdin as raw bytes (for binary attachments — no text decoding). */ +export async function readStdinBytes(): Promise<Buffer> { + return readStdinBuffer() } /** @@ -83,9 +92,7 @@ export async function loadAttachments( for (const path of paths) { try { const buffer = - path === '-' - ? Buffer.from(await readStdin(), 'utf8') - : await readFile(path) + path === '-' ? await readStdinBytes() : await readFile(path) out.push({ path, mimeType: diff --git a/packages/ai-cli/src/core/providers.ts b/packages/ai-cli/src/core/providers.ts index c808a5b9e..e61645b2e 100644 --- a/packages/ai-cli/src/core/providers.ts +++ b/packages/ai-cli/src/core/providers.ts @@ -239,9 +239,24 @@ async function importProvider( try { return await import(entry.pkg) } catch (cause) { + const code = (cause as { code?: string }).code + const message = cause instanceof Error ? cause.message : String(cause) + // Only treat a genuinely-absent package as "not installed". If the package + // is present but throws while loading (broken build, missing transitive), + // surface the real error instead of a misleading install hint. + const missingPackage = + (code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') && + message.includes(entry.pkg) + if (missingPackage) { + throw new CliError( + 'PROVIDER_NOT_INSTALLED', + `Provider "${provider}" requires ${entry.pkg}. Install it: pnpm add ${entry.pkg}`, + { provider, detail: { package: entry.pkg }, cause }, + ) + } throw new CliError( - 'PROVIDER_NOT_INSTALLED', - `Provider "${provider}" requires ${entry.pkg}. Install it: pnpm add ${entry.pkg}`, + 'RUNTIME', + `Failed to load provider "${provider}" (${entry.pkg}): ${message}`, { provider, detail: { package: entry.pkg }, cause }, ) } diff --git a/packages/ai-cli/src/render/ink.tsx b/packages/ai-cli/src/render/ink.tsx index 35f507f0e..f39c47bf0 100644 --- a/packages/ai-cli/src/render/ink.tsx +++ b/packages/ai-cli/src/render/ink.tsx @@ -1,16 +1,18 @@ import { Box, Text, render } from 'ink' import terminalImage from 'terminal-image' -import { DIM, PINK, SUCCESS } from './theme' +import { DIM, PINK, SUCCESS, supportsInlineGraphics } from './theme' import type { RenderedImage } from './lazy' /** - * Encode an image file into a terminal-renderable string (native iTerm2/Kitty - * graphics where supported, ANSI block-art otherwise). Returns null on failure - * so a missing preview never breaks the run. + * Encode an image file into a terminal-renderable string. Only attempted on + * terminals with inline raster graphics (iTerm2/Kitty/WezTerm), where it renders + * crisply; elsewhere returns null so we show just the saved path instead of + * heavily pixelated ANSI block-art. Returns null on any failure too. */ async function encodePreview(path: string): Promise<string | null> { + if (!supportsInlineGraphics()) return null try { - return await terminalImage.file(path, { height: 20 }) + return await terminalImage.file(path, { height: 24 }) } catch { return null } diff --git a/packages/ai-cli/src/render/lazy.ts b/packages/ai-cli/src/render/lazy.ts index f39ca01a7..2b8ab53bc 100644 --- a/packages/ai-cli/src/render/lazy.ts +++ b/packages/ai-cli/src/render/lazy.ts @@ -38,9 +38,9 @@ export async function renderArtifactPath(input: { await renderArtifactPathInk(input) } -export async function renderMenu(): Promise<MenuChoice> { +export async function renderMenu(animate = true): Promise<MenuChoice> { const { runMenuInk } = await import('./menu') - return runMenuInk() + return runMenuInk(animate) } export async function renderChatRepl(input: { diff --git a/packages/ai-cli/src/render/menu.tsx b/packages/ai-cli/src/render/menu.tsx index ddc2f8474..29e6f501c 100644 --- a/packages/ai-cli/src/render/menu.tsx +++ b/packages/ai-cli/src/render/menu.tsx @@ -68,9 +68,11 @@ const ITEMS: Array<MenuItem> = [ function Menu({ onChoose, logo, + animate, }: { onChoose: (choice: MenuChoice) => void logo: string | null + animate: boolean }) { const { exit } = useApp() const [index, setIndex] = useState(0) @@ -134,7 +136,7 @@ function Menu({ return ( <Box flexDirection="column"> - <WelcomeHeader logo={logo} animate /> + <WelcomeHeader logo={logo} animate={animate} /> <Text color={DIM}>What do you want to do?</Text> <Box flexDirection="column" marginTop={1}> {ITEMS.map((item, i) => { @@ -158,7 +160,7 @@ function Menu({ } /** Render the home screen and resolve the user's choice. */ -export async function runMenuInk(): Promise<MenuChoice> { +export async function runMenuInk(animate = true): Promise<MenuChoice> { const logo = await loadLogo() // Clear the screen (and scrollback) so the welcome splash starts clean. if (process.stdout.isTTY) process.stdout.write('') @@ -167,6 +169,7 @@ export async function runMenuInk(): Promise<MenuChoice> { const { waitUntilExit } = render( <Menu logo={logo} + animate={animate} onChoose={(c) => { choice = c }} diff --git a/packages/ai-cli/tests/core.test.ts b/packages/ai-cli/tests/core.test.ts index c6ae0aa93..9a52b871f 100644 --- a/packages/ai-cli/tests/core.test.ts +++ b/packages/ai-cli/tests/core.test.ts @@ -1,3 +1,4 @@ +import { join } from 'node:path' import { describe, expect, it } from 'vitest' import { bundledProviders, @@ -10,7 +11,6 @@ import { resolveOutputMode } from '../src/core/output' import { coerceFlags } from '../src/cli/options' import { CliError, ExitCode, toCliError } from '../src/core/exit-codes' import { inferMimeType, resolvePrompt } from '../src/core/io' -import { join } from 'node:path' import { tokenizeCommand } from '../src/cli/mcp-clients' import { resolveOutputPath } from '../src/cli/artifact' import { describeMcpServer } from '../src/cli/mcp' From a87c3efff7d3f88419cadea7f6c7466e509d3547 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:05:47 +0000 Subject: [PATCH 10/13] ci: apply automated fixes --- packages/ai-cli/src/cli/activities/chat.ts | 5 +---- packages/ai-cli/src/cli/interactive.ts | 4 +++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/ai-cli/src/cli/activities/chat.ts b/packages/ai-cli/src/cli/activities/chat.ts index 0003127aa..8dd27474f 100644 --- a/packages/ai-cli/src/cli/activities/chat.ts +++ b/packages/ai-cli/src/cli/activities/chat.ts @@ -56,10 +56,7 @@ export async function runChat(ctx: RunContext, prompt: string): Promise<void> { : typeof ctx.options.maxSteps === 'string' ? Number(ctx.options.maxSteps) : undefined - if ( - maxSteps !== undefined && - (!Number.isInteger(maxSteps) || maxSteps < 1) - ) { + if (maxSteps !== undefined && (!Number.isInteger(maxSteps) || maxSteps < 1)) { throw new CliError('USAGE', '--max-steps must be a positive integer.') } diff --git a/packages/ai-cli/src/cli/interactive.ts b/packages/ai-cli/src/cli/interactive.ts index ee8b59972..0576f7b1d 100644 --- a/packages/ai-cli/src/cli/interactive.ts +++ b/packages/ai-cli/src/cli/interactive.ts @@ -33,7 +33,9 @@ export async function runHome(modelOverride?: string): Promise<number> { try { if (choice.command === 'chat') { // Esc in the REPL unmounts it and returns here → back to the menu. - await runChatRepl(modelOverride ?? DEFAULT_MODELS['chat'] ?? 'openai/gpt-5.5') + await runChatRepl( + modelOverride ?? DEFAULT_MODELS['chat'] ?? 'openai/gpt-5.5', + ) continue } From e4f4d839008cb1236e67c5d732b152d64569f5a7 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak <t.zlak@hotmail.com> Date: Mon, 8 Jun 2026 13:41:58 +0200 Subject: [PATCH 11/13] feat(ai-cli): always preview, pause results until Esc, loading spinner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Revert the graphics-only gate on image previews: render the inline preview everywhere (crisp in iTerm2/Kitty/WezTerm, ANSI block-art otherwise) — a blocky preview beats none. Keep the magic-byte format detection. - Result views (image/text/artifact) now stay on screen until the user presses Esc/Enter on an interactive terminal, then unmount. This fixes results being instantly cleared when returning to the interactive hub (and a latent hang where these views never exited); non-interactive output unmounts immediately. - Add a stderr progress spinner (quiet- and TTY-aware) around every generation (chat, image, video, audio, speech, transcribe, summarize) so there's a clear loading indicator; stdout stays the clean machine payload. --- packages/ai-cli/src/cli/activities/audio.ts | 25 ++++---- packages/ai-cli/src/cli/activities/chat.ts | 17 ++--- packages/ai-cli/src/cli/activities/image.ts | 27 ++++---- packages/ai-cli/src/cli/activities/speech.ts | 27 ++++---- .../ai-cli/src/cli/activities/summarize.ts | 27 ++++---- .../ai-cli/src/cli/activities/transcribe.ts | 29 +++++---- packages/ai-cli/src/cli/activities/video.ts | 25 ++++---- packages/ai-cli/src/cli/context.ts | 29 +++++++-- packages/ai-cli/src/core/spinner.ts | 28 +++++++++ packages/ai-cli/src/render/ink.tsx | 63 ++++++++++++++----- 10 files changed, 192 insertions(+), 105 deletions(-) create mode 100644 packages/ai-cli/src/core/spinner.ts diff --git a/packages/ai-cli/src/cli/activities/audio.ts b/packages/ai-cli/src/cli/activities/audio.ts index aa2dd59e6..c918626c4 100644 --- a/packages/ai-cli/src/cli/activities/audio.ts +++ b/packages/ai-cli/src/cli/activities/audio.ts @@ -2,7 +2,7 @@ import { generateAudio } from '@tanstack/ai' import { instantiateAdapter } from '../../core/providers' import { emitJson } from '../../core/emit' import { mediaSourceToBytes, writeArtifact } from '../artifact' -import { resolveAdapterContext } from '../context' +import { resolveAdapterContext, withSpinner } from '../context' import { renderArtifactPath } from '../../render/lazy' import type { RunContext } from '../context' @@ -37,10 +37,6 @@ export async function runAudio(ctx: RunContext, prompt: string): Promise<void> { config: adapterConfig, }) - ctx.logger.info( - `Generating audio with ${resolved.provider}/${resolved.model}…`, - ) - const duration = typeof ctx.options.duration === 'number' ? ctx.options.duration @@ -48,13 +44,18 @@ export async function runAudio(ctx: RunContext, prompt: string): Promise<void> { ? Number(ctx.options.duration) : undefined - const result = (await generateAudio({ - adapter: adapter as never, - prompt, - duration, - modelOptions: modelOptions as never, - debug: false, - })) as AudioResultLike + const result = (await withSpinner( + ctx, + `Generating audio with ${resolved.provider}/${resolved.model}…`, + () => + generateAudio({ + adapter: adapter as never, + prompt, + duration, + modelOptions: modelOptions as never, + debug: false, + }), + )) as AudioResultLike const bytes = await mediaSourceToBytes(result.audio) const ext = EXT_BY_CONTENT_TYPE[result.audio.contentType ?? ''] ?? 'mp3' diff --git a/packages/ai-cli/src/cli/activities/chat.ts b/packages/ai-cli/src/cli/activities/chat.ts index 8dd27474f..baf4d5d0d 100644 --- a/packages/ai-cli/src/cli/activities/chat.ts +++ b/packages/ai-cli/src/cli/activities/chat.ts @@ -9,7 +9,7 @@ import { emitEvent, emitJson } from '../../core/emit' import { CliError } from '../../core/exit-codes' import { loadJsonInput } from '../../core/config' import { loadAttachments } from '../../core/io' -import { resolveAdapterContext } from '../context' +import { resolveAdapterContext, withSpinner } from '../context' import { renderText } from '../../render/lazy' import { buildMcpClients } from '../mcp-clients' import { buildCodeMode } from '../code-mode' @@ -98,12 +98,15 @@ export async function runChat(ctx: RunContext, prompt: string): Promise<void> { : {}), } + const thinking = `Thinking with ${resolved.provider}/${resolved.model}…` + // Structured output: schema-bearing call resolves to the validated object. if (schema !== undefined) { - const data = await (chat({ - ...base, - outputSchema: schema as never, - }) as Promise<unknown>) + const data = await withSpinner( + ctx, + thinking, + () => chat({ ...base, outputSchema: schema as never }) as Promise<unknown>, + ) if (ctx.mode === 'pretty') { await renderText(JSON.stringify(data, null, 2)) return @@ -123,8 +126,8 @@ export async function runChat(ctx: RunContext, prompt: string): Promise<void> { // Buffered: accumulate the stream into a rich envelope via StreamProcessor. const processor = new StreamProcessor() processor.setMessages(modelMessagesToUIMessages(messages as never)) - const result = await processor.process( - chat({ ...base, stream: true }) as AsyncIterable<never>, + const result = await withSpinner(ctx, thinking, () => + processor.process(chat({ ...base, stream: true }) as AsyncIterable<never>), ) if (ctx.mode === 'pretty') { diff --git a/packages/ai-cli/src/cli/activities/image.ts b/packages/ai-cli/src/cli/activities/image.ts index ee2c53a2f..ac1270215 100644 --- a/packages/ai-cli/src/cli/activities/image.ts +++ b/packages/ai-cli/src/cli/activities/image.ts @@ -3,7 +3,7 @@ import { instantiateAdapter } from '../../core/providers' import { emitJson } from '../../core/emit' import { CliError } from '../../core/exit-codes' import { mediaSourceToBytes, writeArtifact } from '../artifact' -import { resolveAdapterContext } from '../context' +import { resolveAdapterContext, withSpinner } from '../context' import { renderImageResult } from '../../render/lazy' import type { RunContext } from '../context' @@ -37,19 +37,20 @@ export async function runImage(ctx: RunContext, prompt: string): Promise<void> { config: adapterConfig, }) - ctx.logger.info( + const result = (await withSpinner( + ctx, `Generating image with ${resolved.provider}/${resolved.model}…`, - ) - - const result = (await generateImage({ - // The CLI resolves adapters at runtime; the static generic shape is erased. - adapter: adapter as never, - prompt, - numberOfImages: numberValue(ctx.options.count) ?? 1, - size: stringValue(ctx.options.size) as never, - modelOptions: modelOptions as never, - debug: false, - })) as ImageResultLike + () => + generateImage({ + // The CLI resolves adapters at runtime; the static generic is erased. + adapter: adapter as never, + prompt, + numberOfImages: numberValue(ctx.options.count) ?? 1, + size: stringValue(ctx.options.size) as never, + modelOptions: modelOptions as never, + debug: false, + }), + )) as ImageResultLike const output = stringValue(ctx.options.output) const outputDir = stringValue(ctx.options.outputDir) diff --git a/packages/ai-cli/src/cli/activities/speech.ts b/packages/ai-cli/src/cli/activities/speech.ts index aa2ff88e1..1f160e528 100644 --- a/packages/ai-cli/src/cli/activities/speech.ts +++ b/packages/ai-cli/src/cli/activities/speech.ts @@ -2,7 +2,7 @@ import { generateSpeech } from '@tanstack/ai' import { instantiateAdapter } from '../../core/providers' import { emitJson } from '../../core/emit' import { writeArtifact } from '../artifact' -import { resolveAdapterContext } from '../context' +import { resolveAdapterContext, withSpinner } from '../context' import { renderArtifactPath } from '../../render/lazy' import type { RunContext } from '../context' @@ -27,19 +27,20 @@ export async function runSpeech(ctx: RunContext, text: string): Promise<void> { config: adapterConfig, }) - ctx.logger.info( + const result = (await withSpinner( + ctx, `Synthesizing speech with ${resolved.provider}/${resolved.model}…`, - ) - - const result = (await generateSpeech({ - adapter: adapter as never, - text, - voice: str(ctx.options.voice), - format: str(ctx.options.format) as SpeechFormat | undefined, - speed: num(ctx.options.speed), - modelOptions: modelOptions as never, - debug: false, - })) as TTSResultLike + () => + generateSpeech({ + adapter: adapter as never, + text, + voice: str(ctx.options.voice), + format: str(ctx.options.format) as SpeechFormat | undefined, + speed: num(ctx.options.speed), + modelOptions: modelOptions as never, + debug: false, + }), + )) as TTSResultLike const bytes = new Uint8Array(Buffer.from(result.audio, 'base64')) const ext = result.format || 'mp3' diff --git a/packages/ai-cli/src/cli/activities/summarize.ts b/packages/ai-cli/src/cli/activities/summarize.ts index 0e3a8a1a9..a375cec65 100644 --- a/packages/ai-cli/src/cli/activities/summarize.ts +++ b/packages/ai-cli/src/cli/activities/summarize.ts @@ -2,7 +2,7 @@ import { summarize } from '@tanstack/ai' import { instantiateAdapter } from '../../core/providers' import { emitJson } from '../../core/emit' import { CliError } from '../../core/exit-codes' -import { resolveAdapterContext } from '../context' +import { resolveAdapterContext, withSpinner } from '../context' import { renderText } from '../../render/lazy' import type { RunContext } from '../context' @@ -29,8 +29,6 @@ export async function runSummarize( config: adapterConfig, }) - ctx.logger.info(`Summarizing with ${resolved.provider}/${resolved.model}…`) - const focus = Array.isArray(ctx.options.focus) ? (ctx.options.focus as Array<string>) : undefined @@ -47,15 +45,20 @@ export async function runSummarize( throw new CliError('USAGE', '--max-length must be a positive integer.') } - const result = (await summarize({ - adapter: adapter as never, - text, - maxLength, - style: ctx.options.style as SummaryStyle | undefined, - focus, - modelOptions: modelOptions as never, - debug: false, - })) as SummaryResultLike + const result = (await withSpinner( + ctx, + `Summarizing with ${resolved.provider}/${resolved.model}…`, + () => + summarize({ + adapter: adapter as never, + text, + maxLength, + style: ctx.options.style as SummaryStyle | undefined, + focus, + modelOptions: modelOptions as never, + debug: false, + }), + )) as SummaryResultLike if (ctx.mode === 'pretty') { await renderText(result.summary) diff --git a/packages/ai-cli/src/cli/activities/transcribe.ts b/packages/ai-cli/src/cli/activities/transcribe.ts index 754573984..c1945e37e 100644 --- a/packages/ai-cli/src/cli/activities/transcribe.ts +++ b/packages/ai-cli/src/cli/activities/transcribe.ts @@ -3,7 +3,7 @@ import { generateTranscription } from '@tanstack/ai' import { instantiateAdapter } from '../../core/providers' import { emitJson } from '../../core/emit' import { CliError } from '../../core/exit-codes' -import { resolveAdapterContext } from '../context' +import { resolveAdapterContext, withSpinner } from '../context' import { renderText } from '../../render/lazy' import type { RunContext } from '../context' @@ -54,18 +54,21 @@ export async function runTranscribe( }) } - ctx.logger.info(`Transcribing with ${resolved.provider}/${resolved.model}…`) - - const result = (await generateTranscription({ - adapter: adapter as never, - audio, - language: - typeof ctx.options.language === 'string' - ? ctx.options.language - : undefined, - modelOptions: modelOptions as never, - debug: false, - })) as TranscriptionResultLike + const result = (await withSpinner( + ctx, + `Transcribing with ${resolved.provider}/${resolved.model}…`, + () => + generateTranscription({ + adapter: adapter as never, + audio, + language: + typeof ctx.options.language === 'string' + ? ctx.options.language + : undefined, + modelOptions: modelOptions as never, + debug: false, + }), + )) as TranscriptionResultLike if (ctx.mode === 'pretty') { await renderText(result.text) diff --git a/packages/ai-cli/src/cli/activities/video.ts b/packages/ai-cli/src/cli/activities/video.ts index 3f4bac542..69485b48d 100644 --- a/packages/ai-cli/src/cli/activities/video.ts +++ b/packages/ai-cli/src/cli/activities/video.ts @@ -3,7 +3,7 @@ import { instantiateAdapter } from '../../core/providers' import { emitJson } from '../../core/emit' import { CliError } from '../../core/exit-codes' import { fetchBytes, writeArtifact } from '../artifact' -import { resolveAdapterContext } from '../context' +import { resolveAdapterContext, withSpinner } from '../context' import { renderArtifactPath } from '../../render/lazy' import type { RunContext } from '../context' @@ -26,20 +26,21 @@ export async function runVideo(ctx: RunContext, prompt: string): Promise<void> { config: adapterConfig, }) - ctx.logger.info( + const job = await withSpinner( + ctx, `Creating video job with ${resolved.provider}/${resolved.model}…`, + () => + generateVideo({ + adapter: adapter as never, + prompt, + size: (typeof ctx.options.size === 'string' + ? ctx.options.size + : undefined) as never, + modelOptions: modelOptions as never, + debug: false, + }), ) - const job = await generateVideo({ - adapter: adapter as never, - prompt, - size: (typeof ctx.options.size === 'string' - ? ctx.options.size - : undefined) as never, - modelOptions: modelOptions as never, - debug: false, - }) - if (ctx.options.wait === false) { if (ctx.mode === 'pretty') { await renderArtifactPath({ diff --git a/packages/ai-cli/src/cli/context.ts b/packages/ai-cli/src/cli/context.ts index 9297f553d..53ad339b9 100644 --- a/packages/ai-cli/src/cli/context.ts +++ b/packages/ai-cli/src/cli/context.ts @@ -1,6 +1,7 @@ import { loadConfig, mergeOptions } from '../core/config' import { resolveOutputMode } from '../core/output' import { CliLogger } from '../core/logger' +import { startSpinner } from '../core/spinner' import { resolveApiKey, resolveModelSlug } from '../core/providers' import { CliError } from '../core/exit-codes' import type { OutputMode } from '../core/output' @@ -8,7 +9,8 @@ import type { ResolvedModel } from '../core/providers' /** * Everything a command handler needs after common flags are resolved: the - * merged option bag (flags > config), the output mode, and a stderr logger. + * merged option bag (flags > config), the output mode, a stderr logger, and a + * progress spinner. */ export interface RunContext { mode: OutputMode @@ -17,6 +19,8 @@ export interface RunContext { options: Record<string, unknown> /** Wall-clock used for deterministic artifact naming within one invocation. */ now: number + /** Start a progress spinner (stderr); returns a stop function. No-op when --quiet. */ + spinner: (label: string) => () => void } export async function createRunContext( @@ -28,11 +32,24 @@ export async function createRunContext( json: Boolean(options.json), stream: Boolean(options.stream), }) - const logger = new CliLogger({ - verbose: Boolean(options.verbose), - quiet: Boolean(options.quiet), - }) - return { mode, logger, options, now: Date.now() } + const quiet = Boolean(options.quiet) + const logger = new CliLogger({ verbose: Boolean(options.verbose), quiet }) + const spinner = (label: string) => (quiet ? () => {} : startSpinner(label)) + return { mode, logger, options, now: Date.now(), spinner } +} + +/** Run `fn` with a progress spinner showing `label` until it settles. */ +export async function withSpinner<T>( + ctx: RunContext, + label: string, + fn: () => Promise<T>, +): Promise<T> { + const stop = ctx.spinner(label) + try { + return await fn() + } finally { + stop() + } } export interface ResolvedAdapterContext { diff --git a/packages/ai-cli/src/core/spinner.ts b/packages/ai-cli/src/core/spinner.ts new file mode 100644 index 000000000..8a0d764a4 --- /dev/null +++ b/packages/ai-cli/src/core/spinner.ts @@ -0,0 +1,28 @@ +const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] +const PINK = '' +const RESET = '' + +/** + * Show a progress spinner on stderr while a long operation runs, returning a + * stop function. On a non-TTY stderr (e.g. a harness capturing logs) it writes + * the label once instead of animating. stdout — the machine payload — is never + * touched. + */ +export function startSpinner(label: string): () => void { + if (!process.stderr.isTTY) { + process.stderr.write(`${label}\n`) + return () => {} + } + let i = 0 + process.stderr.write('[?25l') // hide cursor + const render = () => { + process.stderr.write(`\r${PINK}${FRAMES[i % FRAMES.length]}${RESET} ${label}`) + i += 1 + } + render() + const id = setInterval(render, 80) + return () => { + clearInterval(id) + process.stderr.write('\r[?25h') // clear line + show cursor + } +} diff --git a/packages/ai-cli/src/render/ink.tsx b/packages/ai-cli/src/render/ink.tsx index f39c47bf0..9716c5acd 100644 --- a/packages/ai-cli/src/render/ink.tsx +++ b/packages/ai-cli/src/render/ink.tsx @@ -1,16 +1,16 @@ -import { Box, Text, render } from 'ink' +import { useEffect } from 'react' +import { Box, Text, render, useApp, useInput } from 'ink' import terminalImage from 'terminal-image' -import { DIM, PINK, SUCCESS, supportsInlineGraphics } from './theme' +import { DIM, PINK, SUCCESS } from './theme' +import type { ReactNode } from 'react' import type { RenderedImage } from './lazy' /** - * Encode an image file into a terminal-renderable string. Only attempted on - * terminals with inline raster graphics (iTerm2/Kitty/WezTerm), where it renders - * crisply; elsewhere returns null so we show just the saved path instead of - * heavily pixelated ANSI block-art. Returns null on any failure too. + * Encode an image file into a terminal-renderable string — native iTerm2/Kitty + * graphics where supported, ANSI block-art otherwise (blocky, but a preview is + * better than none). Returns null only on failure. */ async function encodePreview(path: string): Promise<string | null> { - if (!supportsInlineGraphics()) return null try { return await terminalImage.file(path, { height: 24 }) } catch { @@ -18,6 +18,42 @@ async function encodePreview(path: string): Promise<string | null> { } } +/** + * Wrap a finished result. On an interactive terminal it stays on screen until + * the user presses Esc/Enter (so a hub action's output isn't instantly cleared, + * and one-shot renders don't hang); otherwise it unmounts immediately. + */ +function ResultView({ children }: { children: ReactNode }) { + const { exit } = useApp() + const interactive = Boolean(process.stdin.isTTY) + + useInput( + (_input, key) => { + if (key.escape || key.return) exit() + }, + { isActive: interactive }, + ) + useEffect(() => { + if (!interactive) exit() + }, [interactive, exit]) + + return ( + <Box flexDirection="column"> + {children} + {interactive ? ( + <Box marginTop={1}> + <Text color={DIM}>Press Esc to continue</Text> + </Box> + ) : null} + </Box> + ) +} + +async function renderResult(content: ReactNode): Promise<void> { + const { waitUntilExit } = render(<ResultView>{content}</ResultView>) + await waitUntilExit() +} + /** Render generated images with an inline preview + the saved path(s). */ export async function renderImageResultInk(input: { model: string @@ -28,7 +64,7 @@ export async function renderImageResultInk(input: { ? await Promise.all(input.images.map((image) => encodePreview(image.path))) : input.images.map(() => null) - const { waitUntilExit } = render( + await renderResult( <Box flexDirection="column" gap={1}> <Text> <Text color={SUCCESS}>✓ </Text> @@ -46,17 +82,11 @@ export async function renderImageResultInk(input: { ))} </Box>, ) - await waitUntilExit() } /** Render a block of finished text (e.g. chat one-shot, summary). */ export async function renderTextInk(text: string): Promise<void> { - const { waitUntilExit } = render( - <Box> - <Text>{text}</Text> - </Box>, - ) - await waitUntilExit() + await renderResult(<Text>{text}</Text>) } /** Render a saved-artifact confirmation with the path and metadata. */ @@ -65,7 +95,7 @@ export async function renderArtifactPathInk(input: { path: string meta?: Record<string, unknown> }): Promise<void> { - const { waitUntilExit } = render( + await renderResult( <Box flexDirection="column"> <Text> <Text color={SUCCESS}>✓ </Text> @@ -81,5 +111,4 @@ export async function renderArtifactPathInk(input: { : null} </Box>, ) - await waitUntilExit() } From aacf97e8727b3cfc49e7d698d6a4b92f937f4b68 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:43:37 +0000 Subject: [PATCH 12/13] ci: apply automated fixes --- packages/ai-cli/src/cli/activities/chat.ts | 7 +++++-- packages/ai-cli/src/core/spinner.ts | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/ai-cli/src/cli/activities/chat.ts b/packages/ai-cli/src/cli/activities/chat.ts index baf4d5d0d..6bc2c08d6 100644 --- a/packages/ai-cli/src/cli/activities/chat.ts +++ b/packages/ai-cli/src/cli/activities/chat.ts @@ -105,7 +105,8 @@ export async function runChat(ctx: RunContext, prompt: string): Promise<void> { const data = await withSpinner( ctx, thinking, - () => chat({ ...base, outputSchema: schema as never }) as Promise<unknown>, + () => + chat({ ...base, outputSchema: schema as never }) as Promise<unknown>, ) if (ctx.mode === 'pretty') { await renderText(JSON.stringify(data, null, 2)) @@ -127,7 +128,9 @@ export async function runChat(ctx: RunContext, prompt: string): Promise<void> { const processor = new StreamProcessor() processor.setMessages(modelMessagesToUIMessages(messages as never)) const result = await withSpinner(ctx, thinking, () => - processor.process(chat({ ...base, stream: true }) as AsyncIterable<never>), + processor.process( + chat({ ...base, stream: true }) as AsyncIterable<never>, + ), ) if (ctx.mode === 'pretty') { diff --git a/packages/ai-cli/src/core/spinner.ts b/packages/ai-cli/src/core/spinner.ts index 8a0d764a4..67205c5d2 100644 --- a/packages/ai-cli/src/core/spinner.ts +++ b/packages/ai-cli/src/core/spinner.ts @@ -16,7 +16,9 @@ export function startSpinner(label: string): () => void { let i = 0 process.stderr.write('[?25l') // hide cursor const render = () => { - process.stderr.write(`\r${PINK}${FRAMES[i % FRAMES.length]}${RESET} ${label}`) + process.stderr.write( + `\r${PINK}${FRAMES[i % FRAMES.length]}${RESET} ${label}`, + ) i += 1 } render() From 87a99a9e4e762c0a7214c69369368589d75f3db6 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak <t.zlak@hotmail.com> Date: Mon, 8 Jun 2026 13:57:06 +0200 Subject: [PATCH 13/13] fix(ai-cli): Ctrl+C always exits the CLI from any interactive screen Previously, inside the interactive hub, Ctrl+C in the chat REPL or a result view only unmounted that Ink app and looped back to the menu. Disable Ink's default Ctrl+C handling (exitOnCtrlC: false) and handle it explicitly in the menu, REPL, and result views: restore the terminal (show cursor, leave raw mode) and exit the process with code 130. Esc keeps its return-to-menu / dismiss behavior. --- packages/ai-cli/src/render/exit.ts | 15 +++++++++++++++ packages/ai-cli/src/render/ink.tsx | 8 ++++++-- packages/ai-cli/src/render/menu.tsx | 3 +++ packages/ai-cli/src/render/repl.tsx | 3 +++ 4 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 packages/ai-cli/src/render/exit.ts diff --git a/packages/ai-cli/src/render/exit.ts b/packages/ai-cli/src/render/exit.ts new file mode 100644 index 000000000..f23ddd3b2 --- /dev/null +++ b/packages/ai-cli/src/render/exit.ts @@ -0,0 +1,15 @@ +/** True when an Ink key event is Ctrl+C. */ +export function isCtrlC(input: string, key: { ctrl: boolean }): boolean { + return key.ctrl && input === 'c' +} + +/** + * Exit the whole CLI immediately (used for Ctrl+C from any Ink screen). Restores + * the terminal first — show the cursor and leave raw mode — so the user's shell + * isn't left without echo. Uses exit code 130 (128 + SIGINT), the convention. + */ +export function forceExit(): never { + process.stdout.write('[?25h') // show cursor + if (process.stdin.isTTY) process.stdin.setRawMode(false) + process.exit(130) +} diff --git a/packages/ai-cli/src/render/ink.tsx b/packages/ai-cli/src/render/ink.tsx index 9716c5acd..bdd3a96ac 100644 --- a/packages/ai-cli/src/render/ink.tsx +++ b/packages/ai-cli/src/render/ink.tsx @@ -2,6 +2,7 @@ import { useEffect } from 'react' import { Box, Text, render, useApp, useInput } from 'ink' import terminalImage from 'terminal-image' import { DIM, PINK, SUCCESS } from './theme' +import { forceExit, isCtrlC } from './exit' import type { ReactNode } from 'react' import type { RenderedImage } from './lazy' @@ -28,7 +29,8 @@ function ResultView({ children }: { children: ReactNode }) { const interactive = Boolean(process.stdin.isTTY) useInput( - (_input, key) => { + (input, key) => { + if (isCtrlC(input, key)) forceExit() if (key.escape || key.return) exit() }, { isActive: interactive }, @@ -50,7 +52,9 @@ function ResultView({ children }: { children: ReactNode }) { } async function renderResult(content: ReactNode): Promise<void> { - const { waitUntilExit } = render(<ResultView>{content}</ResultView>) + const { waitUntilExit } = render(<ResultView>{content}</ResultView>, { + exitOnCtrlC: false, + }) await waitUntilExit() } diff --git a/packages/ai-cli/src/render/menu.tsx b/packages/ai-cli/src/render/menu.tsx index 29e6f501c..789e0c818 100644 --- a/packages/ai-cli/src/render/menu.tsx +++ b/packages/ai-cli/src/render/menu.tsx @@ -1,6 +1,7 @@ import { useState } from 'react' import { Box, Text, render, useApp, useInput } from 'ink' import { DIM, PINK } from './theme' +import { forceExit, isCtrlC } from './exit' import { WelcomeHeader, loadLogo } from './welcome' /** A selectable action from the home menu. */ @@ -80,6 +81,7 @@ function Menu({ const [draft, setDraft] = useState('') useInput((input, key) => { + if (isCtrlC(input, key)) forceExit() if (promptFor) { if (key.return) { onChoose({ command: promptFor.command, prompt: draft }) @@ -174,6 +176,7 @@ export async function runMenuInk(animate = true): Promise<MenuChoice> { choice = c }} />, + { exitOnCtrlC: false }, ) void waitUntilExit().then(() => resolve(choice)) }) diff --git a/packages/ai-cli/src/render/repl.tsx b/packages/ai-cli/src/render/repl.tsx index 05ea55344..13638e8a9 100644 --- a/packages/ai-cli/src/render/repl.tsx +++ b/packages/ai-cli/src/render/repl.tsx @@ -1,6 +1,7 @@ import { useState } from 'react' import { Box, Text, render, useApp, useInput } from 'ink' import { DIM, ERROR_RED, PINK } from './theme' +import { forceExit, isCtrlC } from './exit' import { BrandMark } from './welcome' export interface ReplMessage { @@ -27,6 +28,7 @@ function Repl({ const [error, setError] = useState<string | null>(null) useInput((input, key) => { + if (isCtrlC(input, key)) forceExit() if (busy) return if (key.return) { const text = draft.trim() @@ -104,6 +106,7 @@ export async function runChatReplInk(input: { }): Promise<void> { const { waitUntilExit } = render( <Repl model={input.model} respond={input.respond} />, + { exitOnCtrlC: false }, ) await waitUntilExit() }