@oxy-hq/cli 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,163 +1,357 @@
1
- # `@oxy-hq/cli` — `oxyc`
1
+ # `@oxy-hq/cli`
2
2
 
3
- The Oxy CLI. Two halves under one command:
3
+ `oxyc` — a `gh api`-shaped client for the Oxy HTTP API, plus the tooling that
4
+ manages customer workspace repos.
4
5
 
5
- - **Talk to the API** — a `gh api`-shaped client, with discovery, so a caller
6
- with a token and nothing else can find and call any endpoint.
7
- - **Work on a customer account** — which customer, where their repo is, keeping
8
- it in line with the template, and scoping a Claude session to them.
6
+ - **API client** — `api`, `routes`, `schema`, `openapi`, `login`, `whoami`, `assume`
7
+ - **Customer workspaces** — `list`, `new`, `import`, `doctor`, `update`, `adopt`, `launch`
8
+ - **Custom apps** — `publish`, `init-ci`, `proxy`
9
+ - **Development** — `validate`, `mcp`, `guide`, `skills`
9
10
 
10
- They are one tool because they share one answer to "who is this about": stand
11
- in a customer's repo and `oxyc api {org}/workspaces` needs no ids typed.
12
-
13
- > **Not on npm yet.** `@oxy-hq/cli` is deliberately absent from the publish
14
- > workflow until npm Trusted Publishing is configured for it, so `npx
15
- > @oxy-hq/cli` and `pnpm add -g @oxy-hq/cli` 404 today. Install from a
16
- > checkout:
11
+ ## Install
17
12
 
18
13
  ```bash
19
- cd sdk/cli && pnpm install && pnpm build
20
- node dist/main.mjs --help # or `pnpm link --global`, then: oxyc
14
+ npm install -g @oxy-hq/cli # then: oxyc
15
+ npx @oxy-hq/cli routes # zero-install; works for everything but `skills install`
21
16
  ```
22
17
 
23
- Once it is published, the two lines above become:
18
+ From a checkout:
24
19
 
25
20
  ```bash
26
- npx @oxy-hq/cli --help # no install
27
- pnpm add -g @oxy-hq/cli # then: oxyc
21
+ cd sdk/cli && pnpm install && pnpm build
22
+ node dist/main.mjs --help
23
+ pnpm link --global # or, for `oxyc` on PATH
28
24
  ```
29
25
 
30
- ## Talking to the API
26
+ Requires `node >= 20`. `gh` (authenticated) is required for the customer
27
+ commands, `jq` for `--jq`, `git` for the repo commands. Each is reported by
28
+ name with its install line the first time a command needs it.
29
+
30
+ ## Quick start
31
31
 
32
32
  ```bash
33
- oxyc login --env dev # browser flow; shares its token with `oxy login`
34
- oxyc whoami # who the token is, checked against the server
33
+ oxyc login --env dev # browser flow; stores a token
34
+ oxyc whoami --env dev # verify it against the deployment
35
35
 
36
- oxyc routes threads # what exists, and what each endpoint does
37
- oxyc schema {workspace}/threads -X POST # the body it expects
38
- oxyc api {workspace}/threads --jq '.threads[].title'
36
+ oxyc routes threads # find the endpoint
37
+ oxyc schema {workspace}/threads -X POST # find the body it takes
38
+ oxyc api {workspace}/threads --md # call it
39
39
  ```
40
40
 
41
- ## Development
41
+ ## Global flags
42
42
 
