# alpaca
Unix native interface for interacting with LLMs.
`alpaca` is a fork of [`cogni`](https://github.com/leoshimo/cogni) by
[leoshimo](https://github.com/leoshimo).
## Focus
`alpaca` brings language model scripting (prompting) into the familiar Unix
environment. It focuses on:
- ergonomics and accessibility in the Unix shell
- composability and interop with other programs, including `alpaca` itself
- easy language model programming, both ad-hoc and repeatable
`alpaca` reads and writes standard streams, so it works with files, editor
buffers, clipboards, system logs, sockets and many external tools, with no
special integrations needed.
## Features
`alpaca` gives you:
- a Unix-minded design (input and output redirection, composability, interop)
- ad-hoc language model scripting
- flexible input and output formats (text, JSON, transcript)
- a standalone binary, with no Python required
- support for any compatible chat endpoint, hosted or local
- editor-backed prompting and provider quota reporting in the same binary
## Non-features
`alpaca` is not built for interactive use. Invoke it from within interactive
environments instead, such as REPLs and Emacs.
## Installation
Building requires Rust and `scdoc`:
```sh
$ make
$ make test
$ make install
```
`PREFIX` defaults to `/usr/local`. You can override `DESTDIR`, `BINDIR` and
`MANDIR` for packaging.
## Setup
`alpaca` reads an API key from `--apikey` or, more simply, the `API_KEY`
environment variable:
```sh
# in shell configuration
export API_KEY=your-api-key
```
`alpaca` sends requests to `https://api.openai.com/v1` by default. Set
`API_ENDPOINT` or pass `--base-url` to target another host, such as a local
server:
```sh
export API_ENDPOINT=http://localhost:11434
```
The base URL sets the dialect of the endpoint. A base URL ending in a
version segment, such as `https://host/v1`, gets `/chat/completions`: this
covers any OpenAI-compatible endpoint, hosted or local, not just OpenAI
itself. Any other base URL gets `/api/chat`, the Ollama dialect. `alpaca`
sends authorization only when it knows a key, so endpoints needing none also
work.
Pick a model with `-m/--model` (default `gpt-4o-mini`). Model identifiers are
whatever your endpoint serves.
Shared settings, honoured by every subcommand:
| variable | meaning | default |
| --- | --- | --- |
| `API_KEY` | API key | none |
| `API_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` |
## Configuration
Put shared settings in `$XDG_CONFIG_HOME/alpaca/config.toml`, or
`~/.config/alpaca/config.toml` if `XDG_CONFIG_HOME` is unset.
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: it inherits top-level settings
and overrides only the keys it sets. `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 thing, named
alpaca quota [OPTIONS] # provider quota usage
alpaca compose [OPTIONS] # edit a prompt in $EDITOR, then send it
```
`alpaca` treats a first argument matching a subcommand name as that
subcommand. Read a file with such a name using `--`, as in `alpaca -- quota`.
---
## Basic usage
See `alpaca --help` for 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> Sets system prompt (Always first)
# -a, --assistant <MSG> Appends assistant message
# -u, --user <MSG> Appends user message
$ alpaca --system "Solve the following math problem" --user "50 + 50"
50 + 50 equals 100.
# Via repetitions of same 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 / file
$ 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` edits a prompt in `$VISUAL`, `$EDITOR`, or `vim`, sends the
saved text, then writes the reply to standard output. 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.
```sh
# Write a prompt in the editor, send it, see the reply
$ 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
- `-c, --continue`: continue the newest saved conversation
- `--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,
so use a wrapper script for complex editor commands. `alpaca` always removes
the temporary file. When the editor exits nonzero, `alpaca` exits with that
same status.
`alpaca` saves each successful request as a JSON transcript in
`$XDG_DATA_HOME/alpaca` (or `~/.local/share/alpaca`), mode 0600 in a mode
0700 directory. `alpaca compose -c` continues the newest transcript: its
final reply opens in the editor, and each follow-up sends the whole
conversation as context, reusing the saved model, effort and system prompt
unless you override them on the command line. Save an empty buffer to leave
the loop.
When the editor is `vim` or `neovim`, `-c` keeps one editor open for the
whole conversation. Writing the buffer sends the prompt, and the reply
replaces the buffer in place, so every turn happens in the same window. Quit
the editor to end the session; writing an empty buffer sends nothing. In this
mode the reply is written to standard output only when standard output is not
a terminal, since the editor owns the screen.
See `man/alpaca-compose.1`.
---
## alpaca quota
`alpaca quota` shows how much quota you have used: a bar per window, with a
countdown to its reset, and for some providers a table of per-model counts.
Pick the 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
```
A provider with no usable credential, or a failed request or response, is
left off the screen. The rest are still drawn, and alpaca exits successfully.
With `-p`, failures for that provider are reported 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` |
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 one of these is required.
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 one from the CLI and pass it again
once it expires.
The OpenAI endpoint is the documented organization Usage API, reporting spend
and token usage for the whole organization rather than 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
(`sk-proj-...`) gets a 403.
DeepInfra bills per token or per second, with no fixed quota, so alpaca
prints no window for it. Like OpenAI, it instead prints the spend for the
current month as a note, and billed units per model as a table.
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 deliberately override the endpoint for a provider too, select a profile
by name with `--profile` instead. `default_profile` does not apply here.
Command-line options and `API_ENDPOINT` still override any profile.
Synthetic and DeepInfra also fall back to the top-level `apikey` when nothing
more specific names one, since both 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,
never sending the top-level `apikey` to them all.
Options:
- `-p, --provider <PROVIDER>`: one of `synthetic`, `anthropic`, `openai`,
`deepinfra`. 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
Examples to get you started.
Whatever you feed `alpaca` is sent to the endpoint you configure. Point
`API_ENDPOINT` at a local server if the data should not leave your machine.
### In the shell
```sh
# Creating Summary of Meeting Transcripts
$ 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 Weather Summary
$ curl -s "wttr.in/?1" \
| alpaca -s "Summarize today's weather using the output. Respond in 1 short sentence." \
| say
# Create a ffmpeg cheatsheet from 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`, optionally
replacing 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 runs external shell commands on the whole buffer or a visual selection
too, giving similar workflows to Emacs. See `h :!` 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"`.