# CLI reference (https://docs-ecmrhub-v2.lucaspiera.com/docs/cli)

> The ecmrhub command line for terminals, coding agents and CI. Sign-in, commands, JSON output, exit codes and approvals.

> **In build (CLI):** `ecmrhub` is part of phase 1 and not released yet; it is not on npm. `ecmrhub --help` and
> `ecmrhub <command> --help` show the exact flags of your version.

`ecmrhub` works in one company's account. It is built from the same operation catalog as the MCP
server, so anything a chat assistant can do over MCP, a terminal agent can do here.

## Sign-in and profiles [#sign-in-and-profiles]

| Command | What it does |
| --- | --- |
| `ecmrhub login` | Browser sign-in (OAuth 2.1 with PKCE, callback on `http://127.0.0.1:<port>/callback`). The client registers itself once per profile. Options: `--no-browser` (print the address), `--timeout S`, `--new-client`. |
| `ecmrhub login --api-key` | Fallback: a company API key from stdin or `ECMR_HUB_API_KEY`. |
| `ecmrhub whoami` | Company (`tenantId`), permissions and how the CLI is connected (`auth`: `oauth`, `apikey`, `env-key`, `env-token`). |
| `ecmrhub logout` | Forget the tokens or key of the profile. |

- Tokens and the refresh token live in `~/.config/ecmr-hub/config.json`, readable only by you
  (0600). `--profile <name>` keeps several companies apart.
- Environment: `ECMR_HUB_API_KEY` or `ECMR_HUB_ACCESS_TOKEN`; `ECMR_HUB_URL`, `ECMR_HUB_ANON_KEY`,
  `ECMR_HUB_PROFILE`, `ECMR_HUB_CONFIG`, `ECMR_HUB_APP_URL`. A key in the environment wins over a
  browser sign-in. Never pass a key as an argument.

## Output [#output]

- `--json`, or any pipe, prints JSON on stdout.
- Errors go to stderr as `{"error":{"status","code","message","retryable","approval"?}}`.
- `retryable: true` (429, 5xx, transport errors) means: repeat the same command with the same ids
  and keys. Writes are idempotent by draft revision and idempotency key.

## Exit codes [#exit-codes]

| Code | Meaning | What the agent does |
| --- | --- | --- |
| 0 | Success | Continue |
| 1 | Error | Read `error.code`; retry only when `retryable` is true |
| 2 | Wrong usage | Check `--help` |
| 3 | Not signed in, session expired, key or connection revoked | Ask the user to run `ecmrhub login`; do not retry |
| 4 | Forbidden: a missing permission, or a person must do it | Tell the user which permission or step |
| 5 | Waiting for a person's approval | Show `error.approval.url`, wait, run `error.approval.retry` |

## Commands [#commands]

| Command | Operation | Notes |
| --- | --- | --- |
| `fields` | (local) | CMR fields: key, label, box, required, eFTI, codes |
| `drafts check --data FILE` | (local) | Validation and eFTI gaps, no network |
| `drafts list` | `draft.list` | Drafts and documents by stage |
| `drafts create --data FILE` | `draft.save` | New draft; returns `id` and `revision` |
| `drafts update <id> --data FILE [--revision N]` | `draft.save` | Without `--revision` only the given fields change; an empty string clears a field |
| `drafts delete <id> [--revision N]` | `draft.delete` | Needs approval |
| `drafts issue <id> [--revision N]` | `draft.publish` | Needs approval; issuing twice returns the same document |
| `documents list [--limit N] [--before ISO]` | `document.list` | Issued documents |
| `documents get <id>` | `document.get` | One document |
| `documents evidence <id>` | `document.evidence` | Versions, events, consents |
| `documents pdf <id> --out FILE` | `document.pdf` | Stored PDF, checked against its SHA-256 |
| `sign request <documentId> --role sender\|carrier\|consignee [--idempotency-key K]` | `signing.createLink` | Needs approval; link valid 7 days |
| `sign revoke <documentId> --role sender\|carrier\|consignee` | `signing.revokeLink` | Needs approval |
| `efti check <documentId>` or `efti check --data FILE` | (local) | eFTI gaps; nothing is sent to any gateway |
| `approvals list [--status S]` | | Approvals this connection requested |
| `approvals status <id>` | | One approval |
| `approvals wait <id> [--timeout S] [--interval S]` | | Exit 0 approved, 4 rejected, 1 expired or used, 5 still pending |
| `connections list` | | A person's session only; an agent gets exit 4 |
| `connections revoke <id>` | | A person's session only; an agent gets exit 4 |
| `operations list` | | The whole catalog; `cli: "unavailable"` marks what only a person or an app screen can run |
| `operations describe <name>` | | Input schema of one operation |
| `operation <name> --data FILE` | any | Run any operation of the catalog by name |
| `skill` | | Print the agent skill (SKILL.md) |

Global options: `--approval ID`, `--json`, `--profile NAME`, `--url URL`, `--help`, `--version`.

There is no flag that approves an operation. `--approval <id>` only repeats a call a person has
already approved. Pass `--data` as a file rather than stdin, so the repeated call sends the same
input.

## Approvals [#approvals]

When a command needs approval it exits with `5` and prints:

```json
{
  "error": {
    "status": 428,
    "code": "APPROVAL_REQUIRED",
    "retryable": false,
    "approval": {
      "id": "6f1c2a9e-...",
      "url": "https://ecmrhub-v2.lucaspiera.com/dashboard/approvals/6f1c2a9e-...",
      "expiresAt": "2026-10-20T10:00:00.000Z",
      "operation": "draft.publish",
      "retry": "ecmrhub drafts issue 1b2c... --revision 3 --approval 6f1c2a9e-... --json"
    }
  }
}
```

Give the user `error.approval.url`, wait with `approvals wait`, then run `error.approval.retry` exactly as
given. More: [Human approvals](https://docs-ecmrhub-v2.lucaspiera.com/docs/approvals.md).

## Calling any operation [#calling-any-operation]

Every operation the company allows has a command, even without a dedicated subcommand:

```bash
ecmrhub operations describe vehicle.add --json      # input schema
ecmrhub operation vehicle.add --data vehicle.json --json
```

```json title="vehicle.json"
{ "name": "Truck 1", "values": { "registration": "WX12345", "country": "PL" } }
```

The full list with permissions and approval rules: [Operation catalog](https://docs-ecmrhub-v2.lucaspiera.com/docs/cli/operations.md).

## The agent skill [#the-agent-skill]

```bash
mkdir -p ~/.claude/skills/ecmr-hub-cli
ecmrhub skill > ~/.claude/skills/ecmr-hub-cli/SKILL.md
```

The skill teaches a coding agent the same rules as this page: sign-in, JSON output, exit codes,
approvals and the order of steps to issue a consignment note.
