# ai-cli: media generation and typed decisions from a terminal

> The ai-cli project turns a text prompt into an image, a video, an audio clip or a machine-readable decision, with everything reading from stdin and writing to files. The interesting part is the decide command, which asks named Boolean, choice and score questions in one request and returns probabilities, so a ticket triage script can branch on a model's answer without parsing prose.

**vercel-labs/ai-cli** — Generate anything from your terminal

- Repository: https://github.com/vercel-labs/ai-cli
- Website: https://ai-cli.dev
- Stars: 825 · Forks: 63
- Language: TypeScript
- License: not declared
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/vercel-labs-ai-cli

## One command per medium, and one for decisions

ai-cli is described as generate anything from your terminal, and the project is labelled a Vercel Labs experiment in its badge row. The site is ai-cli.dev, the package is on npm as ai-cli, and the licence badge says Apache-2.0, although the repository's own licence field records a custom classification that GitHub cannot identify.

What the tool does is stated in one line: create text, images, video, and audio, and make typed decisions with composable commands, stdin support, and predictable outputs. It is built with the Vercel AI SDK and the AI Gateway, which is how it reaches hundreds of models without knowing any of their APIs, and the key it needs is either an AI Gateway API key or a provider-specific key such as OPENAI_API_KEY.

The command surface is deliberately small. There is a command per medium, image, video, text and audio, with the audio command taking a subcommand for speaking and another for transcription. There is a decide command for typed questions. And there is a models command that lists what is available.

That last command is worth noticing in a tool that talks to many providers, because the model catalogue is fetched from the gateway rather than hardcoded, and the way a model is named can be short.

The project is TypeScript, the default branch is main, and the releases are v0.6.0 on 2026-09-30, v0.6.1 on 2026-10-01 and v0.7.0 on 2026-10-16, with the last push on the same day as the newest tag.

## Install and run something

Installation is a global npm install:

```bash
npm install -g ai-cli
```

and the requirements are Node.js 22 or later, plus an AI Gateway API key or a provider-specific key such as OPENAI_API_KEY.

The first commands look like this:

```bash
ai image "a cute dog"
ai video "a spinning triangle"
ai text "explain quantum computing"
ai audio speak "Thanks for trying ai-cli"
ai audio transcribe recording.mp3
ai decide --boolean "refund=Refund requested?" < ticket.txt
ai models                          # list available models
```

Each of those is a different shape of work. Image and video are generative and take a moment. Text is a prompt. Audio has two directions, speaking from text and transcribing a recording. Decide is the one that returns structured data rather than media, and the ticket file on standard input is the shape of the whole interface, because a question is asked about a document and the document arrives on stdin.

The model selection is deliberately forgiving. A model ID can be given as creator/model-name or as just model-name, and the short form is resolved against the models fetched from the gateway, so the same command works whether you know the full name:

```bash
ai text -m gpt-5.5 "hello"          # resolves to openai/gpt-5.5
ai image -m flux-2-pro "a sunset"   # resolves to bfl/flux-2-pro
ai audio speak -m tts-1 "hello"     # resolves to openai/tts-1
```

One constraint is stated: model IDs must contain printable ASCII characters without spaces, and that applies both to the --model values and to the AI_CLI_*_MODEL environment variables, so the same rule governs the command line and the configuration.

## Piping is the interface

The section on piping and references is the one that makes this a pipeline tool rather than a command line toy.

```bash
ai image "a dragon" | ai video "animate this"
ai video -i input.png "animate this"
ai image --images reference.png "make a sticker in this style"
ai image -i sketch.png -i palette.jpg "render this product concept"
ai text --image screenshot.png "what is broken in this UI?"
cat photo.png | ai text "describe this image"
cat notes.txt | ai text "summarize this"
git diff | ai text "explain these changes"
echo "Ship the changelog" | ai audio speak -o changelog.mp3
cat recording.mp3 | ai audio transcribe
```

Two different mechanisms are at work in that list. The pipe carries one command's output into the next, so an image becomes the input to a video generation, and a diff becomes the input to an explanation. The -i and --images flags pass files alongside the prompt instead, and both accept more than one, so a sketch and a palette can be sent together to a single image request.