43
- ```bash
44
- oxyc validate # check the workspace's YAML against the schemas
45
- oxyc proxy --env dev # local app dev against cloud data
46
- oxyc mcp # serve the API as MCP tools, for an agent runtime
47
- oxyc guide # a page to paste into AGENTS.md / CLAUDE.md
48
- ```
49
-
50
- **`oxyc validate`** checks `config.yml`, `.automation.yml`, `.agentic.yml`,
51
- `.app.yml` and `.agent.test.yml` against `json-schemas/*.json` — which are
52
- generated from the Rust config types, so this is a second *reader* of one
53
- definition rather than a second definition. It does not replace `oxy validate`:
54
- that one loads the workspace and can resolve `databases:` and `llm.ref`. Where
55
- they disagree, it wins. No network, no token — the one command that works
56
- entirely offline.
57
-
58
- **`oxyc proxy`** forwards a dev server's Oxy calls to a cloud target with your
59
- login token attached. Side-effecting calls are **held** and tracking events
60
- **dropped** by default (`--allow-writes` / `--allow-events`); a production
61
- target is refused without `--yes`.
62
-
63
- **`oxyc mcp`** serves the API as four MCP tools — `oxy_routes`, `oxy_schema`,
64
- `oxy_request`, `oxy_whoami` — over stdio. Four rather than one per endpoint
65
- because an agent runtime ships every tool's schema on every turn, and ~670 of
66
- them is tens of KB per request; discovery stays a question the agent asks.
43
+ Accepted by every command except `validate`, `guide`, `exit-codes`, `skills`
44
+ and `cache`.
67
45
 
68
- ```bash
69
- claude mcp add oxyc -- npx -y @oxy-hq/cli mcp --env production
46
+ | Flag | Default | Meaning |
47
+ | --- | --- | --- |
48
+ | `--env <name\|url>` | `production` | deployment to target |
49
+ | `--target <url>` | — | explicit base URL; overrides `--env` |
50
+ | `--token-env <VAR>` | `OXY_TOKEN` | env var holding the bearer token |
51
+ | `--api-key-env <VAR>` | `OXY_API_KEY` | env var holding the key for `/external/api` |
52
+ | `--org <slug>` | — | value for the `{org}` placeholder |
53
+ | `--workspace <id>` | — | value for the `{workspace}` placeholder |
54
+ | `--project <id>` | — | value for the `{project}` placeholder |
55
+ | `--customer <name>` | — | act as though run inside this customer's repo |
56
+ | `--quiet` | — | suppress progress messages on stderr |
57
+
58
+ ### Environments
59
+
60
+ `--env` takes a name or any URL. Anything not in this table is used as a URL.
61
+
62
+ | `--env` | URL |
63
+ | --- | --- |
64
+ | `local` | `http://localhost:5173` |
65
+ | `dev`, `development` | `https://aip.dev.oxy.tech` |
66
+ | `staging` | `https://aip.staging.oxy.tech` |
67
+ | `production`, `prod` (default) | `https://app.oxygen-hq.com` |
68
+
69
+ Credentials are stored **per deployment**, and the default is production. A
70
+ 401 against dev usually means you are logged into prod — pass `--env` to
71
+ `login` and to the call.
72
+
73
+ ## `oxyc api`
74
+
75
+ ```
76
+ oxyc api <path> [flags]
70
77
  ```
71
78
 
72
- `oxyc api` follows `gh api` down to the flag letters:
79
+ `<path>` is relative to `/api`; a leading `/` or `api/` is accepted.
73
80
 
74
- | | |
81
+ | Flag | Meaning |
75
82
  | --- | --- |
