# 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 familiar Unix
environment by focusing on:
- Ergonomics and accessibility in Unix shell
- Composability and interop with other programs - including `alpaca` itself
- Easy language model programming, both ad-hoc and repeatable
For example, designing for IO redirection (`stdin`, `stdout`) allows `alpaca` to
work with files, editor buffers, clipboards, syslogs, sockets, and many external
tools without bespoke integrations.
## Features
- Unix-minded design (IO redirection, composability, interop)
- Ad-hoc language model scripting
- Flexible input and output formats (text, JSON, transcript)
- Standalone binary, with no Python required
- Works against any compatible chat endpoint, hosted or local
- Editor-backed prompting and provider quota reporting in the same binary
## Non-features
- Interactive use. Instead, invoke `alpaca` from within interactive environments, such as REPLs and emacs.
## Installation
Building requires Rust and `scdoc`:
```sh
$ make
$ make test
$ sudo make install
```
`PREFIX` defaults to `/usr/local`; `DESTDIR`, `BINDIR`, and `MANDIR` may be
overridden for packaging.
## Setup
`alpaca` expects an API key supplied with the `--apikey` option or, more
conveniently, the `API_KEY` environment variable:
```sh
# in shell configuration
export API_KEY=your-api-key
```
Requests go to `https://ollama.com` unless told otherwise. To target a
different host, for example a local server, set `API_ENDPOINT` or pass
`--base-url`:
```sh
export API_ENDPOINT=http://localhost:11434
```
The endpoint's dialect follows from the base URL. A base URL ending in a
version segment, such as `https://host/v1`, is sent to `/chat/completions`;
any other base URL is sent to `/api/chat`. Authorization is only sent when a
key is known, so endpoints that need no authentication also work.
Pick a model with `-m/--model` (default `gpt-oss:120b`). 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://ollama.com` |
| `ALPACA_MODEL` | model | `gpt-oss:120b` |
| `ALPACA_EFFORT` | reasoning effort | `none` |
| `ALPACA_TIMEOUT` | request timeout, in seconds | `60` |
## Configuration
Put shared settings in `$XDG_CONFIG_HOME/alpaca/config.toml`. Alpaca uses
`~/.config/alpaca/config.toml` when `XDG_CONFIG_HOME` is not set.
Command-line options override environment variables. Environment variables
override the config file. The config file overrides built-in defaults.
```toml
output_format = "plaintext"
model = "gpt-oss:120b"
temperature = 0.7
timeout = 60
system = "Answer concisely."
assistant = ["An example assistant message"]
user = ["An example user message"]
apikey = "your-api-key"
base_url = "https://ollama.com"
reasoning_effort = "none"
```
The config file applies to chat, compose and quota where they share an option.
See `alpaca-config(5)` for all fields and command-specific behavior. Keep the
file private if it contains `apikey`.
## 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
```
A first argument matching a subcommand name is treated as that subcommand. To
read messages from a file with such a name, separate it with `--`, 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 stdout. Piped stdin prefills the buffer.
The editor runs on `/dev/tty`, keeping editor I/O out of the pipeline. 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; no quote or escape parsing. Use a
wrapper script for complex editor commands. The temporary file is always
removed. An editor that exits non-zero sets alpaca's own exit status.
Each successful request is saved as a JSON transcript in `$XDG_DATA_HOME/alpaca`
(or `~/.local/share/alpaca`), with mode 0600 in a directory with mode 0700. Run
`alpaca compose -c` to continue the newest transcript: its final reply opens in
the editor, and each follow-up is sent with the whole conversation as context.
The saved model, effort and system prompt are reused unless given on the command
line. Save an empty buffer to leave the loop.
See `man/alpaca-compose.1`.
---
## alpaca quota
`alpaca quota` asks a provider how much of your quota you have used. It prints
each quota window as a bar showing the used share, with a countdown to the next
reset. Where the provider reports per-model request counts, it prints those as a
table.
Pick the provider with `-p` (default `ollama`):
```sh
$ alpaca quota # Ollama Cloud
$ alpaca quota -p anthropic # Claude Code
$ alpaca quota -p openai # Codex CLI
```
Each provider has its own default credential and base URL:
| provider | credential | base URL |
| --- | --- | --- |
| `ollama` | `$OLLAMA_API_KEY` | `$OLLAMA_API_ENDPOINT`, then `https://ollama.com` |
| `anthropic` | `$CLAUDE_CODE_OAUTH_TOKEN`, then `~/.claude/.credentials.json` | `https://api.anthropic.com` |
| `openai` | `$CODEX_ACCESS_TOKEN`, then `~/.codex/auth.json` | `https://chatgpt.com` |
The last two read the credential file the vendor CLI writes when you log in.
They work as soon as you have logged into that CLI. Those endpoints serve the
vendor CLIs and are not documented public APIs, so they may change without
notice. Both expect a short lived OAuth token: if the credential file is stale,
you get an authentication error until the vendor CLI refreshes it.
Options:
- `-p, --provider <PROVIDER>`: one of `ollama`, `anthropic`, `openai`
- `-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
- `--json`, `--jsonp`: print the normalised view as JSON
- `--color <auto|always|never>`: colour policy. `auto` colours only when stdout
is a terminal and `NO_COLOR` is unset
See `man/alpaca-quota.1`.
---
## Tour of alpaca
Examples to get you started.
> :warning: Whatever you feed `alpaca` is sent to the endpoint you configure, so 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 use `shell-command-on-region` to pipe buffer regions to `alpaca`.
For example, the following defines a command that plumbs region to `alpaca`, optionally replacing original contents:
```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)
```
This binding is useful across a wide range of tasks, for example:
- Normalizing non-uniform text, such as unstructured logs to structured JSON events.
- Editing or organizing text semantically, such as rewording or grouping by category.
- Generating summary for an Org Agenda doc.
### In Vim
Vim can run external shell commands on entire buffer or visual selection to
power similar workflows possible from Emacs. See `h :!` in vim.
For example, a bulleted list of fruits can be sorted by color by:
1. Selecting the list of fruits in visual mode
2. Type `:!alpaca -s "Sort this list by color"`