# Set up Curviate

You are an agent. This file is a complete instruction: follow it top to bottom and you will
finish with a working `curviate` CLI, authenticated, with a LinkedIn account selected.

Curviate is a LinkedIn API for agents. The CLI is one of its four surfaces (REST, CLI, MCP and a
TypeScript SDK) and it is the fastest one to get working from a shell.

Nothing here posts, messages, invites, follows or endorses anyone. Connecting an account in
step 5 is the one step that hands Curviate a credential, and it is done by a human, not by you.

## 1. Install the CLI

```bash
npm install -g @curviate/cli
curviate --version    # needs Node 18 or newer
```

## 2. Install the skills (optional, recommended)

If your agent supports Claude Code plugins, install the official skills. They carry the full
command surface, the traps, and the exit codes to branch on:

```
/plugin marketplace add curviate/curviate-plugin
/plugin install curviate@curviate
```

If it does not, keep reading. The CLI works on its own.

## 3. Authenticate this machine

`curviate setup` saves an API key into a local profile. The key is never printed and never passed
on a command line. It runs in two legs, because a human has to press Authorize in a browser in
between.

```bash
curviate setup --json
# {"authorize_url":"...","next_step":"curviate setup --code -","instructions":"..."}
```

Give `authorize_url` to your user, ask them to authorize and read back the short code, then hand
that code to the second leg on stdin:

```bash
printf '%s' "$CODE" | curviate setup --code -
# {"ok":true,"tenant":"...","profile":"default"}
```

`account_id` appears in that object only when the workspace already had a connected account to hand
back. A first run does not, which is what step 5 is for.

Three things worth knowing before you script this:

- **The shape is decided by stdout.** Piped, or with `--json`, `setup` prints JSON and exits `0`,
  which is the path above. Left on a terminal it prompts instead.
- **Both legs run on the same machine, under the same configuration directory.** Leg one writes a
  short-lived private file beside the config; leg two reads it and deletes it. Split them and the
  exchange cannot complete.
- **`--code -` with no setup in progress exits `2`.** Run leg one first. Never retry an exit `2`
  unchanged.

Already have a key? `curviate login --api-key -` reads one from stdin, or set `CURVIATE_API_KEY`
in the environment.

## 4. Verify

```bash
curviate doctor --json
```

`doctor` reports the CLI version, the active profile, where the credential resolved from, which
workspace it authenticated as, whether the API is reachable, whether the credential is accepted,
and every connected account.

- **Run `setup` before `doctor`, not the other way round.** Reachability is tested through a real
  call, so with no credential resolved `doctor` reports the API as unreachable. Nothing was wrong
  with the network; there was nothing to call with.
- **Zero connected accounts is a pass.** `doctor` prints `0 connected` and exits `0`. That is the
  expected state of a fresh workspace.
- **Exit `5` means the workspace has no active seat.** The credential is fine and the network is
  fine; re-running `setup` will not change it. That is the one an early run meets most often.

The codes with a `doctor`-specific meaning are `0`, `3`, `7` and `1`. That list is **not
exhaustive**: `doctor`'s credential check is a real API call and it passes the API's own refusal
straight through, so any code the API can produce can surface here. Branch on the exit code, never
on the message text.

## 5. Connect a LinkedIn account, and select it

Curviate acts through a LinkedIn account your user connects. Until one is connected, authentication
is complete and correct while every account-scoped command is unusable. That is a state, not an
error.

```bash
curviate account list --json
```

If it is empty, one has to be connected, and **this is the step your user runs, not you.** It takes
their LinkedIn credential, and a credential must never be pasted to you, quoted back to you, or put
anywhere it would end up in your context or your transcript.

Hand them the link command:

```bash
curviate account link --auth-method cookie --li-at-stdin --user-agent "<their browser User-Agent>" --country <their country, e.g. DE>
# or, for email and password:
curviate account link --auth-method credentials --email <them@example.com> --password-stdin --country <their country, e.g. DE>
```