76
- | `-X, --method` | HTTP method. Defaults to GET, or POST with a body. |
77
- | `-f, --raw-field k=v` | a **string** parameter |
78
- | `-F, --field k=v` | a **typed** parameter — `true` / `3` / `["a"]` / `@file` / `@-` |
79
- | `-F 'ids[]=a' -F 'ids[]=b'` | repeats accumulate into an array |
80
- | `--input @body.json` | raw body from a file, or `-` for stdin |
81
- | `-q, --jq <expr>` | filter through `jq` |
82
- | `--md` | render results as a markdown table — handles arrays of objects, `{columns, rows}`, and the header-row-first arrays `/sql/query` returns |
83
- | `--paginate` / `--slurp` | walk every page; `--slurp` keeps them separate |
84
- | `--cache 5m` | reuse a recent successful GET |
85
- | `-i` / `--silent` / `--verbose` | headers / nothing / log the request |
86
-
87
- On GET, HEAD and DELETE, fields become query parameters instead of a body.
83
+ | `-X, --method <verb>` | HTTP method. Default GET, or POST when a body is present. |
84
+ | `-f, --raw-field <k=v>` | string parameter |
85
+ | `-F, --field <k=v>` | typed parameter — `true`, `3`, `["a"]`, `@file`, `@-` |
86
+ | `-H, --header <name:value>` | extra header (repeatable) |
87
+ | `--input <file\|->` | raw request body from a file, or `-` for stdin |
88
+ | `-q, --jq <expr>` | filter the response through `jq` |
89
+ | `--md` | render the result as a markdown table |
90
+ | `--paginate` | follow every page and return one document |
91
+ | `--paginate-key <field>` | the field holding the rows, when the guess is wrong |
92
+ | `--max-pages <n>` | stop after `n` pages (default 100) |
93
+ | `--slurp` | with `--paginate`, emit an array of pages instead of merging |
94
+ | `--cache <duration>` | reuse a recent successful GET (`30s`, `5m`, `2h`) |
95
+ | `-i, --include` | print the status line and response headers |
96
+ | `--silent` | make the request, print nothing |
97
+ | `--verbose` | log the request before making it |
98
+ | `--timeout <duration>` | request timeout (default `2m`) |
99
+
100
+ **Bodies.** `-F 'ids[]=a' -F 'ids[]=b'` accumulates into an array. On GET, HEAD
101
+ and DELETE, fields become query parameters instead of a body.
88
102
 
89
103
  **Placeholders.** `{org}`, `{workspace}`, `{project}`, `{customer}` and `{me}`
90
- are filled from context — the customer repo you are standing in, a pasted
91
- `--env` URL, or `--org` / `--workspace` / `--project`. An unresolved
92
- placeholder is an error naming the flag that would fill it, never a literal
93
- sent to the server.
104
+ are resolved from the customer repo you are standing in, from an org URL passed
105
+ to `--env`, or from `--org` / `--workspace` / `--project`. An unresolvable
106
+ placeholder is an error naming the flag that fills it — never a literal sent to
107
+ the server.
94
108
 
95
- **Surfaces.** `/api/**` takes the bearer from `oxyc login`; `/external/api/**`
96
- takes `X-API-Key` from `$OXY_API_KEY`. The path picks, so you never have to.
109
+ **Surfaces.** The path selects the credential: `/api/**` sends the bearer from
110
+ `oxyc login`; `/external/api/**` sends `X-API-Key` from `$OXY_API_KEY`.
97
111
 
98
- **Exit codes** are the point of the tool for a script or an agent — `oxyc
99
- exit-codes` prints them. `4` is "log in again", `5` is "no such thing", `7` is
100
- "retryable", `2` is "you called it wrong".
112
+ **`--paginate` is a heuristic.** Oxy sends `Link: rel="next"` on some endpoints
113
+ and nothing on others; where there is no header, `oxyc` reads
114
+ `pagination.has_next`, then `has_more`, then `page < total_pages`. Nothing
115
+ recognised means one page, and it warns when the server said nothing at all.
101
116
 
102
- ## Working on a customer account
117
+ ```bash
118
+ oxyc api {org}/workspaces --md
119
+ oxyc api {workspace}/threads -q '.threads[].title'
120
+ oxyc api {workspace}/sql/query -X POST -f 'sql=select 1' --md
121
+ oxyc api /admin/users --paginate --md
122
+ ```
123
+
124
+ ## Discovery
103
125
 
