Tree


.gitignorecommits | blame
Cargo.lockcommits | blame
Cargo.tomlcommits | blame
LICENSE-APACHEcommits | blame
LICENSE-MITcommits | blame
Makefilecommits | blame
README.mdcommits | blame
config.example.tomlcommits | blame
man/
src/
tests/

README.md

# 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
$ 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`. If you do not set
`XDG_CONFIG_HOME`, Alpaca uses `~/.config/alpaca/config.toml`.

Settings take priority in this order:
- command-line options
- environment variables
- the config file
- built-in defaults

See `config.example.toml` for an example with every supported key. See
`alpaca-config(5)` for all fields and how each command uses them. Keep the file
private if it contains an `apikey`.

Use a `[profile-name]` table to define a profile. Profiles inherit top-level
settings and override only the keys they set. Set `default_profile` to activate
a profile automatically. Use `--profile` to override it:

```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
```

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` shows how much of your quota you have used. It asks the
provider, then prints each quota window as a bar with a countdown to the next
reset. If the provider reports per-model counts, it also prints those as a
table.

Pick the provider with `-p`. It defaults to `ollama`:

```sh
$ alpaca quota                 # Ollama Cloud
$ alpaca quota -p anthropic    # Claude Code
$ alpaca quota -p openai       # Codex CLI
$ alpaca quota -p deepinfra    # DeepInfra
```

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` |
| `deepinfra` | `$DEEPINFRA_API_KEY` | `https://api.deepinfra.com` |

For Anthropic and OpenAI, alpaca reads the credential file the vendor CLI
writes when you log in. Log in to that CLI once and it works. Those endpoints
serve the vendor CLIs, not a documented public API, so they may change without
notice. Both use a short-lived OAuth token. If the credential file goes stale,
you get an authentication error until the vendor CLI refreshes it.

DeepInfra bills per token or per second, with no fixed quota. So alpaca prints
no window for it. Instead, it prints the current month's spend as a note, and
billed units per model as a table.

The top-level `apikey` and `base_url` settings apply to Ollama only. To use
them with Anthropic, OpenAI or DeepInfra, declare them in a profile and select
it with `--profile`. A profile chosen automatically through `default_profile`
does not apply to those providers. Command-line options and
`API_KEY`/`API_ENDPOINT` override any profile.

DeepInfra also reads a key from the top-level `deepinfra_apikey` setting, with
no profile needed. An explicit profile's `apikey` still overrides it.

Options:

- `-p, --provider <PROVIDER>`: one of `ollama`, `anthropic`, `openai`, `deepinfra`
- `-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"`