The diff example is the one that shows the intent. Asking a model to explain a diff is a task that would otherwise be a page of boilerplate review, and the pipe makes it a one-liner that works on any repository.

The audio examples show the same symmetry in the other direction. Text goes in and a spoken file comes out, with -o giving the path, and a recording goes in and a transcript comes out, with no output file named at all because the result is text on standard output.

That symmetry is why the README says outputs are written to files even when stdout is piped. The media results are artefacts you keep, and the text results are the thing you pipe onward, so the tool does not make you choose.

## The options that control cost, concurrency and output

The common options are listed in a block, and each one exists because a default would otherwise cost you money or hide a result.

The model option is -m, --model, taking a model ID and accepting a comma-separated list for multi-model runs. The output option is -o, --output, a file path or a directory. The count option is spelled two ways on purpose, -n, --n for image and video outputs per model and -n, --count for text and audio generations per model, both defaulting to 1.

Concurrency is -p, --concurrency, the maximum number of parallel generations, defaulting to 4, and 2 for video. The timeout is --timeout in seconds, defaulting to 120 for text and audio, 300 for image and 600 for video, which tells you what the tool considers normal for each medium. Quiet mode is -q, --quiet, which suppresses progress output, and --json outputs metadata as JSON.

That last flag has a precise contract, and it is the kind of detail that matters in a script. When --json is used, stdout contains only metadata, and the generated text, image, video and audio outputs are written to files even when stdout is piped. So a pipeline can parse stdout for structure and read the media from disk, and the two never get mixed up in the same pipe.

The defaults are conservative enough for interactive use and a little loose for batch work, which is why the concurrency and timeout options are worth setting explicitly before you point a scheduler at it.

## Exit codes that distinguish partial success from failure

The failure semantics are documented in detail, and they are unusual enough to be worth quoting, because a tool that reports partial success as failure will page you for nothing.

For image and video, fewer returned outputs than requested are reported as an incomplete result with a nonzero exit code, and the available media is still saved. When a batching limit splits generation into several calls, the successful calls are saved even if another call fails, and the JSON results include successful call diagnostics in a batches key and failed call or download details in a failures key.

The exit codes then draw a distinction most tools do not. Exit 1 means every model result was unsuccessful, including a partial result that saved files. Exit 2 means some models succeeded and others did not. Input validation errors also exit 1.

So a run that produced three of four images is a different outcome from a run that produced nothing, and the difference is one bit. For a script that retries, that is the difference between a retry and a pager.

The other detail is that the failure details are structured. A failures key in the JSON gives the reason per call, so a script can tell a rate limit from a content rejection from a download failure, which is the difference between backing off and giving up.

## ai decide: typed questions with machine-readable answers

The decide command is the part of the tool with a data contract, and it is what makes a model usable inside a script.

You ask named questions, and each has an explicit type. A Boolean question returns probability, the probability of true, from zero to one, and the README notes that strong no answers land near zero. A Choice question returns one of the supplied options and its distribution when one is available. A Score question returns a fractional position on the ordered levels, starting at zero, along with the distribution when available, and for the default model the position is the probability-weighted mean.

The flags are one per type:

```bash
cat ticket.txt |
  ai decide \
    --boolean "refund=Refund requested?" \
    --choice "team=Which team?" \
    --choices "team=billing,support" \
    --score "tone=How positive?" \
    --levels "tone=angry,neutral,happy"
```

Every question shares one unchanged input, so text keeps its line breaks and JSON objects and arrays keep their shape. Each question has a unique ID, and choices and levels bind to that ID regardless of the order of the flags, so the flag order on the command line does not matter. Repeating the flags asks more questions in the same request.

Underneath, the command calls the AI SDK's experimental_decide, with stdin mapping to state and the typed flags building the named questions. The --choices flag creates a choice criteria map and --levels creates a score criteria array, and the file form uses the SDK question schema directly. The default evaluation model is Jev, and other supported evaluation models use the same interface. The older ai evaluate command remains available as a backward-compatible alias.