- **`--country` is required on a new connect**: the ISO code of the country they normally sign in
  from. It is where LinkedIn sees the account connecting from, and a mismatch is a risk signal
  LinkedIn acts on. Ask them; do not guess.
- **`--user-agent` is required with `--auth-method cookie`.** The session cookie only works paired
  with the browser User-Agent it was minted under, so they copy that string from the same browser.
  It is not a secret and it is not needed for the credentials form.
- **`--auth-method` is required; `--seat-id` is not.** Left out, the command uses the workspace's
  only free seat and says which one it took. With no free seat, or with more than one, it exits `2`
  and says so instead of connecting anything, and the second case wants
  `--seat-id <seat_id>` added.
- **`account seats` lists every seat and whether it is free or already bound** (not a secret, you
  can run it yourself), including on a workspace with no seats at all:

  ```bash
  curviate account seats --json
  ```

  If it comes back empty, the workspace needs a seat added from the dashboard before a link can
  succeed.
- **The credential goes in on stdin, typed by them.** `--li-at-stdin` and `--password-stdin` exist
  so the value never appears on a command line, where other processes can read it from `ps` and the
  shell writes it to history. The same reasoning applies to you: do not offer to run the command on
  their behalf with the value inline, and do not ask them to tell you what it is.
- **LinkedIn usually then wants a verification code.** On a non-interactive shell the command exits
  `12`, and they finish with `curviate account checkpoint solve <acc_id> --code <otp>`. A one-time
  code is not a standing credential, so reading that one back to you is fine.

When they are done, `curviate account list --json` shows the account and you can carry on.

Then tell the CLI which account to act as. This step is required, and it is easy to miss:

```bash
curviate config set-account <acc_id>
```

`setup` stores a default account only when the workspace already had one to hand back, which a
first run does not. Without a default, every account-scoped command stops at exit `2` asking for
`--account`. If your credential came from the environment rather than `setup`, there is no stored
profile to write to: pass `--account <acc_id>` on each call, or set `CURVIATE_ACCOUNT`.

## 6. Prove it works

Read your own profile twice: once live, once from the store.

```bash
curviate profile me --mode live --json          # "source":"live"
curviate profile me --mode cache_only --json    # "source":"store", with "observed_at"
```

The first forces a call to LinkedIn. The second refuses to go to LinkedIn at all and answers from
the copy the first one stored. Read `source` rather than assuming it: that field, not the freshness
you asked for, is what actually happened.

Only four commands accept `--mode`/`--max-age`: `profile me`, `profile <id>`, `inbox get` and
`inbox messages`. Every other command refuses the flags rather than ignoring them. `job publish
--mode` is an unrelated flag of the same name that costs money.

## First tasks

| Goal | Start with |
|---|---|
| Look someone up, or a company | `curviate profile <id>` · `curviate company <id>` |
| Find people, companies, posts or jobs | `curviate search people --help` |
| Read and triage messages | `curviate inbox list` · `curviate inbox messages <id>` |
| Post, comment, react | `curviate post get <id>` · `curviate comment list <id>` |
| Connections and follows | `curviate connect received` · `curviate profile relations` |
| Job postings and applicants | `curviate job list --state ALL` · `curviate job applicants <id>` |
| Sales Navigator or Recruiter | `curviate sales-nav --help` · `curviate recruiter --help` |

`curviate --help` lists everything. Add `--json` to any command for machine-readable output, and
branch on the exit code.

Two codes to handle deliberately. Exit `13` means Curviate's own ceiling refused the action:
nothing reached LinkedIn, nothing was spent, and the reset can be weeks out, so backing off and
retrying is the wrong move. Exit `6` means LinkedIn rate-limited it and carries `retry_after` in
whole seconds, where backing off is right.

Docs: https://docs.curviate.com · API: https://api.curviate.com
