# CLI commands

`seekrit <command> [options]`. At runtime a **service token selects the org, app,
and environment** — so `seekrit run`/`export` need no config file. Management
commands select their target with `--org`/`--app`/`--group`/`--env` flags
(plus `--branch` for an ephemeral [branch config](/docs/guides/branches)),
falling back to the optional `seekrit.json` written by `seekrit init`.

## Environment variables

| Variable | Purpose |
| --- | --- |
| `SEEKRIT_TOKEN` | Service token (`skt_…`). Carries its bound org + app + environment. Also accepts a CLI session token (`skc_…`) if you'd rather pass one explicitly than save it. |
| `SEEKRIT_CLIENT_ID` / `SEEKRIT_CLIENT_SECRET` | Machine (M2M) credentials. When set (and no `SEEKRIT_TOKEN` is), an admin token is minted from them automatically and cached — the seamless path for autonomous agents. |
| `SEEKRIT_API_URL` | API base URL. Overrides saved config; defaults to `https://api.seekrit.dev`. |
| `SEEKRIT_PASSPHRASE` | Passphrase to unlock your private key non-interactively. |
| `SEEKRIT_CACHE` | Set to `1` to enable the [last-known-good cache](#last-known-good-cache) for `run` / `export` without passing `--cache`. |
| `SEEKRIT_CACHE_DIR` | Where cached responses live. Defaults to `$XDG_CACHE_HOME/seekrit`, else `~/.cache/seekrit`. |
| `SEEKRIT_CACHE_MAX_AGE` | How stale a cached response may be and still be used. Defaults to `24h`. |

Precedence: environment variables override values saved by `seekrit login` in
`~/.config/seekrit/config.json`. `seekrit run` additionally reads these from its
`.env` file(s) (below `process.env`, above saved config) — see
[Running & exporting](#running--exporting).

## Output conventions

Everything the CLI can do from the dashboard, it can do from the terminal, and
every listing reads the same way:

- **Listings** print an aligned table with a header when stdout is a terminal,
  and plain **tab-separated rows with no header** when it is piped — so
  `seekrit app list | cut -f1` keeps working.
- **`--json`** on any read command prints the API's own response instead. Use it
  when a table would flatten something you need (ids, nested config, timestamps).
- **Data goes to stdout; everything else goes to stderr** — confirmations
  (`created app …`), "nothing to list" notes, and pagination hints. A command's
  stdout is always safe to pipe.
- **Destructive commands ask first.** Anything that deletes or revokes prompts
  for confirmation at a terminal, and refuses outright when there is no terminal
  unless you pass `--yes` (or, for `sync enable`, `--acknowledge-decryption`).
- `list` is aliased as `ls`, and `rm` as `delete`, wherever both read naturally.

## Auth & identity

### `seekrit login`

With no flags, signs you in **through your browser**: it prints a URL and a
pairing code, opens the URL when you press `[Enter]`, and waits while you
authorize the device in the dashboard (re-entering your authenticator code if you
have one). The session it saves authenticates as *you* — every org you belong to,
at your role — and lasts 90 days. Nothing to copy and paste, and no org, app, or
environment to choose.

Passing a credential stores that instead and skips the browser entirely — the path
machines and CI use.

| Flag | Description |
| --- | --- |
| `--token <token>` | Service token (`skt_…`). Stored as-is; replaces any browser session. |
| `--client-id <id>` / `--client-secret <secret>` | Machine (M2M) credentials (given together). An admin token is minted from them automatically on next use and cached. |
| `--api-url <url>` | API base URL. |
| `--no-browser` | Print the sign-in URL instead of opening it (SSH sessions, headless machines). |

A browser session carries no key material, so reading a secret's value still
unlocks *your* private key with your passphrase (`SEEKRIT_PASSPHRASE`, else
prompted) — exactly as the dashboard does. Service tokens carry their own key and
don't prompt, which is why they remain the right credential for unattended jobs.

### `seekrit logout`

Forget the saved credentials. A browser-authorized session is also **revoked**
server-side, so the token can't be used again; a service token is only removed
locally (other machines may hold it — revoke it with `seekrit token revoke`).
Machine (M2M) credentials are left in place.

### `seekrit whoami`

Show the authenticated identity. For a service token, prints its bound
`org/app/env` scope; for a browser session, names the session so you can revoke it.

### `seekrit keys setup`

Generate your P-256 keypair and upload your public key plus a passphrase-encrypted private key.
Run once per account. Honors `SEEKRIT_PASSPHRASE`, otherwise prompts.

## Resources

### `seekrit init`

`--org <slug> --app <slug>` — write `seekrit.json` naming default org + app for
management commands. **Environment-independent and safe to commit** — it never
pins an environment (the token does that at runtime).

### Organizations (`seekrit org`)

| Command | Description |
| --- | --- |
| `seekrit org list` | Every organization you can reach, with your role in each. |
| `seekrit org show [slug]` | One org: slug, id, your role, and how many applications, groups, members, and service tokens it holds. A count your role can't read prints as `—`. |
| `seekrit org tree [--org <slug>]` | The whole org as a tree — every application and group with their environments. The fastest "what is in here?". |
| `seekrit org create --name <name> --slug <slug>` | Create an organization (you become owner). |
| `seekrit org rename [--org <slug>] --name <name>` | Change the display name. Slugs are permanent identifiers and never change. |
| `seekrit org member list [--org <slug>]` | Members, their roles, and whether each has finished key setup. Someone showing `pending` cannot be granted an environment key yet. |
| `seekrit org invite list [--org <slug>]` | Outstanding invitations. |
| `seekrit org invite add <email> [--role admin\|member]` | Invite someone. Membership is created when they first sign in, so they will not appear in `member list` until then. |
| `seekrit org invite rm <inviteId>` | Rescind an invitation. |
| `seekrit org mfa [--set required\|optional]` | Show — or change — the org-wide second-factor requirement. Reports "no identity provider configured" on a deployment without one. |

### Applications (`seekrit app`)

| Command | Description |
| --- | --- |
| `seekrit app list [--org <slug>]` | Applications in an org. |
| `seekrit app show [slug]` | One application, its environments, and **whether you hold a key for each** — `no key` is why `secrets get` would fail there. Lists live branches too. |
| `seekrit app create [--org <slug>] --name <name> --slug <slug>` | Create an application. |
| `seekrit app rename [slug] --name <name>` | Change the display name. |
| `seekrit app rm <slug> [--yes]` | Delete an application, its environments, and every secret in them. Names the number of environments before asking. Cannot be undone. |

### Environments (`seekrit env`)

| Command | Description |
| --- | --- |
| `seekrit env list [--app <slug>]` | An application's environments and your access to each. |
| `seekrit env show --env <slug> [--app <slug>]` | One environment: its composed groups in precedence order, who holds a key for it (admins only), its secret count, and its branches. |
| `seekrit env create [--org <slug>] --app <slug> --name <name> --slug <slug>` | Create an application environment. Generates the environment's data key locally and wraps it to your public key. |
| `seekrit env rm --env <slug> [--app <slug>\|--group <slug>] [--yes]` | Delete an environment and every secret in it. Names the secret count before asking. Works for group environments too, via `--group`. |

### `seekrit branch create`

`<slug> [--org <slug>] [--app <slug>] --from <env> [--name <name>] [--ttl <duration>]
[--no-share]` — fork an application environment into an ephemeral **branch** (a
per-PR / preview config). The branch inherits its parent's secrets by layering at
read time — nothing is copied or re-encrypted — and stores only what you override
on it. Generates the branch's own data key locally and, unless `--no-share`, wraps
it to everyone who already holds a grant on the parent.

`--ttl` accepts `30m`, `12h`, `7d`, `2w`, … or `never` (default `7d`, max 30 days).
Expired branches are deleted automatically, along with their overrides, key grants,
and any service token bound to them.

### `seekrit branch list`

`[--org <slug>] [--app <slug>] [--env <slug>]` — list branches in an application,
or just those of one environment. Prints `slug`, id, and expiry.

### `seekrit branch delete`

`<slug> [--org <slug>] [--app <slug>]` — tear down a branch and every value it
overrode. The environment it overlays is untouched. Aliased as `branch rm`.

### Shared groups (`seekrit group`)

A **group** is a reusable secret bag shared across applications, holding one
environment per slug (its variants).

| Command | Description |
| --- | --- |
| `seekrit group list [--org <slug>]` | Groups in an org. |
| `seekrit group show <slug>` | One group and the environments it holds, with your access to each. |
| `seekrit group create [--org <slug>] --name <name> --slug <slug>` | Create a group. |
| `seekrit group rename <slug> --name <name>` | Change the display name. |
| `seekrit group rm <slug> [--yes]` | Delete a group, its environments, and their secrets. Every application environment composing it stops receiving these values on its next resolve. |
| `seekrit group env list --group <slug>` | The group's environments. |
| `seekrit group env create --group <slug> --name <name> --slug <slug>` | Create a group environment (a per-slug value set / variant). Generates its data key locally. |

## Composition (`env groups`)

Compose shared groups into an application environment. At resolve time each group
is matched to the environment whose slug matches the app environment's (or a
`--with` override).

| Command | Description |
| --- | --- |
| `seekrit env groups add --app <slug> --env <slug> --group <slug> [--position <n>]` | Compose a group (higher position wins). |
| `seekrit env groups list --app <slug> --env <slug>` | List composed groups, lowest precedence first. |
| `seekrit env groups rm --app <slug> --env <slug> --group <slug>` | Remove a group. |

## Secrets

Every `secrets` command targets one environment via flags: an application
environment (`--app --env`, or the config's app + `--env`) or a group environment
(`--group --env`). `--org` is inferred from `seekrit.json` or a lone org.

| Command | Description |
| --- | --- |
| `seekrit secrets list --env <slug> [--app <slug>\|--group <slug>]` | List secret names, versions, update times (no values). `--json` prints the same metadata — never a value, and never the stored ciphertext. |
| `seekrit secrets get <name> --env <slug> [--raw] [--version <n>] [--pretty] …` | Decrypt and print one value. `${OTHER_SECRET}` [references](/docs/guides/references) are expanded against that environment's own secrets; `--raw` prints the stored text. `--version` prints an earlier version instead of the current one, always as stored. `--pretty` re-indents the value if it is JSON and leaves it untouched if it is not — for reading, not for piping. |
| `seekrit secrets set <name> [value] --env <slug> [--file <path>] …` | Encrypt and store a value. Reads stdin if `value` is omitted or `-`; `--file` reads it from a file. Both drop one trailing newline and store the rest byte-for-byte — the way to store a JSON credential or a PEM key without shell quoting. |
| `seekrit secrets import [file] --env <slug> [--dry-run] …` | Bulk-import a `.env` file (default `.env`; `-` reads stdin). Each `KEY=VALUE` is encrypted and stored; existing names are overwritten. Aborts before writing if any name is invalid; `--dry-run` lists what would change (marking each `new`/`update`) without writing. |
| `seekrit secrets history <name> [--limit <n>] --env <slug> …` | List the secret's versions — when each was saved, who saved it, and which ones were restores. Never prints values (default 20, max 200). |
| `seekrit secrets restore <name> <version> --env <slug> …` | Roll the secret back to an earlier version. |
| `seekrit secrets rm <name> --env <slug> …` | Delete a secret, and with it every earlier version. |

### JSON and multi-line values

A value is an opaque string to seekrit — a service-account key, a PEM block, or
a certificate chain is stored and delivered byte-for-byte. The only thing that
needs care is getting it *in* without a shell or a `.env` file mangling it
first. Read it from a file and neither is involved:

```bash
seekrit secrets set GOOGLE_SERVICE_ACCOUNT --file ./service-account.json \
  --app storefront --env production
cat ./key.pem | seekrit secrets set TLS_KEY - --app storefront --env production
```

Both drop a single trailing newline and store everything else exactly as
written. To read one back formatted:

```bash
seekrit secrets get GOOGLE_SERVICE_ACCOUNT --pretty --app storefront --env production
```

`--pretty` only re-indents values that parse as JSON; anything else prints
unchanged. Without it, output stays byte-exact, which is what you want when
piping to a file or another tool.

### `.env` syntax

`seekrit secrets import`, the `--env-file` overlays, and the dashboard's **paste
.env** tab all use one parser — as does the [`seekrit-run`](#seekrit-run-launcher)
launcher, byte-for-byte:

| Syntax | Meaning |
| --- | --- |
| `KEY=value` | Unquoted: single-line, and a trailing ` # comment` is dropped. |
| `KEY='value'` | Single quotes: **literal**. No escapes are interpreted, so a `\n` inside stays two characters. |
| `KEY="value"` | Double quotes: `\n`, `\r`, `\t`, `\"`, and `\\` are interpreted. |
| `export KEY=value` | The `export` prefix is ignored. |
| `# comment` | Whole-line comments and blank lines are skipped. |

A quoted value **may span lines** — it runs to its closing quote, wherever that
lands. That is what makes a pretty-printed credential storable:

```bash
GOOGLE_SERVICE_ACCOUNT='{
  "type": "service_account",
  "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQ…\n-----END PRIVATE KEY-----\n"
}'
```

Single quotes are the right choice for JSON: the value is full of double quotes
and `\n` escapes that must survive as written, and single quotes pass them
through untouched.

> **Warning:** An **unquoted** value stops at the end of its line. Paste a multi-line JSON credential without quotes and the lines below it are read as further assignments — you get `KEY={` plus a handful of garbage names, not an error. Quote it, or skip the file entirely and use `--file`.

`seekrit export --format dotenv` writes this same syntax and quotes as needed,
so exporting and re-importing a value round-trips exactly, escapes included.

### Point-in-time restore

Every write appends to the secret's history, so a bad value is always one
command away from being undone:

```bash
seekrit secrets history DATABASE_URL --app storefront --env production
```

```
VERSION  WHEN                      BY                  NOTE
v3       2026-07-24T18:22:05.994Z  user:usr_KvxTL40S…  current
v2       2026-07-24T18:19:41.769Z  user:usr_KvxTL40S…
v1       2026-07-24T18:02:13.539Z  user:usr_KvxTL40S…
```

Check what you're rolling back to, then roll back:

```bash
seekrit secrets get DATABASE_URL --version 2 --app storefront --env production
seekrit secrets restore DATABASE_URL 2 --app storefront --env production
```

Restoring is a **roll-forward**: v2's value is written back as v4, so nothing
in the history is lost and the rollback is itself undoable. Because the server
simply replays ciphertext it already stores, no key is involved — a machine
(M2M) credential that cannot decrypt the value can still roll it back. The
restore is audited as `secret.restored`.

> **Note:** Deleting a secret deletes its history with it. After `seekrit secrets rm` there is nothing left to restore.

## Running & exporting

Both resolve a **layered** environment for the current principal:

```
group secrets  <  app-env secrets  <  .env file  <  process env   (highest wins)
```

With a service token, org/app/env come from the token. As a logged-in user, pass
`--app --env` (and `--org` if ambiguous).

Once the layers are merged, `${OTHER_SECRET}` [references](/docs/guides/references)
inside values are expanded — locally, in the same process that decrypted them.

| Flag | Applies to | Description |
| --- | --- | --- |
| `--branch <slug>` | run, export | Read an ephemeral [branch](/docs/guides/branches) of the target environment. Defaults to `SEEKRIT_BRANCH` (process env, then `.env`). |
| `--with <group>=<slug>` | run, export | Resolve one group at a different slug for this invocation (repeatable). |
| `--env-file <path>` | run, export | A `.env` file to overlay; repeatable; defaults to `.env`. |
| `--explain` | run, export | Print each variable's source layer to stderr (never values). Marks variables whose `${OTHER_SECRET}` references were expanded, and lists references that matched nothing. |
| `--no-interpolate` | run, export | Leave `${OTHER_SECRET}` [references](/docs/guides/references) as literal text. |
| `--cache` | run, export | Keep a [last-known-good](#last-known-good-cache) copy of the encrypted response and fall back to it when the API is unreachable. Off by default. |
| `--cache-dir <path>` | run, export | Where to keep it. Defaults to `$XDG_CACHE_HOME/seekrit`, else `~/.cache/seekrit`. |
| `--cache-max-age <duration>` | run, export | How stale that copy may be and still be used — `15m`, `24h`, `7d`. Defaults to `24h`. |

### `seekrit run -- <command…>`

Run a command with the resolved environment injected. Everything after `--` is the command.

```bash
SEEKRIT_TOKEN=skt_… seekrit run -- ./start-server
# swap only the auth group to its staging slice for this boot:
seekrit run --with auth-providers=staging -- pnpm dev
```

`run` reads its `.env` file(s) before authenticating, so `SEEKRIT_TOKEN` and
`SEEKRIT_API_URL` can live in `.env` — resolved as
`process.env` > `.env` > saved config, matching the `seekrit-run` launcher. Drop
a token into a project's `.env` and `seekrit run` uses it with no global login.

Resolving seekrit secrets is **best-effort**: if no credentials are configured
or seekrit can't be reached (network, auth, or decryption failure), `run` logs a
warning to stderr and still launches the command with just the `.env` overlay
and `process.env`. This mirrors the [`seekrit-run` launcher](#seekrit-run-launcher).
(`seekrit export` does not degrade — it errors if it can't resolve the secrets.)

`run` is a transparent wrapper: it forwards termination signals (`SIGINT` from
Ctrl-C, plus `SIGTERM`/`SIGHUP`/`SIGQUIT`) to the command **and every process
below it**, then stays alive until the command exits and exits with its status
(signal death is re-raised, so Ctrl-C yields `130`). Relaying down the whole
process tree, rather than to the one process, is what makes Ctrl-C reliably tear
down commands that spawn their own children (`npm`/`pnpm` scripts running
`nodemon`, for example), since those don't always pass the signal on themselves.

The command keeps your terminal: `run` does not put it in a separate session, so
it stays visible in `ps` as usual, still gets the terminal's own Ctrl-C and its
hangup when you close the window, and can still prompt on `/dev/tty` for things
like a `git` credential or an SSH passphrase.

**Nothing the command started outlives `run`.** Once the command itself has
exited, anything still alive underneath it has both ignored the signal and lost
the process that started it, so nothing is left to stop it — not the terminal
either, which only ever hangs up its own foreground group. Rather than trust it,
`run` escalates: `SIGTERM`, then `SIGKILL` two seconds later, printing the pids
it had to force-kill. A process that ignores both is usually a signal handler in
the app that cleans up but never exits — in Node, `process.on("SIGINT", …)`
*replaces* the default action, so the process stops dying on Ctrl-C unless the
handler itself exits. Watch for that if you see the message; it names the pids.
A command that exits on its own is never chased this way, so if it deliberately
leaves a daemon running in the background, that keeps working.

The compiled `seekrit-run` launcher replaces itself with the command (`exec`), so
signals and the process tree behave exactly as if you had run the command
directly — with no wrapper left to escalate, cleaning up a command that ignores
signals is up to the command. See its exit-code notes below.

### `seekrit export`

Print the resolved secrets (managed layers + `.env`, without `process.env`).
`--format <dotenv|json|shell>` (default `dotenv`).

## `seekrit-run` launcher

`seekrit-run` is a separate, compiled single-file binary — a dependency-free
`seekrit run` for machines (containers, CI, agents). It is **service-token
only** and reproduces `seekrit run`'s precedence and `.env` parsing exactly. See
the [launcher guide](/docs/guides/run) for install and container usage.

```
seekrit-run [OPTIONS] [--] <command> [args...]
seekrit-run run [OPTIONS] [--] <command> [args...]   # `run` is optional
```

| Flag | Default | Description |
| --- | --- | --- |
| `-t, --token <skt_…>` | `SEEKRIT_TOKEN` (env or `.env`) | Service token. |
| `--api-url <url>` | `SEEKRIT_API_URL` or `https://api.seekrit.dev` | API base URL. |
| `-e, --env-file <path>` | `.env` | A `.env` file to overlay (repeatable). |
| `--no-env-file` | | Do not load the default `.env`. |
| `--branch <slug>` | `SEEKRIT_BRANCH` (env or `.env`) | Read an ephemeral [branch](/docs/guides/branches) of the bound environment. |
| `--with <group=env>` | | Override one composed group's slice (repeatable). |
| `--explain` | | Print each variable's source to stderr (names only). |
| `--no-interpolate` | | Leave `${OTHER_SECRET}` [references](/docs/guides/references) as literal text. |
| `--cache` | off | Keep a [last-known-good](#last-known-good-cache) copy of the encrypted response and fall back to it when the API is unreachable. Also `SEEKRIT_CACHE=1`. |
| `--no-cache` | | Override `SEEKRIT_CACHE=1` for this run. |
| `--cache-dir <path>` | `SEEKRIT_CACHE_DIR`, else `$XDG_CACHE_HOME/seekrit` | Where to keep it. |
| `--cache-max-age <d>` | `24h` | How stale that copy may be and still be used. |

Like `seekrit run`, it degrades gracefully: a missing/malformed token or an
unreachable API is logged to stderr, and the command runs with just `.env` +
the live environment. Exit codes: `2` usage error, `1` a local failure (an
unreadable explicit `--env-file`, or a reference cycle in one), `127` command
not found; otherwise the command's own exit code
(Unix `exec`). Honors `HTTPS_PROXY` / `ALL_PROXY`. Its default API URL is the
hosted `https://api.seekrit.dev` — the same default the Node `seekrit run` uses
when nothing else is configured.

## Last-known-good cache

Off by default. With `--cache`, a successful resolve is written to disk and a
later run **falls back to it when the seekrit API cannot be reached** — so a
deploy, a CI job, or an agent boot still works during an outage.

```bash
seekrit run --cache -- ./start-server
```

```
seekrit-run: could not reach the seekrit API: … — using cached secrets fetched 6m ago
```

**Only the encrypted response is stored** — ciphertext plus your token's wrapped
data keys, the same bytes the API serves. Decrypting still requires the service
token's private key, so the file is no more sensitive than the token sitting
beside it; it is written `0600` inside a `0700` directory. The zero-knowledge
[invariant](/docs/concepts/security) is untouched: nothing is written in plaintext,
and nothing new is sent to the server.

How it behaves:

- **Live first, always.** Every invocation tries the API before touching the
  cache, so a recovered network is picked up immediately — the cache never
  serves a request that could have been answered fresh.
- **A refused resolve does not fall back.** If the API answers `401`/`403`/`404`,
  the entry is **deleted** and the command fails as it would have anyway.
  Revoking a token still takes effect on the next run. Only "the API is
  unreachable" — a network failure, a `5xx`, or a rate limit — uses the cache.
- **Bounded by `--cache-max-age`** (default `24h`). Past that, the entry is
  ignored and pruned.
- **Scoped to the exact request.** The entry is keyed by API URL, token,
  `--branch`, and `--with` overrides, so a different environment or override
  never reads another's copy.
- **Shared with `seekrit-run`.** Both use the same file format and directory, so
  whichever one runs first warms the cache for the other.

Two trade-offs worth stating plainly, both consequences of a copy that outlives
the network:

- A **revoked token keeps working offline** until the entry expires. That window
  is exactly `--cache-max-age`, and it only applies while the API is unreachable
  (a reachable API that refuses the token clears the entry immediately). Set a
  shorter max-age if that window matters more to you than the outage coverage.
- A run served from cache **makes no resolve call**, so it produces no
  `env.resolve_denied` audit entry. If you monitor for revoked credentials still
  in use, a cached client is invisible to that signal until it next reaches the
  API.

For the long-lived integrations, the same cache is configured in their own way:
`[cache] enabled = true` in the [egress proxy](/docs/guides/agent-proxy)'s config,
and `cache.enabled=true` in the [Kubernetes chart](/docs/guides/kubernetes). Both
also retry in the background and switch to live secrets as soon as the API
answers.

## Access

Who can decrypt an environment. Every command targets the environment with
`--env` plus `--app` or `--group`.

| Command | Description |
| --- | --- |
| `seekrit grant --env <slug> --user <email>\|--token <tokenId>` | Give a member or service token the environment's key. The key is unwrapped on your machine and re-wrapped to the recipient, so the API only ever sees ciphertext. Members must have finished key setup (`seekrit org member list` shows who has). |
| `seekrit grant list --env <slug>` | Who currently holds a key, by email or token name rather than raw ids. Admins only. |
| `seekrit grant rm --env <slug> --user <email>\|--token <tokenId> [--yes]` | Take the key away. This removes their wrapped copy — **anything they already decrypted stays decrypted**, so rotate the value too if it may have leaked. Aliased as `grant revoke`. |

`seekrit grant --user … --env …` is the bare grant verb and behaves exactly as
it always has; `list` and `rm` are subcommands beside it.

## Service tokens

| Command | Description |
| --- | --- |
| `seekrit token create --name <name> --app <slug> --env <slug> [--allow <group>=<slug>] [--no-grant]` | Mint a **runtime** token bound to an app environment; prints it once. Auto-grants that env's key and every composed group's matching slice. `--allow` pre-authorizes an alternate group slice for `run --with`; `--no-grant` skips granting. |
| `seekrit token create --name <name> --admin [--org <slug>]` | Mint an **admin** token: org-scoped, no env binding, passes admin-gated routes (create apps/groups/envs, compose, grant, mint tokens). For headless provisioning by agents/automation. Only an admin caller may create one. |
| `seekrit token list [--org <slug>]` | List tokens with role, status, and last-used time. |
| `seekrit token revoke <tokenId> [--org <slug>]` | Revoke a token. Reversible-safe: a revoked token authenticates nothing and frees its plan slot, but stays in the list. |
| `seekrit token delete <tokenId> [--org <slug>]` | Delete a token and drop the keys granted to it. Only allowed once the token has been revoked. Cannot be undone. |

An admin token can also be bound to an environment (pass `--admin --app --env`)
to both provision structure **and** decrypt that environment.

## Honey tokens

Decoy credentials that unlock nothing and alert your admins the moment anyone
presents one. See the [honey tokens guide](/docs/guides/honey-tokens).

| Command | Description |
| --- | --- |
| `seekrit honey-token create --name <name> [--placement <note>] [--org <slug>]` | Mint a decoy credential and print it once (stdout), so it can be piped straight into the file or variable you're baiting. `--placement` records where you planted it and is repeated back in the alert email. |
| `seekrit honey-token list [--org <slug>]` | List decoys with trip count, placement, and the time and source IP of the last trip. Aliased as `honey-token ls`. |
| `seekrit honey-token delete <honeyTokenId> [--yes] [--org <slug>]` | Delete a decoy, which stops it alerting — pull the planted bait too. Trips already recorded stay in the audit log. Aliased as `honey-token rm`. |

A decoy is byte-for-byte indistinguishable from a real `skt_` token, and
presenting one returns exactly the same `401 unknown service token` an
unregistered token gets — so an attacker probing credentials can't tell bait from
a typo. Plant them where a thief would look, never anywhere your own tooling
reads: a deploy script that tries one by mistake trips the alarm just as loudly.

## Agent integration

### `seekrit mcp`

Run an [MCP](https://modelcontextprotocol.io) server over stdio so AI agents
(Claude Code and other MCP clients) can drive seekrit as tools. It reads the
same credentials as every other command — a `SEEKRIT_TOKEN`, machine credentials
(`SEEKRIT_CLIENT_ID` + `SEEKRIT_CLIENT_SECRET`, which auto-mint an admin token),
or the saved config.

```bash
# Register with Claude Code (token selects the org; admin token enables provisioning):
SEEKRIT_TOKEN=skt_… claude mcp add seekrit -- seekrit mcp
```

For a fully autonomous agent, pass machine credentials instead — the server
mints and caches its own admin token from them (see the
[AI agents guide](/docs/guides/ai-agents)):

```bash
claude mcp add seekrit \
  --env SEEKRIT_CLIENT_ID=… --env SEEKRIT_CLIENT_SECRET=… -- seekrit mcp
```

All decryption happens **locally**, in this process — the server exposes the
tools that touch plaintext (secret values, data keys, decryption-capable
grants), which is why it runs on your machine rather than a hosted endpoint.
Call `get_started` first — an in-protocol tool (and the server's `instructions`,
shown to the model on connect) that returns the recommended first-project recipe
so an agent landing here mid-context can orient itself. Other tools include
`create_org`/`create_app`/`create_env`, `set_secret`/`get_secret`,
`create_token`, `grant_env`, and `run_command` (inject secrets into a subprocess
without returning their values). Because stdin is the transport, user-auth
sessions that decrypt need `SEEKRIT_PASSPHRASE` set (token auth needs nothing
extra). Prefer `run_command` over `get_secret` with `reveal:true` so plaintext
never enters the agent's context.

## Temporary Postgres credentials (`pg`)

Mint short-lived database logins that auto-expire. Minting happens
**client-side**: the password and its SCRAM verifier are generated on your
machine and only the verifier is sent, so the plaintext never reaches seekrit
or Postgres at rest. See [Temporary access](/docs/concepts/temporary-access).

| Command | Description |
| --- | --- |
| `seekrit pg target add --name <name> --host <h> --database <db> [--access readonly\|readwrite\|custom] [--schema public] [--port 5432] [--executor in_do\|remote] [--provisioner-url <url>] [--hmac-key <base64>] [--admin-url <url>] [--create-statement <sql>]…` | Register a provisioning target. `--access` (default `readonly`) sets what leased credentials can do; for the presets the command prints a one-time group-role setup query to run as admin. `custom` uses `--create-statement`/`--revoke-statement`. For `--executor in_do`, the admin connection string (or `SEEKRIT_PG_ADMIN_URL`) is wrapped to the broker locally; for `--executor remote`, pass the shared HMAC key via `--hmac-key` (or `SEEKRIT_PROVISIONER_HMAC_KEY`) instead — see the [self-hosted provisioner guide](/docs/guides/provisioner). *(admin)* |
| `seekrit pg target list [--org <slug>]` | List targets (id, name, connection, access level, executor). |
| `seekrit pg target setup-sql <targetId> [--org <slug>]` | Reprint the group-role setup SQL for a preset target. |
| `seekrit pg target rm <targetId> [--org <slug>]` | Remove a target. |
| `seekrit pg lease <target> [--role <name>] [--ttl 1h] [--json]` | Mint a credential; prints a ready-to-use `postgres://` URL (the password is shown once and stored nowhere). `--ttl` accepts `30m`/`1h`/`7d`. |
| `seekrit pg leases [--org <slug>]` | List the lease ledger (status, role, expiry). |
| `seekrit pg revoke <leaseId> [--org <slug>]` | Revoke a lease now (drops the role immediately). |

```bash
# Register a read-only target (prints the group-role setup SQL to run once):
SEEKRIT_PG_ADMIN_URL=postgres://admin:…@db.example.com:5432/app \
  seekrit pg target add --name prod-db --host db.example.com --database app --access readonly

# Lease a 30-minute credential and hand the URL straight to psql:
psql "$(seekrit pg lease prod-db --ttl 30m)"
```

## Temporary MySQL / MariaDB credentials (`mysql`)

The MySQL/MariaDB analog of `pg`. Minting is client-side too: the password and
its `mysql_native_password` hash are generated on your machine and only the hash
is sent, so the plaintext never reaches seekrit or MySQL at rest. Presets apply
their `GRANT`s inline per user (no one-time setup step), and there is no
account-level expiry — the broker drops the user at the deadline. See
[Temporary access](/docs/concepts/temporary-access).

| Command | Description |
| --- | --- |
| `seekrit mysql target add --name <name> --host <h> --database <db> [--access readonly\|readwrite\|custom] [--user-host %] [--port 3306] [--executor in_do\|remote] [--provisioner-url <url>] [--hmac-key <base64>] [--admin-url <url>] [--create-statement <sql>]…` | Register a provisioning target. `--access` (default `readonly`) sets what leased credentials can do; `custom` uses `--create-statement`/`--revoke-statement`. `--user-host` is the host part of created accounts (`'name'@'<host>'`, default `%`). For `--executor in_do`, the admin connection string (or `SEEKRIT_MYSQL_ADMIN_URL`) is wrapped to the broker locally; for `--executor remote`, pass the shared HMAC key via `--hmac-key` (or `SEEKRIT_PROVISIONER_HMAC_KEY`) instead — see the [self-hosted provisioner guide](/docs/guides/provisioner). *(admin)* |
| `seekrit mysql target list [--org <slug>]` | List MySQL targets (id, name, connection, access level, executor). |
| `seekrit mysql target rm <targetId> [--org <slug>]` | Remove a target. |
| `seekrit mysql lease <target> [--user <name>] [--ttl 1h] [--json]` | Mint a credential; prints a ready-to-use `mysql://` URL (the password is shown once and stored nowhere). `--ttl` accepts `30m`/`1h`/`7d`. |
| `seekrit mysql leases [--org <slug>]` | List the MySQL lease ledger (status, user, expiry). |
| `seekrit mysql revoke <leaseId> [--org <slug>]` | Revoke a lease now (drops the user immediately). |

```bash
# Register a read-only target (no setup SQL needed — grants apply inline):
SEEKRIT_MYSQL_ADMIN_URL=mysql://admin:…@db.example.com:3306/app \
  seekrit mysql target add --name prod-db --host db.example.com --database app --access readonly

# Lease a 30-minute credential and hand the URL straight to the mysql client:
mysql "$(seekrit mysql lease prod-db --ttl 30m)"
```

## Temporary Redis credentials (`redis`)

The Redis (6+) analog of `pg`/`mysql`. Minting is client-side too: the password
and its SHA-256 digest are generated on your machine and only the digest is sent
(`ACL SETUSER … on #<digest>`), so the plaintext never reaches seekrit or Redis
at rest. Presets apply their ACL rules inline per user (no one-time setup step),
and there is no account-level expiry — the broker deletes the ACL user at the
deadline. See [Temporary access](/docs/concepts/temporary-access).

| Command | Description |
| --- | --- |
| `seekrit redis target add --name <name> --host <h> [--access readonly\|readwrite\|custom] [--port 6379] [--db <index>] [--executor in_do\|remote] [--provisioner-url <url>] [--hmac-key <base64>] [--admin-url <url>] [--create-statement <cmd>]…` | Register a provisioning target. `--access` (default `readonly`) sets what leased credentials can do; `custom` uses `--create-statement`/`--revoke-statement` (each a Redis command line, `{{name}}`/`{{verifier}}` templated). `--db` is the logical database index used in the printed URL. For `--executor in_do`, the admin `redis://`/`rediss://` connection string (or `SEEKRIT_REDIS_ADMIN_URL`) is wrapped to the broker locally; for `--executor remote`, pass the shared HMAC key via `--hmac-key` (or `SEEKRIT_PROVISIONER_HMAC_KEY`) instead — see the [self-hosted provisioner guide](/docs/guides/provisioner). *(admin)* |
| `seekrit redis target list [--org <slug>]` | List Redis targets (id, name, connection, access level, executor). |
| `seekrit redis target rm <targetId> [--org <slug>]` | Remove a target. |
| `seekrit redis lease <target> [--user <name>] [--ttl 1h] [--json]` | Mint a credential; prints a ready-to-use `redis://` URL (the password is shown once and stored nowhere). `--ttl` accepts `30m`/`1h`/`7d`. |
| `seekrit redis leases [--org <slug>]` | List the Redis lease ledger (status, user, expiry). |
| `seekrit redis revoke <leaseId> [--org <slug>]` | Revoke a lease now (deletes the ACL user immediately). |

```bash
# Register a read-only target (ACL rules apply inline, no setup step):
SEEKRIT_REDIS_ADMIN_URL=rediss://default:…@cache.example.com:6379 \
  seekrit redis target add --name prod-cache --host cache.example.com --access readonly

# Lease a 30-minute credential and hand the URL straight to redis-cli:
redis-cli -u "$(seekrit redis lease prod-cache --ttl 30m)"
```

## Agent egress proxy (`proxy`)

Fetch, configure, and run [`seekrit-proxy`](/docs/guides/agent-proxy) without a
Rust toolchain and without hand-writing a config file. The proxy itself is
unchanged by these commands — they resolve the released binary (verifying its
SHA-256) and generate the same TOML you would otherwise write.

| Command | Description |
| --- | --- |
| `seekrit proxy run` | Fetch the binary if needed and run it. With `--preset`/`--host`/`--agent` it generates a config on the fly and leaves nothing behind. |
| `seekrit proxy init` | Write a reviewable `seekrit-proxy.toml`. |
| `seekrit proxy presets` | List the ready-made upstream presets. |
| `seekrit proxy compose` | Print a `docker compose` sidecar snippet for a generated config. |
| `seekrit proxy install` | Download the binary and print its path. |
| `seekrit proxy where` | Show which binary `run` would use, without fetching it. |

### Generating a config

`init` and `run` share these flags. Pass presets, ad-hoc hosts, or an agent
identity — the last takes the rules from
[published policy](/docs/guides/agent-proxy/policy) and cannot be combined with
the first two, because server-policy mode rejects local rules rather than
silently ignoring them.

| Flag | Default | Description |
| --- | --- | --- |
| `--preset <name>` | | A preset from `seekrit proxy presets` (repeatable). |
| `--host <host[=SECRET,…]>` | | Ad-hoc rule: a bare hostname and, optionally, the secrets that may reach it (repeatable). Omitting `=SECRET` permits the operation without letting a credential travel with it. |
| `--base-url <url>` | | Upstream base URL for an OpenAI-compatible gateway. Required by the `openai-compatible` preset. |
| `--secret <NAME>` | preset's own | Override a preset's secret name. |
| `--prefix <path>` | preset's own | Override a preset's route prefix. |
| `--agent <slug>` | | Take the rules from published agent policy (server mode). |
| `--agents <slug>` | the `--agent` one | Additional identities this proxy may serve, for [session tickets](/docs/guides/agent-proxy#several-agents-behind-one-proxy) (repeatable). |
| `--org <slug>` | | Organization, for `--agent`. |
| `--mode <reverse\|forward\|both>` | `reverse` | Which data plane(s) to configure. |
| `--listen <addr>` | `127.0.0.1:8080` | Reverse-proxy address. |
| `--forward-listen <addr>` | `127.0.0.1:8081` | Forward-proxy address. |
| `--unmatched <tunnel\|deny>` | `tunnel` | What to do with an unruled host in forward mode. |
| `--ca-cert <path>` / `--ca-key <path>` | `seekrit-proxy-ca[-key].pem` | Interception CA paths (forward mode). |
| `--cache` | off | Add a `[cache]` block so the proxy can start during a seekrit outage. |
| `--cache-max-age <dur>` | `24h` | How stale a cached resolve may be (implies `--cache`). |
| `--refresh <dur>` | `30s` file / `10s` server | Re-resolve (and, in server mode, re-fetch) interval. |
| `--control <addr>` | | Add a `[control]` listener for per-agent session tickets. |

`init` additionally takes `-o, --out <path>` (default `./seekrit-proxy.toml`),
`--print` to write to stdout instead, and `--force` to overwrite.

`run` additionally takes `-c, --config <path>`, `--proxy-version <version>`, and
`--print-config`. With no generation flags it runs the config file as-is.

### Resolving the binary

| Variable | Default | Purpose |
| --- | --- | --- |
| `SEEKRIT_PROXY_BIN` | | Path to a binary you already have. Skips the download entirely. |
| `SEEKRIT_PROXY_VERSION` | the CLI's pinned version | Version to fetch. `latest` is re-resolved every run rather than cached. |
| `SEEKRIT_PROXY_BASE_URL` | `https://proxy.seekrit.dev` | Where artifacts come from. |

Downloads are cached under `$XDG_CACHE_HOME/seekrit/proxy/v<version>/<target>/`,
so the fetch happens once per version. A binary already on `PATH` is deliberately
**not** used: silently running a different version than the one the CLI pins is
the kind of surprise that costs an afternoon.

> **Note:** `seekrit proxy` never sees a secret *value* — it generates config and launches a process. The proxy resolves and decrypts on its own, with its own `SEEKRIT_TOKEN`, which is also why that token must live somewhere the workload cannot read.

## Self-hosted provisioner (`provisioner`)

Helpers for the [remote executor](/docs/guides/provisioner) — the
`seekrit-provisioner` daemon that runs a target's provisioning SQL inside your
own network, so seekrit never sees the database admin credential.

| Command | Description |
| --- | --- |
| `seekrit provisioner keygen` | Generate a shared HMAC key (base64) for a `remote` target. Use the same value for `--hmac-key` when registering the target and for the daemon's `SEEKRIT_PROVISIONER_HMAC_KEY`. |

## Temporary SSH access (`ssh`)

Issue short-lived SSH **certificates**. seekrit acts as a certificate authority:
`target add` generates a CA keypair locally (only the private half is wrapped and
uploaded) and prints the public key to install on your hosts. Minting generates
an ephemeral keypair on your machine and sends only the public key; the signed
certificate comes back and the private key never leaves. See
[Temporary access](/docs/concepts/temporary-access).

| Command | Description |
| --- | --- |
| `seekrit ssh target add --name <name> [--host <h>] [--user <login>] [--principal <name>]… [--extension <name>]… [--max-ttl <dur>]` | Create an SSH CA target. Generates the CA locally, wraps its private key to the broker, and prints the CA public key + one-time host setup (`TrustedUserCAKeys`). `--principal` allow-lists which login users a cert may request (blank = any); `--host`/`--user` seed the printed `ssh` command. *(admin)* |
| `seekrit ssh target list [--org <slug>]` | List SSH targets (id, name, host, allowed principals). |
| `seekrit ssh target setup <targetId> [--org <slug>]` | Reprint the host setup instructions for a target. |
| `seekrit ssh target rm <targetId> [--org <slug>]` | Remove a target. |
| `seekrit ssh lease <target> [--principal <name>]… [--ttl 1h] [--out <dir>] [--json]` | Mint a certificate; writes `id_ed25519` + `id_ed25519-cert.pub` and prints a ready-to-run `ssh` command. `--principal` defaults to the target's user/allow-list. `--ttl` accepts `30m`/`1h`/`8h`. |
| `seekrit ssh leases [--org <slug>]` | List the SSH lease ledger (status, expiry). |
| `seekrit ssh revoke <leaseId> [--org <slug>]` | Mark a lease revoked in the ledger. The issued certificate stays valid until it expires (short TTLs are the control). |

```bash
# Create a CA target and install the printed CA key on your hosts:
seekrit ssh target add --name prod-fleet --host bastion.example.com --user deploy --principal deploy

# Issue an 8-hour cert, then run the ssh command it prints:
seekrit ssh lease prod-fleet --ttl 8h
```

## Temporary AWS credentials (`aws`)

Mint short-lived AWS credentials via STS **AssumeRole** (a tier-2 provider — the
credential comes back **wrapped to your machine's ephemeral key**, never in the
clear through seekrit). `target add` registers one assumable IAM role and wraps
a base IAM credential (needs only `sts:AssumeRole`) to the broker. Minting
generates an ephemeral P-256 keypair locally, sends only the public key, and
unwraps the returned credential on your machine. See
[Temporary access](/docs/concepts/temporary-access).

| Command | Description |
| --- | --- |
| `seekrit aws target add --name <name> --role-arn <arn> --region <region> [--external-id <id>] [--session-policy <file>] [--max-ttl <dur>] [--access-key-id <id>] [--secret-access-key <secret>]` | Register an AWS role target. The base IAM credential comes from the flags or `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY` (and `AWS_SESSION_TOKEN` if set) and is wrapped to the broker locally. `--session-policy` further restricts the leased credential; `--max-ttl` clamps requested lifetime. Prints the IAM trust policy the role needs. *(admin)* |
| `seekrit aws target list [--org <slug>]` | List AWS targets (id, name, region, role ARN). |
| `seekrit aws target trust <targetId> [--org <slug>]` | Reprint the IAM trust-policy setup for a target. |
| `seekrit aws target rm <targetId> [--org <slug>]` | Remove a target. |
| `seekrit aws lease <target> [--ttl 1h] [--json]` | Mint credentials; prints `export AWS_…` lines (or JSON with `--json`). `--ttl` accepts `15m`–`12h` (STS minimum is 15 minutes; clamped to the role's `MaxSessionDuration`). |
| `seekrit aws leases [--org <slug>]` | List the AWS lease ledger (status, expiry). |
| `seekrit aws revoke <leaseId> [--org <slug>]` | Mark a lease revoked in the ledger. The issued credential stays valid until it expires (short TTLs are the control). |

```bash
# Register a role (base creds from your AWS env vars), then source a 1h credential:
seekrit aws target add --name prod-deploy \
  --role-arn arn:aws:iam::123456789012:role/seekrit-deploy --region us-east-1

eval "$(seekrit aws lease prod-deploy --ttl 1h)"
aws sts get-caller-identity
```

## Temporary GCP credentials (`gcp`)

Mint short-lived GCP access tokens via IAM **`generateAccessToken`** (a tier-2
provider — the token comes back **wrapped to your machine's ephemeral key**, never
in the clear through seekrit). `target add` registers one impersonable service
account and wraps a base service-account key (needs
`roles/iam.serviceAccountTokenCreator` on the target) to the broker. Minting
generates an ephemeral P-256 keypair locally, sends only the public key, and
unwraps the returned token on your machine. See
[Temporary access](/docs/concepts/temporary-access).

| Command | Description |
| --- | --- |
| `seekrit gcp target add --name <name> --service-account <email> [--scope <scope>…] [--delegate <email>…] [--max-ttl <dur>] [--key-file <path>]` | Register a GCP service-account target. The source SA key JSON comes from `--key-file` or `GOOGLE_APPLICATION_CREDENTIALS` and is wrapped to the broker locally. `--scope` (repeatable) sets the OAuth scopes (default `cloud-platform`); `--max-ttl` clamps requested lifetime. Prints the IAM binding the target needs. *(admin)* |
| `seekrit gcp target list [--org <slug>]` | List GCP targets (id, name, service account). |
| `seekrit gcp target setup <targetId> [--org <slug>]` | Reprint the IAM setup for a target. |
| `seekrit gcp target rm <targetId> [--org <slug>]` | Remove a target. |
| `seekrit gcp lease <target> [--ttl 1h] [--json]` | Mint a token; prints `export CLOUDSDK_AUTH_ACCESS_TOKEN` / `export GOOGLE_OAUTH_ACCESS_TOKEN` lines (or JSON with `--json`). `--ttl` accepts `1m`–`12h` (tokens over 1h need the credential-lifetime-extension org policy). |
| `seekrit gcp leases [--org <slug>]` | List the GCP lease ledger (status, expiry). |
| `seekrit gcp revoke <leaseId> [--org <slug>]` | Mark a lease revoked in the ledger. The issued token stays valid until it expires (short TTLs are the control). |

```bash
# Register a service account (source key from GOOGLE_APPLICATION_CREDENTIALS),
# then source a 1h token:
seekrit gcp target add --name prod-deploy \
  --service-account deploy@my-project.iam.gserviceaccount.com

eval "$(seekrit gcp lease prod-deploy --ttl 1h)"
gcloud storage ls
```

## Temporary MongoDB credentials (`mongodb`)

Mint short-lived MongoDB users (a tier-2 provider — MongoDB hashes the password
server-side, so the broker generates it, runs `createUser`, and returns the
credential **wrapped to your machine's ephemeral key**, never in the clear
through seekrit). `target add` registers one cluster and wraps an admin
`mongodb://` connection string (needs `userAdmin` on the database) to the broker.
Minting generates an ephemeral P-256 keypair locally, sends only the public key,
and unwraps the returned credential on your machine. Unlike AWS, revoke is
real — it drops the user. See [Temporary access](/docs/concepts/temporary-access).

| Command | Description |
| --- | --- |
| `seekrit mongodb target add --name <name> --database <db> [--uri <uri>] [--access readonly\|readwrite\|custom] [--role <role@db>…] [--auth-source <db>] [--max-ttl <dur>] [--no-tls]` | Register a MongoDB target. The admin connection string comes from `--uri` or `SEEKRIT_MONGODB_ADMIN_URL` and is wrapped to the broker locally. `--access` picks the built-in `read`/`readWrite` role on `--database` (or `custom` with one or more `--role role@db`); `--max-ttl` clamps requested lifetime. Prints the provisioning-user setup. *(admin)* |
| `seekrit mongodb target list [--org <slug>]` | List MongoDB targets (id, name, host:port, database, access). |
| `seekrit mongodb target rm <targetId> [--org <slug>]` | Remove a target. |
| `seekrit mongodb lease <target> [--ttl 1h] [--json]` | Mint a user; prints `export MONGODB_URI='…'` (or JSON with `--json`). `--ttl` accepts `60s`–`7d`, clamped to the target's `--max-ttl`. |
| `seekrit mongodb leases [--org <slug>]` | List the MongoDB lease ledger (status, expiry). |
| `seekrit mongodb revoke <leaseId> [--org <slug>]` | Revoke a lease now — drops the MongoDB user immediately. |

```bash
# Register a cluster (admin creds from SEEKRIT_MONGODB_ADMIN_URL), then lease a 1h user:
export SEEKRIT_MONGODB_ADMIN_URL='mongodb://admin:pw@mongo.example.com:27017/?authSource=admin'
seekrit mongodb target add --name prod-app --database app --access readonly

eval "$(seekrit mongodb lease prod-app --ttl 1h)"
mongosh "$MONGODB_URI"
```

## Managed keys (`kms`)

Client-side managed keys for application-layer encryption and signing. Material
is generated locally and wrapped to grantees; the server never sees it. See the
[Managed keys guide](/docs/guides/kms).

| Command | Description |
| --- | --- |
| `seekrit kms create --name <name> --purpose <encrypt\|sign> [--org <slug>] [--app <slug>\|--group <slug>] [--grant-user <email>…] [--grant-token <id>…]` | Create a key. Generates material locally, self-grants, and optionally grants others. `encrypt` = AES-256-GCM; `sign` = ECDSA P-256 (its public key is published). Scope to an app or group, or leave org-wide. |
| `seekrit kms ls [--org <slug>]` | List keys you can see (admins see all; members see granted keys). |
| `seekrit kms grant --key <name> (--user <email>\|--token <id>) [--org <slug>]` | Grant a principal the current version. |
| `seekrit kms revoke --key <name> (--user <email>\|--token <id>) [--org <slug>]` | Revoke a principal from the key (all versions). |
| `seekrit kms rotate --key <name> [--org <slug>]` | Add a new version and re-wrap it for every current grantee. Old versions stay valid. |
| `seekrit kms disable --key <name> [--org <slug>]` | Block all use: encrypt, decrypt, sign, and new grants/rotations. |
| `seekrit kms delete --key <name> [--org <slug>]` | Delete a key — hides it from every listing and read path; the name frees up for reuse. |
| `seekrit kms encrypt --key <name> [--context <ctx>] [--org <slug>]` | Encrypt stdin → a `ce1.` blob. `--context` is bound as AAD and must match on decrypt. |
| `seekrit kms decrypt --key <name> [--context <ctx>] [--org <slug>]` | Decrypt a `ce1.` blob from stdin. |
| `seekrit kms generate-data-key --key <name> [--org <slug>]` | Print `{ plaintextBase64, wrapped }` — an envelope data key for large payloads. |
| `seekrit kms open-data-key --key <name> [--org <slug>]` | Recover a data key from a `dk1.` blob on stdin (prints base64). |
| `seekrit kms sign --key <name> [--org <slug>]` | Sign stdin with a signing key → an `sg1.` signature. |
| `seekrit kms verify --key <name> --signature <sg1…> [--org <slug>]` | Verify an `sg1.` signature over stdin (exit 0 = valid). Needs only the published public key. |

```bash
# Encrypt a field, bound to its context, then read it back:
echo -n "$SSN" | seekrit kms encrypt --key pii --context "field=ssn" > ssn.enc
seekrit kms decrypt --key pii --context "field=ssn" < ssn.enc

# Sign a release and verify it:
echo -n "release-v1.2.3" | seekrit kms sign --key release-signer > r.sig
echo -n "release-v1.2.3" | seekrit kms verify --key release-signer --signature "$(cat r.sig)"
```

## Secret rotation (`rotation`)

Managed, scheduled replacement of a stored secret's value — and, for the database
kinds, of the password on the account it belongs to. *(admin)*. Enabling rotation
wraps the environment's data key to your org's rotator key locally; see the
[rotation guide](/docs/guides/rotation) and
[Secret rotation](/docs/concepts/rotation).

Every command accepts a rotation id (`rot_…`) or the secret's name when that name
rotates in only one environment. Durations accept `s`/`m`/`h`/`d` suffixes
(minimum 5 minutes, maximum 365 days).

| Command | Description |
| --- | --- |
| `seekrit rotation enable <SECRET_NAME> --env <slug> --kind <generated\|postgres\|mysql\|redis> --every <duration> [--app <slug>\|--group <slug>] [--target <idOrName>] [--username <name>] [--user-host <host>] [--length <n>] [--alphabet <set>] [--now] [--org <slug>]` | Configure rotation for an existing secret. `--target` + `--username` are required for the database kinds and name an account that **already exists**. `--alphabet` (`generated` only) is `alphanumeric`, `hex`, `base64url`, or `printable`. `--now` also rotates immediately. Prints the `rot_…` id. *(admin)* |
| `seekrit rotation list [--org <slug>] [--json]` | List policies: id, secret, kind, cadence, status, next run. Never values. *(admin)* |
| `seekrit rotation show <rotationOrSecret> [--org <slug>]` | One policy in full, including `lastError` from the last failed attempt. *(admin)* |
| `seekrit rotation rotate <rotationOrSecret> [--org <slug>]` | Rotate now — the same path the scheduler uses. Prints the new version. *(admin)* |
| `seekrit rotation pause <rotationOrSecret> [--org <slug>]` | Stop rotating, keeping the policy. *(admin)* |
| `seekrit rotation resume <rotationOrSecret> [--org <slug>]` | Resume rotating; also clears a failed streak. *(admin)* |
| `seekrit rotation set-interval <rotationOrSecret> --every <duration> [--org <slug>]` | Change the cadence. *(admin)* |
| `seekrit rotation disable <rotationOrSecret> [--org <slug>]` | Remove the policy. The secret and its versions are untouched; the rotator's key grant is dropped when the environment has no rotation left. *(admin)* |

```bash
# Re-key an existing Postgres role weekly, against a registered target:
seekrit rotation enable DATABASE_PASSWORD --app web --env production \
  --kind postgres --target prod-db --username app_user --every 7d --now

# A value only your own code checks — nothing external is contacted:
seekrit rotation enable API_SIGNING_KEY --app web --env production \
  --kind generated --every 30d

seekrit rotation list
seekrit rotation rotate DATABASE_PASSWORD   # e.g. after a suspected exposure
```

## Customer-controlled recovery (`recovery`)

Org-scoped M-of-N recovery. The recovery key is generated and split locally; seekrit stores only
its public half and shares it cannot open. Management is *(admin)*; any custodian can approve a
ceremony. See the [recovery guide](/docs/guides/recovery).

| Command | Description |
| --- | --- |
| `seekrit recovery setup --threshold <M> --custodian <email\|skt_id>… [--org <slug>]` | Enable recovery: split a fresh recovery key across the custodians, then cover the environments you can decrypt. *(admin)* |
| `seekrit recovery status [--org <slug>]` | Show the threshold, custodians, and environment coverage. *(admin)* |
| `seekrit recovery sync [--org <slug>]` | Recovery-protect environments you can decrypt but that aren't yet covered. *(admin)* |
| `seekrit recovery rotate --threshold <M> --custodian <email\|skt_id>… [--org <slug>]` | Rotate to a fresh recovery key and custodian set; re-wraps the environments you can decrypt. *(admin)* |
| `seekrit recovery disable [--org <slug>]` | Remove the recovery key and every recovery grant. *(admin)* |
| `seekrit recovery request [--target-user <email>\|--target-token <id>] [--reason <text>] [--org <slug>]` | Start a recovery ceremony (defaults to recovering access for yourself). Prints an `rrq_…` id. *(admin)* |
| `seekrit recovery approve <requestId> [--org <slug>]` | As a custodian, unwrap your share and contribute it, re-wrapped to the target. |
| `seekrit recovery complete <requestId> [--org <slug>]` | As the target, reconstruct the recovery key and restore your environment access. *(admin)* |
| `seekrit recovery cancel <requestId> [--org <slug>]` | Cancel an open recovery request. *(admin)* |

```bash
# Enable 3-of-5 recovery, then check coverage:
seekrit recovery setup --threshold 3 \
  --custodian alice@example.com --custodian bob@example.com --custodian carol@example.com \
  --custodian dan@example.com --custodian erin@example.com
seekrit recovery status

# Run a ceremony: start it, custodians approve, then the target completes.
seekrit recovery request                 # prints rrq_…
seekrit recovery approve rrq_XXXXXXXX     # each custodian, until the quorum is met
seekrit recovery complete rrq_XXXXXXXX
```

## Your account

Your devices and your email preferences. Both are per-user, not per-org, so
neither takes `--org`.

| Command | Description |
| --- | --- |
| `seekrit session list [--all]` | The devices `seekrit login` has authorized, newest first, with the one you're calling from marked. Shows only live sessions unless you pass `--all` (which includes revoked and expired ones — the ledger is kept). |
| `seekrit session revoke <sessionId> [--yes]` | Sign a device out; its token stops authenticating on its next request (revoking drops the cached copy as it commits, and any copy that outlives it is re-checked within a minute of use). Revoking the session you're currently using is allowed, and says so before it asks. |
| `seekrit notifications` | Your per-type email preferences, on or off. |
| `seekrit notifications set <type> <on\|off>` | Turn one notification on or off. An unknown type lists the valid ones. |

## Audit

### `seekrit audit`

Print the org's audit trail — the append-only record of every mutation. Admins
only.

| Flag | Description |
| --- | --- |
| `--org <slug>` | Which organization. |
| `--limit <n>` | Entries per page (default 50, max 200). |
| `--action <action>` | Only this action, e.g. `env.key_granted`. An unrecognized action is rejected rather than silently returning nothing. |
| `--resource-type <type>` | Only this resource type, e.g. `environment`. |
| `--cursor <cursor>` | Continue from the cursor a previous page printed. |
| `--all` | Page through the whole trail rather than stopping after one page. |
| `--metadata` | Add each entry's metadata as a JSON column. |

When more entries remain, the next cursor is printed to stderr so it doesn't
corrupt piped output.

### `seekrit audit actions`

List every action the trail can record — the vocabulary for `--action`.

```bash
# Everything that changed who can decrypt something, as JSON:
seekrit audit --action env.key_granted --all --json
```

## Audit export (`log-sink`)

Ship every audit row to your own OTLP/HTTP collector (SIEM) within about a
minute of it being written. Admins only. Header **values** are write-only —
encrypted at rest and never returned — so `show` reports only the header names.

| Command | Description |
| --- | --- |
| `seekrit log-sink` | The configured endpoint, whether it's enabled, the header names, and delivery health (last success, last attempt, last error). |
| `seekrit log-sink set <endpoint> [--header "Name: value"] [--clear-headers] [--disabled]` | Point the export at an OTLP/HTTP logs endpoint. `--header` is repeatable and **replaces** the whole header set, so pass all of them each time. Passing none leaves the stored headers alone — that's how you change the endpoint without re-entering the credential; `--clear-headers` removes them. |
| `seekrit log-sink test` | Send a probe and report the result. Exits non-zero on failure, so it works as a health check. |
| `seekrit log-sink rm [--yes]` | Stop exporting and forget the endpoint. |

```bash
seekrit log-sink set https://collector.example.com/v1/logs \
  --header "Authorization: Bearer $SIEM_TOKEN"
seekrit log-sink test
```

## Third-party sync

Push an environment's resolved secrets to a platform that keeps its own copy:
Vercel project env vars, Cloudflare Worker secret bindings, Cloudflare Pages env
vars, Cloudflare Secrets Store secrets, Railway service variables, AWS Secrets
Manager secrets, AWS SSM parameters, a Render service or environment group, a
Fly.io app's secrets, a Northflank secret group, DigitalOcean App Platform
variables, Heroku config vars, Netlify site variables, Bunnyshell environment or
project variables, GitHub Actions secrets, or Google Secret Manager secrets.
Admins only. Each destination has its own guide under
[Third-party sync](/docs/guides/third-party-sync).

> **Warning:** Sync is the **one** place seekrit's servers hold plaintext: a destination needs the value and runs when nobody is logged in, so the sync engine decrypts in memory for the length of a push. Enabling it for an environment is therefore an explicit, audited decision — `sync enable` requires `--acknowledge-decryption` (or a yes at the prompt), and who acknowledged is recorded. Where the runtime lets you decrypt on your own side instead, prefer [`seekrit run`](#running--exporting), the proxy, or the SDKs.

A **connection** is a destination account; a **binding** is one environment
syncing to one place in it.

| Command | Description |
| --- | --- |
| `seekrit sync connections` | Destination accounts, with status and last error. |
| `seekrit sync connect --name <name> [--provider <kind>] [--team-id <id>] [--account-id <id>] [--token-kind <kind>] [--region <region>] [--access-key-id <id>] [--base-url <url>] [--project-id <id>] [--langgraph-region <region>] [--langgraph-tenant <id>]` | Register an account. The credential is **read from stdin** (or prompted) — never a flag, so it can't land in shell history — and is wrapped to the connection's public key before it is sent. `--team-id` is Vercel's (omit for a personal account); `--account-id` is required for every Cloudflare provider and for Netlify, where it is the team slug (or account ID) whose environment variables the connection writes; `--token-kind` is Railway's (`account` or `project` — Railway sends the two in different headers); `--region` and `--access-key-id` for every AWS one; `--project-id` is `gcp-secret-manager`'s, the project whose Secret Manager the connection writes; `--base-url` is GitHub's and LangGraph Platform's, and only for a self-hosted install — a **GitHub Enterprise Server** appliance (`https://github.acme.com/api/v3`) or a self-hosted LangSmith control plane (`https://langsmith.acme.com/api-host`) — omit it for github.com, Enterprise Cloud, and a LangChain-hosted LangSmith, and note it must be `https`, since the URL carries the token; `--langgraph-region` picks which of LangChain's four control-plane hosts a `langgraph-platform` connection addresses (`us` by default, then `eu`, `apac`, `aws-us`) and matters because a key minted in one region is refused by another with a bare `401`, so the wrong one looks exactly like a bad credential — it is mutually exclusive with `--base-url`; `--langgraph-tenant` is the LangSmith workspace UUID, needed only for an organization-scoped key, which reaches several workspaces and is refused without it; `render`, `fly`, `northflank`, `digitalocean`, `heroku`, and `bunnyshell` take none of them — a Render API key is user-scoped, a Northflank token names its own team, a DigitalOcean token its own account, a Heroku token its user's access to every app they can reach, a Bunnyshell token its user's access to every organization they belong to, and a Fly token is pasted whole, `FlyV1` prefix included, because seekrit reads which auth scheme it takes from the token itself. On AWS the credential read from stdin is the **secret access key** — the access key id is an identifier, not a secret, so it is stored in the clear where the dashboard can show it. On GCP it is the whole **service-account key JSON**, the same credential shape `seekrit gcp target add` takes, so it is usually piped from the key file (`… < key.json`). |
| `seekrit sync verify <connection> [--provider <kind>] <destination flags>` | Check the stored credential against a destination. Takes the same destination flags as `sync enable`. Exits non-zero on failure. |
| `seekrit sync disconnect <connection> [--yes]` | Delete an account, its bindings, and its keypair. Values already pushed stay on the destination. |
| `seekrit sync bindings` | What is syncing where, with mode, last run, and last error. |
| `seekrit sync enable …` | Start syncing one environment (see below). |
| `seekrit sync pause <bindingId>` / `resume <bindingId>` | Stop and restart pushing without deleting the binding. |
| `seekrit sync disable <bindingId> [--yes]` | Delete a binding and revoke seekrit's key for that environment. Aliased as `sync rm`. |
| `seekrit sync run <bindingId>` | Push now, synchronously, and report what landed. Exits non-zero unless the run fully succeeded — `partial` is a real outcome. |
| `seekrit sync runs [--binding <id>]` | The run history. |

`seekrit sync enable` takes the environment (`--env`, plus `--app`), the
connection (`--connection`), and the destination:

| Flag | Description |
| --- | --- |
| `--connection <name>` | Destination account, by name or id. |
| `--provider <kind>` | `vercel` (default), `cloudflare-workers`, `cloudflare-pages`, `cloudflare-secrets-store`, `railway`, `aws-secrets-manager`, `aws-parameter-store`, `render`, `fly`, `northflank`, `digitalocean`, `heroku`, `netlify`, `bunnyshell`, `github-actions`, `gcp-secret-manager`, or `langgraph-platform`. |
| `--project <id>` | **vercel:** project id (`prj_…`) or name. **cloudflare-pages:** project name. **northflank:** project id — the slug in its URL. **bunnyshell:** project ID — writes the project's variables, which every environment created in it afterwards inherits. |
| `--target <list>` | Comma-separated deployment targets (default `production`). **vercel:** `production`, `preview`, `development`. **cloudflare-pages:** `production`, `preview`. **netlify:** `production`, `deploy-preview`, `branch-deploy`, `branch`, `dev`. |
| `--git-branch <branch>` | **vercel:** restrict `preview` writes to one git branch. **netlify:** the branch a `--target branch` context applies to (required with it). |
| `--script <name>` | **cloudflare-workers:** the Worker's name. A Wrangler environment is its own Worker — `my-api --env staging` is the Worker `my-api-staging`. |
| `--store-id <id>` | **cloudflare-secrets-store:** the store ID (32 hex). |
| `--scopes <list>` | **cloudflare-secrets-store:** comma-separated scopes for secrets seekrit creates (default `workers`). |
| `--railway-project <id>` | **railway:** the project ID (a UUID). |
| `--railway-environment <id>` | **railway:** the Railway environment ID (a UUID) — its deployment environment, not the seekrit one. |
| `--service <id>` | **railway:** the service ID (a UUID). Omit to write the environment's shared variables. **render:** the service ID from its dashboard URL (`srv-…`, or `crn-…` for a cron job). |
| `--skip-deploys` | **railway:** stage values without triggering the redeploy that would put them live. |
| `--path <path>` | **aws-parameter-store:** the hierarchy to write under, `/prod/storefront/` (leading and trailing slash). **aws-secrets-manager:** an optional name prefix, `prod/storefront/`. |
| `--layout <layout>` | **aws-secrets-manager, gcp-secret-manager:** `secret-per-name` (default) or `json-bundle` — every value as one JSON secret, the shape ECS and Lambda read with `secret-arn:json-key::`. |
| `--secret-name <name>` | **aws-secrets-manager, gcp-secret-manager:** required with `--layout json-bundle` — the one secret to write. |
| `--param-type <type>` | **aws-parameter-store:** `SecureString` (default) or `String`. |
| `--tier <tier>` | **aws-parameter-store:** `Standard` (default), `Advanced`, or `Intelligent-Tiering`. |
| `--kms-key-id <id>` | **aws:** a customer-managed KMS key, as an ID, ARN, or alias. Defaults to the AWS-managed key. |
| `--env-group <id>` | **render:** the environment group ID (`evg-…`). Pass this *or* `--service`, not both — which flag you use picks the destination. |
| `--fly-app <name>` | **fly:** the Fly app name (not `--app`, which is the seekrit application). One secret set per app, staged until Machines restart — `fly secrets deploy` rolls them out. |
| `--secret-group <id>` | **northflank:** the secret group to write, by the slug in its URL. It must already exist. |
| `--do-app <id>` | **digitalocean:** the App Platform app ID — the UUID in its dashboard URL, not the app's name. |
| `--component <name>` | **digitalocean:** write one component's own variables instead of the app-level ones. A component-level name overrides an app-level one. |
| `--env-scope <scope>` | **digitalocean:** `RUN_TIME` (default), `BUILD_TIME`, or `RUN_AND_BUILD_TIME`. Run time keeps values out of build logs and buildpacks. |
| `--heroku-app <name>` | **heroku:** the Heroku app name, or its UUID (not `--app`, which is the seekrit application). One set of config vars per app; writing them cuts a release and restarts the app's dynos. |
| `--netlify-site <id>` | **netlify:** the site's **API ID** — the UUID under Project configuration → General → Project information, not the site name or its `.netlify.app` address. Netlify resolves no site names on the environment variable endpoints, and a site it cannot resolve gets the variables written to the whole team. |
| `--no-netlify-secret` | **netlify:** create readable variables instead of write-only Netlify secrets. seekrit creates secrets by default; pass this if your plan has no Secrets Controller. |
| `--bunnyshell-environment <id>` | **bunnyshell:** the environment ID, from `bns environments list` or the dashboard URL — its variables are inherited by every component in the environment. Omit it and pass `--project` to write the project's variables instead; passing both is an error, since a binding writes to one. |
| `--no-bunnyshell-secret` | **bunnyshell:** create variables visible in Bunnyshell's dashboard instead of secret ones. seekrit marks them secret by default. Bunnyshell encrypts every variable at rest either way, so the flag decides who can read it, not whether it is stored in the clear. |
| `--gh-repo <owner/name>` | **github-actions:** the repository, exactly as GitHub writes it (`acme/storefront`). Taken as one flag because that is how GitHub writes a repository everywhere; asking for it in two invites pasting the pair into one of them. |
| `--gh-environment <name>` | **github-actions:** write to one deployment environment's secrets rather than the repository's. Needs `--gh-repo`. The environment must already exist — seekrit will not create one, because an environment is a deployment gate and creating an unprotected one from a typo would remove it. |
| `--gh-org <login>` | **github-actions:** write **organization** secrets instead of a repository's. Mutually exclusive with `--gh-repo` and `--gh-environment`. |
| `--gh-visibility <v>` | **github-actions** org secrets: `all`, `private` (CLI default), or `selected`. `all` includes repositories added later, and public ones. |
| `--gh-repo-ids <ids>` | **github-actions:** comma-separated numeric repository **IDs** for `--gh-visibility selected`. GitHub's API takes IDs, not names — `gh api repos/acme/storefront --jq .id`. |
| `--gcp-prefix <prefix>` | **gcp-secret-manager:** prepended to every secret ID, e.g. `prod-storefront-`. Not `--path`: a Secret Manager ID takes letters, digits, hyphens, and underscores — no slashes or dots. |
| `--gcp-replication <policy>` | **gcp-secret-manager:** `automatic` (default) or `user-managed`. Set when a secret is created and immutable after. |
| `--gcp-locations <list>` | **gcp-secret-manager:** comma-separated regions, required with `--gcp-replication user-managed`. Each is billed as its own active version. |
| `--gcp-kms-key <name>` | **gcp-secret-manager:** a Cloud KMS key, as its full resource name (`projects/…/cryptoKeys/…`). KMS keys are regional, so one key covers automatic replication or a single location. |
| `--langgraph-deployment <id>` | **langgraph-platform:** the deployment UUID, from its dashboard URL or the `id` in `GET /v2/deployments`. A deployment is the whole destination — its secrets belong to the deployment, so there is no narrower scope to name. Every write creates a new **revision**, which rebuilds and rolls out the Agent Server; seekrit reads first and sends nothing when nothing would change, so a stable environment never redeploys on the reconcile timer. |
| `--gcp-prune-versions` | **gcp-secret-manager:** destroy the version each push supersedes, keeping one active version per secret. Only ever a version seekrit itself wrote. |
| `--prefix <prefix>` | Prepend this to every destination key name. |
| `--include <globs>` / `--exclude <globs>` | Comma-separated name globs. `include` allows, `exclude` then removes; exclusion wins. |
| `--on-delete delete\|retain` | What happens on the destination when a secret is removed here (default `delete`). |
| `--mode auto\|manual` | Push on every write (default), or only on `sync run`. |
| `--acknowledge-decryption` | Required non-interactively; confirms seekrit's servers may decrypt this environment. |

Enabling wraps the environment's data key — and the key of every group it
composes — to the connection, on your machine. You need a key for all of them,
so run it as someone who can already read the environment.

```bash
printf '%s' "$VERCEL_TOKEN" | seekrit sync connect --name acme-vercel --team-id team_…
seekrit sync enable --connection acme-vercel --app storefront --env production \
  --project prj_… --target production --acknowledge-decryption
seekrit sync run syb_…

# Cloudflare: one scoped token per product, plus the account id
printf '%s' "$CLOUDFLARE_API_TOKEN" | seekrit sync connect --name acme-cf \
  --provider cloudflare-workers --account-id 0123456789abcdef0123456789abcdef
seekrit sync enable --connection acme-cf --provider cloudflare-workers \
  --script my-api --app storefront --env production --acknowledge-decryption

# Railway: three UUIDs address a variable, and the token kind picks the header
printf '%s' "$RAILWAY_TOKEN" | seekrit sync connect --name acme-railway \
  --provider railway --token-kind project
seekrit sync enable --connection acme-railway --provider railway \
  --railway-project 11111111-1111-4111-8111-111111111111 \
  --railway-environment 22222222-2222-4222-8222-222222222222 \
  --service 33333333-3333-4333-8333-333333333333 \
  --app storefront --env production --acknowledge-decryption

# AWS: the region and key id are config, the secret access key is the credential
printf '%s' "$AWS_SECRET_ACCESS_KEY" | seekrit sync connect --name acme-aws \
  --provider aws-secrets-manager --region us-east-1 --access-key-id AKIA…
seekrit sync enable --connection acme-aws --provider aws-secrets-manager \
  --path prod/storefront/ --app storefront --env production --acknowledge-decryption

# Render: one key for the whole workspace, then a service or an env group
printf '%s' "$RENDER_API_KEY" | seekrit sync connect --name acme-render --provider render
seekrit sync enable --connection acme-render --provider render \
  --service srv-abc123 --app storefront --env production --acknowledge-decryption
seekrit sync enable --connection acme-render --provider render \
  --env-group evg-xyz789 --app storefront --env staging --acknowledge-decryption

# Fly.io: the app name is the whole destination; nothing to set on the connection
printf '%s' "$(fly tokens create deploy -a storefront-production)" \
  | seekrit sync connect --name acme-fly --provider fly
seekrit sync enable --connection acme-fly --provider fly \
  --fly-app storefront-production \
  --app storefront --env production --acknowledge-decryption

# Northflank: one secret group, named by slug — the token carries its own team
printf '%s' "$NORTHFLANK_API_TOKEN" | seekrit sync connect --name acme-northflank \
  --provider northflank
seekrit sync enable --connection acme-northflank --provider northflank \
  --project default-project --secret-group app-secrets \
  --app storefront --env production --acknowledge-decryption

# DigitalOcean App Platform: app-level variables, written encrypted.
# Each push submits a new app spec, which starts a deployment.
printf '%s' "$DIGITALOCEAN_TOKEN" | seekrit sync connect --name acme-digitalocean \
  --provider digitalocean
seekrit sync enable --connection acme-digitalocean --provider digitalocean \
  --do-app 4f6c71e2-1e90-4762-9fee-6cc4a0a9f2cf \
  --app storefront --env production --acknowledge-decryption

# Heroku: config vars on one app — each push restarts its dynos
printf '%s' "$(heroku authorizations:create --short)" \
  | seekrit sync connect --name acme-heroku --provider heroku
seekrit sync enable --connection acme-heroku --provider heroku \
  --heroku-app storefront-production \
  --app storefront --env production --acknowledge-decryption

# Netlify: one site's variables, per deploy context — the team is on the connection
printf '%s' "$NETLIFY_AUTH_TOKEN" \
  | seekrit sync connect --name acme-netlify --provider netlify --account-id acme
seekrit sync enable --connection acme-netlify --provider netlify \
  --netlify-site 3970e0fe-8564-4903-9a55-c5f8de49fb8b \
  --target production,deploy-preview \
  --app storefront --env production --acknowledge-decryption

# Bunnyshell: one environment's variables — the connection carries no scope
printf '%s' "$BUNNYSHELL_TOKEN" \
  | seekrit sync connect --name acme-bunnyshell --provider bunnyshell
seekrit sync enable --connection acme-bunnyshell --provider bunnyshell \
  --bunnyshell-environment env-9f3a2b \
  --app storefront --env production --acknowledge-decryption

# Bunnyshell: the project instead, to seed ephemeral environments that don't exist yet
seekrit sync enable --connection acme-bunnyshell --provider bunnyshell \
  --project prj-4c8d1e \
  --app storefront --env preview --acknowledge-decryption

# GitHub Actions: prefer seekritdev/github-action, which decrypts in the run.
# Sync only for what an Action cannot reach — `with:` inputs of third-party
# actions, `secrets: inherit`, job-level container/services credentials.
printf '%s' "$GITHUB_TOKEN" \
  | seekrit sync connect --name acme-github --provider github-actions
# ...to one deployment environment's secrets, the narrowest scope GitHub has
seekrit sync enable --connection acme-github --provider github-actions \
  --gh-repo acme/storefront --gh-environment production \
  --app storefront --env production --acknowledge-decryption
# Google Secret Manager: the credential is the whole key file, so pipe it in
seekrit sync connect --name acme-gcp --provider gcp-secret-manager \
  --project-id acme-prod < key.json
seekrit sync enable --connection acme-gcp --provider gcp-secret-manager \
  --gcp-prefix prod-storefront- --gcp-prune-versions \
  --app storefront --env production --acknowledge-decryption
```

## Billing

| Command | Description |
| --- | --- |
| `seekrit billing` | The org's plan, subscription status, current usage against what the plan includes, and which self-serve actions this deployment supports. While limits aren't enforced it says so — the numbers are informational. |
| `seekrit billing entitlements` | Every entitlement the org resolves to, and where each value came from. |
| `seekrit billing checkout <family>` | Start a self-serve upgrade. Prints a checkout URL to open. |
| `seekrit billing portal` | Print a billing-portal URL for payment methods and invoices. |
| `seekrit billing cancel [--yes]` | Cancel the subscription and drop back to the Free plan. |
