---
title: "CLI Reference"
description: "Install, configure, and use the forkline command-line interface."
section: "reference"
url: "https://forkline.dev/docs/reference/cli"
markdown: "https://forkline.dev/docs/reference/cli.md"
source: "src/content/docs/reference/cli.mdx"
---

# Forkline CLI

The `forkline` CLI lets you manage repositories, runners, routines, MCP servers, secrets, tokens, and analytics from your terminal. It supports human-readable tables, structured JSON output for scripting, and shell completions.

> [!NOTE]
> **Command aliases and prefix inference**
> The CLI accepts **plural forms and long variants** as aliases for the canonical singular resource nouns shown below — `repo`, `repos`, `repository`, and `repositories` all work identically. **Verb synonyms** are also accepted: `view`/`get`/`show`, `list`/`ls`, `create`/`new`/`add`, `update`/`edit`, `delete`/`rm`/`remove`. Unique **prefixes are inferred** automatically (e.g. `rep...` resolves to `repo`); ambiguous prefixes produce a clear error. Nothing is breaking — all old command forms keep working.

## Installation

Install the `forkline` CLI via npm (recommended), the shell installer, a direct binary download, or from source. Every method produces the same binary; only shell completions require an extra step (see [Shell completions](#shell-completions) below).

### npm (recommended)

npm automatically selects the correct pre-built binary for your platform:

```bash
npm install -g @forkline/cli
```

Or run without installing:

```bash
npx @forkline/cli --version
```

npm installs a platform-specific optional dependency based on your OS, architecture, and libc:

| Platform | Package |
|----------|---------|
| Linux x86_64 (glibc) | `@forkline/cli-linux-x64-gnu` |
| Linux x86_64 (musl) | `@forkline/cli-linux-x64-musl` |
| Linux arm64 (glibc) | `@forkline/cli-linux-arm64-gnu` |
| Linux arm64 (musl) | `@forkline/cli-linux-arm64-musl` |
| macOS x86_64 | `@forkline/cli-darwin-x64` |
| macOS arm64 | `@forkline/cli-darwin-arm64` |
| Windows x86_64 | `@forkline/cli-win32-x64` |
| Windows arm64 | `@forkline/cli-win32-arm64` |

### Shell installer

The installer detects your OS, architecture, and libc (Linux glibc vs musl), downloads the latest release, verifies the SHA256 checksum, and installs the binary to `/usr/local/bin` if writable — otherwise `$HOME/.local/bin`.

```bash
curl -fsSL https://forkline.dev/install.sh | sh
```

Two environment variables control the installer:

| Variable | Description |
|----------|-------------|
| `FORKLINE_VERSION` | Pin a specific version (e.g. `0.37.2` or `v0.37.2`) |
| `FORKLINE_INSTALL_DIR` | Override the install directory |

Example:

```bash
FORKLINE_VERSION=0.37.2 FORKLINE_INSTALL_DIR=$HOME/bin \
  sh -c "$(curl -fsSL https://forkline.dev/install.sh)"
```

The shell installer supports **Linux and macOS only**. On Windows use npm or the direct download below.

### Direct download

Grab the archive for your platform from [GitHub Releases](https://github.com/forkline/cli/releases). Archives are named `forkline-{version}-{target}.tar.gz` (`.zip` on Windows):

| Target | Platform |
|--------|----------|
| `x86_64-unknown-linux-gnu` | Linux x86_64 (glibc) |
| `x86_64-unknown-linux-musl` | Linux x86_64 (musl, Alpine) |
| `aarch64-unknown-linux-gnu` | Linux arm64 (glibc) |
| `aarch64-unknown-linux-musl` | Linux arm64 (musl) |
| `x86_64-apple-darwin` | macOS x86_64 |
| `aarch64-apple-darwin` | macOS arm64 (Apple Silicon) |
| `x86_64-pc-windows-msvc` | Windows x86_64 |
| `aarch64-pc-windows-msvc` | Windows arm64 |

Every release ships a `SHA256SUMS.txt` you can verify locally:

```bash
sha256sum -c SHA256SUMS.txt
```

Then unpack and move the binary onto your `PATH`:

```bash
tar -xzf forkline-<version>-<target>.tar.gz
sudo mv forkline-<version>-<target>/forkline /usr/local/bin/forkline
```

Each archive also contains shell completions (bash, zsh, fish, powershell, elvish) and a man page — see the *Tarball releases* tip under [Shell completions](#forkline-completions-shell).

### Build from source

Requires Rust stable and `protoc`, and a checkout of the (private) Calypso repository:

```bash
cargo install --path crates/calypso-cli --locked
```

The binary is installed as `forkline` (not `calypso-cli`).

### Verify the install

```bash
forkline --version
```

### Platform notices

- **macOS Gatekeeper** — if macOS refuses the binary with "cannot be opened because it is from an unidentified developer", run `xattr -d com.apple.quarantine /usr/local/bin/forkline`, or right-click the binary in Finder and choose **Open**.
- **Windows SmartScreen** — Windows may show "Windows protected your PC". Click **More info** → **Run anyway**.
- **Unsupported platforms** — if no pre-built binary matches your platform, the npm package exits with an "Unsupported platform" error rather than building from source. Set `FORKLINE_BINARY` to point at a compatible binary, or open an issue on [GitHub](https://github.com/forkline/cli/issues).

### Shell completions

Installing the CLI puts the `forkline` binary on your `PATH` but **does not enable shell completion automatically**. Run `forkline completions install` once after installation; see [`forkline completions <shell>`](#forkline-completions-shell) for details and per-shell options.

## Authentication

### First-time setup

```bash
forkline auth login
```

The CLI starts a browser-based sign-in: it prints a verification URL and short code, opens your browser when possible, and receives an API token automatically once you approve the request in the Forkline web app. The token is stored in your system keyring by default, or in a `0600`-permissions file at `~/.config/forkline/credentials.toml` if no keyring is available.

### Without a browser

On SSH sessions or headless containers, skip the browser with `--no-browser`. The CLI prints the URL where you can create a Personal Access Token in the web UI (**Settings → API Tokens**) and waits for you to paste it:

```bash
forkline auth login --no-browser
```

### Non-interactive login

Pipe a token via stdin:

```bash
echo "$FORKLINE_TOKEN" | forkline auth login --token-stdin
```

### CI / headless environments

Set the `FORKLINE_TOKEN` environment variable. It takes priority over stored credentials:

```bash
export FORKLINE_TOKEN="fl_pat_v1_..."
forkline me
```

### Profiles

The CLI supports named profiles for managing multiple accounts or environments:

```bash
forkline auth login --profile staging --api-url https://api.staging.forkline.dev/v1
forkline me --profile staging
```

The default profile is `default` with API URL `https://api.forkline.dev/v1`.

## Quickstart

```bash
# Verify authentication
forkline me

# List repositories
forkline repo list

# View a specific repository
forkline repo view my-project

# List available runner sizes
forkline runner sizes

# Start a runner (consumes credits)
forkline runner start --repository my-project --size basic --wait

# List running runners
forkline runner list

# List routines
forkline routine list

# View analytics
forkline analytics overview

# Manage secrets (value via stdin, never argv)
echo "my-secret-value" | forkline secret set DB_PASSWORD --repository my-project --value-stdin

# Browse the MCP catalog and connect a server
forkline mcp catalog list --search grafana
forkline mcp create --from-catalog context7 --credential CTX7_KEY=account/CTX7_KEY --repository my-project --enable

# Create a token (shown once)
forkline token create --name "ci-token" --scopes "runners:read,runners:write"
```

> **Warning**: Starting a runner consumes billing credits. Use `forkline billing credits` to check your balance.

## Command reference

### `forkline auth`

| Subcommand | Description |
|---|---|
| `login` | Authenticate with a PAT (opens browser) |
| `status` | Show current authentication status |
| `logout` | Remove local credentials |

### `forkline me`

Display the authenticated user's profile.

### `forkline repo`

| Subcommand | Description |
|---|---|
| `list` | List repositories |
| `view <ref>` | View a repository by name or ID |
| `create` | Register a new repository |
| `update <ref>` | Update repository settings |
| `delete <ref>` | Delete a repository |
| `restore <ref>` | Restore a deleted repository |
| `opencode-config <ref>` | View or update OpenCode configuration |

### `forkline runner`

| Subcommand | Description |
|---|---|
| `list` | List runners |
| `view <ref>` | View runner details |
| `start` | Start a new runner |
| `stop <ref>` | Stop a runner |
| `restart <ref>` | Restart a runner |
| `attach <ref>` | Attach a local OpenCode session to a runner's current session |
| `attach <ref> new` | Start a fresh OpenCode session instead of joining the runner's current session |
| `events <ref>` | View runner event history |
| `wait <ref>` | Wait for a runner to reach a terminal state |
| `settings get <ref>` | View runner settings |
| `settings set <ref> --settings '<json>'` | Update runner settings from a JSON object (`--settings`, default `{}`) |
| `sizes` | List available runner sizes |
| `bulk` | Bulk operations on runners |

### `forkline routine`

`<ref>` is a routine name, UUID or id prefix. Arguments written `<file|ref>` take a routine file when the path exists, otherwise a ref.

| Subcommand | Description |
|---|---|
| `list` | List routines, with REPOS and LAST RUN columns |
| `view <ref>` | View routine details |
| `create -f <path>` | Create from a YAML or JSON file (`--file`; alias `add`). `new` is not an alias: it writes a local scaffold |
| `update <ref> -f <path>` | Update from a YAML or JSON file |
| `delete <ref>` | Delete a routine |
| `publish <ref>` | Publish a routine |
| `disable <ref>` | Disable a routine |
| `enable <ref>` | Enable a disabled routine |
| `run <ref>` | Run now. Does a dry run, prints the table and the estimate, asks to confirm (`--yes` skips it), then starts the runs. `--payload <file\|->` (webhook routines), `--event <uuid>` (event routines), `--repository owner/name` (repeatable), `--dry-run`, `--show-prompt`, `--idempotency-key <key>`, `--wait` (`--timeout`, default `300s`; `--poll-interval`, default `5s`; both are checked before anything is sent). The real run goes to the repositories that passed the dry run. Exit 9 when rate limited or busy (429) |
| `webhook <ref>` | Show the webhook URL, the authentication method and the secret status of an `on.webhook` routine; `--rotate-secret` replaces a generated secret (refused with exit 2 before the rotate request is sent for a pasted one, nothing changes), `--set-secret <file\|->` stores the secret a vendor gave you |
| `tasks <ref>` | List routine tasks |
| `task <ref> <task_id>` | View a specific task |
| `new [NAME] --example <name>` | Write a local routine file from an embedded example: `review-prs`, `fix-ci`, `triage-issues`, `fix-command`, `weekly-deps`, `custom-webhook` (`--out PATH`, `--force`) |
| `new [NAME] --from-event <id>` | Write a local routine file drafted from a recent event (`--fact KEY` repeatable, `--widen`, `--out PATH`, `--force`) |
| `validate <file\|dir\|ref>...` | Check files, directories or stored routines against the server rules without saving; exit 2 if any is invalid |
| `test <file\|ref> --event <id\|file>` | Replay one event (a recent event ID, or a payload file with `--forge` and `--event-header`) and show each stage; or `--repository owner/name [--body FILE]` for schedule and webhook routines. `--delivery-id <id>` (payload file only), `--prompt-only`, `--expect run\|skip` |
| `backtest <file\|ref>` | Replay the stored events of the last days (`--since 1d..7d`, `--show would-run\|skipped\|all`, `--limit N`) and estimate cost; `--fail-if-zero` exits 10 when nothing would have run: no event (event routines), no runs per week or no repository (schedules), no repository (webhooks) |
| `runs <ref>` | List what a routine did with events (`--outcome`, `--repo owner/name`, `--pr N`, `--since Nd`, `--limit N`). Starts with a counts strip `Ran · Skipped · Limited · Errors` for the `--since` window; `--outcome skipped` also returns the limited runs |
| `events` | List recent events your routines could react to (`--repo`, `--event`, `--since`, `--reacted any\|none\|some`, `--limit`). The last column is the event ID to pass to `event` and `new --from-event`; a hint on stderr says so |
| `event <id>` | Show one recent event with its facts and what each routine did |
| `push [PATH]` | Validate every file in `.forkline/routines/` (`.yaml`, `.yml`, `.json`) or `PATH`, then create or update routines by name; writes nothing if any file is invalid. `--dry-run` prints the plan, `--publish` also publishes drafts and enables disabled routines, `--yes` skips confirmations |
| `pull [REF]...` | Download stored routines to `<dir>/<name>.yaml` (`--dir PATH`, default `.forkline/routines`); a file with different content is skipped with exit 6 unless `--force` |

`new` writes a local file only and never overwrites without `--force` (exit 6). `validate`, `test`, `backtest`, `events` and `runs` change nothing and work with a token that has only the `routines:read` scope. Every command accepts the global `--json` flag. With `--json`, `list` prints `{"routines": [...]}`, and `run --wait` prints the run response plus a `tasks` array (only when at least one task started).

### `forkline mcp`

Manage MCP servers: browse the catalog, create and switch servers on and off, and store the credentials they reference. `mcps` and `servers` are aliases for `mcp`; `cat` is an alias for `catalog`.

The catalog and server endpoints are only available to accounts with MCP enabled. Without it those endpoints return `404` and the CLI reports that the resource was not found; `forkline mcp secrets` is not gated.

| Subcommand | Description |
|---|---|
| `catalog list` | Browse the catalog: Verified servers first, then Unverified |
| `catalog view <entry>` | Show one entry plus its revisions |
| `list` | List your MCP servers with their status |
| `view <server>` | Show one server, including its launch and credential bindings |
| `create` | Create a server from a catalog entry (`--from-catalog`) or pasted JSON (`--file`) |
| `update <server>` | Change name, repositories, non-secret inputs, credential bindings or a custom config |
| `upgrade <server>` | Move a catalog server to another version (`--dry-run` shows what changes) |
| `enable <server>` | Turn a server on |
| `disable <server>` | Turn a server off |
| `delete <server>` | Delete the server (asks for confirmation; `--yes` to skip) |
| `preview --repository <ref>` | Show which servers apply to one repository and their state there |
| `secrets list` | List account credentials (metadata only; values live in Vault) and the servers using each |
| `secrets set <name>` | Create or rotate an account credential |
| `secrets describe <name>` | Print a credential's metadata. Note that this **writes** the description it sends |
| `secrets delete <name>` | Delete an account credential |

`view`, `get` and `show` are interchangeable verbs, `ls` for `list`, `new`/`add` for `create`, `edit` for `update`, `rm`/`remove` for `delete`. Servers are addressed by Server ID (slug) or UUID; a Server ID that matches more than one server is reported as ambiguous rather than guessed.

#### Catalog browsing

| Option | Description |
|---|---|
| `--search <text>` | Substring match against title, slug and canonical name. A search also finds servers that are not available on Forkline |
| `--limit <n>` | Page size (default: 50, clamped to 500 by the server) |
| `--all` | Walk every page using the server's cursor |
| `--sort <order>` | `name` or `updated` (the default order is Verified first, then Unverified) |

`catalog list` shows two groups, Verified first and then Unverified, with no filters. A server that cannot run on Forkline appears only in a search, with a `not available: <slug> — <reason>` line.

`--sort` is refused unless you pass `--all`. The server pages by keyset (group, then title) and `after` continues that order, so sorting one page locally would not match the cursor you would use for the next one. Ties keep the server's order, which makes repeated runs byte-identical.

```bash
forkline mcp catalog list --search postgres
forkline mcp catalog view context7
```

#### Servers

| Option | Commands | Description |
|---|---|---|
| `--from-catalog <entry>` | `create` | Catalog entry to install (external id or slug) |
| `--version <version>` | `create` | Catalog version (default: the latest installable one) |
| `--file <path>` | `create` | Pasted MCP JSON from Claude Code, Cursor, VS Code or OpenCode; `-` reads stdin |
| `--server <key>` | `create` | With `--file`: which server of the pasted JSON to create (needed when it holds several) |
| `--name <text>`, `--slug <id>` | `create`, `update` | Display name and Server ID (defaults: the catalog title or pasted key) |
| `--credential <name>=<ref>` | `create`, `update`, `upgrade` | Bind a declared credential to a stored secret, e.g. `CTX7_KEY=account/CTX7_KEY` (repeatable; on `update` it replaces all bindings) |
| `--input <name>=<value>` | `create`, `update`, `upgrade` | A non-secret input (repeatable; on `update` it replaces all inputs) |
| `--repository <ref>` | `create`, `update`, `list`, `preview` | On `create`/`update`: a repository to include, `owner/name`, a glob such as `acme/*`, or a UUID (repeatable; default: all). On `list`/`preview`: narrow to one repository |
| `--all-repositories` | `update` | Include every repository |
| `--exclude <ref>` | `create`, `update` | A repository to exclude (repeatable) |
| `--enable` | `create` | Turn the server on right away (it is saved off otherwise) |
| `--config-file <path>` | `update` | JSON config for a custom server; `-` reads stdin |
| `--to-version <version>` | `upgrade` | Target version (default: the latest installable one) |
| `--dry-run` | `upgrade` | Show what would change and stop |
| `--repositories` | `view` | List the repositories the server reaches, with its state on each |
| `--status <status>` | `list` | `working`, `failing`, `needs_setup`, `unavailable`, `not_used_yet` or `off` |

```bash
forkline mcp create --from-catalog context7 \
  --credential CTX7_KEY=account/CTX7_KEY \
  --repository 'acme/*' --enable
cat mcp.json | forkline mcp create --file - --server grafana
forkline mcp list --status failing
forkline mcp preview --repository acme/api
forkline mcp view context7 --repositories
forkline mcp upgrade context7 --to-version 3.3.0 --dry-run
forkline mcp disable context7
```

The status says whether a server runs, never how it was applied. `unavailable` means a problem on our side ("Not running on this repository right now. This is on our side and we've been alerted."). A server that is incomplete is saved off and reads `needs_setup`; `enable` it once its required inputs and credentials are filled in.

With no `--repository`, a server targets every repository you own. The server evaluates `owner/name` globs per repository, so the CLI cannot say locally which repositories a glob will match: use `view --repositories` (one server) or `preview` (one repository).

`--credential` takes a *reference* to a stored secret, never a value. The binding is `NAME=account/<SECRET>` or `NAME=repo/<SECRET>`, and the secret name must be `^[A-Z_][A-Z0-9_]*$`. The `env:VAR` form that appears inside an install plan is a placeholder the runner resolves, not a binding; the CLI rejects it with that explanation rather than letting the API reject it later. `--input` values are sent in the request body and may appear in logs, so keep anything secret out of them. When `--file` finds a literal secret-looking value in the pasted JSON, the CLI warns on stderr; store it with `secrets set` and bind it with `--credential`.

`preview` prints one row per server that applies to the repository. The table also lists the read-only servers from OpenCode settings and the repository settings, marked `read only` (and `replaced by <server>` when a server above overrides one); `-o json` and `-o jsonl` print the whole view (`servers`, `raw`, `warnings`). It reports on stderr any `warning: <server>: <message>`, for example a declared credential that has no stored value.

#### Account credentials

```bash
pass show personal/grafana/token | forkline mcp secrets set GRAFANA_TOKEN --value-stdin
forkline mcp secrets set GRAFANA_TOKEN --value-env GRAFANA_TOKEN
forkline mcp secrets list
forkline mcp secrets describe GRAFANA_TOKEN --description "Grafana viewer token"
forkline mcp secrets delete GRAFANA_TOKEN
```

`secrets set` reads the value from stdin (`--value-stdin`), from a named environment variable (`--value-env <VAR>`), or by prompting on a terminal. There is no `--value` flag: command-line arguments are visible to any process through `ps(1)` and `/proc/<pid>/cmdline`, and for the whole lifetime of the process in some configurations. Non-interactive use without `--value-stdin` or `--value-env` is refused. A trailing newline from a pipe is stripped.

`secrets describe` is the one command to be careful with: it is implemented as `PATCH /me/secrets/{name}`, and the description it sends replaces the stored one. `forkline mcp secrets describe GRAFANA_TOKEN --description "…"` sets it; `forkline mcp secrets describe GRAFANA_TOKEN` with no `--description` prints the metadata **and clears the stored description**. For a read-only listing use `secrets list`.

`secrets list` shows names and when each was last rotated. Values are write-only — the API stores them in Vault and no read endpoint returns them. Deleting a credential leaves the servers that reference it in place; they read `needs_setup` until it is provided again.

### `forkline model`

| Subcommand | Description |
|---|---|
| `list` | List available AI models |
| `providers` | List model providers |

### `forkline connector`

| Subcommand | Description |
|---|---|
| `list` | List configured connectors (read-only) |

### `forkline analytics`

| Subcommand | Description |
|---|---|
| `overview` | Usage overview |
| `providers` | Usage by provider |
| `models` | Usage by model |
| `repositories` | Usage by repository |
| `history` | Historical usage |

All analytics subcommands accept `--from` and `--to` (YYYY-MM-DD).

### `forkline secret`

| Subcommand | Description |
|---|---|
| `list --repository <ref>` | List secrets for a repository |
| `set <name> --repository <ref>` | Set a secret value (via `--value-stdin` or `--value-env`) |
| `delete <name> --repository <ref>` | Delete a secret |

### `forkline token`

| Subcommand | Description |
|---|---|
| `list` | List your API tokens |
| `create --name <n> --scopes <s>` | Create a token (value shown once) |
| `revoke <ref>` | Revoke a token |

### `forkline provider`

| Subcommand | Description |
|---|---|
| `list` | List custom AI providers |
| `import --file <path>` | Import a custom provider from JSON |
| `delete <id>` | Delete a custom provider |

## Routine definitions

Routines are defined as YAML (or JSON) files. Create or update them with `forkline routine create --file <path>` / `forkline routine update <ref> --file <path>`, or sync a directory with `forkline routine push [PATH]` (defaults to `.forkline/routines/`). A file ending in `.json` is read as JSON, anything else as YAML. `name` and `description` sit beside the spec fields:

```yaml
name: weekly-deps
description: Update dependencies every Monday
on:
  schedule:
    cron: "0 6 * * 1"
    timezone: Europe/Berlin
repositories:
  include:
    - name: "acme/*"
agent:
  prompt: Update outdated dependencies in {{ repository.fullName }} and open a PR.
```

The same routine as JSON:

```json
{
  "name": "weekly-deps",
  "on": { "schedule": { "cron": "0 6 * * 1", "timezone": "Europe/Berlin" } },
  "repositories": { "include": [{ "name": "acme/*" }] },
  "agent": { "prompt": "Update outdated dependencies in {{ repository.fullName }} and open a PR." }
}
```

`on` takes exactly one of `events`, `schedule` and `webhook`. The Routines guide (not published yet) covers events, filters, the `when` condition, template variables, repositories, agent settings and limits.

A typical loop is `forkline routine new --example review-prs`, edit the file, `forkline routine validate <file>`, `forkline routine backtest <file>`, `forkline routine test <file> --event <id>`, then `forkline routine push --publish`. Use `forkline routine pull` to download routines that were created elsewhere.

If the server rejects a definition, the CLI prints one line per problem as `file:line: message (pointer)` (`file: message (pointer)` when the line is unknown), and exits with code 2:

```text
routine.yaml:9: Unknown field `inlcude`. Allowed here: `include`, `exclude`. Did you mean `include`? (/filters/labels/inlcude)
```

### Custom webhook secrets

A routine with `on.webhook` and a token or `preset: forkline` gets a secret when it is created. The CLI prints it **once**, with a notice that it will not be shown again; store it then. For `preset: github` and `preset: sentry` the sender holds the secret: store it with `forkline routine webhook <ref> --set-secret secret.txt` (or `-` for stdin). Forkline never shows it back. Use `forkline routine webhook <ref>` to print the webhook URL, the authentication method (for a token, the header name too, so a custom header is not mistaken for `Authorization`) and whether a secret is configured and where it came from, and `forkline routine webhook <ref> --rotate-secret` to replace a generated secret (the old one stops working at once; it asks for confirmation, or pass `--yes` in scripts). The flags exclude each other.

### `forkline billing`

| Subcommand | Description |
|---|---|
| `credits` | Show current credit balance |

### `forkline config`

| Subcommand | Description |
|---|---|
| `path` | Show configuration file path |
| `view` | Show resolved configuration |
| `list-profiles` | List configured profiles |
| `set-profile <name>` | Create or update a profile (use `--api-url` to set API URL) |
| `use-profile <name>` | Set the default profile |

### `forkline completions <shell>`

Generate shell completions. Supported shells: `bash`, `zsh`, `fish`, `powershell`, `elvish`.

The command prints a completion script to stdout. You must install it into your shell's completion directory — **installing the CLI does not enable shell completion automatically**.

> [!TIP]
> **Recommended: one-command install**
> Run `forkline completions install` to auto-detect your shell and install completions to the correct location. Re-run after upgrades to update the script.
>
> ```bash
> forkline completions install
> ```
>
> Or specify a shell explicitly:
>
> ```bash
> forkline completions install zsh
> ```

> [!TIP]
> **Tarball releases**
> Tarball archives from [GitHub Releases](https://github.com/forkline/cli/releases) include pre-generated completion files under `completions/` (`forkline.bash`, `_forkline`, `forkline.fish`, etc.) and a man page. You can copy those files directly instead of regenerating with `forkline completions`.

#### zsh

Choose one of the following methods.

**Option 1 — oh-my-zsh (recommended if you use oh-my-zsh):**

```bash
mkdir -p "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/completions"
forkline completions zsh > "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/completions/_forkline"
exec zsh
```

**Option 2 — plain zsh (user directory, no root required):**

```bash
mkdir -p ~/.zfunc
forkline completions zsh > ~/.zfunc/_forkline
```

Then add these two lines to your `~/.zshrc` **in this order** — `fpath` must be updated **before** `compinit` runs:

```zsh
fpath=($HOME/.zfunc $fpath)
autoload -Uz compinit && compinit
```

If completions still do not appear after restarting your shell, clear the completion cache and restart:

```bash
rm -f ~/.zcompdump*
exec zsh
```

> [!NOTE]
> **Why ordering matters**
> `compinit` indexes the directories in `fpath` at the time it runs. If `fpath` is updated after `compinit`, the new directory is ignored until the next `compinit` call or until `~/.zcompdump` is deleted.

**Option 3 — system-wide (requires sudo):**

```bash
forkline completions zsh | sudo tee /usr/local/share/zsh/site-functions/_forkline >/dev/null
```

#### bash

**System-wide (requires bash-completion and sudo):**

```bash
forkline completions bash | sudo tee /etc/bash_completion.d/forkline >/dev/null
```

**Per-user (loaded automatically by bash-completion):**

```bash
mkdir -p ~/.local/share/bash-completion/completions
forkline completions bash > ~/.local/share/bash-completion/completions/forkline
```

**Per-user (appended to `.bashrc`):**

```bash
forkline completions bash >> ~/.bashrc
source ~/.bashrc
```

#### fish

```bash
mkdir -p ~/.config/fish/completions
forkline completions fish > ~/.config/fish/completions/forkline.fish
```

#### powershell

```powershell
forkline completions powershell
```

Add the output to your PowerShell profile. Run `echo $PROFILE` to find its location.

#### elvish

```elvish
forkline completions elvish
```

Add the output to your Elvish configuration (typically `~/.config/elvish/rc.elv`).

### `forkline man`

Generate a man page (roff format) to stdout:

```bash
forkline man > ~/.local/share/man/man1/forkline.1
```

### `forkline schema`

Print a machine-readable JSON description of the entire CLI command tree, generated directly from the CLI's own parser definitions. Requires no authentication, network access, or configuration file. Useful for tooling and AI agents that want to discover exact command/argument names, types, defaults, environment variables, and conflicts without parsing human help text:

```bash
forkline schema
```

The document is versioned independently of the CLI release (`schema_version`), so consumers should check that field before relying on the structure.

### `forkline agent-guide`

Print a machine-readable JSON guide intended for AI coding agents operating the CLI: a catalog of top-level commands (with auth requirements and subcommands), output-format conventions, non-interactive flags, destructive-operation names, exit codes (including partial bulk failure), and general tips. Requires no authentication:

```bash
forkline agent-guide
```

## Output modes

Control output with `--output` (or `FORKLINE_OUTPUT`):

| Mode | Description |
|---|---|
| `human` | Aligned key-value pairs (default for terminals) |
| `json` | Pretty-printed JSON objects |
| `jsonl` | One JSON object per line (streaming) |
| `quiet` | IDs only, one per line |

`-o` is the short form of `--output`. `--json` is a global shorthand for `--output json`; it wins over `FORKLINE_OUTPUT` but cannot be combined with an explicit `--output` or `-o`. With `--json`, an error is also printed to stdout as `{"error": {"kind", "code", "message", "http_status", "request_id", "retry_after_seconds", "issues"}}`, and the exit code still tells you what failed.

**stdout / stderr contract**: Data output goes to stdout. Progress messages, prompts, and errors go to stderr. This allows piping data output while still seeing errors.

```bash
# Pipe JSON to jq
forkline repo list --output json | jq '.items[].name'

# Collect IDs only
forkline runner list --output quiet > runner-ids.txt
```

## Exit codes

| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General error (API error including `413` payload too large, transport failure, a response the CLI cannot decode, I/O error) |
| 2 | Usage error (invalid arguments, config error, ambiguous reference, or a routine file the server rejects with 422; also `routine run` when the server refuses it with 422, when nothing can run, or when it needs a confirmation and has no terminal) |
| 3 | Authentication error (invalid or missing token) |
| 4 | Authorization error (missing scope or insufficient permissions) |
| 5 | Not found (resource does not exist or you lack access) |
| 6 | Conflict (resource already exists or state conflict; `routine new` and `routine pull` refusing to overwrite a file) |
| 7 | Connection error (cannot reach API server) |
| 8 | Timeout (request or connection timed out) |
| 9 | Rate limited or overloaded (429; check `retry_after`). `routine run` uses it when Forkline is busy or the limit of 10 real runs a minute is reached |
| 10 | Expectation failed (`routine test --expect` does not match, or `routine backtest --fail-if-zero` finds nothing that would run: no event for event routines, no runs per week or no repository for schedules, no repository for webhooks), or partial bulk runner failure (some operations succeeded, some failed) |
| 130 | Interrupted (Ctrl+C / SIGINT) |

## Changes

Breaking changes to the CLI, for scripts written against earlier versions:

- `forkline runner settings set` takes its JSON in `--settings` (it was `--json`). `--json` is now the global output flag.
- `--json` is a global flag for every command, the same as `--output json`. `-o` is the short form of `--output`.
- `forkline routine list --json` prints `{"routines": [...]}`.
- `forkline routine new` writes a local scaffold and is no longer an alias of `routine create`. `create` keeps the alias `add`.
- `forkline routine run --wait --json` prints the run response with a `tasks` array of the final task states, only when at least one task started.
- Responses are decoded strictly: an unknown value or a missing field exits with code 1 and `failed to decode response`, instead of being tolerated. `--json` on the routine commands that print the server document as is (`runs`, `events`, `event`, `test`, `backtest`, `new --from-event`) prints it even when this CLI cannot decode it.

## Custom provider import

Import a custom AI provider from JSON using `forkline provider import --file <path>`:

```json
{
  "provider": {
    "my-provider": {
      "name": "My Custom Provider",
      "npm": "@ai-sdk/example",
      "models": {
        "model-1": {
          "name": "Model 1",
          "contextWindow": 128000,
          "modalities": {
            "input": ["text"],
            "output": ["text"]
          }
        }
      },
      "options": {
        "baseURL": "https://api.example.com/v1"
      }
    }
  }
}
```

> **Note**: `baseURL` must be nested under `options`; the API rejects a top-level `baseURL` field. `contextWindow` uses camelCase (matching the API); the snake_case form `context_window` is also accepted for convenience. Exactly one provider may be included per import file.

## OpenCode configuration

Manage a repository's OpenCode configuration with `forkline repo opencode-config`:

```bash
# View current OpenCode config
forkline repo opencode-config get my-repo

# Set OpenCode config from a file
forkline repo opencode-config set my-repo --file opencode-config.json

# Set OpenCode config from stdin (use --file -)
echo '{"instructions":["Be helpful"]}' | forkline repo opencode-config set my-repo --file -

# Delete OpenCode config (requires --yes when non-interactive)
forkline repo opencode-config delete my-repo --yes
```

The config is a free-form JSON object validated against the bundled OpenCode settings schema; the keys `permission`, `autoupdate`, `model`, and `provider` are reserved for the platform and rejected. Allowed keys include `logLevel`, `mcp`, and `instructions`, for example:

```json
{
  "logLevel": "INFO",
  "mcp": {},
  "instructions": ["Be helpful"]
}
```

## Safety and confirmations

Destructive operations (delete, stop, revoke) prompt for confirmation in interactive mode. In non-interactive mode (piped stdin or `--no-input`), these commands require `--yes`:

```bash
forkline runner stop my-runner --yes
forkline token revoke old-token --yes
```

The `--no-input` flag (or `FORKLINE_NO_INPUT=true`) disables all interactive prompts. Combined with `--yes`, it allows fully scripted usage.

## Environment variables

| Variable | Description |
|---|---|
| `FORKLINE_TOKEN` | API token (takes priority over stored credentials) |
| `FORKLINE_API_URL` | Override the API base URL |
| `FORKLINE_PROFILE` | Select a configuration profile |
| `FORKLINE_OUTPUT` | Default output format (`human`, `json`, `jsonl`, `quiet`) |
| `FORKLINE_NO_INPUT` | Disable interactive prompts (`true` or `false`) |
| `FORKLINE_YES` | Auto-confirm destructive operations (`true` or `false`) |
| `FORKLINE_LOG` | Tracing filter for debug logs (e.g. `debug`, `forkline=trace`) |

## Troubleshooting

| Symptom | Cause | Fix |
|---|---|---|
| Exit code 3 | Invalid or expired token | Run `forkline auth login` or check `FORKLINE_TOKEN` |
| Exit code 4 | Missing API scope | Recreate token with required scopes |
| Exit code 7 | Cannot reach API | Check network, `FORKLINE_API_URL`, and `--connect-timeout` |
| Exit code 8 | Request timed out | Increase `--timeout` (e.g. `--timeout 120s`) |
| Exit code 9 | Rate limited, or Forkline is busy | Wait for `retry_after` seconds |
| Exit code 10 | Expectation failed, or partial bulk runner failure | `routine test --expect` did not match, or `routine backtest --fail-if-zero` found nothing that would run; or some bulk runner operations succeeded and some failed (check the output for details) |

Enable debug logging with `FORKLINE_LOG=debug` for detailed request/response traces.
