# CLI

> The Reply command line — sign in once, then every v3 API request runs as you, from your terminal or your scripts. Install with `npm i -g reply-cli`. It handles authentication, profiles and teams, reaches the whole v3 API through `reply api`, installs the Reply agent skills into your AI assistants, and keeps itself up to date.

## Status: Available (v0.5.1)

`reply` ships four things today:

- **Authentication and identity** — OAuth or API-key login, multi-account profiles, and team
  selection, so one machine can act as several users across several workspaces.
- **`reply api`** — an authenticated passthrough that reaches every documented endpoint of the
  [Reply.io v3 API](/apis).
- **`reply skills`** — installs the [Reply agent skills](/skills) into every AI assistant on the
  machine, dependencies resolved.
- **`reply install`** — updates the CLI itself, so nobody has to remember an npm command.

There are **no dedicated commands for sequences, contacts, the inbox, or reporting yet** — see
[Where the command surface is going](#where-the-command-surface-is-going) for why that is a
deliberate wait rather than a backlog. `reply api /v3/...` reaches all of it today. The CLI is
**pre-1.0** — see [Versioning & stability](#versioning--stability).

Source: [github.com/reply-team/reply-cli](https://github.com/reply-team/reply-cli) · Package:
[npmjs.com/package/reply-cli](https://www.npmjs.com/package/reply-cli) · Docs:
[docs.reply.io/cli/overview](https://docs.reply.io/cli/overview).

## Install

```bash
npm install -g reply-cli      # installs the `reply` command (Node 20+)
reply --version
```

### Staying current

One command, and you never type an npm invocation again:

```bash
reply install                 # update to the newest release, or say you are already on it
reply install --dry-run       # check without changing anything — usable as a CI gate
```

It looks up the newest release and updates the copy it can safely update. Where the copy is not
the CLI's to change — installed inside a project, run through `npx`, built from a checkout — it
prints the command that fits your setup and touches nothing. `reply --version` mentions a newer
release when one exists; that check reads the public GitHub releases, is cached for a day, times
out after a second and a half, and stays silent when it fails. It never runs with `--json`, when
output is piped, in CI, or with `REPLY_NO_UPDATE_CHECK=1` set.

## Authentication

Sign in once; every request then runs as you.

```bash
reply auth login              # browser OAuth
reply auth login --with-token # store an API key, read from stdin (never hits shell history)
reply auth status             # who you're signed in as, and how — no secrets shown
reply auth whoami             # verify the stored credential against the API
reply auth logout             # remove the stored credential
```

Create an API key in Reply → **Settings → API Keys**. For a single command, pass `--api-key` or
set `REPLY_API_KEY`; both take precedence over a stored login and are never written to disk.
Resolution order: `--api-key` → `REPLY_API_KEY` → stored login.

## Command map

Run `reply <command> --help` for flags and examples on any command — the installed help always
matches your version; add `--json` for machine-readable output. The canonical command reference,
including every global flag, is
[docs.reply.io/cli/overview](https://docs.reply.io/cli/overview).

| Command | What it does |
|---------|--------------|
| `reply auth login\|status\|whoami\|logout` | Sign in (OAuth or `--with-token`), inspect, verify, or clear the credential |
| `reply profile add\|use\|list\|current\|show\|set\|rename\|unset\|delete` | Keep multiple accounts signed in; each profile stores its own credential |
| `reply team list\|current\|use\|clear` | See the teams you can act in and pin one (sent as `X-TEAM-ID`) |
| `reply api <path>` | Authenticated passthrough to any v3 endpoint — the agent/CI escape hatch |
| `reply skills install\|list\|update\|remove` | Put the [Reply agent skills](/skills) into the AI assistants on this machine, or take them out |
| `reply install` | Update the CLI itself (`reply update` is the same command) |

## Raw API access

`reply api` is a raw, authenticated passthrough to any v3 endpoint. Use the path exactly as it
appears in the [API reference](https://docs.reply.io/api-reference/introduction) (starting with
`/v3`); the query string goes in the path. A `--body` switches the method to POST (it also accepts
`@file` or `-` for stdin).

```bash
reply api /v3/whoami                          # your identity + team
reply api /v3/sequences                       # list sequences
reply api /v3/contacts --pretty               # list contacts, indented
reply api /v3/sequences/12345                 # one sequence by id
reply api /v3/contacts --body @contact.json   # create a contact (POST; body schema per the docs)
echo '<json>' | reply api /v3/contacts --body -   # body from stdin
reply api /v3/whoami --verbose                # full request/response on stderr
```

It prints `{ "code": <status>, "data": <body> }` and exits non-zero on HTTP `>= 400`. `--verbose`
adds a full request/response trace on stderr with credentials redacted; stdout stays plain JSON, so
pipes keep working.

## Installing the agent skills

The [Reply agent skills](/skills) are the reasoning layer — they teach an agent how outbound
actually works and how to compose the API calls this CLI executes. The CLI installs them: it
detects the AI assistants on the machine and puts the packs into each one, resolving
dependencies so `reply-adapter` pulls `ai-sdr-core` with it.

```bash
reply skills install                   # every pack, every detected assistant
reply skills install core              # aliases: core · adapter · runtime
reply skills install adapter runtime
reply skills install --agent codex     # only this assistant
reply skills install --project         # into this repository, not your home directory
```

```
✓ detected Claude Code, Codex
✓ Claude Code · ai-sdr-core, reply-adapter, agentic-runtime installed
✓ Codex       · ai-sdr-core, reply-adapter, agentic-runtime installed
Start a new session in each assistant so the skills load.
```

Then manage them:

```bash
reply skills list             # what is installed where, and what has an update available
reply skills update           # bring installed packs to the latest version
reply skills remove runtime   # remove one pack; `reply skills remove` removes all of them
```

`--json` gives a machine-readable report and `--dry-run` shows the plan without changing
anything. On Claude Code and Codex the packs go in through the assistant's own plugin mechanism,
so they keep updating through it; other `SKILL.md` hosts receive the skills as files.

**Claude Code and Codex are the two hosts this is verified against.** For Cursor, Windsurf,
Gemini CLI and GitHub Copilot the installer writes to each host's documented skills directory,
but that those hosts read from it has not been confirmed — the command labels them
`(paths not yet verified)` in its output and marks them `"verified": false` in `--json`.

Installing skills is not the same as connecting Reply: the `reply-adapter` pack needs a login
(`reply auth login`) before it can do anything.

## Profiles & teams

A **profile** is a signed-in account; a **team** is the workspace a request acts in. Most people
never touch either — the built-in default profile is used until you create another. They matter when
an agent has to act as more than one Reply.io user, or across more than one workspace, without
re-authenticating between calls.

### Profiles

Each profile stores its own credential. Name them however you like; account emails work well:

```bash
reply profile add alice@reply.io           # --team-id optionally pins a team at creation
reply profile use alice@reply.io           # make it the active profile
reply auth login                           # signs in alice@reply.io
reply profile list                         # '*' marks the active profile
reply profile current                      # just the active profile name
reply --profile bob@reply.io auth whoami   # override for a single command
```

The active profile resolves as `--profile` → `REPLY_PROFILE` → the profile set with `profile use`
→ the built-in default.

Manage them after creation:

```bash
reply profile show                              # active profile: team + which authorization would be used
reply profile show alice@reply.io               # a specific one
reply profile set alice@reply.io --team-id 1045 # pin a team on a profile
reply profile unset alice@reply.io team-id      # clear a field (authority|api_base|team-id)
reply profile rename alice@reply.io ally        # also moves the stored credential
reply profile delete ally                       # removes it and its stored credential (-y skips the prompt)
```

`profile show` prints the backend URLs, the pinned team, and which authorization *would* be used —
in priority order `--api-key` → `REPLY_API_KEY` → stored credential. It never prints tokens or keys,
and neither does any other command, `--verbose` included.

**Where credentials live.** JSON in the config directory (`~/.config/reply`, or `%APPDATA%\reply` on
Windows), written `0600` inside a `0700` directory — the same plaintext-file model as `gh`, `aws`,
and `az`. Records are keyed by profile name, so two accounts on the same backend never collide. An
OAuth record refreshes its access token automatically; if the refresh fails the record is cleared and
you're asked to log in again.

### Teams

A profile can pin a team (workspace). The pin is sent as the `X-TEAM-ID` header on every request,
with precedence `--team-id` → `REPLY_TEAM_ID` → the profile's team. The `team` command reads and
writes the **active profile's** team:

```bash
reply team list                          # teams you can act in ('*' marks the profile's team)
reply team current                       # the pinned team, plus the effective team from whoami
reply team use 1045                      # verify 1045 is one of your teams, then pin it
reply team clear                         # remove the pin
reply --team-id 1045 api /v3/sequences   # or scope a single call, no pin
```

If a call needs a team and your credential spans several, the API answers `TEAM_REQUIRED` listing
your teams, and the CLI adds a one-line fix-it hint on stderr. Pin one with `reply team use <id>`,
or pass `--team-id` for that command only.

With an [organization API key](https://docs.reply.io/api-reference/authentication) you can also name
the acting user per call — `--user-id <id>` or `--user-email <email>` (the email form requires a team
id). Both are ignored by ordinary personal keys.

## Built for agents

- **Sign in once, run as you** — OAuth or API key; subsequent calls carry your identity with no
  per-request auth code.
- **JSON everywhere** — add `--json` for machine-readable output; `reply api` emits
  `{ "code", "data" }`, safe to pipe into `jq` or agent logic.
- **Full API reach today** — `reply api /v3/...` covers every documented endpoint before the
  ergonomic wrappers land, and the [skills](/skills) tell the agent which call to compose.
- **Multi-account** — profiles and team pinning let one agent act across workspaces without
  re-authing.
- **It installs its own reasoning layer** — `reply skills install` puts the outbound expertise
  into the assistant, so procedure and execution arrive together rather than being wired up by
  hand.
- **A stable machine contract** — data on stdout, status and errors on stderr; exit `0` success,
  `1` API or runtime failure, `2` usage error; `429`/`5xx` retried internally (honoring
  `Retry-After`), so don't wrap your own retry loop around writes. Full contract:
  [docs.reply.io/cli/agents](https://docs.reply.io/cli/agents).

```bash
# reach the whole v3 API through the passthrough available today
reply api /v3/sequences | jq '.data'
```

## Environment variables

| Variable | Description |
|----------|-------------|
| `REPLY_API_KEY` | API key used as the credential for the current invocation |
| `REPLY_PROFILE` | Profile to use (same as `--profile`) |
| `REPLY_TEAM_ID` | Team/workspace id sent as `X-TEAM-ID` (same as `--team-id`) |
| `REPLY_CONFIG_DIR` | Config directory (default `~/.config/reply`; `%APPDATA%\reply` on Windows) |
| `REPLY_NO_UPDATE_CHECK` | Set to `1` to never check whether a newer release exists |

## Where the command surface is going

There are **no dedicated commands for sequences, contacts, the inbox, or reporting yet**, and the
gap is deliberate rather than a backlog nobody got to.

A command surface is a promise: once `reply sequence start` exists, its name, flags and output
shape are a contract, and a wrong shape is expensive to unpick after people script against it. So
rather than translating the endpoint list into commands one for one, the commands will follow the
**SDR operation contract** — the vendor-neutral set of business operations already written down in
[`sdr-operations`](https://github.com/reply-team/reply-skills/blob/main/plugins/ai-sdr-core/skills/sdr-operations/SKILL.md),
where each operation carries what it means, whether it is reversible, and whether it needs a
human's approval before it runs. Commands that mirror that contract stay stable as the API grows.
Commands that mirror today's endpoint list do not. No date is published for them, and this page
does not imply one.

**Nothing is blocked in the meantime.** `reply api /v3/...` reaches every documented endpoint,
and the [agent skills](/skills) exist precisely so an agent does not have to guess which call to
make: they carry the operation-to-endpoint mapping, the order to call things in, the scopes each
one needs, and where to stop for human approval. An agent with the skills installed composes
correct `reply api` calls today — the ergonomic commands will shorten what it types, not unlock
something it cannot currently do.

Where a shell is the wrong fit, use the surface that is: [REST](/apis) for your own integration,
[MCP](/mcp) for a tool surface inside an LLM client.

## Versioning & stability

`reply-cli` follows [semantic versioning](https://semver.org), with the version derived
automatically from Conventional Commit messages (`fix:` → patch, `feat:` → minor). **It is pre-1.0**:
while the version is `0.x`, a minor release (`0.x.0`) may contain breaking changes, per semver's own
0.x rule — pin an exact version if you need stability before `1.0.0`. 1.0.0 is cut once the command
surface and the `--json` output shape are declared stable.

From `1.0.0` on, a major bump is required to remove or rename a command, flag, or argument; change
the meaning of an exit code; change the `--json` / `--pretty` output shape; change the config format
or a `REPLY_*` environment variable; or raise the minimum Node.js version. Anything on that list is
deprecated first — it keeps working and warns on **stderr** (never stdout, so `--json` stays clean)
for at least one minor release before it is removed.

## Contributing

The CLI is developed in the open, and **anyone can clone it, build it, and open a pull request** —
contributions and bug reports are welcome. Merging is restricted: only Reply employees approve and
merge, so open a PR from a branch or a fork and a maintainer reviews it.

```bash
git clone https://github.com/reply-team/reply-cli && cd reply-cli
npm install
npm run build   # compile TypeScript to dist/
npm test        # vitest; the suite is fully offline — no test touches Reply.io
npm link        # put your build's `reply` binary on your PATH
```

The house rules are the same contract your scripts depend on, so they're worth knowing either way:

- **stdout is data, stderr is everything else** — status and error messages never pollute a pipe.
- **`--json` is compact, `--pretty` is indented**; on either, an error is one machine-readable line:
  `{"error":{"status":…,"code":…,"title":…,"detail":…,"hint":…}}`.
- **Exit codes** — `0` success, `1` API or runtime failure, `2` usage error.
- **Secrets are never printed** — token and key fields are redacted in every code path.
- **Conventional Commits are enforced** by commitlint (a local `commit-msg` hook and a PR check),
  because the release version is derived from them. While the CLI is on `0.x`, don't use `!` or
  `BREAKING CHANGE:` — a breaking change rides in an ordinary `feat:`.

Build layout, the test approach, credential-store internals, and the release process are documented
in [CONTRIBUTING.md](https://github.com/reply-team/reply-cli/blob/main/CONTRIBUTING.md).

Source and issues: [github.com/reply-team/reply-cli](https://github.com/reply-team/reply-cli) ·
Package: [npmjs.com/package/reply-cli](https://www.npmjs.com/package/reply-cli) · Docs:
[docs.reply.io/cli/overview](https://docs.reply.io/cli/overview)

## FAQ

**Does the CLI exist today?**

Yes. Install it with `npm i -g reply-cli` (Node 20+). The current version covers authentication
(OAuth and API key), multi-account profiles, team selection, `reply api` — a raw authenticated
passthrough to the full v3 API — plus `reply skills` to install the agent skills into your AI
assistants and `reply install` to keep the CLI itself current. Dedicated commands for sequences,
contacts and the inbox are not there yet; use `reply api /v3/...` for those today. The canonical
API reference is at docs.reply.io.

**Why a CLI when there's already REST and MCP?**

Coding agents are exceptionally good at running shell commands. The CLI signs in once and carries
your identity into every call, so an agent reaches the whole v3 API through `reply api` without
writing HTTP client or auth code — and humans get a scriptable surface for one-off operations.

**How does it relate to the agent skills?**

The [skills](/skills) are the reasoning layer — they teach an agent what an outbound operation is,
which one this situation calls for, and where it must stop and ask a human. The CLI is the
execution layer that carries it out. The CLI also installs them: `reply skills install` puts the
packs into every AI assistant it finds. Today a skill composes `reply api /v3/...` calls, using
the endpoint mapping it carries; as the higher-level commands land, it will shell out to those
instead, and the procedure it follows will not change.

**Can I contribute to the CLI?**

Yes — it's developed in the open. Clone the repo, run `npm install && npm run build && npm test`
(the test suite is fully offline), and open a pull request from a branch or a fork. Only Reply
employees can approve and merge, so a maintainer reviews every PR. Commit messages follow
Conventional Commits because the release version is derived from them. Full guide: CONTRIBUTING.md
in github.com/reply-team/reply-cli.

**How stable is it?**

Pre-1.0. While `reply-cli` is on 0.x, a minor release may contain breaking changes — pin an exact
version if you need stability. From 1.0.0 on, anything in the compatibility surface (commands,
flags, exit codes, `--json` shape, config format, `REPLY_*` variables, minimum Node version) needs a
major bump, and is deprecated with a stderr warning for at least one minor release first.
