# CLI reference

**This page documents what every `hlix` command has in common:** the global options, how a command decides which workspace and API it is talking to, the shape of `--json` output, and the codes automation branches on. Each command group has its own page, linked below.

Run `hlix <command> --help` for the installed version's source-of-truth usage. The packaged skill guide at `skills/hlix/SKILL.md` is tested against the command table in both directions, so a command documented there but unregistered — or registered but undocumented — fails the build.

## Command groups

| Group | Commands | Page |
| --- | --- | --- |
| Authentication | `auth login`, `auth status`, `auth logout` | [auth](/reference/cli/auth/) |
| Orientation | `status` | [status](/reference/cli/status/) |
| Local setup | `init` | [init](/reference/cli/init/) |
| Project creation | `import` | [import](/reference/cli/import/) |
| Reconciliation | `push`, `pull`, `sync` | [push, pull & sync](/reference/cli/sync/) |
| Explicit resources | `skill import`, `agent import`, `mcp import` | [resources](/reference/cli/resources/) |
| Inspection | `projects list`, `projects get` | [projects](/reference/cli/projects/) |
| Inspection | `tasks list`, `tasks get`, `tasks review`, `tasks watch` | [tasks](/reference/cli/tasks/) |
**Two things this CLI deliberately does not do:** **No execution commands.** There is no way to start a run, execute a cycle, or dispatch a task from the CLI — dispatching from a dropped connection can duplicate work. Use the API or the dashboard; see [Cycles](/running/cycles/).

**No `tasks logs --follow`.** The public API has a task-status stream, not a log stream, so a `--follow` here would be a poll dressed up as a stream. `tasks watch` is named for what it actually does.

## Global options

Accepted by every command, whether or not the command's own usage lists them.

- **`--json`** (`boolean`, default `false`): print a stable, versioned machine envelope instead of the human view. Streams emit JSONL.
- **`--cwd <dir>`** (`string`, default the process working directory): operate on another directory, including the project-binding lookup. A path that is not a directory exits `invalid_usage` before the command runs.
- **`--workspace <id>`** (`string`, default resolved — see below): the workspace to act in, overriding the folder's binding. Contradicting a bound folder is refused, not resolved.
- **`--base-url <url>`** (`string`, default `https://server.hlix.ai`): the API to talk to, overriding the folder's binding. [`hlix auth login`](/reference/cli/auth/) additionally requires HTTPS before it will store a credential — plain HTTP is accepted there only for `localhost`, `127.0.0.1`, and `::1`.
- **`--help`** (`boolean`): show global help, or the matched command's usage.
- **`--version`** (`boolean`): print the installed CLI version.

A command-specific flag passed to a command that does not accept it exits `invalid_usage` naming the flag, rather than being ignored.
**`--api-key` does not exist:** `hlix` refuses `--api-key` in any position, before parsing anything else, and exits `2`. Command arguments are retained in shell history and visible in process listings. The key comes from a hidden prompt or `HLIX_API_KEY`.

## Where a command points

Every command resolves its workspace and API through one chain, highest first:

```text
1. an explicit flag            --workspace, --base-url
2. the HLIX_* environment      HLIX_WORKSPACE_ID, HLIX_BASE_URL
3. the nearest .hlix/config.json   walking up from the working directory
4. the stored credential       ~/.config/hlix/credentials.json
5. the built-in default        https://server.hlix.ai
```

The folder's binding therefore **outranks the machine-wide credential**: a folder bound to workspace A is queried against workspace A even when the last login named workspace B.
**A contradiction is refused, not resolved:** When a flag or environment variable **contradicts** the folder's binding, precedence does not apply — the command exits `workspace_mismatch` naming both, because guessing which one was meant is how a push reaches the wrong tenant.

```text
/Users/you/acme-invoices is bound to workspace org_2p9xk4, but --workspace
names org_7t1mz2. Drop the override, or point --cwd at a folder bound to it.
```

`hlix auth login` sits deliberately outside the chain, so signing in to a second workspace from inside a bound folder still works.

Commands that act on a bound project — `push`, `pull`, `sync`, `status`, `projects get`, `tasks list`, and the three resource imports — find that binding by walking **up** from the working directory. Running `hlix push` from `packages/api` pushes the project, not a fragment of it.

## JSON contract

Success:

```json
{
  "schemaVersion": 1,
  "command": "projects list",
  "data": []
}
```

Failure:

```json
{
  "schemaVersion": 1,
  "command": "projects list",
  "error": {
    "code": "unauthenticated",
    "message": "…",
    "status": 401
  }
}
```

Human formatting may improve between releases. The versioned JSON envelope, command name, documented command data, and stable error code are the automation contract. For inspection commands, `data` is the API's response passed through **unmodified** — the CLI does not reshape, rename, or prune it. Local project commands such as `import`, `push`, `pull`, and `sync` return their documented local result shapes.

`tasks watch --json` is the one exception to "one document": it emits one envelope per line (JSONL), because a live stream cannot be a single JSON document.
**For AI agents:** Always pass `--json`, and read `error.code` — never the human output. Column layouts, wording, and spacing may change between releases without a major bump; the envelope, the command name, and the code list may not.

Two consequences worth encoding: a `--json` run can never answer a prompt, so a gate returns `approval_required` rather than blocking — review with `--dry-run`, then repeat with `--yes`. And `tasks watch --json` is JSONL, so read it line by line rather than waiting for one parsable document.

## Error codes

Branch on `error.code`, never on `error.message` — the message is human-readable and not stable.

| Code | Raised when |
| --- | --- |
| `unauthenticated` | No credential, or the key was rejected |
| `forbidden` | The key is valid but may not act here |
| `not_found` | No such resource in this workspace |
| `conflict` | The operation collided with another change |
| `invalid_request` | The request failed schema validation |
| `bad_request` | The API refused the request |
| `rate_limited` | Too many requests |
| `not_implemented` | The endpoint exists but does nothing yet |
| `server_error` | The API failed |
| `unreachable` | The API could not be contacted |
| `missing_argument` | A required positional argument was omitted |
| `invalid_usage` | Unknown flag, bad flag value, or too many arguments |
| `unknown_command` | No such command |
| `workspace_mismatch` | An explicit target contradicts the folder's binding |
| `workspace_required` | Nothing in the resolution chain named a workspace |
| `approval_required` | A destructive step needs approval the caller cannot give |
| `cancelled` | An interactive prompt was declined |
| `not_initialized` | The folder is not bound to a project |
| `already_imported` | The folder is already bound; use `hlix push` |
| `scan_blocked` | The import scan found a blocker |
| `invalid_base_url` | `--base-url` is not HTTPS, and is not a loopback host |
| `missing_credential` | No API key was entered at the prompt |
| `unknown` | Unclassified |

`approval_required` and `cancelled` are the two halves of every gate: the first means "you cannot answer here — review with `--dry-run` and repeat with `--yes`", the second means "you answered no".

## Exit codes

| Code | Meaning |
| --- | --- |
| `0` | success |
| `1` | operational failure or scan blocker |
| `2` | usage, parsing, or unknown-command failure |
| `3` | authentication or permission failure |

A `--dry-run` that would end in `conflict` exits `1`, as `import --dry-run` does for a blocker — the report is the deliverable, and the exit code still carries the verdict.
**Check both exit and payload:** For JSONL streams, parse each line as it arrives and still check the final process exit. For one-shot commands, a non-zero exit remains the authoritative failure signal.

## Environment variables

| Variable | Purpose |
| --- | --- |
| `HLIX_API_KEY` | API credential; overrides the saved file |
| `HLIX_WORKSPACE_ID` | workspace selection |
| `HLIX_BASE_URL` | API origin; defaults to production |
| `HLIX_CONFIG_HOME` | hlix-specific credential directory root |
| `XDG_CONFIG_HOME` | standard config root when `HLIX_CONFIG_HOME` is absent |

## If a command fails before it runs

- `unknown_command` — the words did not match a registered command. `hlix --help` lists them.
- `invalid_usage` — ``Unsupported option for push: --force`` — the flag exists globally or on another command, not this one. The per-command page lists what each accepts.
- `invalid_usage` — ``--cwd is not a directory: /path`` — `--cwd` is checked before dispatch, so this exits `2` even for a command that would otherwise always exit `0`.
- `missing_argument` — a required positional was omitted. The message is the command's usage line.
- `error: API keys are never accepted as command-line arguments.` — remove `--api-key` and use the hidden prompt or `HLIX_API_KEY`.

## Next steps

[hlix auth](/reference/cli/auth/)
  [hlix status](/reference/cli/status/)
  [hlix import](/reference/cli/import/)
  [Troubleshooting](/reference/troubleshooting/)