104
126
  ```bash
105
- oxyc list # who the customers are
106
- oxyc <customer> # a Claude session scoped to them
107
- oxyc <customer> --here # …while you work in one of OUR repos
108
- oxyc doctor <customer> # what the tool knows, changing nothing
109
- oxyc update <customer> # drift from the workspace template
110
- oxyc adopt <customer> # give an IMPORTED repo the managed files
111
- oxyc activity <customer> # merged PRs, theirs and ours
127
+ oxyc routes [filter] [--json] [--all] [--refresh] # endpoints this deployment mounts
128
+ oxyc schema <path> [-X <verb>] # request/response shape for one
129
+ oxyc openapi # the whole OpenAPI document
112
130
  ```
113
131
 
114
- ## Acting as an org
132
+ `routes` is served by `GET /api/_catalog`, so it describes the deployment you
133
+ are talking to rather than a baked table. It is cached per host; `--refresh`
134
+ re-asks. `--all` includes ide-only and worker-only mounts. `--json` adds each
135
+ route's surface and fleet role.
136
+
137
+ ## Authentication
115
138
 
116
139
  ```bash
117
- oxyc assume start --org acme -r "triage #123" # 60 min, not renewable
118
- oxyc assume status # what is live, minutes left
119
- oxyc assume end # or --all
120
- oxyc login --login-env dev,staging # log into several at once
140
+ oxyc login [--env <e>] [--login-env <e...>] [--assume <org> -r <why>]
141
+ oxyc whoami [--json]
142
+ oxyc token # print the bearer, for a raw curl
143
+ oxyc logout
121
144
  ```
122
145
 
123
- A staff 403 on a tenant surface usually means **no active session**, not a
124
- mis-modeled role. The session belongs to your account rather than this
125
- terminal, so your browser is in there too — and `end` gets you out of both.
146
+ `--login-env` is repeatable and comma-separated (`--login-env dev,staging`);
147
+ the browser opens once per environment, in sequence.
148
+
149
+ Credentials live in the OS config directory under **`oxy`** — the same file the
150
+ Rust `oxy login` wrote before it was removed, so an existing login still works:
151
+
152
+ - macOS: `~/Library/Application Support/oxy/credentials.json`
153
+ - Linux: `$XDG_CONFIG_HOME/oxy/credentials.json`
154
+
155
+ `OXY_CREDENTIALS_PATH` overrides the path. Caches are separate, under `oxyc`
156
+ (`~/.cache/oxyc`). In CI set `OXY_TOKEN` instead of logging in.
157
+
158
+ ## Acting as an organization
159
+
160
+ ```bash
161
+ oxyc assume start --org <slug|uuid|url> -r "<reason>"
162
+ oxyc assume status [--json]
163
+ oxyc assume end [--org <slug>] [--all]
164
+ ```
165
+
166
+ Sessions last **60 minutes and are not renewable** — re-running `start` returns
167
+ the existing session rather than extending it. `--org` is optional when `--env`
168
+ is itself an org URL. The reason is recorded in the audit log.
169
+
170
+ The session belongs to your account, not this terminal, so your browser shares
171
+ it and `end` ends it in both. A staff 403 on a tenant surface is usually no
172
+ active session rather than a role problem; `assume status` answers "none"
173
+ without failing.
174
+
175
+ ## Customer workspaces
126
176
 
127
177
  A customer **is** a GitHub repo carrying the `oxy-customer` topic. Tagging the
