> ## Documentation Index
> Fetch the complete documentation index at: https://code.storage/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# CLI

> Install the pierre command, sign in with a key, and call any Code Storage operation from a terminal, a CI job, or Git.

`@pierre/cli` installs the `pierre` command. Each API command calls one operation of the HTTP API
through `@pierre/sdk`, with the same name and the same fields as the SDK method. The helpers sign
tokens and let plain Git work with each Code Storage repository.

The reference page of each operation shows its CLI command. This page gives the parts that every
command shares.

## Install

```bash theme={"theme":{"light":"github-light","dark":"min-dark"}}
npm install -g @pierre/cli
pierre version
```

```text theme={"theme":{"light":"github-light","dark":"min-dark"}}
pierre 0.1.0
@pierre/sdk 0.1.0
```

The CLI needs Node.js 22.23.4 or a later 22 release, 24.22.1 or a later 24 release, or 26.12.1 or
later.

To run one command with no install, use `npx @pierre/cli version`. Use a global install for
`pierre auth setup-git`: Git stores the path of the `pierre` that it runs, and `npx` runs the CLI
from a cache that can change.

## Sign in

Create a key on the **Keys** page of your organization dashboard.
[Authentication](https://code.storage/docs/platform/authentication.md#create-a-key) explains keys. Then store the private key
for your Git host:

```bash theme={"theme":{"light":"github-light","dark":"min-dark"}}
pierre auth login --hostname acme.code.storage --with-key < key.pem
```

With `--with-key`, the CLI reads the PEM from stdin. You can also pass `--from-file key.pem`, or
paste the key at the prompt. The CLI checks the key with [`repos.list`](https://code.storage/docs/api/repos/repos-list), then
stores it. `--no-verify` skips the check. The command prints the host, the organization, the key
file, the key ID, and the result of the check.

The CLI keeps one entry for each Git host in `~/.config/pierre/hosts.yml`. It keeps the private key
in `~/.config/pierre/keys/<host>.pem`, with mode 600. When `XDG_CONFIG_HOME` is set, it replaces
`~/.config`. `PIERRE_CONFIG_DIR` replaces the whole `~/.config/pierre` path.

A host in the `<org>.code.storage` shape gives the organization and the API URL. For a Git host on
another domain, pass `--org` and `--api-url` once, at login. The **Settings** page of the dashboard
shows the Git host and the API URL.

```bash theme={"theme":{"light":"github-light","dark":"min-dark"}}
pierre auth login --hostname git.example.com --org acme --api-url https://api.example.com --with-key < key.pem
```

### Check the credential

```bash theme={"theme":{"light":"github-light","dark":"min-dark"}}
pierre auth status
```

`pierre auth status` reports each saved host: the organization, the API URL, the key source, the key
ID, a live check, and the Git helper settings. `--hostname <host>` reports one host, and `--json`
prints the report as JSON. When a check fails, the command exits with the code of the problem.

To rotate a key, run `pierre auth login` again for the same host with the new key. Then delete the
old key on the dashboard. `pierre auth logout` deletes only the local key file and the host entry,
so also revoke the key on the dashboard.

### Select a host

Each command uses one Git host. The CLI takes the first match:

1. `--hostname <host>`.
2. `PIERRE_GIT_HOST`.
3. The Code Storage remote of the current Git repository: an HTTPS remote on a saved host or on a
   `*.code.storage` host. When several remotes match, a saved host comes first, then the remote
   named `storage`.
4. The single saved host.
5. `<org>.code.storage`, for the organization in `PIERRE_ORG` or in the `iss` claim of
   `PIERRE_TOKEN`.

When `hosts.yml` holds more than one host, and the first three give no host, the command stops with
exit code 2. The error lists the saved hosts:

```text theme={"theme":{"light":"github-light","dark":"min-dark"}}
pierre: Several hosts are configured. Pass --hostname <host> or set PIERRE_GIT_HOST.
  acme.code.storage
  git.example.com
```

Pass the Git host with `--hostname` for one command, or set `PIERRE_GIT_HOST` for the shell or the
job:

```bash theme={"theme":{"light":"github-light","dark":"min-dark"}}
pierre repos list --hostname acme.code.storage

export PIERRE_GIT_HOST=acme.code.storage
pierre repos list
```

* `--hostname` takes the Git host, such as `acme.code.storage`, not the API URL. It accepts an
  `http://` or `https://` prefix.
* `--hostname` is a global flag. Put it before or after the command.
* `--hostname` wins over `PIERRE_GIT_HOST`, and both win over the remote of the current repository.
* A saved host uses its host entry. Another host uses the variables of
  [CI and containers](#ci-and-containers), as the SDKs do.
* `pierre auth status` reports every saved host, so it needs no host.

### CI and containers

A CI job usually has no `hosts.yml`. Give the credential in environment variables:

```bash theme={"theme":{"light":"github-light","dark":"min-dark"}}
export PIERRE_ORG=acme
export PIERRE_PRIVATE_KEY="$(cat key.pem)"
# Only for a cluster outside <org>.code.storage:
export PIERRE_GIT_HOST=git.example.com
export PIERRE_API_BASE_URL=https://api.example.com
pierre repos list --json
```

| Variable | Meaning |
| - | - |
| `PIERRE_ORG` | The organization. With no other host, the CLI uses the Git host `<org>.code.storage` and the API URL `https://api.<org>.code.storage`. |
| `PIERRE_PRIVATE_KEY` | The private key PEM itself. Nothing is read from disk. A value with `\n` in place of each line break also works. |
| `PIERRE_PRIVATE_KEY_FILE` | The path of the private key file. |
| `PIERRE_TOKEN` | A JWT that another system signed. The CLI sends it on each call and signs nothing. The claims of the token limit the access. Its `iss` claim gives the organization. |
| `PIERRE_API_BASE_URL` | The API URL, for a host with no saved entry. An origin, or a URL that ends in `/api/v1`. |
| `PIERRE_GIT_HOST` | The Git host, when no `--hostname` is given. It selects the saved entry of that host, or else the CLI uses these variables. See [Select a host](#select-a-host). |

The CLI uses the first credential that it finds: `PIERRE_TOKEN`, `PIERRE_PRIVATE_KEY`,
`PIERRE_PRIVATE_KEY_FILE`, then the key file of the host entry. A saved host entry sets the
organization and the API URL, so `PIERRE_ORG` and `PIERRE_API_BASE_URL` apply only to a host with no
entry. When the runner has a `hosts.yml` with several hosts, also set `PIERRE_GIT_HOST`.

In a container, configure Git when you build the image, and put no secret in the image:

```bash theme={"theme":{"light":"github-light","dark":"min-dark"}}
pierre auth setup-git --hostname acme.code.storage
```

At runtime, set `PIERRE_PRIVATE_KEY`. For a host in the `<org>.code.storage` shape, Git needs
nothing else: the helper gets the organization from the host. Also set `PIERRE_ORG` for a host on
another domain, and for an API command outside a clone. An API command on a host on another domain
also needs `PIERRE_GIT_HOST` and `PIERRE_API_BASE_URL`.

## Command form

```bash theme={"theme":{"light":"github-light","dark":"min-dark"}}
pierre repos:branches create feat -R acme/web --base-ref main
```

The catalog ID of an operation gives its command. Join the resource path with `:`, then add the
method. A name in camel case, such as `createFromDiff` or `gitCredentials`, becomes kebab case.

| Catalog ID | Command |
| - | - |
| `repos.list` | `pierre repos list` |
| `repos.get` | `pierre repos get acme/web` |
| `repos.branches.create` | `pierre repos:branches create feat -R acme/web` |
| `repos.commits.createFromDiff` | `pierre repos:commits create-from-diff` |

The reference page of each operation shows its command, for example
[`repos.branches.create`](https://code.storage/docs/api/repos/branch-create). `pierre --help` lists the resources and their
methods. `pierre <resource> <method> --help` gives the flags, the catalog ID, the HTTP route, and
the scopes of one command:

```bash theme={"theme":{"light":"github-light","dark":"min-dark"}}
pierre repos:branches create --help
```

The resource path is one word. `pierre repos branches create` is a usage error that names
`pierre repos:branches create`.

### Singular nouns

Each resource also has a singular name. `pierre repo list` runs `pierre repos list`, and
`pierre repo:branch create` runs `pierre repos:branches create`. `pierre repo view` runs
`pierre repos get`.

### Flags

The flags are the fields of the SDK method, with the wire names in kebab case. `target_branch` is
`--target-branch`, and `base_ref` is `--base-ref`.

* A boolean is `--force` or `--no-force`.
* An array takes its flag once for each item.
* An object takes JSON, `@file`, or `@-` for stdin. Each scalar field of the object also has a
  dotted flag, such as `--author.name` and `--author.email`.

### The argument

A command takes at most one positional argument: the resource that it acts on. The argument and its
flag do the same thing:

```bash theme={"theme":{"light":"github-light","dark":"min-dark"}}
pierre repos:branches create feat -R acme/web --base-ref main
pierre repos:branches create --target-branch feat -R acme/web --base-ref main
```

Pass each other value with its flag. A second argument is a usage error.

### The repository

`-R` is the short form of `--repo-name`. Inside a clone with a Code Storage remote, `--repo-name`
defaults to the repository of that remote:

```bash theme={"theme":{"light":"github-light","dark":"min-dark"}}
git clone https://acme.code.storage/acme/web.git
cd web
pierre repos:branches create feat --base-ref main
```

The default needs an HTTPS remote on the selected host. `pierre repos delete` has no default, so
name the repository. `--debug` prints the repository that the CLI took from the remote.

### Global flags

| Flag | Meaning |
| - | - |
| `--hostname <host>` | Select an organization by its Git host. See [Select a host](#select-a-host). |
| `--json` | Print the JSON response body. |
| `--quiet` | Print only results and errors. |
| `--no-input` | Fail instead of prompt. |
| `--yes` | Skip confirmations, such as the prompt of `pierre repos delete`. With no terminal, a confirmation needs `--yes`. |
| `--debug` | Print a few diagnostics to stderr. See [Debug output](#debug-output). |
| `-h`, `--help` | Show help. |
| `-v`, `--version` | Print the CLI version. |

## Output

```bash theme={"theme":{"light":"github-light","dark":"min-dark"}}
pierre repos create acme/web
```

```text theme={"theme":{"light":"github-light","dark":"min-dark"}}
201 Created
repo_name  acme/web
message    Repository 'acme/web' created
```

Without `--json`, an object prints its main fields, one row for each field. A list prints a table.
When an operation can answer with more than one success status, the output starts with the status.
`pierre repos create` prints `201 Created` for a new repository, and `200 OK` for an existing one.

`--json` prints the response body, with every field. Use it in scripts:

```bash theme={"theme":{"light":"github-light","dark":"min-dark"}}
pierre repos get acme/web --json
```

Warnings and errors go to stderr, so stdout holds the result.

### Lists

A list command prints one page. `--limit` and `--cursor` are the wire fields of the operation. When
more items exist, the CLI writes the next cursor to stderr:
`More items: pass --cursor <cursor>, or --paginate.`

```bash theme={"theme":{"light":"github-light","dark":"min-dark"}}
pierre repos list --limit 20
pierre repos list --paginate --json
```

`--paginate` reads every page. Without `--json`, it prints one table of all the items. With
`--json`, it prints one response body for each page.

### Bytes

A byte response, such as the file of `pierre repos:files get`, goes to stdout or to the file that
`--output` names:

```bash theme={"theme":{"light":"github-light","dark":"min-dark"}}
pierre repos:files get README.md -R acme/web --output README.md
pierre repos:files get README.md -R acme/web > README.md
```

When stdout is a terminal and you give no `--output`, the command stops with a usage error. It sends
no request.

## pierre api

`pierre api <catalog-id>` calls any operation through its SDK method, and prints the JSON response
body. As in `gh api`, path parameters are fields, with their wire names:

```bash theme={"theme":{"light":"github-light","dark":"min-dark"}}
pierre api repos.branches.create -F repo_name=acme/web -F target_branch=feat -F base_ref=main
```

* `-F key=value` sends a typed value: `true`, `false`, `null`, an integer, the text of `@file`,
  stdin for `@-`, or text.
* `-F 'key[name]=value'` sets one field of an object. For an array, repeat `-F` once for each item.
* `-f key=value` always sends a string.
* `--input <file>` gives the body fields as one JSON object. `--input -` reads stdin. Then `-F` and
  `-f` set only path, query, and header fields.
* `--paginate` reads every page of a list, and prints one body for each page.
* `--output <file>` writes a byte response to the file.

```bash theme={"theme":{"light":"github-light","dark":"min-dark"}}
pierre api repos.create -F repo_name=acme/web -F 'base_repo[name]=acme/template'
pierre api repos.list -F limit=100 --paginate
```

A field that the operation does not have is a usage error, and the error lists the fields. An
unknown catalog ID is a usage error that names the nearest ID. `pierre api` cannot call
`repos.commits.create` or `repos.commits.createFromDiff`, because their body is file content. Use
`pierre repos:commits create` and `pierre repos:commits create-from-diff`.

## Exit codes and errors

The exit code comes from the class of the SDK error, never from the message text.

| Code | Meaning |
| - | - |
| 0 | Success |
| 1 | Another API error, such as a conflict or a 5xx, or no connection |
| 2 | Usage error, such as an unknown flag, a missing field, or several hosts with no selection |
| 3 | Credential problem: no key, a malformed key, 401, or 403 |
| 4 | Not found: 404 |

An API error prints its status, its problem `code`, its detail, the request ID, and the docs of the
code:

```bash theme={"theme":{"light":"github-light","dark":"min-dark"}}
pierre repos get acme/missing
```

```text theme={"theme":{"light":"github-light","dark":"min-dark"}}
pierre: 404 repository_not_found: repository not found
Request ID: 08370ced-8035-9cbf-baa0-1a8495312625
Docs: search for "repository_not_found" in the docs MCP server, https://code.storage/docs/mcp, or read https://code.storage/docs/api/overview.md#error-codes
```

For a 401 or a 403, the CLI also tells you to check the key on the dashboard **Keys** page, or to
run the `pierre auth login` command again. With `--json`, the CLI also prints the problem details
body on stdout. [Error codes](https://code.storage/docs/api/overview.md#error-codes) lists each code. A usage error stops the
command before it sends a request.

### Debug output

`--debug`, or `PIERRE_DEBUG=1`, writes these lines to stderr:

* The repository that the CLI takes from the Git remote when you do not give `--repo-name`. See
  [The repository](#the-repository).
* The steps of the [Git credential helper](#the-git-credential-helper): an operation that it
  ignores, the source of a token that it sends, or the scopes, the repository, the host, the
  organization, and the lifetime of a token that it signs.
* The scopes, the repository, the host, the organization, and the lifetime of a token that
  [`pierre auth token`](#pierre-auth-token) signs.

`--debug` also prints the stack of an unexpected error. `PIERRE_DEBUG=1` does not. An API error and
a usage error print no stack.

Debug output has no request and no response, and it never has a token or a key.

## Helpers

### pierre auth token

`pierre auth token` prints a token that your private key signs, for a script or for `curl`:

```bash theme={"theme":{"light":"github-light","dark":"min-dark"}}
pierre auth token --repo acme/web
curl -H "Authorization: Bearer $(pierre auth token --scope org:read)" https://api.acme.code.storage/api/repos
```

* `--scope` adds a scope. Repeat it for more. The scopes are `git:read`, `git:write`, `org:read`,
  and `repo:write`. The default is `git:read` and `git:write`.
* A token whose scopes all cover the organization, such as `org:read`, has no `repo` claim. Another
  token needs `--repo`, which defaults to the Code Storage remote of the current repository.
* `--ttl` sets the lifetime, such as `15m`, `1h`, or `900s`. The default is 15 minutes, and the
  maximum is 1 hour.

When `PIERRE_TOKEN` is set, the CLI signs no token, so `pierre auth token` stops.
[Scopes](https://code.storage/docs/platform/authentication.md#scopes) gives the access of each scope.

### pierre auth setup-git

```bash theme={"theme":{"light":"github-light","dark":"min-dark"}}
pierre auth setup-git
git clone https://acme.code.storage/acme/web.git
```

`pierre auth setup-git` configures Git to get its credential for the host from `pierre`. Then
`git clone`, `git fetch`, and `git push` work, and no token appears in a URL or in a file. The
command writes these settings once for each host:

```ini theme={"theme":{"light":"github-light","dark":"min-dark"}}
[credential "https://acme.code.storage"]
	helper =
	helper = !/usr/local/bin/pierre auth git-credential
	useHttpPath = true
```

The blank `helper` line stops Git from asking another helper, such as a keychain, for this host.
`useHttpPath` makes Git send the repository path, which the helper needs.

* `--hostname <host>` selects the host. With several saved hosts, run the command once for each
  host.
* `--local` writes the settings of the current repository instead of the global settings.
* `--read-only` makes the helper sign `git:read` only, so the machine cannot push.
* `--uninstall` removes the settings.

When the `pierre` on your `PATH` is this CLI, the helper runs that path. Otherwise, the helper names
the Node.js executable and the CLI script. Then run `pierre auth setup-git` again after you update
Node.js or move the CLI.

### The Git credential helper

Git runs `pierre auth git-credential get` for each operation on the host. Do not run it yourself. On
each call, the helper:

1. Takes the host and the repository from the URL that Git sends. It removes `.git`, and a
   `+ephemeral` or `+import` suffix.
2. Finds the organization and the credential by the rules of
   [CI and containers](#ci-and-containers). Git gives the host, so the helper needs no `--hostname`
   and no `PIERRE_GIT_HOST`.
3. Signs a token for that repository with `git:read` and `git:write`, or with `git:read` only in
   read-only mode. The token lives for 1 hour. `PIERRE_TOKEN_TTL` sets another lifetime in seconds,
   from 1 to 3600. When `PIERRE_TOKEN` is set, the helper sends that token instead.
4. Gives Git the username `t` and the token as the password. It stores nothing.

On a failure, the helper tells Git to stop, so Git never shows a password prompt. The helper writes
one line to stderr that names the cause. Set `PIERRE_DEBUG=1` to see the diagnostics of the helper.

### pierre mcp

```bash theme={"theme":{"light":"github-light","dark":"min-dark"}}
pierre mcp
```

`pierre mcp` prints the docs MCP server, the index of the Markdown pages, the auth note, and the
command that installs the `pierre` agent skill. When a host is selected, it also prints the
organization and the API URL of that host. `pierre mcp --json` prints a config block for an MCP
client:

```json theme={"theme":{"light":"github-light","dark":"min-dark"}}
{
  "mcpServers": {
    "code-storage": {
      "type": "http",
      "url": "https://code.storage/docs/mcp"
    }
  }
}
```

[Agent Setup](https://code.storage/docs/getting-started/agent-setup.md) shows how to connect each agent.

## SDKs

The CLI calls the methods of `@pierre/sdk`. To call the same operations from your code in
TypeScript, Python, or Go, read the [SDK page](https://code.storage/docs/sdks.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.