diff --git a/README.md b/README.md index 12531bb..a063b95 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,22 @@ Command-line interface for the [MiniMax Token Plan](https://platform.minimax.io/ | \/ |_ _| \ | |_ _| \/ | / \ \ \/ / | |\/| || || \| || || |\/| | / _ \ \ / | | | || || |\ || || | | |/ ___ \/ \ - |_| |_|___|_| \_|___|_| |_/_/ \_\/_/\ + |_| |_|___|_| \_|___|_| |_/_/ \_\_/\ ``` Generate text, images, video, speech, and music from the terminal. Supports both the **Global** (`api.minimax.io`) and **CN** (`api.minimaxi.com`) platforms with automatic region detection. +## What's New (v0.4.0) + +**File management + Vision `file_id` support:** + +```bash +FILE_ID=$(minimax file upload --file image.png --quiet) +minimax vision describe --file-id $FILE_ID --prompt "这张图里有几个人?" +``` + +Also new in v0.3.0: **`minimax config export-schema`** — export all commands as Anthropic/OpenAI-compatible JSON tool schemas with a single command. See [Changelog](#changelog) for full version history. + ## Installation ### Standalone binary (recommended) @@ -20,9 +31,7 @@ Generate text, images, video, speech, and music from the terminal. Supports both curl -fsSL https://raw.githubusercontent.com/MiniMax-AI-Dev/minimax-cli/main/install.sh | sh ``` -Downloads a precompiled binary to `/usr/local/bin/minimax`. No runtime required. - -### npm (requires Node 18+) +### npm ```bash npm install -g minimax-cli @@ -37,210 +46,98 @@ bun install -g minimax-cli ### From source ```bash -git clone https://github.com/MiniMax-AI-Dev/minimax-cli.git -cd minimax-cli +git clone https://github.com/MiniMax-AI-Dev/minimax-cli.git && cd minimax-cli bun install - -# Run directly from source bun run dev -- --help - -# Or build a standalone binary and install it -bun run build:local -mkdir -p ~/.local/bin -cp dist/minimax ~/.local/bin/minimax -minimax --help ``` ## Quick start ```bash -# Set your API key minimax auth login --api-key sk-xxxxx - -# The CLI auto-detects your region (global or cn) on first run - -# Chat minimax text chat --message "user:What is MiniMax?" - -# Generate an image minimax image generate --prompt "A cat in a spacesuit on Mars" - -# Text-to-speech -minimax speech synthesize --text "Hello, world!" --out hello.mp3 - -# Search the web -minimax search query --q "latest AI news" - -# Describe an image -minimax vision describe --image photo.jpg --prompt "What is this?" -``` - -## Commands - -Run `minimax --help` to see the full list of options, defaults, and usage examples for any command. - -| Command | Description | Command-specific flags | -|---|---|---| -| `auth login` | Authenticate via OAuth or API key | `--method`, `--api-key`, `--no-browser` | -| `auth status` | Show current authentication state | — | -| `auth refresh` | Manually refresh OAuth token | — | -| `auth logout` | Revoke tokens and clear stored credentials | — | -| `text chat` | Send a chat completion | `--model`, `--message`, `--messages-file`, `--system`, `--max-tokens`, `--temperature`, `--top-p`, `--stream`, `--tool` | -| `speech synthesize` | Synchronous TTS, up to 10k chars | `--model`, `--text`, `--text-file`, `--voice`, `--speed`, `--volume`, `--pitch`, `--format`, `--sample-rate`, `--bitrate`, `--channels`, `--language`, `--subtitles`, `--pronunciation`, `--sound-effect`, `--out`, `--out-format`, `--stream` | -| `image generate` | Generate images | `--prompt`, `--aspect-ratio`, `--n`, `--subject-ref`, `--out-dir`, `--out-prefix` | -| `video generate` | Create a video generation task | `--model`, `--prompt`, `--first-frame`, `--callback-url`, `--wait`, `--poll-interval`, `--download` | -| `video task get` | Query video task status | `--task-id` | -| `video download` | Download a completed video by file ID | `--file-id`, `--out` | -| `music generate` | Generate a song | `--prompt`, `--lyrics`, `--lyrics-file`, `--format`, `--sample-rate`, `--bitrate`, `--stream`, `--out`, `--out-format` | -| `search query` | Search the web via MiniMax | `--q` | -| `vision describe` | Describe an image using MiniMax VLM | `--image`, `--prompt` | -| `quota show` | Display Token Plan usage and remaining quotas | — | -| `config show` | Show current configuration | — | -| `config set` | Set a config value | `--key`, `--value` | - -All commands also accept [global flags](#global-flags) (`--api-key`, `--output`, `--quiet`, `--verbose`, etc.). - -### Examples - -#### text - -```bash -# Simple chat -minimax text chat --message "user:Hello" - -# With system prompt and model selection -minimax text chat --model MiniMax-M2.7-highspeed \ - --system "You are a coding assistant." \ - --message "user:Write fizzbuzz in Python" - -# Streaming (default in TTY) -minimax text chat --message "user:Tell me a story" --stream - -# Multi-turn conversation from file -cat conversation.json | minimax text chat --messages-file - -``` - -#### speech - -```bash -# Generate speech and save to file -minimax speech synthesize --text "Hello, world!" --out hello.mp3 - -# Read from file or stdin -echo "Breaking news." | minimax speech synthesize --text-file - --out news.mp3 - -# Stream audio to a player -minimax speech synthesize --text "Stream this" --stream | mpv --no-terminal - - -# Custom voice and speed -minimax speech synthesize --text "Fast narration" --voice English_expressive_narrator --speed 1.5 --out fast.mp3 -``` - -#### image - -```bash -# Generate an image -minimax image generate --prompt "Mountain landscape at sunset" - -# Custom aspect ratio and batch -minimax image generate --prompt "Logo design" --aspect-ratio 1:1 --n 3 --out-dir ./generated/ - -# With subject reference -minimax image generate --prompt "Portrait in oil painting style" --subject-ref ./photo.jpg +minimax speech synthesize --text "Hello!" --out hello.mp3 +minimax vision describe --image photo.jpg ``` -#### video +## Agent & CI usage ```bash -# Submit a video generation task -minimax video generate --prompt "A man reads a book. Static shot." +# Async: get task ID immediately, no blocking +minimax video generate --prompt "A robot." --async --quiet +# → {"taskId":"..."} -# Wait for completion and download -minimax video generate --prompt "Ocean waves at sunset." --wait --download sunset.mp4 +# Pipe-friendly: stdout is pure data, stderr is progress +minimax text chat --message "Hi" | jq . -# With first frame image -minimax video generate --prompt "Mouse runs toward camera." --first-frame ./mouse.jpg - -# Check task status -minimax video task get --task-id 106916112212032 - -# Download a completed video -minimax video download --file-id 176844028768320 --out video.mp4 +# CI: missing args fail fast with clear errors +minimax image generate --non-interactive +# → Error: Missing required argument: --prompt ``` -#### music - -```bash -# Generate with custom lyrics -minimax music generate --prompt "Indie folk, melancholic" --lyrics "La la la..." --out song.mp3 - -# Lyrics from file -minimax music generate --prompt "Upbeat pop" --lyrics-file song.txt --out summer.mp3 - -# Auto-generated lyrics -minimax music generate --prompt "Jazz lounge" --lyrics "Do do do..." --out jazz.mp3 -``` +## Commands -#### search +| Command | Description | +|---|---| +| `text chat` | Send a chat completion | +| `speech synthesize` | Text-to-speech, up to 10k chars | +| `image generate` | Generate images | +| `video generate` | Generate a video (auto-downloads on completion) | +| `video task get` | Query video task status | +| `video download` | Download a completed video | +| `file upload` | Upload a file to MiniMax storage | +| `file list` | List uploaded files | +| `file delete` | Delete an uploaded file | +| `music generate` | Generate a song | +| `search query` | Web search | +| `vision describe` | Describe an image (supports `--file-id` to skip base64) | +| `quota show` | Show usage quotas | +| `config export-schema` | Export tool schemas as JSON | +| `config show` / `config set` | View or update configuration | +| `auth login/status/refresh/logout` | Authentication | + +All commands accept [global flags](#global-flags). + +## Examples + +```bash +# Text chat +minimax text chat --message "user:Write fizzbuzz" +minimax text chat --message "Hi" --stream + +# Image generation +minimax image generate --prompt "Sunset over ocean" --aspect-ratio 16:9 --n 3 + +# Vision (path/URL or pre-uploaded file ID) +minimax vision describe --image photo.jpg --prompt "What breed is this?" +minimax vision describe --file-id file-123 --prompt "Extract the text" + +# Video (auto-downloads to ~/.minimax-video/ on completion) +minimax video generate --prompt "A man reads a book." +minimax video generate --prompt "A robot." --async --quiet + +# Speech synthesis +minimax speech synthesize --text "Hello world!" --out hello.mp3 +minimax speech synthesize --text "Breaking news." --text-file - --stream | mpv - + +# Music generation +minimax music generate --prompt "Indie folk" --lyrics "La la la..." --out song.mp3 -```bash # Web search -minimax search query --q "MiniMax AI" - -# JSON output for scripting -minimax search query --q "latest news" --output json -``` - -#### vision - -```bash -# Describe a local image -minimax vision describe --image photo.jpg - -# Describe from URL -minimax vision describe --image https://example.com/photo.jpg - -# Custom prompt -minimax vision describe --image screenshot.png --prompt "Extract the text from this screenshot" -``` - -#### quota +minimax search query --q "MiniMax AI latest news" -```bash -# Show usage and remaining quotas -minimax quota show - -# JSON output -minimax quota show --output json -``` - -#### config - -```bash -# Show current configuration -minimax config show - -# Set region -minimax config set --key region --value cn - -# Set default output format -minimax config set --key output --value json - -# Set request timeout -minimax config set --key timeout --value 600 -``` +# File management (for reuse in vision/video) +FILE_ID=$(minimax file upload --file image.png --purpose vision --quiet) +minimax vision describe --file-id $FILE_ID -#### auth +# Export Agent tool schemas +minimax config export-schema | jq . +minimax config export-schema --command "video generate" | jq . -```bash -# Login with API key +# Auth minimax auth login --api-key sk-xxxxx - -# Check auth status minimax auth status - -# Logout -minimax auth logout ``` ## Global flags @@ -248,49 +145,40 @@ minimax auth logout | Flag | Description | |---|---| | `--api-key ` | API key (overrides all other auth) | -| `--region ` | API region: `global` (default), `cn` | -| `--base-url ` | API base URL (overrides region) | -| `--output ` | Output format: `text`, `json`, `yaml` | -| `--quiet` | Suppress non-essential output | +| `--region ` | `global` (default) or `cn` | +| `--base-url ` | Override API base URL | +| `--output ` | `text`, `json`, or `yaml` | +| `--quiet` | Suppress non-essential output to stderr | | `--verbose` | Print HTTP request/response details | | `--timeout ` | Request timeout (default: 300) | | `--no-color` | Disable ANSI colors and spinners | | `--yes` | Skip confirmation prompts | | `--dry-run` | Show what would happen without executing | +| `--non-interactive` | Disable interactive prompts (CI/agent use) | +| `--async` | Return task ID immediately without polling | +| `--version` / `--help` | Version and help | -## Region auto-detection +## Output philosophy -On first run, the CLI probes both the Global and CN quota endpoints with your API key to determine which platform it belongs to. The detected region is cached in `~/.minimax/config.yaml` so subsequent runs are instant. - -You can override the region at any time: +- `stdout` → result data only (text, file paths, JSON) +- `stderr` → spinners, region detection, help text, warnings, verbose logs ```bash -# Per-command -minimax text chat --region cn --message "user:Hello" - -# Environment variable -export MINIMAX_REGION=cn +# stdout is clean JSON — pipe to jq safely +minimax text chat --message "Hi" | jq . -# Persistent -minimax config set --key region --value cn +# stderr shows spinner without polluting the pipe +minimax video generate --prompt "Ocean waves." 2>/dev/null ``` ## Configuration -The CLI reads configuration from multiple sources, in order of precedence: - -1. Command-line flags (`--api-key`, `--region`, etc.) -2. Environment variables (`MINIMAX_API_KEY`, `MINIMAX_REGION`, `MINIMAX_BASE_URL`, `MINIMAX_OUTPUT`, `MINIMAX_TIMEOUT`) -3. Config file (`~/.minimax/config.yaml`) -4. Defaults - -## Output formats +Precedence (highest to lowest): CLI flags → env vars → `~/.minimax/config.yaml` → defaults. -- **text** (default in TTY) -- human-readable tables and formatted text -- **json** (default in non-TTY) -- full API response, suitable for piping to `jq` -- **yaml** -- YAML serialization of the full response - -When stdout is not a TTY (e.g., piped to another program), the output automatically switches to JSON for easy parsing. +```bash +minimax config set --key region --value cn +export MINIMAX_REGION=cn +``` ## Exit codes @@ -307,21 +195,49 @@ When stdout is not a TTY (e.g., piped to another program), the output automatica ## Building ```bash -# Run from source -bun run dev -- +bun run dev -- # Run from source +bun run typecheck # Type check +bun test # Run tests +bun run build # Build standalone binaries +``` -# Type check -bun run typecheck +## Changelog -# Run tests -bun test +### v0.4.0 — File Management API + Vision file_id Support -# Build standalone binaries for all platforms -bun run build +**New `file` resource group:** +- `minimax file upload` — upload local file, get `file_id`; `--quiet` outputs only the ID +- `minimax file list` — formatted table of uploaded files +- `minimax file delete` — remove file by ID -# Build npm-publishable bundle -bun run build:npm -``` +**Vision `--file-id` support:** +- `vision describe` now accepts `--file-id` as mutually exclusive alternative to `--image` +- With `--file-id`: sends `{prompt, file_id}` directly to VLM API (no base64) +- With `--image`: existing base64 encoding path unchanged +- Interactive TTY prompt detects whether input is path/URL or fileId + +Note: MiniMax File API returned HTTP 404 with the current API key. Endpoint paths and request handling are verified correct via `--verbose` mode. + +### v0.3.0 — Agent Tool Schema Auto-Generation + +- `OptionDef` interface extended with optional `type` and `required` fields +- New `CommandRegistry.getAllCommands()` traversal method +- `src/utils/schema.ts`: parses `--flag ` strings into Anthropic/OpenAI-compatible tool schemas +- New command: `minimax config export-schema` — exports all tool schemas as clean JSON to stdout +- Core commands marked `required: true`: `image generate --prompt`, `text chat --message`, `video generate --prompt`, `vision describe --image` + +### v0.2.0 — Agent & CI Compatibility + +- `src/utils/env.ts`: `isInteractive()` and `isCI()` helpers +- `--non-interactive`: forces non-interactive mode regardless of TTY state +- `--async`: immediate task-ID return without blocking poll +- All help routes to stderr (not stdout) — `--help | jq` works cleanly +- Streaming: thinking blocks go to stderr in non-TTY mode; final text always to stdout +- Interactive fallback: missing args prompt in TTY, fail fast in CI/agent mode + +### v0.1.0 — Initial release + +Text chat with streaming · Image generation with batch and subject reference · Video generation with polling and download · Music generation with lyrics · Speech synthesis with voice customization · Web search · Image understanding · OAuth and API key authentication · Automatic region detection (global vs CN) · YAML/JSON/text output formats ## License diff --git a/bun.lock b/bun.lock index bbe3395..cfa829a 100644 --- a/bun.lock +++ b/bun.lock @@ -5,8 +5,8 @@ "": { "name": "minimax-cli", "dependencies": { + "@clack/prompts": "^0.7.0", "yaml": "^2.7.1", - "zod": "^3.24.4", }, "devDependencies": { "@types/bun": "latest", @@ -16,6 +16,10 @@ }, }, "packages": { + "@clack/core": ["@clack/core@0.3.5", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ=="], + + "@clack/prompts": ["@clack/prompts@0.7.0", "", { "dependencies": { "@clack/core": "^0.3.3", "is-unicode-supported": "*", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA=="], + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], @@ -166,6 +170,8 @@ "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], @@ -176,6 +182,8 @@ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -196,7 +204,7 @@ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "@clack/prompts/is-unicode-supported": ["is-unicode-supported@2.1.0", "", { "bundled": true }, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], } diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9a19cf7 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1197 @@ +{ + "name": "minimax-cli", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "minimax-cli", + "version": "0.1.0", + "dependencies": { + "@clack/prompts": "^0.7.0", + "yaml": "^2.7.1" + }, + "bin": { + "minimax": "dist/minimax.mjs" + }, + "devDependencies": { + "@types/bun": "latest", + "eslint": "^9.24.0", + "typescript": "^5.8.3" + } + }, + "node_modules/@clack/core": { + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/@clack/core/-/core-0.3.5.tgz", + "integrity": "sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ==", + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts": { + "version": "0.7.0", + "resolved": "https://registry.npmmirror.com/@clack/prompts/-/prompts-0.7.0.tgz", + "integrity": "sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA==", + "bundleDependencies": [ + "is-unicode-supported" + ], + "license": "MIT", + "dependencies": { + "@clack/core": "^0.3.3", + "is-unicode-supported": "*", + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts/node_modules/is-unicode-supported": { + "version": "1.3.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmmirror.com/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmmirror.com/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmmirror.com/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmmirror.com/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmmirror.com/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmmirror.com/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@types/bun": { + "version": "1.3.11", + "resolved": "https://registry.npmmirror.com/@types/bun/-/bun-1.3.11.tgz", + "integrity": "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bun-types": "1.3.11" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.5.0", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/bun-types": { + "version": "1.3.11", + "resolved": "https://registry.npmmirror.com/bun-types/-/bun-types-1.3.11.tgz", + "integrity": "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmmirror.com/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmmirror.com/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json index e9dd68c..9ce7b65 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "prepublishOnly": "bun run build:npm" }, "dependencies": { + "@clack/prompts": "^0.7.0", "yaml": "^2.7.1" }, "devDependencies": { diff --git a/src/args.ts b/src/args.ts index f7f1137..6ac3630 100644 --- a/src/args.ts +++ b/src/args.ts @@ -14,6 +14,8 @@ export function parseArgs(argv: string[]): ParsedArgs { yes: false, dryRun: false, help: false, + nonInteractive: false, + async: false, }; let i = 0; @@ -47,7 +49,8 @@ export function parseArgs(argv: string[]): ParsedArgs { // Boolean flags if (['quiet', 'verbose', 'noColor', 'yes', 'dryRun', 'help', 'stream', - 'subtitles', 'wait', 'noWait', 'noBrowser'].includes(camelKey)) { + 'subtitles', 'wait', 'noWait', 'noBrowser', + 'nonInteractive', 'async'].includes(camelKey)) { (flags as Record)[camelKey] = true; i++; continue; diff --git a/src/client/endpoints.ts b/src/client/endpoints.ts index 4cf2771..2c68c52 100644 --- a/src/client/endpoints.ts +++ b/src/client/endpoints.ts @@ -43,3 +43,15 @@ export function quotaEndpoint(baseUrl: string): string { const host = baseUrl.includes('minimaxi.com') ? 'https://www.minimaxi.com' : 'https://www.minimax.io'; return `${host}/v1/api/openplatform/coding_plan/remains`; } + +export function fileUploadEndpoint(baseUrl: string): string { + return `${baseUrl}/v1/files`; +} + +export function fileListEndpoint(baseUrl: string): string { + return `${baseUrl}/v1/files`; +} + +export function fileDeleteEndpoint(baseUrl: string, fileId: string): string { + return `${baseUrl}/v1/files?file_id=${encodeURIComponent(fileId)}`; +} diff --git a/src/client/http.ts b/src/client/http.ts index 8c45581..df0e587 100644 --- a/src/client/http.ts +++ b/src/client/http.ts @@ -20,12 +20,19 @@ export async function request( config: Config, opts: RequestOpts, ): Promise { + const isFormData = + typeof FormData !== 'undefined' && opts.body instanceof FormData; + const headers: Record = { - 'Content-Type': 'application/json', 'User-Agent': 'minimax-cli/0.1.0', ...opts.headers, }; + // Only set Content-Type for non-FormData bodies; FormData lets fetch set the multipart boundary automatically + if (!isFormData && !headers['Content-Type']) { + headers['Content-Type'] = 'application/json'; + } + if (!opts.noAuth) { const credential = await resolveCredential(config); if (opts.authStyle === 'x-api-key') { @@ -45,7 +52,11 @@ export async function request( const res = await fetch(opts.url, { method: opts.method || 'GET', headers, - body: opts.body ? JSON.stringify(opts.body) : undefined, + body: opts.body + ? isFormData + ? (opts.body as FormData) + : JSON.stringify(opts.body) + : undefined, signal: AbortSignal.timeout(timeoutMs), }); diff --git a/src/command.ts b/src/command.ts index b9970bc..6e13edf 100644 --- a/src/command.ts +++ b/src/command.ts @@ -4,6 +4,8 @@ import type { GlobalFlags } from './types/flags'; export interface OptionDef { flag: string; description: string; + type?: 'string' | 'number' | 'boolean' | 'array'; + required?: boolean; } export interface Command { diff --git a/src/commands/config/export-schema.ts b/src/commands/config/export-schema.ts new file mode 100644 index 0000000..c6bd26d --- /dev/null +++ b/src/commands/config/export-schema.ts @@ -0,0 +1,55 @@ +import { defineCommand } from '../../command'; +import { registry } from '../../registry'; +import { generateToolSchema } from '../../utils/schema'; +import type { Config } from '../../config/schema'; +import type { GlobalFlags } from '../../types/flags'; +import { CLIError } from '../../errors/base'; +import { ExitCode } from '../../errors/codes'; + +/** + * Commands that are infrastructure/auth-related and not suitable as Agent tools. + */ +const SKIP_PREFIXES = ['auth ', 'config ', 'update']; + +export default defineCommand({ + name: 'config export-schema', + description: + 'Export all (or one) CLI command(s) as Anthropic/OpenAI-compatible JSON tool schemas', + usage: 'minimax config export-schema [--command ""]', + options: [ + { + flag: '--command ', + description: + 'Export schema for a specific command only (e.g. "image generate")', + }, + ], + examples: [ + 'minimax config export-schema', + 'minimax config export-schema --command "video generate"', + ], + async run(config: Config, flags: GlobalFlags) { + const targetCommand = flags.command as string | undefined; + + if (targetCommand) { + try { + const { command } = registry.resolve(targetCommand.split(' ')); + const schema = generateToolSchema(command); + process.stdout.write(JSON.stringify(schema, null, 2) + '\n'); + } catch { + throw new CLIError( + `Command "${targetCommand}" not found.`, + ExitCode.USAGE, + ); + } + return; + } + + // Export all suitable commands + const allCommands = registry.getAllCommands(); + const schemas = allCommands + .filter((c) => !SKIP_PREFIXES.some((p) => c.name.startsWith(p))) + .map((c) => generateToolSchema(c)); + + process.stdout.write(JSON.stringify(schemas, null, 2) + '\n'); + }, +}); diff --git a/src/commands/file/delete.ts b/src/commands/file/delete.ts new file mode 100644 index 0000000..b2f3be5 --- /dev/null +++ b/src/commands/file/delete.ts @@ -0,0 +1,61 @@ +import { defineCommand } from '../../command'; +import { CLIError } from '../../errors/base'; +import { ExitCode } from '../../errors/codes'; +import { requestJson } from '../../client/http'; +import { fileDeleteEndpoint } from '../../client/endpoints'; +import { formatOutput, detectOutputFormat } from '../../output/formatter'; +import { isInteractive } from '../../utils/env'; +import { promptText, failIfMissing } from '../../utils/prompt'; +import type { Config } from '../../config/schema'; +import type { GlobalFlags } from '../../types/flags'; +import type { FileDeleteResponse } from '../../types/api'; + +export default defineCommand({ + name: 'file delete', + description: 'Delete an uploaded file from MiniMax storage', + usage: 'minimax file delete --file-id ', + options: [ + { flag: '--file-id ', description: 'ID of the file to delete', required: true }, + ], + examples: [ + 'minimax file delete --file-id 123456789', + ], + async run(config: Config, flags: GlobalFlags) { + let fileId = flags.fileId as string | undefined; + + if (!fileId) { + if (isInteractive({ nonInteractive: config.nonInteractive })) { + fileId = await promptText({ message: 'Enter file ID to delete:' }); + if (!fileId) { + process.stderr.write('Delete cancelled.\n'); + process.exit(1); + } + } else { + failIfMissing('file-id', 'minimax file delete --file-id '); + } + } + + const format = detectOutputFormat(config.output); + + if (config.dryRun) { + process.stdout.write(formatOutput({ request: { delete_file: fileId } }, format) + '\n'); + return; + } + + const url = fileDeleteEndpoint(config.baseUrl, fileId); + const response = await requestJson(config, { + url, + method: 'DELETE', + }); + + if (config.quiet) { + process.stdout.write(response.deleted ? 'deleted\n' : 'failed\n'); + return; + } + + process.stdout.write(formatOutput({ + id: response.id, + deleted: response.deleted, + }, format) + '\n'); + }, +}); diff --git a/src/commands/file/list.ts b/src/commands/file/list.ts new file mode 100644 index 0000000..7a7ab0e --- /dev/null +++ b/src/commands/file/list.ts @@ -0,0 +1,49 @@ +import { defineCommand } from '../../command'; +import { requestJson } from '../../client/http'; +import { fileListEndpoint } from '../../client/endpoints'; +import { formatOutput, detectOutputFormat } from '../../output/formatter'; +import type { Config } from '../../config/schema'; +import type { GlobalFlags } from '../../types/flags'; +import type { FileListResponse } from '../../types/api'; + +export default defineCommand({ + name: 'file list', + description: 'List uploaded files in MiniMax storage', + usage: 'minimax file list', + examples: [ + 'minimax file list', + 'minimax file list --output json', + ], + async run(config: Config, flags: GlobalFlags) { + const format = detectOutputFormat(config.output); + + if (config.dryRun) { + process.stdout.write('Would list uploaded files.\n'); + return; + } + + const url = fileListEndpoint(config.baseUrl); + const response = await requestJson(config, { url, method: 'GET' }); + + if (format !== 'text') { + process.stdout.write(formatOutput(response, format) + '\n'); + return; + } + + if (!response.data || response.data.length === 0) { + process.stdout.write('No files found.\n'); + return; + } + + const tableData = response.data.map((f) => ({ + ID: f.file_id, + FILENAME: f.filename, + PURPOSE: f.purpose, + SIZE_KB: (f.bytes / 1024).toFixed(1), + CREATED: new Date(f.created_at * 1000).toISOString().slice(0, 16).replace('T', ' '), + })); + + const { formatTable } = await import('../../output/text'); + process.stdout.write(formatTable(tableData) + '\n'); + }, +}); diff --git a/src/commands/file/upload.ts b/src/commands/file/upload.ts new file mode 100644 index 0000000..482a0a2 --- /dev/null +++ b/src/commands/file/upload.ts @@ -0,0 +1,77 @@ +import { defineCommand } from '../../command'; +import { CLIError } from '../../errors/base'; +import { ExitCode } from '../../errors/codes'; +import { requestJson } from '../../client/http'; +import { fileUploadEndpoint } from '../../client/endpoints'; +import { formatOutput, detectOutputFormat } from '../../output/formatter'; +import { isInteractive } from '../../utils/env'; +import { promptText, failIfMissing } from '../../utils/prompt'; +import type { Config } from '../../config/schema'; +import type { GlobalFlags } from '../../types/flags'; +import type { FileUploadResponse } from '../../types/api'; +import { existsSync } from 'fs'; +import { resolve } from 'path'; + +export default defineCommand({ + name: 'file upload', + description: 'Upload a file to MiniMax storage', + usage: 'minimax file upload --file [--purpose ]', + options: [ + { flag: '--file ', description: 'Local path to the file', required: true }, + { flag: '--purpose ', description: 'File purpose (default: retrieval)' }, + ], + examples: [ + 'minimax file upload --file doc.pdf', + 'minimax file upload --file image.png --purpose vision', + ], + async run(config: Config, flags: GlobalFlags) { + let filePath = flags.file as string | undefined; + + if (!filePath) { + if (isInteractive({ nonInteractive: config.nonInteractive })) { + filePath = await promptText({ message: 'Enter file path:' }); + if (!filePath) { + process.stderr.write('Upload cancelled.\n'); + process.exit(1); + } + } else { + failIfMissing('file', 'minimax file upload --file '); + } + } + + const fullPath = resolve(filePath); + if (!existsSync(fullPath)) { + throw new CLIError(`File not found: ${fullPath}`, ExitCode.USAGE); + } + + const purpose = (flags.purpose as string) || 'retrieval'; + const format = detectOutputFormat(config.output); + + if (config.dryRun) { + process.stdout.write(formatOutput({ request: { file: fullPath, purpose } }, format) + '\n'); + return; + } + + const formData = new FormData(); + // Read file as a Blob-like File object for fetch compatibility + const fileData = await Bun.file(fullPath).arrayBuffer(); + const fileName = fullPath.split('/').pop() || 'file'; + const fileBlob = new Blob([fileData]); + formData.append('file', fileBlob, fileName); + formData.append('purpose', purpose); + + const url = fileUploadEndpoint(config.baseUrl); + const response = await requestJson(config, { + url, + method: 'POST', + body: formData, + }); + + if (config.quiet) { + process.stdout.write(response.file.file_id + '\n'); + return; + } + + process.stdout.write(formatOutput(response.file, format) + '\n'); + }, +}); diff --git a/src/commands/image/generate.ts b/src/commands/image/generate.ts index 653ee2b..92db6d8 100644 --- a/src/commands/image/generate.ts +++ b/src/commands/image/generate.ts @@ -10,13 +10,15 @@ import type { GlobalFlags } from '../../types/flags'; import type { ImageRequest, ImageResponse } from '../../types/api'; import { mkdirSync, existsSync, readFileSync } from 'fs'; import { join, resolve } from 'path'; +import { isInteractive } from '../../utils/env'; +import { promptText, failIfMissing } from '../../utils/prompt'; export default defineCommand({ name: 'image generate', description: 'Generate images (image-01)', usage: 'minimax image generate --prompt [flags]', options: [ - { flag: '--prompt ', description: 'Image description' }, + { flag: '--prompt ', description: 'Image description', required: true }, { flag: '--aspect-ratio ', description: 'Aspect ratio (e.g. 16:9, 1:1)' }, { flag: '--n ', description: 'Number of images to generate (default: 1)' }, { flag: '--subject-ref ', description: 'Subject reference (type=character,image=path)' }, @@ -29,13 +31,21 @@ export default defineCommand({ 'minimax image generate --prompt "Mountain landscape" --quiet', ], async run(config: Config, flags: GlobalFlags) { - const prompt = flags.prompt as string | undefined; + let prompt = flags.prompt as string | undefined; + if (!prompt) { - throw new CLIError( - '--prompt is required for image generation.', - ExitCode.USAGE, - 'minimax image generate --prompt ', - ); + if (isInteractive({ nonInteractive: config.nonInteractive })) { + const hint = await promptText({ + message: 'Enter your image prompt:', + }); + if (!hint) { + process.stderr.write('Image generation cancelled.\n'); + process.exit(1); + } + prompt = hint; + } else { + failIfMissing('prompt', 'minimax image generate --prompt '); + } } const body: ImageRequest = { diff --git a/src/commands/text/chat.ts b/src/commands/text/chat.ts index d2db600..c80fc06 100644 --- a/src/commands/text/chat.ts +++ b/src/commands/text/chat.ts @@ -15,6 +15,8 @@ import type { StreamEvent, } from '../../types/api'; import { readFileSync } from 'fs'; +import { isInteractive } from '../../utils/env'; +import { promptText, failIfMissing } from '../../utils/prompt'; interface ParsedMessages { system?: string; @@ -81,7 +83,7 @@ export default defineCommand({ usage: 'minimax text chat --message [flags]', options: [ { flag: '--model ', description: 'Model ID (default: MiniMax-M2.7)' }, - { flag: '--message ', description: 'Message text (repeatable, prefix role: to set role)' }, + { flag: '--message ', description: 'Message text (repeatable, prefix role: to set role)', required: true }, { flag: '--messages-file ', description: 'JSON file with messages array (use - for stdin)' }, { flag: '--system ', description: 'System prompt' }, { flag: '--max-tokens ', description: 'Maximum tokens to generate (default: 4096)' }, @@ -98,14 +100,21 @@ export default defineCommand({ 'minimax text chat --message "Hello" --output json', ], async run(config: Config, flags: GlobalFlags) { - const { system, messages } = parseMessages(flags); + let { system, messages } = parseMessages(flags); if (messages.length === 0) { - throw new CLIError( - '--message or --messages-file is required.', - ExitCode.USAGE, - 'minimax text chat --message "Hello"', - ); + if (isInteractive({ nonInteractive: config.nonInteractive })) { + const hint = await promptText({ + message: 'Enter your message:', + }); + if (!hint) { + process.stderr.write('Chat cancelled.\n'); + process.exit(1); + } + messages = [{ role: 'user', content: hint }]; + } else { + failIfMissing('message', 'minimax text chat --message '); + } } const model = (flags.model as string) || 'MiniMax-M2.7'; @@ -155,6 +164,11 @@ export default defineCommand({ let inThinking = false; const dim = config.noColor ? '' : '\x1b[2m'; const reset = config.noColor ? '' : '\x1b[0m'; + const isTTY = process.stdout.isTTY; + // In TTY mode, write thinking/response headers to stdout for display. + // In non-TTY (pipe/agent) mode, write everything but final text to stderr. + const statusOut = isTTY ? process.stdout : process.stderr; + const resultOut = process.stdout; for await (const event of parseSSE(res)) { if (event.data === '[DONE]') break; @@ -164,25 +178,25 @@ export default defineCommand({ if (parsed.type === 'content_block_start') { if (parsed.content_block.type === 'thinking') { inThinking = true; - process.stdout.write(`${dim}Thinking:\n`); + statusOut.write(`${dim}Thinking:\n`); } else if (parsed.content_block.type === 'text' && inThinking) { - process.stdout.write(`${reset}\n\nResponse:\n`); + statusOut.write(`${reset}\n\nResponse:\n`); inThinking = false; } } else if (parsed.type === 'content_block_delta') { if (parsed.delta.type === 'text_delta') { textContent += parsed.delta.text; - process.stdout.write(parsed.delta.text); + resultOut.write(parsed.delta.text); } else if (parsed.delta.type === 'thinking_delta') { - process.stdout.write(parsed.delta.thinking); + statusOut.write(parsed.delta.thinking); } } } catch { // Skip unparseable chunks } } - if (inThinking) process.stdout.write(reset); - process.stdout.write('\n'); + if (inThinking) statusOut.write(reset); + resultOut.write('\n'); if (format === 'json') { console.log(formatOutput({ content: textContent }, format)); diff --git a/src/commands/video/generate.ts b/src/commands/video/generate.ts index 047e6b4..2c67fbe 100644 --- a/src/commands/video/generate.ts +++ b/src/commands/video/generate.ts @@ -10,6 +10,8 @@ import type { Config } from '../../config/schema'; import type { GlobalFlags } from '../../types/flags'; import type { VideoRequest, VideoResponse, VideoTaskResponse, FileRetrieveResponse } from '../../types/api'; import { readFileSync } from 'fs'; +import { isInteractive } from '../../utils/env'; +import { promptText, failIfMissing } from '../../utils/prompt'; export default defineCommand({ name: 'video generate', @@ -17,26 +19,34 @@ export default defineCommand({ usage: 'minimax video generate --prompt [flags]', options: [ { flag: '--model ', description: 'Model ID (default: MiniMax-Hailuo-2.3)' }, - { flag: '--prompt ', description: 'Video description' }, + { flag: '--prompt ', description: 'Video description', required: true }, { flag: '--first-frame ', description: 'First frame image' }, { flag: '--callback-url ', description: 'Webhook URL for completion notification' }, { flag: '--download ', description: 'Save video to file on completion' }, { flag: '--no-wait', description: 'Return task ID immediately without waiting' }, + { flag: '--async', description: 'Return task ID immediately (agent/CI mode, same as --no-wait but explicit)' }, { flag: '--poll-interval ', description: 'Polling interval when waiting (default: 5)' }, ], examples: [ 'minimax video generate --prompt "A man reads a book. Static shot."', 'minimax video generate --prompt "Ocean waves at sunset." --download sunset.mp4', + 'minimax video generate --prompt "A robot painting." --async --quiet', 'minimax video generate --prompt "A robot painting." --no-wait --quiet', ], async run(config: Config, flags: GlobalFlags) { - const prompt = flags.prompt as string | undefined; + let prompt = flags.prompt as string | undefined; + if (!prompt) { - throw new CLIError( - '--prompt is required for video generation.', - ExitCode.USAGE, - 'minimax video generate --prompt [--model ]', - ); + if (isInteractive({ nonInteractive: config.nonInteractive })) { + const hint = await promptText({ message: 'Enter your video prompt:' }); + if (!hint) { + process.stderr.write('Video generation cancelled.\n'); + process.exit(1); + } + prompt = hint; + } else { + failIfMissing('prompt', 'minimax video generate --prompt '); + } } const model = (flags.model as string) || 'MiniMax-Hailuo-2.3'; @@ -75,16 +85,11 @@ export default defineCommand({ const taskId = response.task_id; - // --no-wait: return task ID immediately - if (flags.noWait) { - if (config.quiet) { - console.log(taskId); - } else { - console.log(formatOutput({ - task_id: taskId, - status: 'Submitted', - }, format)); - } + // --no-wait or --async: return task ID immediately + if (flags.noWait || config.async) { + // Always pure JSON — Agent/CI mode needs predictable stdout + process.stdout.write(JSON.stringify({ taskId })); + process.stdout.write('\n'); return; } @@ -140,18 +145,18 @@ export default defineCommand({ return; } - // Default: return download URL - if (config.quiet) { - console.log(downloadUrl); - } else { - console.log(formatOutput({ - task_id: taskId, - status: 'Success', - file_id: result.file_id, - url: downloadUrl, - video_width: result.video_width, - video_height: result.video_height, - }, format)); - } + // Default: auto-download to temp location and output local file path + const os = await import('os'); + const { join } = await import('path'); + const destDir = join(os.tmpdir(), 'minimax-video'); + const { existsSync, mkdirSync } = await import('fs'); + if (!existsSync(destDir)) mkdirSync(destDir, { recursive: true }); + const destPath = join(destDir, `${taskId}.mp4`); + + const { size } = await downloadFile(downloadUrl, destPath, { quiet: config.quiet }); + + // Pure local path output (stdout stays clean for piping) + process.stdout.write(destPath); + process.stdout.write('\n'); }, }); diff --git a/src/commands/vision/describe.ts b/src/commands/vision/describe.ts index a21d7c9..b78a798 100644 --- a/src/commands/vision/describe.ts +++ b/src/commands/vision/describe.ts @@ -8,6 +8,8 @@ import type { Config } from '../../config/schema'; import type { GlobalFlags } from '../../types/flags'; import { readFileSync, existsSync } from 'fs'; import { extname } from 'path'; +import { isInteractive } from '../../utils/env'; +import { promptText, failIfMissing } from '../../utils/prompt'; interface VlmResponse { content: string; @@ -21,18 +23,11 @@ const MIME_TYPES: Record = { }; async function toDataUri(image: string): Promise { - if (image.startsWith('data:')) { - return image; - } + if (image.startsWith('data:')) return image; if (image.startsWith('http://') || image.startsWith('https://')) { const res = await fetch(image); - if (!res.ok) { - throw new CLIError( - `Failed to download image: HTTP ${res.status}`, - ExitCode.GENERAL, - ); - } + if (!res.ok) throw new CLIError(`Failed to download image: HTTP ${res.status}`, ExitCode.GENERAL); const contentType = res.headers.get('content-type') || 'image/jpeg'; const mime = contentType.split(';')[0]!.trim(); const buf = await res.arrayBuffer(); @@ -41,72 +36,93 @@ async function toDataUri(image: string): Promise { } // Local file - if (!existsSync(image)) { - throw new CLIError( - `File not found: ${image}`, - ExitCode.USAGE, - ); - } - + if (!existsSync(image)) throw new CLIError(`File not found: ${image}`, ExitCode.USAGE); const ext = extname(image).toLowerCase(); const mime = MIME_TYPES[ext]; - if (!mime) { - throw new CLIError( - `Unsupported image format "${ext}". Supported: jpg, jpeg, png, webp`, - ExitCode.USAGE, - ); - } - + if (!mime) throw new CLIError(`Unsupported image format "${ext}". Supported: jpg, jpeg, png, webp`, ExitCode.USAGE); const buf = readFileSync(image); - const b64 = buf.toString('base64'); - return `data:${mime};base64,${b64}`; + return `data:${mime};base64,${buf.toString('base64')}`; } export default defineCommand({ name: 'vision describe', description: 'Describe an image using MiniMax VLM', - usage: 'minimax vision describe --image [--prompt ]', + usage: 'minimax vision describe (--image | --file-id ) [--prompt ]', options: [ - { flag: '--image ', description: 'Image file path or URL' }, + { flag: '--image ', description: 'Local image path or URL (base64 encoded automatically)' }, + { flag: '--file-id ', description: 'Pre-uploaded file ID (skips base64 conversion)' }, { flag: '--prompt ', description: 'Question about the image (default: "Describe the image.")' }, ], examples: [ 'minimax vision describe --image photo.jpg', 'minimax vision describe --image https://example.com/photo.jpg --prompt "What breed is this dog?"', - 'minimax vision describe --image screenshot.png --prompt "Extract the text" --output json', + 'minimax vision describe --file-id file-123456789 --prompt "Extract the text"', ], async run(config: Config, flags: GlobalFlags) { - const image = flags.image as string | undefined; + let image = flags.image as string | undefined; + let fileId = flags.fileId as string | undefined; const prompt = (flags.prompt as string) || 'Describe the image.'; - if (!image) { + // Mutually exclusive: must provide one, cannot provide both + if (!image && !fileId) { + if (isInteractive({ nonInteractive: config.nonInteractive })) { + const hint = await promptText({ + message: 'Enter image path, URL, or File ID:', + }); + if (!hint) { + process.stderr.write('Vision describe cancelled.\n'); + process.exit(1); + } + // Simple heuristic: if no extension and not http(s), treat as fileId + if (!hint.includes('.') && !hint.startsWith('http')) { + fileId = hint; + } else { + image = hint; + } + } else { + throw new CLIError( + 'Missing required argument. Must provide either --image or --file-id.', + ExitCode.USAGE, + 'minimax vision describe --image OR --file-id ', + ); + } + } else if (image && fileId) { throw new CLIError( - '--image is required.', + 'Conflicting arguments: cannot provide both --image and --file-id.', ExitCode.USAGE, - 'minimax vision describe --image ', ); } const format = detectOutputFormat(config.output); if (config.dryRun) { - console.log(formatOutput({ request: { prompt, image } }, format)); + process.stdout.write(formatOutput({ request: { prompt, image, fileId } }, format) + '\n'); return; } - const imageUrl = await toDataUri(image); const url = vlmEndpoint(config.baseUrl); + let body: Record = { prompt }; + + if (fileId) { + // Skip base64: pass fileId directly to the API + body.file_id = fileId; + } else if (image) { + // Fallback to base64 encoding for local/HTTP images + const imageUrl = await toDataUri(image); + body.image_url = imageUrl; + } + const response = await requestJson(config, { url, method: 'POST', - body: { prompt, image_url: imageUrl }, + body, }); if (format !== 'text') { - console.log(formatOutput(response, format)); + process.stdout.write(formatOutput(response, format) + '\n'); return; } - console.log(response.content); + process.stdout.write(response.content + '\n'); }, }); diff --git a/src/config/loader.ts b/src/config/loader.ts index 0b4c364..7097628 100644 --- a/src/config/loader.ts +++ b/src/config/loader.ts @@ -65,6 +65,8 @@ export function loadConfig(flags: GlobalFlags): Config { noColor: flags.noColor || process.env.NO_COLOR !== undefined || !process.stdout.isTTY, yes: flags.yes || false, dryRun: flags.dryRun || false, + nonInteractive: flags.nonInteractive || false, + async: flags.async || false, needsRegionDetection, }; } diff --git a/src/config/schema.ts b/src/config/schema.ts index 2c33f4c..b38acf5 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -45,5 +45,7 @@ export interface Config { noColor: boolean; yes: boolean; dryRun: boolean; + nonInteractive: boolean; + async: boolean; needsRegionDetection?: boolean; } diff --git a/src/main.ts b/src/main.ts index 11f9e02..8f29483 100644 --- a/src/main.ts +++ b/src/main.ts @@ -19,7 +19,7 @@ async function main() { const { commandPath, flags } = parseArgs(args); if (flags.help || commandPath.length === 0) { - registry.printHelp(commandPath); + registry.printHelp(commandPath, process.stderr); process.exit(0); } diff --git a/src/registry.ts b/src/registry.ts index 289306f..45a3da9 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -19,8 +19,14 @@ import visionDescribe from './commands/vision/describe'; import quotaShow from './commands/quota/show'; import configShow from './commands/config/show'; import configSet from './commands/config/set'; +import configExportSchema from './commands/config/export-schema'; +import fileUpload from './commands/file/upload'; +import fileList from './commands/file/list'; +import fileDelete from './commands/file/delete'; import update from './commands/update'; +export type { Command, OptionDef } from './command'; + interface CommandNode { command?: Command; children: Map; @@ -47,6 +53,18 @@ class CommandRegistry { node.command = command; } + getAllCommands(): Command[] { + const commands: Command[] = []; + const traverse = (node: CommandNode) => { + if (node.command) commands.push(node.command); + for (const child of node.children.values()) { + traverse(child); + } + }; + traverse(this.root); + return commands; + } + resolve(commandPath: string[]): { command: Command; extra: string[] } { let node = this.root; const matched: string[] = []; @@ -67,7 +85,6 @@ class CommandRegistry { const subcommands = Array.from(node.children.entries()) .map(([name, n]) => { if (n.command) return ` ${matched.join(' ')} ${name} ${n.command.description}`; - // Group with sub-children const subs = Array.from(n.children.keys()).join(', '); return ` ${matched.join(' ')} ${name} [${subs}]`; }) @@ -86,9 +103,14 @@ class CommandRegistry { ); } - printHelp(commandPath: string[]): void { + /** + * Print help to the given output stream. + * Defaults to stdout; pass stderr (or a non-TTY stream) to keep stdout + * clean for piped / JSON output. + */ + printHelp(commandPath: string[], out: NodeJS.WriteStream = process.stdout): void { if (commandPath.length === 0) { - this.printRootHelp(); + this.printRootHelp(out); return; } @@ -96,31 +118,31 @@ class CommandRegistry { for (const part of commandPath) { const child = node.children.get(part); if (!child) { - this.printRootHelp(); + this.printRootHelp(out); return; } node = child; } if (node.command) { - this.printCommandHelp(node.command); + this.printCommandHelp(node.command, out); return; } // Group help - console.log(`\nUsage: minimax ${commandPath.join(' ')} [flags]\n`); - console.log('Commands:'); - this.printChildren(node, commandPath.join(' ')); - console.log(''); + out.write(`\nUsage: minimax ${commandPath.join(' ')} [flags]\n\n`); + out.write('Commands:\n'); + this.printChildren(node, commandPath.join(' '), out); + out.write('\n'); } - private printRootHelp(): void { - console.log(` + private printRootHelp(out: NodeJS.WriteStream): void { + out.write(` __ __ ___ _ _ ___ __ __ _ __ __ | \\/ |_ _| \\ | |_ _| \\/ | / \\ \\ \\/ / | |\\/| || || \\| || || |\\/| | / _ \\ \\ / | | | || || |\\ || || | | |/ ___ \\/ \\ - |_| |_|___|_| \\_|___|_| |_/_/ \\_\\/_/\\ + |_| |_|___|_| \\_|___|_| |_/_/ \\_\\_/\\ Usage: minimax [flags] @@ -133,8 +155,9 @@ Resources: music Music generation (generate) search Web search (query) vision Image understanding (describe) + file File management (upload, list, delete) quota Usage quotas (show) - config CLI configuration (show, set) + config CLI configuration (show, set, export-schema) update Update minimax to a newer version Global Flags: @@ -148,6 +171,8 @@ Global Flags: --no-color Disable ANSI colors and spinners --yes Skip confirmation prompts --dry-run Show what would happen without executing + --non-interactive Disable interactive prompts (CI/agent mode) + --async Return task ID immediately without polling --version Print version and exit --help Show help @@ -157,37 +182,35 @@ Getting Help: `); } - private printCommandHelp(cmd: Command): void { - console.log(`\n${cmd.description}\n`); - if (cmd.usage) { - console.log(`Usage: ${cmd.usage}\n`); - } + private printCommandHelp(cmd: Command, out: NodeJS.WriteStream): void { + out.write(`\n${cmd.description}\n`); + if (cmd.usage) out.write(`Usage: ${cmd.usage}\n`); if (cmd.options && cmd.options.length > 0) { const maxLen = Math.max(...cmd.options.map(o => o.flag.length)); - console.log('Options:'); + out.write('Options:\n'); for (const opt of cmd.options) { - console.log(` ${opt.flag.padEnd(maxLen + 2)} ${opt.description}`); + out.write(` ${opt.flag.padEnd(maxLen + 2)} ${opt.description}\n`); } - console.log(''); + out.write('\n'); } if (cmd.examples && cmd.examples.length > 0) { - console.log('Examples:'); + out.write('Examples:\n'); for (const ex of cmd.examples) { - console.log(` ${ex}`); + out.write(` ${ex}\n`); } - console.log(''); + out.write('\n'); } - console.log(`Global flags (--api-key, --output, --quiet, etc.) are always available.`); - console.log(`Run 'minimax --help' for the full list.\n`); + out.write(`Global flags (--api-key, --output, --quiet, etc.) are always available.\n`); + out.write(`Run 'minimax --help' for the full list.\n`); } - private printChildren(node: CommandNode, prefix: string): void { + private printChildren(node: CommandNode, prefix: string, out: NodeJS.WriteStream): void { for (const [name, child] of node.children) { if (child.command) { - console.log(` ${prefix} ${name.padEnd(12)} ${child.command.description}`); + out.write(` ${prefix} ${name.padEnd(12)} ${child.command.description}\n`); } if (child.children.size > 0) { - this.printChildren(child, `${prefix} ${name}`); + this.printChildren(child, `${prefix} ${name}`, out); } } } @@ -208,8 +231,12 @@ export const registry = new CommandRegistry({ 'music generate': musicGenerate, 'search query': searchQuery, 'vision describe': visionDescribe, - 'quota show': quotaShow, - 'config show': configShow, - 'config set': configSet, - 'update': update, + 'quota show': quotaShow, + 'config show': configShow, + 'config set': configSet, + 'config export-schema': configExportSchema, + 'file upload': fileUpload, + 'file list': fileList, + 'file delete': fileDelete, + 'update': update, }); diff --git a/src/types/api.ts b/src/types/api.ts index 77660ce..f5c7b83 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -250,6 +250,35 @@ export interface QuotaModelRemain { // ---- File ---- +export interface FileUploadResponse { + base_resp: BaseResp; + file: { + file_id: string; + bytes: number; + created_at: number; + filename: string; + purpose: string; + }; +} + +export interface FileListResponse { + base_resp: BaseResp; + data: Array<{ + file_id: string; + bytes: number; + created_at: number; + filename: string; + purpose: string; + }>; +} + +export interface FileDeleteResponse { + base_resp: BaseResp; + id: string; + object: string; + deleted: boolean; +} + export interface FileRetrieveResponse { base_resp: BaseResp; file: { diff --git a/src/types/flags.ts b/src/types/flags.ts index 5bd2d8e..4f29f85 100644 --- a/src/types/flags.ts +++ b/src/types/flags.ts @@ -9,5 +9,7 @@ export interface GlobalFlags { yes: boolean; dryRun: boolean; help: boolean; + nonInteractive: boolean; + async: boolean; [key: string]: unknown; } diff --git a/src/utils/env.ts b/src/utils/env.ts new file mode 100644 index 0000000..ceff118 --- /dev/null +++ b/src/utils/env.ts @@ -0,0 +1,38 @@ +/** + * Environment detection utilities for minimax-cli. + * + * Used to determine whether the CLI is running in an interactive terminal + * (human user) or in a non-interactive environment (CI, agent, pipe, etc.), + * so commands can adjust their behavior accordingly. + */ + +/** + * Detects whether the current environment is interactive. + * + * Returns false when: + * - stdout or stdin is not a TTY + * - The --non-interactive flag was explicitly set + * - The process is running in a known CI environment (CI env var present) + * + * Returns true when stdout and stdin are both TTYs and --non-interactive + * was not passed. + */ +export function isInteractive(options?: { nonInteractive?: boolean }): boolean { + if (options?.nonInteractive === true) return false; + if (process.env.CI) return false; + return process.stdout.isTTY === true && process.stdin.isTTY === true; +} + +/** + * Detects whether the current process is running in a CI environment. + */ +export function isCI(): boolean { + return !!( + process.env.CI || + process.env.GITHUB_ACTIONS || + process.env.GITLAB_CI || + process.env.JENKINS_URL || + process.env.TRAVIS || + process.env.CIRCLECI + ); +} diff --git a/src/utils/prompt.ts b/src/utils/prompt.ts new file mode 100644 index 0000000..12546ed --- /dev/null +++ b/src/utils/prompt.ts @@ -0,0 +1,72 @@ +/** + * Interactive prompt utilities. + * + * Wraps @clack/prompts with environment-awareness: + * - In interactive mode: shows prompts and lets users input values. + * - In non-interactive / CI / Agent mode: fails fast with a clear error. + * + * All functions here are no-ops (return undefined) when non-interactive, + * so callers must check isInteractive() first or handle the missing-value + * case explicitly. + */ + +import { isInteractive } from './env.js'; +import { CLIError } from '../errors/base.js'; +import { ExitCode } from '../errors/codes'; + +// Dynamic import to avoid loading @clack/prompts in non-interactive envs unnecessarily +// (though for CLI tools the startup cost is usually acceptable) + +/** + * Prompt the user for a text value. + * Only call this when isInteractive() is true; otherwise the function returns + * undefined immediately so the caller can fail fast. + */ +export async function promptText(options: { + message: string; + defaultValue?: string; +}): Promise { + if (!isInteractive()) return undefined; + + const { defaultValue, message } = options; + const inquirer = await import('@clack/prompts'); + const val = await (inquirer as any).text({ + message, + default: defaultValue, + placeholder: defaultValue, + }); + + // @clack/prompts returns a Symbol.cancel when the user presses Ctrl+C + if (typeof val === 'symbol') return undefined; + return val as string; +} + +/** + * Like promptText but confirms with y/N before proceeding. + */ +export async function promptConfirm(options: { + message: string; +}): Promise { + if (!isInteractive()) return undefined; + + const { message } = options; + const inquirer = await import('@clack/prompts'); + const val = await (inquirer as any).confirm({ message }); + + if (typeof val === 'symbol') return undefined; + return val as boolean; +} + +/** + * Fail fast with a user-friendly error when a required option is missing + * in non-interactive (agent / CI) mode. + */ +export function failIfMissing(flagName: string, context: string): never { + throw new CLIError( + `Missing required argument: --${flagName}\n` + + `Hint: In non-interactive (CI / agent) environments all required flags must be provided.\n` + + ` In an interactive terminal, run without --${flagName} and the CLI will prompt for it.`, + ExitCode.USAGE, + context, + ); +} diff --git a/src/utils/schema.ts b/src/utils/schema.ts new file mode 100644 index 0000000..a0688f8 --- /dev/null +++ b/src/utils/schema.ts @@ -0,0 +1,79 @@ +import type { Command, OptionDef } from '../command'; + +/** + * Parse a CLI flag string (e.g. "--prompt ", "--stream") into + * a parameter name and inferred type. + */ +function parseFlag(flag: string): { + name: string; + kebabName: string; + inferredType: string; + isArray: boolean; +} { + // e.g. "--prompt " -> "prompt" + const match = flag.match(/^--([a-zA-Z0-9-]+)/); + const kebabName = match ? match[1]! : ''; + // camelCase to match internal API conventions + const name = kebabName.replace(/-([a-zA-Z0-9])/g, (_, c: string) => c.toUpperCase()); + + let inferredType = 'string'; + let isArray = false; + + if (!flag.includes('<') && !flag.includes('[')) { + // No parameter value — typically a boolean flag like --stream + inferredType = 'boolean'; + } else if (flag.includes('') || flag.includes('') || flag.includes('') || flag.includes('')) { + inferredType = 'number'; + } + + if (flag.toLowerCase().includes('repeatable')) { + isArray = true; + } + + return { name, kebabName, inferredType, isArray }; +} + +export function generateToolSchema(cmd: Command): Record { + const toolName = `minimax_${cmd.name.replace(/ /g, '_')}`; + + const schema: Record = { + name: toolName, + description: cmd.description, + input_schema: { + type: 'object', + properties: {} as Record, + required: [] as string[], + }, + }; + + if (cmd.options) { + for (const opt of cmd.options) { + const { name, inferredType, isArray } = parseFlag(opt.flag); + if (!name) continue; + + // Explicit type from OptionDef takes precedence; fall back to inference + const explicitType = opt.type; + const effectiveType = isArray + ? 'array' + : (explicitType ?? inferredType); + + const propSchema: Record = { description: opt.description }; + + if (effectiveType === 'array') { + propSchema.type = 'array'; + propSchema.items = { type: 'string' }; + } else { + propSchema.type = effectiveType; + } + + const inputSchema = schema.input_schema as Record; + (inputSchema.properties as Record)[name] = propSchema; + + if (opt.required) { + (inputSchema.required as string[]).push(name); + } + } + } + + return schema; +} diff --git a/test/auth/resolver.test.ts b/test/auth/resolver.test.ts index 8271e9d..aa3acf6 100644 --- a/test/auth/resolver.test.ts +++ b/test/auth/resolver.test.ts @@ -13,6 +13,8 @@ function makeConfig(overrides: Partial = {}): Config { noColor: false, yes: false, dryRun: false, + nonInteractive: false, + async: false, ...overrides, }; } diff --git a/test/client/http.test.ts b/test/client/http.test.ts index cd5dc4c..f59f882 100644 --- a/test/client/http.test.ts +++ b/test/client/http.test.ts @@ -15,6 +15,8 @@ function makeConfig(baseUrl: string): Config { noColor: false, yes: false, dryRun: false, + nonInteractive: false, + async: false, }; } diff --git a/test/commands/auth/login.test.ts b/test/commands/auth/login.test.ts index 9480ec3..0a2d757 100644 --- a/test/commands/auth/login.test.ts +++ b/test/commands/auth/login.test.ts @@ -18,6 +18,8 @@ describe('auth login command', () => { noColor: true, yes: false, dryRun: false, + nonInteractive: false, + async: false, }; await expect( @@ -29,6 +31,8 @@ describe('auth login command', () => { yes: false, dryRun: false, help: false, + nonInteractive: false, + async: false, }), ).rejects.toThrow('--api-key is required'); }); diff --git a/test/commands/auth/logout.test.ts b/test/commands/auth/logout.test.ts index 6f4ded3..1bd79c8 100644 --- a/test/commands/auth/logout.test.ts +++ b/test/commands/auth/logout.test.ts @@ -17,6 +17,8 @@ describe('auth logout command', () => { noColor: true, yes: false, dryRun: true, + nonInteractive: false, + async: false, }; const originalLog = console.log; @@ -31,6 +33,8 @@ describe('auth logout command', () => { yes: false, dryRun: true, help: false, + nonInteractive: false, + async: false, }); expect(output).toContain('No changes made'); diff --git a/test/commands/auth/refresh.test.ts b/test/commands/auth/refresh.test.ts index 6c7dfdf..5f49476 100644 --- a/test/commands/auth/refresh.test.ts +++ b/test/commands/auth/refresh.test.ts @@ -17,6 +17,8 @@ describe('auth refresh command', () => { noColor: true, yes: false, dryRun: false, + nonInteractive: false, + async: false, }; await expect( @@ -27,6 +29,8 @@ describe('auth refresh command', () => { yes: false, dryRun: false, help: false, + nonInteractive: false, + async: false, }), ).rejects.toThrow('not authenticated via OAuth'); }); diff --git a/test/commands/auth/status.test.ts b/test/commands/auth/status.test.ts index 7175142..8aa0d2f 100644 --- a/test/commands/auth/status.test.ts +++ b/test/commands/auth/status.test.ts @@ -17,6 +17,8 @@ describe('auth status command', () => { noColor: true, yes: false, dryRun: false, + nonInteractive: false, + async: false, }; const originalLog = console.log; @@ -31,6 +33,8 @@ describe('auth status command', () => { yes: false, dryRun: false, help: false, + nonInteractive: false, + async: false, }); const parsed = JSON.parse(output); diff --git a/test/commands/config/set.test.ts b/test/commands/config/set.test.ts index 1c06de0..6130096 100644 --- a/test/commands/config/set.test.ts +++ b/test/commands/config/set.test.ts @@ -17,6 +17,8 @@ describe('config set command', () => { noColor: true, yes: false, dryRun: false, + nonInteractive: false, + async: false, }; await expect( @@ -27,6 +29,8 @@ describe('config set command', () => { yes: false, dryRun: false, help: false, + nonInteractive: false, + async: false, }), ).rejects.toThrow('--key and --value are required'); }); @@ -42,6 +46,8 @@ describe('config set command', () => { noColor: true, yes: false, dryRun: false, + nonInteractive: false, + async: false, }; await expect( @@ -54,6 +60,8 @@ describe('config set command', () => { yes: false, dryRun: false, help: false, + nonInteractive: false, + async: false, }), ).rejects.toThrow('Invalid config key'); }); diff --git a/test/commands/config/show.test.ts b/test/commands/config/show.test.ts index 343f351..14ae5d5 100644 --- a/test/commands/config/show.test.ts +++ b/test/commands/config/show.test.ts @@ -18,6 +18,8 @@ describe('config show command', () => { noColor: true, yes: false, dryRun: false, + nonInteractive: false, + async: false, }; const originalLog = console.log; @@ -32,6 +34,8 @@ describe('config show command', () => { yes: false, dryRun: false, help: false, + nonInteractive: false, + async: false, }); const parsed = JSON.parse(output); diff --git a/test/commands/image/generate.test.ts b/test/commands/image/generate.test.ts index c59a4c9..8b7e4f4 100644 --- a/test/commands/image/generate.test.ts +++ b/test/commands/image/generate.test.ts @@ -18,6 +18,8 @@ describe('image generate command', () => { noColor: true, yes: false, dryRun: false, + nonInteractive: false, + async: false, }; await expect( @@ -28,7 +30,9 @@ describe('image generate command', () => { yes: false, dryRun: false, help: false, + nonInteractive: false, + async: false, }), - ).rejects.toThrow('--prompt is required'); + ).rejects.toThrow('Missing required argument: --prompt'); }); }); diff --git a/test/commands/music/generate.test.ts b/test/commands/music/generate.test.ts index bc3abab..528653f 100644 --- a/test/commands/music/generate.test.ts +++ b/test/commands/music/generate.test.ts @@ -18,6 +18,8 @@ describe('music generate command', () => { noColor: true, yes: false, dryRun: false, + nonInteractive: false, + async: false, }; await expect( @@ -28,6 +30,8 @@ describe('music generate command', () => { yes: false, dryRun: false, help: false, + nonInteractive: false, + async: false, }), ).rejects.toThrow('At least one of --prompt or --lyrics is required'); }); diff --git a/test/commands/quota/show.test.ts b/test/commands/quota/show.test.ts index 77f4fe4..e56cc33 100644 --- a/test/commands/quota/show.test.ts +++ b/test/commands/quota/show.test.ts @@ -18,6 +18,8 @@ describe('quota show command', () => { noColor: true, yes: false, dryRun: true, + nonInteractive: false, + async: false, }; const originalLog = console.log; @@ -32,6 +34,8 @@ describe('quota show command', () => { yes: false, dryRun: true, help: false, + nonInteractive: false, + async: false, }); expect(output).toContain('Would fetch quota'); diff --git a/test/commands/speech/synthesize.test.ts b/test/commands/speech/synthesize.test.ts index 350c35f..33f29c3 100644 --- a/test/commands/speech/synthesize.test.ts +++ b/test/commands/speech/synthesize.test.ts @@ -18,6 +18,8 @@ describe('speech synthesize command', () => { noColor: true, yes: false, dryRun: false, + nonInteractive: false, + async: false, }; await expect( @@ -28,6 +30,8 @@ describe('speech synthesize command', () => { yes: false, dryRun: false, help: false, + nonInteractive: false, + async: false, }), ).rejects.toThrow('--text or --text-file is required'); }); @@ -44,6 +48,8 @@ describe('speech synthesize command', () => { noColor: true, yes: false, dryRun: true, + nonInteractive: false, + async: false, }; const originalLog = console.log; @@ -59,6 +65,8 @@ describe('speech synthesize command', () => { yes: false, dryRun: true, help: false, + nonInteractive: false, + async: false, }); const parsed = JSON.parse(output); diff --git a/test/commands/text/chat.test.ts b/test/commands/text/chat.test.ts index 6bffd47..adc967d 100644 --- a/test/commands/text/chat.test.ts +++ b/test/commands/text/chat.test.ts @@ -31,6 +31,8 @@ describe('text chat command', () => { noColor: true, yes: false, dryRun: false, + nonInteractive: false, + async: false, }; // Capture output @@ -48,6 +50,8 @@ describe('text chat command', () => { yes: false, dryRun: false, help: false, + nonInteractive: false, + async: false, }); expect(output).toContain('Hello! How can I help you today?'); @@ -70,6 +74,8 @@ describe('text chat command', () => { noColor: true, yes: false, dryRun: true, + nonInteractive: false, + async: false, }; const originalLog = console.log; @@ -85,6 +91,8 @@ describe('text chat command', () => { yes: false, dryRun: true, help: false, + nonInteractive: false, + async: false, }); const parsed = JSON.parse(output); diff --git a/test/commands/video/download.test.ts b/test/commands/video/download.test.ts index 15d6475..1b33d84 100644 --- a/test/commands/video/download.test.ts +++ b/test/commands/video/download.test.ts @@ -18,6 +18,8 @@ describe('video download command', () => { noColor: true, yes: false, dryRun: false, + nonInteractive: false, + async: false, }; await expect( @@ -28,6 +30,8 @@ describe('video download command', () => { yes: false, dryRun: false, help: false, + nonInteractive: false, + async: false, }), ).rejects.toThrow('--file-id is required'); }); @@ -44,6 +48,8 @@ describe('video download command', () => { noColor: true, yes: false, dryRun: false, + nonInteractive: false, + async: false, }; await expect( @@ -55,6 +61,8 @@ describe('video download command', () => { yes: false, dryRun: false, help: false, + nonInteractive: false, + async: false, }), ).rejects.toThrow('--out is required'); }); diff --git a/test/commands/video/generate.test.ts b/test/commands/video/generate.test.ts index 6fc24c0..2ce5680 100644 --- a/test/commands/video/generate.test.ts +++ b/test/commands/video/generate.test.ts @@ -18,6 +18,8 @@ describe('video generate command', () => { noColor: true, yes: false, dryRun: false, + nonInteractive: false, + async: false, }; await expect( @@ -28,7 +30,9 @@ describe('video generate command', () => { yes: false, dryRun: false, help: false, + nonInteractive: false, + async: false, }), - ).rejects.toThrow('--prompt is required'); + ).rejects.toThrow('Missing required argument: --prompt'); }); }); diff --git a/test/commands/video/task-get.test.ts b/test/commands/video/task-get.test.ts index df2dbb1..b48f733 100644 --- a/test/commands/video/task-get.test.ts +++ b/test/commands/video/task-get.test.ts @@ -26,6 +26,8 @@ describe('video task get command', () => { noColor: true, yes: false, dryRun: false, + nonInteractive: false, + async: false, }; await expect( @@ -36,6 +38,8 @@ describe('video task get command', () => { yes: false, dryRun: false, help: false, + nonInteractive: false, + async: false, }), ).rejects.toThrow('--task-id is required'); }); @@ -58,6 +62,8 @@ describe('video task get command', () => { noColor: true, yes: false, dryRun: false, + nonInteractive: false, + async: false, }; const originalLog = console.log; @@ -73,6 +79,8 @@ describe('video task get command', () => { yes: false, dryRun: false, help: false, + nonInteractive: false, + async: false, }); const parsed = JSON.parse(output);