128
- repo is the whole of registration — there is no customer file anywhere, which
129
- is why there is nothing to keep in sync.
178
+ repo is the whole of registration; there is no separate registry file. These
179
+ commands need `gh` authenticated.
180
+
181
+ ```bash
182
+ oxyc list [--json] [--refresh] # the customers
183
+ oxyc path <customer> # where their repo is on this machine
184
+ oxyc new <customer> [--display <name>] # create, tag and scaffold a repo
185
+ oxyc import <org/repo> [--clone] # tag an existing repo
186
+ oxyc remove|rm <customer> [--purge --yes] # untag (never deletes the repo)
187
+ oxyc doctor [<customer>] [--all] # report state, change nothing
188
+ oxyc update <customer> [--apply] [--diff-all] # drift from the workspace template
189
+ oxyc adopt <customer> [--apply] # install managed files an import lacks
190
+ oxyc activity <customer> [--since <date>] [--repo <org/name>] [--write] [--json]
191
+ oxyc launch <customer> [claude-args...] [--here] [--dry-run]
192
+ oxyc repos [--refresh] # where OUR repos are checked out
193
+ ```
194
+
195
+ `update` and `adopt` **report by default and write only with `--apply`**.
196
+ Neither commits, branches or pushes. What they may rewrite is decided by
197
+ `template/.oxyc-managed`: an unclassified file belongs to the customer and is
198
+ never touched.
199
+
200
+ `launch` starts a Claude Code session scoped to one customer; `--here` runs in
201
+ the current directory while granting access to the customer's repo.
202
+
203
+ ## Custom apps
204
+
205
+ ```bash
206
+ oxyc publish [--env <e>] [--dir <path>] [--promote] [--build-only | --prebuilt] [--json]
207
+ oxyc init-ci [--app <org>/<app>] [--environment <name>] [--force]
208
+ oxyc proxy [--port <n>] [--allow-writes] [--allow-events] [--yes]
209
+ ```
210
+
211
+ **`publish`**, from an app directory, runs `oxy-app.json`'s build (default
212
+ `pnpm install`, `pnpm build`, output `out/`), bundles each declared Oxy Function
213
+ with `pnpm exec esbuild` into `functions/<name>.js`, resolves the project from
214
+ the target's public `build-config`, and uploads the bundle — to the **draft**
215
+ channel unless `--promote`. `--env` defaults to **production**; name it.
216
+
217
+ - **Identity**: `--org` / `--app`, then `OXY_ORG` / `OXY_APP`, then the manifest's
218
+ `orgSlug` / `slug`, then an `apps/<org>/<app>/` working directory. `--org`
219
+ takes a slug or a UUID. `--project` pins the workspace (and implies its org).
220
+ - **`--dir`** publishes a pre-built directory instead of building; functions are
221
+ still bundled into it. **`--build-only`** stops after building and bundling —
222
+ no credential, no network unless the org must come from `--project`.
223
+ **`--prebuilt`** (with `--dir`) skips esbuild and refuses if a declared
224
+ function's `functions/<name>.js` is missing. The pair splits CI so the job that
225
+ runs package scripts never holds the credential.
226
+ - **Auth**: the `--token-env` variable (`OXY_TOKEN`), then the login cache — or,
227
+ in a GitHub Actions job with `id-token: write` and neither set, **trusted
228
+ publishing**: the job's OIDC token is exchanged for a credential scoped to this
229
+ one app. That needs the org **slug** and a publisher registered for the
230
+ workflow (see `init-ci`).
231
+ - **Provenance**: the checkout's `origin`, `HEAD` and branch (else `GITHUB_SHA` /
232
+ `GITHUB_REF_NAME`) are recorded; a missing half or a dirty app directory is a
233
+ warning, never a failure. The build id defaults to
234
+ `$GITHUB_SHA-$GITHUB_RUN_ID.$GITHUB_RUN_ATTEMPT`, else random — a reused one is
235
+ a `409`.
236
+ - `.env.local`, then `.env`, are read from the directory and its parents without
237
+ overriding the environment. The server's warnings print on stderr; `--json`
238
+ prints its result on stdout.
239
+
240
+ **`init-ci`** writes `.github/workflows/oxy-publish.yml` at the repo root: a
241
+ `build` job (no id-token) that runs `publish --build-only`, and an
242
+ environment-gated `publish` job whose only work is `publish --prebuilt` with
243
+ OIDC. It prints the `oxyc api …/publishers` call that registers the workflow.
244
+
245
+ ## Development commands
130
246
 
