Install in Claude Code
Copygit clone --depth 1 https://github.com/FailproofAI/failproofai /tmp/fp-cloud-cli && cp -r /tmp/fp-cloud-cli/fp-cloud-cli/skill ~/.claude/skills/fp-cloud-cliThen start a new Claude Code session; the skill loads automatically.
Definition
SKILL.md
# FailproofAI Cloud CLI
`fp` is a command-line client for a FailproofAI Cloud deployment. It authenticates
either as a signed-in **user** or with a scoped **API key** (§2), and every command
takes `--json`, so it's built to be driven by an agent.
## 1. Find how to invoke it
Resolve this once, then reuse it for every call:
1. If `fp` is on `PATH` (`command -v fp`) → use **`fp`** (it's
installed via pipx / uv tool / pip). This is the normal case.
2. Else, if you're in (or under) a repo with an `fp-cloud-cli/` directory containing the
`fp_cli` package → run it from there with **`uv run fp`** (a local dev
build). The first run after a code change prints `Building…`/`Installed…` on
stderr — that's `uv`, not CLI output; ignore it.
3. Else the CLI isn't available here → tell the user to install it
(`pipx install fp-cloud-cli` or `uv tool install fp-cloud-cli`) and stop. Don't try to
reach the dashboard another way.
Don't go spelunking in the CLI source tree for flags — if you're unsure of one,
run `fp <group> <cmd> --help`. The source is not the documented contract
and reading it wastes effort.
Throughout this skill, `fp` means "whichever form you resolved."
## 2. The contract (the CLI enforces it, work with it, don't fight it)
- **Global options go BEFORE the command:** `fp --json events`, never
`fp events --json`. Globals are `--base-url`, `--org`, `--token`,
`--api-key`, `--json`, `--insecure`/`--secure`. After the command they're a
usage error.
- **Two ways to authenticate, and they are not interchangeable:**
| | How you supply it | What it is |
|---|---|---|
| **Session** | `fp login` (interactive; it emails a one-time code) | a signed-in **user**, carrying that person's org memberships and permissions |
| **API key** | `--api-key <key>`, or `FP_API_KEY` in the environment | a scoped **credential**, carrying exactly the permissions it was granted |
A key is what you want in CI or any other non-interactive context: no browser, no
emailed code, nothing to expire mid-run.
**Credential precedence, in full** (`resolve_auth`, `fp_cli/_context.py`). Read it
as a ladder — the first rung that applies wins, and an explicit flag outranks
*every* environment variable, not just its own:
0. `--api-key` **and** `--token` together → usage error, exit 2. A silent guess
about which you meant is the one outcome worth refusing.
1. `--api-key <key>` → key mode
2. `--token <tok>` → session mode. **This beats an ambient `FP_API_KEY`** — the
flag is checked before the environment value, so "`FP_API_KEY` wins" is only
true between the two env vars.
3. `FP_API_KEY` → key mode
4. `FP_TOKEN` → session mode
5. the saved session from `fp login` → session mode
The rung that catches people is 2: exporting `FP_API_KEY` in CI and *also*
passing `--token` runs as that user's saved session, with their org memberships,
rather than under the scoped key you meant to audit.
**A key is never written to the CLI's saved config** — pass it every time, from
the environment. `--api-key ""` means "no override" and does **not** fall back to
a saved session (Click treats an empty env var as unset, so it falls to rung 4).
- **Some commands need a signed-in user.** `login`, `logout`, `orgs *`, the whole
`agent` group, `keys update`, and all of `policies *` / `fleet *` /
`guardrails *` refuse a key with a usage error (**exit 2**) and make **no
network call at all** — there is no user to sign in, no saved active org to
switch, no private assistant thread to own, and the enforcement write routes
are root-only and deliberately absent from `/v1`. `keys update` is in that list
rather than a special case: `keys:update` can never be granted to a key, so it
is refused up front like the rest. The key is not the problem to fix — plan
around them rather than retrying or hunting for a flag.
- **Default to `--json` and parse it.** It prints clean JSON to stdout and
nothing else. The plain output is a boxed Rich UI meant for human eyes — it
burns context with box-drawing characters and is awkward to parse. Use the
rendered output only when the user explicitly wants to *look* at something.
- **Data → stdout, status/errors/prompts → stderr.** So a `--json` stdout
capture is pure JSON even when a status line is shown.
- **Branch on exit codes — don't scrape error text:**
| code | meaning | what to do |
|---|---|---|
| 0 | ok | parse stdout |
| 1 | unexpected / server error | report it to the user |
| 2 | usage error (bad flags/args) | fix the command and retry |
| 3 | can't reach the dashboard | check base-url / connectivity |
| 4 | no usable credential — not signed in, session expired, **or the API key was rejected** | session: user must run `fp login`. Key: it's missing, mistyped, disabled, or belongs to another deployment — don't retry, and don't fall back to a session |
| 5 | authenticated but missing permission | message names the exact permission |
| 6 | resource not found | the named resource doesn't exist |
## 3. First call: confirm you're connected
Before real work, run `fp --json whoami` and react to the exit code:
- **exit 4** → no usable credential. If the user is working from a session, tell
them to run `fp login` (it emails a one-time code and prompts
interactively — you can't complete it for them, and don't fabricate a token).
If a key was supplied, the key itself was rejected — say so and stop; logging in
is not the fix, and silently switching to a session would run the command as a
different identity than the user asked for.
- **base-url** → the CLI defaults to the hosted product,
`https://app.befailproof.ai`, so a plain `fp login` works out of the box.
Only pass `--base-url <url>` (or set `FP_DASHBOARD_URL`) for a self-hosted
or dev deployment — a local dev stack is usually `http://localhost:3000`. A
scheme-less URL is rejected as a usage error (exit 2).
- **exit 0** → `whoami` retMore from this repository