# alpaca
Use language models from your Unix shell.
`alpaca` sends chat requests to a model and writes the reply to standard
output. It works in pipelines with files, editor buffers and other programs.
`alpaca` is a fork of [`cogni`](https://github.com/leoshimo/cogni) by
[leoshimo](https://github.com/leoshimo).
## What alpaca does
`alpaca` brings language model scripting into the Unix environment. You can:
- work with standard streams, files and pipes
- compose it with other programs
- run ad-hoc or repeatable prompts
`alpaca` reads and writes standard streams. This means it works with files,
editor buffers, clipboards, system logs, sockets and many other tools. You do
not need any special integrations.
## What you get
- a Unix-minded design with input and output redirection
- ad-hoc language model scripting
- flexible input and output formats, including text, JSON and transcript
- a standalone binary, with no Python needed
- support for any compatible chat endpoint, hosted or local
- editor-backed prompting and provider quota reporting in one binary
## What alpaca does not do
`alpaca` is not built for interactive use. Use it from within interactive
environments, such as REPLs or Emacs.
## Install
You need Rust and `scdoc` to build:
```sh
$ make
$ make test
$ make install
```
`PREFIX` defaults to `/usr/local`. You can override `DESTDIR`, `BINDIR` and
`MANDIR` for packaging.
## Set up
Set an API key with `--apikey` or the `ALPACA_API_KEY` environment variable:
```sh
# in shell configuration
export ALPACA_API_KEY=your-api-key
```
`alpaca` sends requests to `https://api.openai.com/v1` by default. Set
`ALPACA_ENDPOINT` or pass `--base-url` to use another host, such as a local
server:
```sh
export ALPACA_ENDPOINT=http://localhost:11434
```
The base URL sets the endpoint dialect. A base URL ending in a version segment,
such as `https://host/v1`, uses `/chat/completions`. This covers any
OpenAI-compatible endpoint. Any other base URL uses `/api/chat`, the Ollama
dialect. `alpaca` only sends authorization when it knows a key, so endpoints
that need no authentication also work.
Pick a model with `-m` or `--model`. The default is `gpt-4o-mini`. Model
identifiers are whatever your endpoint serves.
Shared settings, used by every subcommand:
| variable | meaning | default |
| --- | --- | --- |
| `ALPACA_API_KEY` | API key | none |
| `ALPACA_ENDPOINT` | base URL | `https://api.openai.com/v1` |
| `ALPACA_MODEL` | model | `gpt-4o-mini` |
| `ALPACA_EFFORT` | reasoning effort | `none` |
| `ALPACA_TIMEOUT` | request timeout, in seconds | `60` |
## Configure
Put shared settings in `$XDG_CONFIG_HOME/alpaca/config.toml`. If you do not set
`XDG_CONFIG_HOME`, use `~/.config/alpaca/config.toml`.
Settings take priority in this order:
- command-line options
- environment variables
- the configuration file
- built-in defaults
See `config.example.toml` for every supported key, and `alpaca-config(5)` for
how each command uses them. Keep the file private if it holds an `apikey`.
A `[profile-name]` table defines a profile. Profiles inherit top-level settings
and override only the keys they set. `default_profile` activates a profile
automatically. `--profile` overrides that:
```toml
model = "gpt-oss:120b"
base_url = "https://ollama.com"
default_profile = "ollama-cloud"
[ollama-cloud]
apikey = "your-ollama-cloud-key"
base_url = "https://ollama.cloud"
[deepinfra]
model = "llama-3.1-70b"
```
```sh
alpaca -u "Hello"
```
## Commands
```
alpaca [OPTIONS] [FILE] # chat, the default command
alpaca chat [OPTIONS] [FILE] # the same command, named
alpaca quota [OPTIONS] # provider quota usage
alpaca compose [OPTIONS] # edit prompts in $EDITOR until you save an empty buffer
```
`alpaca` treats a first argument that matches a subcommand name as that
subcommand. Read a file with such a name using `--`, as in `alpaca -- quota`.
---
## Basic usage
See `alpaca --help` for full documentation.
```sh
# Via stdin
$ echo "What is 50 + 50?" | alpaca
50 + 50 equals 100.
# Via file
$ echo "What is 50 + 50?" > input.txt
$ alpaca input.txt
50 + 50 equals 100.
# Via flags
# -s, --system <MSG> system prompt, always first
# -a, --assistant <MSG> append assistant message
# -u, --user <MSG> append user message
$ alpaca --system "Solve the following math problem" --user "50 + 50"
50 + 50 equals 100.
# Via repeated flags, useful for few-shot prompting
$ alpaca --system "Solve the following math problem" \
-u "1 + 1" \
-a "2" \
-u "22 + 20" \
-a "42" \
-u "50 + 50"
100
# Via both flags and stdin. Flag messages come before stdin or file input.
$ echo "50 + 50" | alpaca --system "Solve the following math problem" \
-u "1 + 1" \
-a "2" \
-u "22 + 20" \
-a "42"
100
```
---
## alpaca compose
`alpaca compose` opens a temporary file in your editor. It sends the saved
text as a prompt. It then opens the editor again with the reply in the buffer,
ready for the next prompt. The session continues until you save an empty
buffer.
Piped input fills the buffer first. The editor runs on the controlling
terminal, so editor input and output stay out of the pipeline. If the process
has no controlling terminal, the editor inherits `alpaca`'s streams. Saving
an empty buffer sends nothing and ends the session.
```sh
# Chat in the editor until you save an empty buffer
$ alpaca compose
# Pick a model and reasoning effort
$ alpaca compose -m gpt-oss:120b --reasoning-effort high
# Set a system prompt
$ alpaca compose -s "Answer in one sentence"
# Edit piped input in the editor before sending
$ git diff --staged | alpaca compose -s "Write a conventional commit message"
# Sit in the middle of a pipeline: pipe in, edit, pipe out
$ curl -s "wttr.in/?1" | alpaca compose -s "Summarize this weather in 1 sentence" | say
```
Options:
- `-m, --model <MODEL>`: model to use
- `-s, --system <MSG>`: system prompt
- `-t, --temperature <TEMP>`: sampling temperature
- `-T, --timeout <SECS>`: request timeout in seconds
- `--reasoning-effort <low|medium|high|none>`: reasoning effort
- `--apikey <KEY>`, `--base-url <URL>`: endpoint settings
- `--json`, `--jsonp`: print the response as JSON instead of the reply text
`$VISUAL` and `$EDITOR` split on whitespace, with no quote or escape parsing.
Use a wrapper script for complex editor commands. `alpaca` always removes the
temporary file. When the editor exits nonzero, `alpaca` exits with the same
status.
`alpaca` saves each successful request as a JSON transcript in
`$XDG_DATA_HOME/alpaca` or `~/.local/share/alpaca`. `alpaca` writes files with
mode `0600` in a directory with mode `0700`. Each turn sends the whole
conversation as context.
When the editor is `vim` or `neovim`, `alpaca` keeps one editor open for the
whole conversation instead of reopening it each turn. Writing the buffer sends
the prompt, and the reply replaces the buffer in place. Every turn happens in
the same window. Quit the editor to end the session. Writing an empty buffer
sends nothing. The editor reloads the buffer through `autoread` and
`checktime`, so a reply that arrives while you have unsaved changes waits for
your next write. In this mode, `alpaca` only writes the reply to standard
output when standard output is not a terminal, because the editor owns the
screen.
In neovim, a spinner shows while `alpaca` waits for the reply. It appears when
the session starts and again after you save a non-empty buffer, which clears
the buffer display so only the spinner shows until the reply arrives. Classic
vim gets no spinner.
See `man/alpaca-compose.1`.
---
## alpaca quota
`alpaca quota` shows how much quota you have used. It prints each quota
window as a bar showing the used share, with a countdown to the next reset.
Some providers also report a table of per-model counts.
Pick a provider with `-p`. Without it, `alpaca` reports all providers in one
output and skips the ones it cannot reach:
```sh
$ alpaca quota # all providers
$ alpaca quota -p synthetic # Synthetic
$ alpaca quota -p anthropic # Claude Code
$ alpaca quota -p openai # OpenAI organization usage
$ alpaca quota -p deepinfra # DeepInfra
$ alpaca quota -p codex # OpenAI Codex
$ alpaca quota -p ollama # Ollama Cloud
```
A provider with no usable credential, or a failed request or response, is left
off the screen. `alpaca` still draws the rest and exits successfully. With
`-p`, `alpaca` reports failures for that provider instead. Without `-p`,
`--apikey` and `--base-url` apply to all providers. `--raw` requires `-p`.
Each provider has its own default credential and base URL:
| provider | default credential | base URL |
| --- | --- | --- |
| `synthetic` | `$SYNTHETIC_API_KEY` | `https://api.synthetic.new` |
| `anthropic` | none | `https://api.anthropic.com` |
| `openai` | none | `https://api.openai.com` |
| `deepinfra` | `$DEEPINFRA_API_KEY` | `https://api.deepinfra.com` |
| `codex` | the OAuth token in `$CODEX_HOME/auth.json` | `https://chatgpt.com` |
| `ollama` | `$OLLAMA_API_KEY` | `https://ollama.com` |
Every provider also accepts `--apikey`, an explicit `--profile`, or a table
named after the provider, such as `[anthropic]`. Anthropic and OpenAI have no
default credential, so you must supply one of these.
The Anthropic endpoint serves the Claude Code CLI rather than a documented
public API, so it may change without notice. It expects the same short-lived
OAuth token the CLI uses. Take a fresh token from the CLI and pass it again
when it expires.
The OpenAI endpoint is the documented organization Usage API. It reports
spend and token usage for the whole organization, not the rate limit for one
account. It needs an Admin API key with the `api.usage.read` scope, from
`platform.openai.com/settings/organization/admin-keys`. A regular project
key, such as `sk-proj-...`, gets a 403.
DeepInfra bills per token or per second, with no fixed quota. `alpaca` prints
no window for it. It prints the remaining credit as a note. A negative balance
is funds ready to spend, and a positive one is money owed.
The Ollama provider reports Ollama Cloud usage. It takes the key from
`$OLLAMA_API_KEY` and defaults to `https://ollama.com`. You can override this
with `$OLLAMA_API_ENDPOINT`. It shows the monthly usage pool as the share of
the plan's credits used, and any extra spend beyond the plan. The endpoint
reports no reset time, so no reset is shown. Accounts on the old billing get
the 5 hour and weekly windows instead, with locally computed reset boundaries.
The Codex provider reports your ChatGPT plan usage, not organization spend.
`alpaca` reads the OAuth token the Codex CLI stores in `$CODEX_HOME/auth.json`,
defaulting to `~/.codex/auth.json`. Sign in once with `codex` and the report
works. `--apikey` replaces that token. The endpoint serves the Codex CLI and
the ChatGPT client, so it is not a documented public API and may change
without notice. `alpaca` prints the 5 hour and weekly windows with their reset
times, the plan name, and the credits balance when the plan has one.
The top-level `base_url` configures the chat endpoint only, so `alpaca quota`
never uses it. Each provider already has the correct endpoint built in. A
table named after a provider is picked up automatically, but only for its
`apikey`. Its `base_url`, if set, is assumed to be for chat, not quota. To
also override the endpoint for a provider, select a profile by name with
`--profile`. `default_profile` does not apply here. Command-line options
and `ALPACA_ENDPOINT` still override any profile.
Synthetic, DeepInfra and Ollama also fall back to the top-level `apikey` when
nothing more specific names one, because all three double as chat endpoints.
Anthropic and OpenAI never serve chat, so they ignore the top-level `apikey`
and always need a key from a table or profile. Without a chosen provider,
`alpaca` resolves each provider key on its own, exactly as an explicit `-p`
would. It never sends the top-level `apikey` to all providers.
Options:
- `-p, --provider <PROVIDER>`: one of `synthetic`, `anthropic`, `openai`,
`deepinfra`, `codex`, `ollama`. Without it, all providers are reported and
the unreachable ones are omitted
- `-T, --timeout <SECS>`: request timeout in seconds
- `--apikey <KEY>`: API key or OAuth token, replacing the provider default
- `--base-url <URL>`: base URL, replacing the provider default
- `--raw`: print the provider response body verbatim. Requires `-p`
- `--json`, `--jsonp`: print the normalised view as JSON. Without `-p`, one
object keyed by provider
- `--color <auto|always|never>`: colour policy. `auto` colours only when
standard output is a terminal and `NO_COLOR` is unset
See `man/alpaca-quota.1`.
---
## Tour of alpaca
These examples will help you get started.
Whatever you send to `alpaca` goes to the endpoint you configure. Point
`ALPACA_ENDPOINT` at a local server if the data should not leave your machine.
### In the shell
```sh
# Summarise a meeting transcript
$ cat meeting_saved_chat.txt \
| alpaca -s "Extract the links mentioned in this transcript, and provide a high level summary of the discussion points"
# Narrate a weather summary
$ curl -s "wttr.in/?1" \
| alpaca -s "Summarize today's weather using the output. Respond in 1 short sentence." \
| say
# Create an ffmpeg cheatsheet from a man page
$ man ffmpeg \
| alpaca -T 300 -s "Create a cheatsheet given a man page. Output should be in Markdown, and should be a set of example usages under headings." \
> cheatsheet.md
# Create a commit message for staged changes
$ git diff --staged \
| alpaca -s "Create a commit message for the given staged changes. Use conventional commit format. Answer in a single-line raw plaintext. Don't use markdown." \
| git commit -F -
```
### In Emacs
Emacs can pipe buffer regions to `alpaca` with `shell-command-on-region`. The
following command sends the selected region to `alpaca`, and can replace the
original text:
```emacs-lisp
(defun leoshimo/alpaca-on-region (start end prompt replace)
"Run alpaca on region. Prefix arg means replace region, instead of separate output buffer"
(interactive "r\nsPrompt: \nP")
(shell-command-on-region start end
(format "alpaca -s \"%s\"" prompt)
nil replace))
(global-set-key (kbd "M-c") #'leoshimo/alpaca-on-region)
```
Use this binding for tasks such as:
- normalising nonuniform text, such as unstructured logs to structured JSON events
- editing or organising text semantically, such as rewording or grouping by category
- generating a summary for an Org Agenda document
### In Vim
Vim can also run external shell commands on the whole buffer or a visual
selection, giving similar workflows to Emacs. See `:help :!` in Vim.
For example, to sort a bulleted list of fruits by colour:
1. Select the list of fruits in visual mode.
2. Type `:!alpaca -s "Sort this list by color"`.