131
- `update` and `adopt` **report by default and write only with `--apply`**, and
132
- neither ever commits, branches or pushes. What each may rewrite is decided by
133
- `template/.oxyc-managed` and nothing else: a file nobody classified is the
134
- customer's and is never touched.
247
+ ```bash
248
+ oxyc validate [-f <file>] [--json]
249
+ oxyc mcp
250
+ oxyc guide
251
+ oxyc skills install | list
252
+ oxyc cache clear
253
+ oxyc exit-codes
254
+ ```
135
255
 
136
- ## Environment
256
+ **`validate`** checks workspace YAML against `json-schemas/*.json`, which are
257
+ generated from the Rust config types. No network, no token.
137
258
 
138
- | | |
259
+ | File | Schema |
139
260
  | --- | --- |
140
- | `OXY_TOKEN` | bearer, overriding the login cache (the CI path) |
141
- | `OXY_API_KEY` | key for the `/external/api` surface |
142
- | `OXYC_ORG` | the GitHub org customer repos live in (default `oxy-hq`) |
143
- | `OXYC_DOSSIER_ROOT` | where customer clones go (default `~/.oxyc/dossiers`) |
144
- | `OXYC_TEMPLATE_DIR` | use a working copy of the template instead of the shipped one |
145
- | `NO_COLOR` | plain output |
261
+ | `config.yml` / `config.yaml` | `config.json` |
262
+ | `*.automation.yml`, `*.procedure.yml`, `*.workflow.yml` | `workflow.json` |
263
+ | `*.agentic.yml` | `agentic.json` |
264
+ | `*.app.yml` | `app.json` |
265
+ | `*.agent.test.yml` | `agent-test.json` |
266
+
267
+ Structural checks only. `oxy validate` additionally resolves `databases:` and
268
+ `llm.ref` against the loaded workspace, and wins where the two disagree.
269
+
270
+ **`proxy`** forwards a local dev server's Oxy calls to a cloud target with your
271
+ login token attached. Defaults: side-effecting calls are **held**, tracking
272
+ events are **dropped**, auth endpoints reach the backend unauthenticated so
273
+ sign-in works, and the cached token never overrides a real browser session. A
274
+ production target (any host under `oxygen-hq.com`) is refused without `--yes`.
275
+ Each Oxy Function call carries a W3C trace — the SDK's, or one minted here — and
276
+ prints `↳ <status> fn <name> request_id=… trace_id=…`.
277
+
278
+ **`mcp`** serves the API over stdio as four tools — `oxy_routes`, `oxy_schema`,
279
+ `oxy_request`, `oxy_whoami` — rather than one per endpoint, so the tool schemas
280
+ cost ~2 KB per turn and reach endpoints added after this package shipped.
146
281
 
147
- ## Two audiences, one tool
282
+ ```bash
283
+ claude mcp add oxyc -- npx -y @oxy-hq/cli mcp --env production
284
+ ```
148
285
 
149
- Attached to a terminal you get colour, aligned tables and a searchable picker.
150
- Piped — or read by an agent — you get markdown tables and no colour, because
151
- that is what an LLM reads most reliably and most cheaply. Raw `api` response
152
- bodies are verbatim in both, so `| jq` always works.
286
+ **`guide`** prints a page to paste into `AGENTS.md` / `CLAUDE.md`.
287
+ **`skills install`** symlinks the six bundled Claude skills into
288
+ `~/.claude/skills`; it refuses to run from an `npx` cache, whose symlinks dangle
289
+ once npm reclaims it.
153
290
 
154
- Everything that is not the answer goes to **stderr**. stdout carries the
155
- response body and nothing else.
291
+ ## Exit codes
156
292
 
