# bitty CLI

> Install the bitty command line, sign in with an access token, and use its commands, output modes and exit codes from a terminal, scripts and AI agents.

`bitty` is the Bitty Course command line. It talks to your workspace's [MCP server](/docs/mcp), the same tools an AI assistant uses, so permissions, plan limits and validation all come from the server. Use it to check courses, links, stats and discounts from a terminal, a script or an agent.

## Install

`bitty` is a single Ruby script that uses only Ruby's standard library. It needs Ruby 3.2 or newer and nothing else.

To install it:

1. Download it from your Bitty Course app address at `/cli/bitty`, for example `curl -fsSLo bitty <your Bitty Course app address>/cli/bitty`. The **Docs** link in the studio sidebar is on the same address.
2. Make it executable: `chmod +x bitty`.
3. Put it on your `PATH`, for example with a link: `ln -s "$PWD/bitty" /usr/local/bin/bitty`.
4. Check it runs: `bitty --version` prints `bitty 0.1.0`.

## Sign in

You sign in with a personal access token. See [Create a token](/docs/mcp#create-a-token).

To sign in:

1. Create a token in **Settings → AI assistant**. Choose the access level you want the CLI to have.
2. Run `bitty auth login --url <your Bitty Course app address>`. Use the address only, without `/mcp`; `bitty` adds that itself.
3. Paste the token when asked. The input is hidden.

`bitty` checks the token with the server before saving it. If the token is refused, nothing is saved and it exits with code 3.

To sign in without a prompt (in a script), pass the token on standard input:

```sh
bitty auth login --url <address> --with-token < token.txt
```

Without `--with-token`, `auth login` needs a terminal to prompt in.

### Auth commands

| Command | What it does |
| --- | --- |
| `bitty auth login [--url URL] [--with-token]` | Check a token and save it with the address. |
| `bitty auth status` | Whether you're signed in, to which workspace, your role, the token's access level, and where the token came from. `bitty auth` alone does the same. Exits with code 3 when you're not signed in. |
| `bitty auth logout` | Forget the token on this computer. |
| `bitty auth token` | Print the saved token. |

`bitty auth logout` only removes the token from this computer. It keeps working until you revoke it in **Settings → AI assistant**.

## Config and environment variables

The address and token are saved to `~/.config/bitty/config.json`, readable only by you (file mode 600).

| Variable | Effect |
| --- | --- |
| `BITTY_URL` | Bitty Course address to use instead of the saved one. |
| `BITTY_TOKEN` | Token to use instead of the saved one. |
| `BITTY_CONFIG_DIR` | Folder for `config.json` instead of `~/.config/bitty`. |
| `XDG_CONFIG_HOME` | If set (and `BITTY_CONFIG_DIR` isn't), the config goes in `$XDG_CONFIG_HOME/bitty`. |

The address is taken from `--url`, then `BITTY_URL`, then the saved config. If none is set, `bitty` can't reach your workspace, so always sign in with `--url`.

## Commands

The shape is `bitty <command> [action] [args] [flags]`. `bitty --help` lists everything, and `bitty <command> --help` shows one command's usage.

| Command | Description |
| --- | --- |
| `auth` | Sign in with a token and check the connection |
| `workspace` | Your workspace: plan, limits and courses |
| `courses` | List courses, see one, get its links, stats or publish it |
| `discounts` | Coupon codes and timed sales for a course |
| `stats` | Workspace totals over a date range |
| `tools` | List the MCP tools this token can use |
| `call` | Call any MCP tool with JSON or key=value arguments |
| `commands` | Every command and action, as JSON |

### workspace

```sh
bitty workspace
```

Your workspace's name, plan, limits, video storage, whether payments are set up, the token's access level and your courses.

### courses

```sh
bitty courses                                    # list: id, title, status, access, price, lessons
bitty courses show <course>                      # details, sections and what still blocks publishing
bitty courses link <course> [--url-only]         # public, preview and studio links, plus coupon links
bitty courses stats <course> [--range 7d|30d|90d|12m|all]
bitty courses publish <course>
```

`<course>` is an id, a slug, or part of the title when it matches exactly one course. If a name matches several courses, `bitty` lists them and exits with code 8; use the id.

`--url-only` prints just the public link. Until the course is published, the public link shows "not available".

`courses publish` needs a **Build and publish** token. If something blocks publishing, the error lists what.

`bitty course` works the same as `bitty courses`.

### discounts

```sh
bitty discounts <course>                         # coupons and sales
bitty discounts show <course> <id|CODE>          # uses, revenue, savings (no student names)
bitty discounts create <course> --code CODE (--percent N | --amount N) [--name NAME] [--starts TIME] [--ends TIME] [--limit N]
bitty discounts sale <course> --name NAME (--percent N | --amount N) --ends TIME [--starts TIME]
bitty discounts off <course> <id|CODE>
bitty discounts on <course> <id|CODE>
```

- Times are ISO 8601 with a zone, for example `2026-12-01T09:00:00-05:00`.
- `--limit` is how many students can use a coupon. Leave it out for no limit.
- Only workspace owners create or switch discounts. A published course also needs a **Build and publish** token.
- Amounts and dates can't be edited. Turn the discount off and make another.

`bitty discount` and `bitty coupons` work the same as `bitty discounts`. See [Coupons and sales](/docs/coupons-and-sales).

### stats

```sh
bitty stats [--range 7d|30d|90d|12m|all]
```

Workspace totals and a row per course: students, new students, revenue and average progress. The default range is 30 days. Stats never include student names or emails. See [Analytics](/docs/analytics).

### tools and call

```sh
bitty tools                                      # the MCP tools this token can call
bitty call get_transcript lesson_id=12           # key=value arguments
bitty call get_course '{"course_id": 1}'         # or one JSON object
```

`call` reaches every tool the token can use, including ones without their own command, such as building outlines or adding captions. Values in `key=value` are read as JSON when they parse (numbers, `true`, arrays), otherwise as text. See [MCP tool reference](/docs/mcp-tools) for each tool's arguments.

### commands

```sh
bitty commands
```

Every command, its description and its actions, as JSON. Useful for an agent to discover what's available.

## Output modes

| Flag | Output |
| --- | --- |
| (none, in a terminal) | Readable text: a summary line, a table or fields, then suggested next commands. |
| (none, piped) | The JSON envelope, as with `--json`. |
| `-j`, `--json` | JSON envelope. |
| `-q`, `--quiet` | The data only, as JSON, no envelope. |
| `--agent` | Same as `--quiet`, for scripts and assistants. |
| `-m`, `--md` | Markdown: a bold summary, then a table or a list. |

The JSON envelope on success:

```json
{
  "ok": true,
  "data": { },
  "summary": "One line saying what happened",
  "breadcrumbs": [ { "action": "Show a course", "cmd": "bitty courses show <id>" } ]
}
```

`breadcrumbs` suggest next commands and are left out when there are none.

On failure, in JSON, quiet and agent modes, the error is printed to standard output:

```json
{ "ok": false, "error": "Not signed in.", "code": "auth", "retryable": false, "hint": "Run: bitty auth login" }
```

In text and Markdown modes, the error and hint go to standard error instead.

`retryable` is `true` for rate limits and network trouble.

## Exit codes

| Exit | `code` | Meaning |
| --- | --- | --- |
| 0 | | OK |
| 1 | `usage`, `invalid` | Usage or invalid input |
| 2 | `not_found` | Not found |
| 3 | `auth` | Not signed in, or the token was refused |
| 4 | `forbidden` | Forbidden (role or token permission) |
| 5 | `rate_limit` | Rate limited (retryable) |
| 6 | `network` | Network trouble or a server error (retryable) |
| 7 | `api` | Other server error |
| 8 | `ambiguous` | A name matched several courses |

## Examples

For a script:

```sh
# Stop if not signed in (auth status exits 3 when there's no token)
bitty auth status -q || exit 1

# The public link of course 1
url=$(bitty courses link 1 --url-only)

# Course ids and titles, with jq
bitty courses --agent | jq -r '.[] | "\(.id)\t\(.title)"'

# Retry once after a minute on a retryable failure (exit 5 or 6)
bitty stats --range 7d --json > stats.json
status=$?
if [ $status -eq 5 ] || [ $status -eq 6 ]; then
  sleep 60
  bitty stats --range 7d --json > stats.json
fi
```

For an AI agent, in an agent's instructions:

```text
Use the bitty CLI with --agent for Bitty Course. Start with `bitty commands` and `bitty workspace`.
Exit code 8 means the course name was ambiguous: use the id. Exit 4 means the token or role doesn't allow it.
Only run `bitty courses publish` or create discounts when I ask.
```

Owner examples:

```sh
bitty discounts create 1 --code FRIENDS --percent 20 --limit 50
bitty discounts sale 1 --name "Launch week" --percent 30 --ends 2026-12-01T00:00:00Z
bitty discounts show 1 FRIENDS --json
bitty discounts off 1 FRIENDS
```

## Good to know

- The token's access level applies: **Read only** can look, **Build drafts** can't touch published courses, and discounts need a workspace owner's token. See [MCP server](/docs/mcp).
- Changes made through `bitty` appear in the **Activity** list under **Settings → AI assistant**, under the token's name.
- The server's rate limit applies to `bitty` too. Looking up a course by name or slug takes extra requests; ids are fastest.