For richer criteria you can pass a file of named questions, and the answer is then something a script can act on:

```bash
ai decide --questions triage.json < ticket.json
ai decide --boolean "refund=Refund requested?" < ticket.txt |
  jq -e '.answers.refund.probability >= 0.9'
```

The rules around that file are strict on purpose. Question files support string, JSON object, or array instructions and descriptions, and descriptions may be null. Boolean criteria optionally describe true and false, choice criteria are an option map, and score criteria are ordered levels. There is no implicit rubric, and model-specific limits are enforced by the SDK and the provider. Inline comma-separated choices use each label as both its name and its description, so a file is the answer for labels containing commas, files and inline questions can be combined, and duplicate IDs are an error.

## The monorepo behind the command

The top-level listing shows a workspace, with apps/ and packages/ directories, a turbo.json, a skills/ directory, a CHANGELOG.md, a CONTRIBUTING.md, an AGENTS.md, a .node-version file, an .oxfmtrc.jsonc, a .githooks/ directory and a .vscode/ directory.

The root package manifest says this is the ai-cli-monorepo, marked private, with npm workspaces covering apps/* and packages/*, and every task routed through turbo: build, test, lint, format, format:check and typecheck. The development dependencies are oxfmt for formatting, oxlint for linting, turbo pinned at 2.8.9 and vitest at 5.0.3, and the package manager is npm at 11.12.1.

Two things are worth noticing. The tooling is oxlint and oxfmt rather than the eslint and prettier pair, which is a deliberate move to a faster toolchain and explains the .oxfmtrc.jsonc configuration file. And the skills/ directory, next to the AGENTS.md, suggests the repository is organised for coding assistants as well as for people.

There is also a small inconsistency to be aware of. The README says the tool requires Node.js 22 or later, while the monorepo manifest declares an engines field of node >= 24. The published package and the development workspace are therefore making different statements about the floor, and a contributor working from a clone will hit the stricter one.

## Conclusion

Adopt ai-cli if you want a scriptable front end to a model gateway, because every command reads stdin, writes media to files, reports metadata as JSON and distinguishes partial success from total failure with exit codes, which is what a pipeline needs. Do not adopt it as a chat interface, since the design is built around a request in and a file out, and the decide command calls an experimental API, so expect the shape of it to move. Before you wire it into a workflow, set a concurrency and a timeout, because the defaults of four parallel generations and 120 second text requests will happily spend your quota, and check which licence your build of the tool falls under, since the README badge says Apache-2.0 while the repository's own licence field is recorded as a custom classification.

## FAQ

### How do I install ai-cli and what does it need?

Run npm install -g ai-cli. The README states it requires Node.js 22 or later and an AI Gateway API key or a provider-specific key such as OPENAI_API_KEY. The package is published on npm as ai-cli and the site is ai-cli.dev.

### What does ai decide do and what does it return?

It asks named Boolean, choice and score questions about one input and returns answers you can use in a script. Boolean returns the probability of true, choice returns the selected option and its distribution, and score returns a fractional position on the ordered levels and its distribution.

### How do I use ai-cli in a pipeline?

Every command reads stdin, so a git diff can be piped into ai text, a generated image can be piped into ai video, and a recording can be piped into ai audio transcribe. Files can also be passed alongside the prompt with -i or --images, and generated media is always written to files even when stdout is piped.

### What do the exit codes from ai-cli mean?

Exit 1 means every model result was unsuccessful, including a partial result that still saved files, and input validation errors also exit 1. Exit 2 means some models succeeded and others did not. With --json, successful call diagnostics appear in a batches key and failed call or download details in a failures key.

## Sources

- [Issues](https://github.com/vercel-labs/ai-cli/issues)
- [Project website](https://ai-cli.dev)
- [README](https://github.com/vercel-labs/ai-cli/blob/main/README.md)
- [Releases](https://github.com/vercel-labs/ai-cli/releases)
- [vercel-labs/ai-cli on GitHub](https://github.com/vercel-labs/ai-cli)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/vercel-labs-ai-cli