157
- ## Requirements
293
+ ```
294
+ 0 success
295
+ 1 failure with nothing more specific to say
296
+ 2 usage error — a bad flag, a missing argument, a malformed value
297
+ 4 not authenticated, or the token was rejected (401/403)
298
+ 5 not found (404), or an unknown customer
299
+ 6 the request was malformed (4xx other than 401/403/404)
300
+ 7 unavailable — 5xx, a timeout, or the network failed. Retryable.
301
+ 8 refused — the operation would have destroyed or overwritten something
302
+ ```
303
+
304
+ `4` almost always means the wrong `--env`. `7` is worth retrying; `6` never is.
305
+
306
+ ## Environment variables
307
+
308
+ | Variable | Default | Meaning |
309
+ | --- | --- | --- |
310
+ | `OXY_TOKEN` | — | bearer token, overriding the login cache (the CI path) |
311
+ | `OXY_API_KEY` | — | key for the `/external/api` surface |
312
+ | `OXY_CREDENTIALS_PATH` | OS config dir | the shared `credentials.json` |
313
+ | `OXYC_ORG` | `oxy-hq` | GitHub org the customer repos live in |
314
+ | `OXYC_CUSTOMER_TOPIC` | `oxy-customer` | topic that registers a customer repo |
315
+ | `OXYC_DOSSIER_ROOT` | `~/.oxyc/dossiers` | where customer clones go |
316
+ | `OXYC_REPO_ROOTS` | scanned | where to look for our own checkouts |
317
+ | `OXYC_TEMPLATE_DIR` | shipped `template/` | use a working copy instead |
318
+ | `OXYC_SCHEMAS_DIR` | shipped `json-schemas/` | use a working copy instead |
319
+ | `OXYC_SKILLS_DIR` | shipped `skills/` | use a working copy instead |
320
+ | `OXYC_SKILLS_TARGET` | `~/.claude/skills` | where `skills install` links |
321
+ | `OXYC_CACHE_DIR` | `<cache>/oxyc` | cache root |
322
+ | `OXYC_CACHE_TTL` | `3600` | seconds the customer listing is cached |
323
+ | `OXYC_LIST_LIMIT` | `1000` | max repos listed from GitHub |
324
+ | `OXYC_SEARCH_LIMIT` | `1000` | max pull requests searched by `activity` |
325
+ | `OXYC_DIFF_LINES` | — | cap on diff lines printed by `update` |
326
+ | `OXYC_DEBUG` | — | `1` keeps the stack on an unexpected throw |
327
+ | `OXYC_QUIET` | — | `1` is `--quiet` |
328
+ | `OXYC_DRY_RUN` | — | `1` makes `launch` print the command instead of running it |
329
+ | `NO_COLOR` / `FORCE_COLOR` | — | force plain / coloured output |
330
+
331
+ ## Output contract
332
+
333
+ **stdout carries the response body and nothing else.** Progress, warnings,
334
+ errors and hints go to stderr, and a failure never exits `0`.
335
+
336
+ Attached to a TTY you get colour, aligned tables and a searchable picker.
337
+ Piped, you get markdown tables and no colour. Raw `api` response bodies are
338
+ byte-identical in both, so `| jq` always works.
339
+
340
+ ## Development
341
+
342
+ ```bash
343
+ pnpm install
344
+ pnpm build # tsdown; `prebuild` runs codegen
345
+ pnpm test # vitest; `pretest` builds
346
+ pnpm typecheck
347
+ pnpm lint # biome
348
+ pnpm build:binary # standalone executables (needs bun)
349
+ ```
158
350
 
159
- `node >= 20`. `gh` (authenticated) for anything touching the customer registry;
160
- `jq` for `--jq`; `git` for the repo commands. Each is reported by name, with
161
- its install line, the first time something needs it.
351
+ The package ships four directories — `dist`, `json-schemas`, `skills`,
352
+ `template` — and the last three are resolved at runtime relative to
353
+ `package.json`. `scripts/ci/verify-cli-package.mjs` checks the packed tarball
354
+ on every PR.
162
355
 
163
- Maintainer's notes: [`internal-docs/oxy-api-cli.md`](../../internal-docs/oxy-api-cli.md).
356
+ Maintainer's notes — release process, catalog generation, OpenAPI curation, CI
357
+ gates: [`internal-docs/oxy-api-cli.md`](../../internal-docs/oxy-api-cli.md).