# Onus CLI

The official command-line interface and TypeScript SDK for the
[Onus](https://github.com/BrunyeeStudio/onus) issue tracking platform. It is
**generated from the Onus OpenAPI specification** via
`@openapitools/openapi-generator-cli`, so the CLI and its types are always in
sync with the backend API.

The CLI is designed to be equally useful to human developers and autonomous
AI coding agents (`Pi`, `Claude Code`, `Codex`, `Cursor`, ...). Agent-focused
commands (`onus context`, `onus schema`) and deterministic `--json` output make
it trivial to script and to drive programmatically.

---

## Features

- 🔑 **`onus auth`** — store, inspect, and clear your access token and API endpoint.
- 🤖 **Agent-friendly** — `onus context`, `onus schema`, machine-readable `--json`
  output, and auto-JSON detection when stdout is piped.
- ✅ **Typesafe SDK** — the client models and runtime are generated directly from
  the Onus OpenAPI spec, so the CLI can't drift from the API.
- 📦 **Automated publishing** — versions, changelog, git tags, GitHub releases,
  and npm publishing are handled automatically from Conventional Commits via
  `semantic-release`.
- 🧭 **Rich terminal output** — aligned tables, semantic colors, and clear errors
  with standardized exit codes.

---

## Installation

### Global install

```bash
npm i -g onus-cli@latest
```

### Run without installing

```bash
npx onus --help
```

### From a local checkout (workspace build)

```bash
pnpm install
pnpm openapi:export   # regenerate openapi.json from src/lib/public-api/openapi.ts
pnpm cli:generate     # regenerate the typesafe SDK into packages/cli/src/generated
pnpm cli:build        # bundle packages/cli/dist/bin.js
node packages/cli/dist/bin.js --help
```

---

## Quick start

```bash
# Authenticate interactively with browser OAuth
onus auth login

# Or pass an access token directly (headless/agent mode)
onus auth login --token <your-onus-token>

# Under WSL2 mirror networking, bind the OAuth callback server to 0.0.0.0
# (or set the ONUS_OAUTH_HOST env var instead)
onus auth login --loopback-host 0.0.0.0

# Show the current user
onus me --json

# List issues
onus issue list --status in_progress --json

# List teams
onus team list
```

---

## Configuration

Settings are resolved with the following precedence **(highest wins)**:

1. CLI flags: `-t, --token <token>`, `-u, --api-url <url>`
2. Environment variables (see table below)
3. The persistent config file

### Environment variables

| Variable             | Purpose                | Notes                                                               |
| -------------------- | ---------------------- | ------------------------------------------------------------------- |
| `ONUS_TOKEN`         | Access token / API key | Alias: `ONUS_API_KEY`                                               |
| `ONUS_REFRESH_TOKEN` | OAuth refresh token    | Used for automatic token rotation on expiration                     |
| `ONUS_API_URL`       | API base URL           | Alias: `ONUS_BASE_URL`; default `https://onus.brunyeestudio.com/v1` |
| `ONUS_OUTPUT`        | Force output mode      | `json` enables deterministic JSON output                            |

### Config file

On Unix/macOS, credentials persist to `~/.config/onus/config.json`
(`${XDG_CONFIG_HOME}/onus/config.json` if set). On Windows, they persist to
`%APPDATA%\onus\config.json`. The file is written with `0600`/`0700` permissions.

```json
{
  "token": "<your-onus-token>",
  "apiUrl": "https://onus.brunyeestudio.com/v1"
}
```

> **Note for agents**: prefer passing the token via `ONUS_TOKEN` so you never
> persist credentials on a machine — the environment variable is read at the
> start of each invocation.

---

## Command reference

### `onus auth` — authentication

| Command                                  | Description                                                                                                               |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `onus auth login`                        | Authenticate interactively via browser OAuth PKCE loopback flow                                                           |
| `onus auth login --loopback-host <host>` | Bind the callback server to a custom host (`0.0.0.0` for WSL2 mirror networking); also configurable via `ONUS_OAUTH_HOST` |
| `onus auth login --token <token>`        | Store an access token directly (optionally `--api-url <url>`)                                                             |
| `onus auth login --manual`               | Authenticate with manual out-of-band authorization code prompt (no browser)                                               |
| `onus auth logout`                       | Clear stored credentials                                                                                                  |
| `onus auth status [--json]`              | Show masked token, refresh token status, and active API endpoint                                                          |

### `onus context` — agent environment snapshot

Emit a structured snapshot of the CLI environment: version, API endpoint, auth
state, the authenticated viewer, available resources, capabilities, and quick
examples.

```bash
onus context --json
```

```json
{
  "cliVersion": "1.0.0",
  "apiUrl": "https://onus.brunyeestudio.com/v1",
  "authenticated": true,
  "viewer": { "id": "uuid", "full_name": "Alice" },
  "resources": ["team", "project", "issue", "comment", "label"],
  "capabilities": {
    "jsonOutputSupported": true,
    "envAuthSupported": true,
    "standardExitCodes": {
      "success": 0,
      "generalError": 1,
      "validationError": 2,
      "authError": 3,
      "notFound": 4
    }
  },
  "quickExamples": [
    "onus me --json",
    "onus team list --json",
    "onus project list --json",
    "onus issue list --status in_progress --json",
    "onus issue create --title \"Fix login\" --team <team-id> --json",
    "onus comment list <issue-id> --json"
  ]
}
```

### `onus schema [resource]` — OpenAPI schema introspection

Fetch and print the exact OpenAPI component schemas so you can inspect
payload shapes, required fields, and validation constraints without guessing.

```bash
onus schema issue --json        # schema for a single resource
onus schema                       # full OpenAPI document
onus schema --all                 # full OpenAPI document
```

### `onus me`

Get the authenticated viewer profile.

```bash
onus me [--json]
```

### `onus team`

Read-only team access.

```bash
onus team list [--json]
onus team get <id> [--json]
```

### `onus project`

```bash
onus project list [--json]
onus project get <id> [--json]
onus project create --name <name> --team <teamId> [--status <status>] [--priority <priority>] [--lead <leadId>] [--target-date <date>] [--json]
onus project update <id> [--name <name>] [--status <status>] [--priority <priority>] [--lead <leadId>] [--target-date <date>] [--json]
```

### `onus issue`

Issues are addressed by human reference (`ENG-42`); UUIDs are also accepted.

```bash
onus issue list [--team <id>] [--project <id>] [--assignee <id>] [--parent <id>] [--status <status>] [--q <query>] [--limit <n>] [--cursor <cursor>] [--json]
onus issue get <ref> [--json]
onus issue create --title <title> --team <teamId> [-d <description>] [--status <status>] [--priority <priority>] [--assignee <id>] [--project <id>] [--parent <id>] [--label <labelId>] [--json]
onus issue update <ref> [--title <title>] [-d <description>] [--status <status>] [--priority <priority>] [--assignee <id>] [--project <id>] [--parent <id>] [--add-label <labelId>] [--remove-label <labelId>] [--json]
onus issue delete <ref> [--yes] [--json]
```

`--status` accepts `backlog | todo | in_progress | done | canceled`; `--priority`
accepts `none | urgent | high | medium | low`. `--label` / `--add-label` /
`--remove-label` are repeatable (pass the flag once per label). `update` is
aliased as `edit`.

#### Comment subcommands

```bash
onus issue comment create <ref> --body <body> [--json]
onus issue comment list <ref> [--json]
onus issue comment edit <ref> --comment <commentId> --body <body> [--json]
onus issue comment delete <ref> --comment <commentId> [--json]
```

#### Relation subcommands

```bash
onus issue relation add <ref> --with <ref> --type <type> [--json]
onus issue relation remove <ref> --relation <relationId> [--json]
```

`--type` accepts `blocks | duplicate | related | similar`.

#### Attachment subcommands

```bash
onus issue attachment upload <ref> --file <path> [--name <name>] [--type <type>] [--json]
onus issue attachment list <ref> [--json]
onus issue attachment download <ref> --attachment <attachmentId> [--output <path>] [--json]
onus issue attachment delete <ref> --attachment <attachmentId> [--json]
```

Uploads go through a presigned URL: the CLI requests an upload URL, PUTs the
file bytes to it, and reports the attachment id. Downloads resolve a presigned
read URL and write the file.

#### Subscription subcommands

```bash
onus issue subscribe <ref> [--json]
onus issue unsubscribe <ref> [--json]
```

#### GitHub pull-request link subcommands

```bash
onus issue link-pr <ref> --repo <owner/name> --pr <number> [--json]
onus issue unlink-pr <ref> --pr-link <linkId> [--json]
```

### `onus comment`

```bash
onus comment list <issueId> [--json]
onus comment create <issueId> --body <body> [--json]
```

### `onus label`

```bash
onus label list [--json]
onus label create --name <name> --team <teamId> [--color <color>] [--json]
```

### `onus skill` — agent skill management

The package bundles an `onus-cli` agent skill (a `SKILL.md` teaching AI coding
agents how to drive the Onus CLI). Installation and lifecycle go through the
[`skills`](https://www.npmjs.com/package/skills) npm package, which detects
installed agents (Claude Code, Codex, Cursor, …) and symlinks or copies the
skill into their skill directories.

```bash
onus skill install                       # install into detected agents (project scope)
onus skill install --global              # user-wide (~/<agent>/skills/)
onus skill install --agent claude-code codex
onus skill install --copy                # copy instead of symlink
onus skill update                        # refresh installed copies to the bundled version
onus skill remove                        # uninstall from agents (alias: onus skill rm)
```

---

## Agent usage guidance

The Onus CLI is built to be driven reliably by AI agents.

### 1. Deterministic JSON output

Add the global `-j, --json` flag (available on every command) for pure,
deterministic JSON with no ANSI codes, banners, or table borders:

```bash
onus issue list --json | jq '.[0].title'
```

### 2. Auto-JSON when piping

When stdout is **not a TTY** (piped, redirected, or captured by an agent), the
CLI automatically switches to JSON output. You can also force it with
`ONUS_OUTPUT=json`:

```bash
onus issue list | jq .            # auto-JSON because stdout is piped
ONUS_OUTPUT=json onus issue list  # force JSON mode
```

Diagnostic and informational logs go to **stderr**, so stdout stays clean and
pipeable.

### 3. Introspect before acting

Run `onus context --json` first to learn the environment, then use
`onus schema <resource> --json` to discover exact field names, required
properties, and enum values before constructing a write operation.

### 4. Standardized exit codes

| Exit code | Meaning                                               |
| --------- | ----------------------------------------------------- |
| `0`       | Success                                               |
| `1`       | General / server error                                |
| `2`       | Validation / usage error (e.g. missing required flag) |
| `3`       | Authentication error (`401` / `403`)                  |
| `4`       | Not found (`404`)                                     |

### 5. Headless authentication

Agents can operate without interactive prompts by supplying the token via
`ONUS_TOKEN`:

```bash
export ONUS_TOKEN="<your-onus-token>"
onus me --json
```

---

## How the CLI is generated from OpenAPI

The type-safe SDK boundary of the CLI is produced from the Onus OpenAPI
specification:

```
src/lib/public-api/openapi.ts  (Zod schemas = single source of truth)
        │  pnpm openapi:export
        ▼
openapi.json
        │  pnpm cli:generate  (@openapitools/openapi-generator-cli, typescript-fetch)
        ▼
packages/cli/src/generated/**  (models + runtime + DefaultApi)
        │  wrapped by packages/cli/src/client.ts
        ▼
packages/cli/src/commands/**   (Commander.js CLI surface)
```

- `pnpm openapi:export` statically serializes `/v1/openapi.json` to
  `openapi.json` without needing a running dev server.
- `pnpm cli:generate` runs the generator configured in `openapitools.json`,
  emitting the `typescript-fetch` client into `packages/cli/src/generated`.
- Because it is generated from the canonical spec, the CLI always reflects the
  real API: adding or changing a `/v1` route + its Zod schema, then re-running
  `pnpm openapi:export && pnpm cli:generate`, updates the SDK and CLI surface
  in lockstep.

---

## Development

### Commands

| Command               | Purpose                                            |
| --------------------- | -------------------------------------------------- |
| `pnpm cli:generate`   | Regenerate the SDK from `openapi.json`             |
| `pnpm cli:build`      | Bundle `packages/cli/dist` with `tsup`             |
| `pnpm cli:test`       | Run Vitest unit tests for the CLI                  |
| `pnpm cli:typecheck`  | Run `tsc --noEmit` for the CLI                     |
| `pnpm openapi:export` | Regenerate `openapi.json` from the API definitions |

Run from the repository root. Each script delegates to the `packages/cli`
workspace package via `pnpm --filter onus`.

---

## Automated versioning & publishing

The CLI package is released automatically on push to `main` by a GitHub Actions
workflow (`.github/workflows/release.yml`) running `semantic-release`, driven by
**Conventional Commits** (enforced by the repo's `commitlint` config):

| Commit type                           | Version bump    |
| ------------------------------------- | --------------- |
| `feat:`                               | Minor (`1.1.0`) |
| `fix:`                                | Patch (`1.0.1`) |
| `feat!:` / `BREAKING CHANGE:`         | Major (`2.0.0`) |
| `chore:`, `docs:`, `refactor:`, `ci:` | No release      |

The release pipeline:

1. Checks out with full git history (`fetch-depth: 0`).
2. Exports the OpenAPI spec and regenerates the SDK.
3. Runs tests, typechecks, and builds the CLI.
4. Runs `semantic-release`, which:
   - Bumps `packages/cli/package.json` and appends to `packages/cli/CHANGELOG.md`.
   - Creates a git tag and a GitHub Release with auto-generated notes.
   - Publishes to npm (`@semantic-release/npm`); configure an `NPM_TOKEN`
     repository secret to enable publishing. The GitHub release assets are the
     built `dist/**` bundles.

`packages/cli/CHANGELOG.md` is **auto-generated** by `semantic-release` on each
release and is excluded from formatting and checks — do not edit it by hand
(manual edits are overwritten on the next release).

---

## License

[MIT](./LICENSE) · © Brunyee Studio
