@oxy-hq/cli 0.8.0 → 0.10.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 +312 -31
- package/dist/main.mjs +11236 -2822
- package/dist/main.mjs.map +1 -1
- package/dist/{mcp-xxWL3_dz.mjs → mcp-B2KBWNOU.mjs} +86 -3
- package/dist/mcp-B2KBWNOU.mjs.map +1 -0
- package/package.json +6 -5
- package/skills/oxy-agents/SKILL.md +318 -0
- package/skills/oxy-agents/references/quick-reference.md +222 -0
- package/skills/oxy-agents/templates/agentic-template.yml +141 -0
- package/skills/oxy-airway-pipelines/SKILL.md +223 -316
- package/skills/oxy-automations/SKILL.md +61 -15
- package/skills/oxy-cli/SKILL.md +40 -6
- package/skills/oxy-custom-apps/SKILL.md +380 -294
- package/skills/oxy-custom-apps/references/minimal-starter.md +71 -0
- package/skills/oxy-custom-apps/references/performance-and-usage.md +151 -0
- package/skills/oxy-custom-apps/references/reading-data.md +106 -0
- package/skills/oxy-custom-apps/references/sandbox-agent-loop.md +307 -0
- package/skills/oxy-custom-apps/references/verifying-in-the-browser.md +65 -0
- package/skills/oxy-data-apps/SKILL.md +362 -0
- package/skills/oxy-data-apps/references/examples.md +263 -0
- package/skills/oxy-data-apps/references/quick-reference.md +171 -0
- package/skills/oxy-data-apps/templates/dashboard-with-controls.app.yml +128 -0
- package/skills/oxy-data-apps/templates/simple-dashboard.app.yml +102 -0
- package/skills/oxy-data-architecture/SKILL.md +137 -152
- package/skills/oxy-evals/SKILL.md +362 -0
- package/skills/oxy-evals/references/diagnosis.md +243 -0
- package/skills/oxy-evals/references/results-json.md +145 -0
- package/skills/oxy-evals/references/writing-expected.md +112 -0
- package/skills/oxy-semantic-modeling/SKILL.md +239 -59
- package/skills/oxy-semantic-modeling/references/warehouse-casts.md +57 -0
- package/skills/oxy-semantic-modeling/templates/topic-template.yml +38 -0
- package/skills/oxy-semantic-modeling/templates/view-template.yml +119 -0
- package/template/.claude/hooks/oxyc-cloud-session.sh +66 -0
- package/template/.claude/settings.json +16 -0
- package/template/.github/workflows/publish.yaml +15 -13
- package/template/.nvmrc +1 -1
- package/template/.oxyc-managed +18 -5
- package/template/README.md +21 -10
- package/template/package.json +3 -2
- package/template/pnpm-workspace.yaml +28 -17
- package/dist/mcp-xxWL3_dz.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ manages customer workspace repos.
|
|
|
7
7
|
- **Customer workspaces** — `list`, `new`, `import`, `doctor`, `update`, `adopt`, `launch`
|
|
8
8
|
- **Custom apps** — `publish`, `init-ci`, `proxy`, `apps`
|
|
9
9
|
- **Checks** — `checks run`
|
|
10
|
-
- **Sandboxes** — `env`, `fn call`, `invocations`, `logs`
|
|
10
|
+
- **Sandboxes** — `env`, `fn dev`, `fn call`, `invocations`, `logs`
|
|
11
11
|
- **Workspace previews** — `preview`
|
|
12
12
|
- **Development** — `validate`, `mcp`, `guide`, `skills`
|
|
13
13
|
|
|
@@ -149,7 +149,7 @@ route's surface and fleet role.
|
|
|
149
149
|
## Authentication
|
|
150
150
|
|
|
151
151
|
```bash
|
|
152
|
-
oxyc login [--env <e>] [--login-env <e...>] [--assume <org> -r <why>]
|
|
152
|
+
oxyc login [--env <e>] [--login-env <e...>] [--assume <org> -r <why>] [--no-browser [--approver <email>]]
|
|
153
153
|
oxyc whoami [--json] # who the credential is, and what it can reach
|
|
154
154
|
oxyc token # print the bearer, for a raw curl
|
|
155
155
|
oxyc login-link [--next <path>] [--json] [--open] # a one-time URL that signs a browser in
|
|
@@ -158,9 +158,24 @@ oxyc tokens list [--json] # your personal access tokens
|
|
|
158
158
|
oxyc tokens revoke <id>
|
|
159
159
|
oxyc tokens revoke --current # end the token in OXY_TOKEN
|
|
160
160
|
oxyc tokens create # opens Account → Personal access tokens
|
|
161
|
-
oxyc tokens create --sandbox-agent --app <org>/<app> [--app …] [--hours 8] [--name …]
|
|
161
|
+
oxyc tokens create --sandbox-agent --app <org>/<app> [--app …] [--hours 8] [--name …] [--no-browser [--approver <email>]]
|
|
162
|
+
oxyc tokens create --agent [--standing] [--hours 8] [--name …] [--no-browser [--approver <email>]]
|
|
162
163
|
```
|
|
163
164
|
|
|
165
|
+
**An agent runs on its own token, never on the person's `oxyc login`.** Which
|
|
166
|
+
one depends on the task:
|
|
167
|
+
|
|
168
|
+
| The agent is… | It mints | Which reaches |
|
|
169
|
+
| --- | --- | --- |
|
|
170
|
+
| building a custom app in a sandbox | `tokens create --sandbox-agent --app …` (`oxy_sbx_…`) | the sandboxes of the apps it names, and nothing else |
|
|
171
|
+
| doing anything else: reading data with `oxyc api`, reproducing a bug against live data | `tokens create --agent` (`oxy_pat_…`) | everything its approver can, for hours |
|
|
172
|
+
|
|
173
|
+
Both are approved once in the browser by the person the agent works for, both
|
|
174
|
+
are printed once as `export OXY_TOKEN=…` and never stored, and both end with
|
|
175
|
+
`tokens revoke --current`. After a mint, pass `--token-env OXY_TOKEN` to every
|
|
176
|
+
command: a named variable is the only source, so a command that lost the
|
|
177
|
+
variable exits `4` instead of quietly running on the person's cached login.
|
|
178
|
+
|
|
164
179
|
**Every command resolves its credential the same way, first match wins:**
|
|
165
180
|
|
|
166
181
|
1. **`OXY_TOKEN`** (or the variable `--token-env` names). It holds any
|
|
@@ -168,7 +183,8 @@ oxyc tokens create --sandbox-agent --app <org>/<app> [--app …] [--hours 8] [--
|
|
|
168
183
|
token (`oxy_pat_…`), a service account token (`oxy_sat_…`), a CI token
|
|
169
184
|
(`oxy_ci_…`) or a sandbox agent token (`oxy_sbx_…`) — a legacy API key
|
|
170
185
|
(`oxy_…`), a publish token, or a session token. A blank value counts as
|
|
171
|
-
unset.
|
|
186
|
+
unset. An agent token is a personal access token by its prefix: only its
|
|
187
|
+
own description tells it from a login (`oxyc whoami`).
|
|
172
188
|
2. **The login cache** — what `oxyc login` stored for this deployment.
|
|
173
189
|
3. **GitHub OIDC**, in a GitHub Actions job granted `id-token: write`: the job's
|
|
174
190
|
OIDC token is exchanged, once per process, for a fifteen-minute token acting
|
|
@@ -196,7 +212,7 @@ Settings → Workspace → Legacy API keys. Use an API token for anything new.
|
|
|
196
212
|
|
|
197
213
|
**`login`** runs a browser loopback flow. Against a current deployment it
|
|
198
214
|
receives a one-time code and exchanges it (PKCE) for a **personal access
|
|
199
|
-
token** named `oxyc on <hostname>`: revocable,
|
|
215
|
+
token** named `oxyc on <hostname>`: revocable, a year, and replacing that
|
|
200
216
|
machine's previous one. No credential travels in a URL. Against a deployment
|
|
201
217
|
that predates the exchange it stores the session token the page hands back,
|
|
202
218
|
exactly as before, and says so — nothing needs upgrading first, in either
|
|
@@ -212,7 +228,10 @@ rather than reading as fine from the cache. For a token it also prints what the
|
|
|
212
228
|
token is and what it can reach — `all access`, or one line per grant with the
|
|
213
229
|
role it is capped at, and any organization whose policy blocks it. For a legacy
|
|
214
230
|
API key it prints `Legacy API key`, and that it reaches everything its owner
|
|
215
|
-
can.
|
|
231
|
+
can. For an agent token it says `agent token`, who approved it, whether it
|
|
232
|
+
carries their staff or partner access, and when it expires. When a token in
|
|
233
|
+
`OXY_TOKEN` is refused (exit `4`), the advice is never to log in — a login
|
|
234
|
+
cannot replace what is in the variable. An agent stops and reports.
|
|
216
235
|
|
|
217
236
|
**`token`** prints the bearer on stdout and nothing else. Inside a GitHub
|
|
218
237
|
Actions job with nothing stored it prints the token the job's OIDC identity
|
|
@@ -245,6 +264,37 @@ writes the credentials file, and revokes no other token. `--app` is
|
|
|
245
264
|
`<org>/<app>` and repeats; arguments are checked before the browser opens
|
|
246
265
|
(exit `2`). Only staff who can open the app's sandboxes can approve.
|
|
247
266
|
|
|
267
|
+
**`--no-browser`** is the same approval for a machine no browser can reach
|
|
268
|
+
(a cloud container, a CI runner, a remote dev box), where the loopback's
|
|
269
|
+
`127.0.0.1` port is not the approver's. Instead of opening a page, `oxyc`
|
|
270
|
+
asks for a device code (`POST /api/auth/cli/device`) and prints, on stderr,
|
|
271
|
+
a URL (`<deployment>/cli-auth/device?code=…`) and an eight-letter code. The
|
|
272
|
+
operator opens the URL on any device where they are signed in, types the
|
|
273
|
+
code from the terminal (the page never shows it), and approves; `oxyc` polls
|
|
274
|
+
(`POST /api/auth/cli/device/poll`, with the PKCE verifier) and prints the
|
|
275
|
+
token exactly as above. A request lives ten minutes. A decline, or a refusal
|
|
276
|
+
on the page, stops the command with exit `4` (`access_denied`) and the
|
|
277
|
+
reason; nobody deciding in time is exit `7`. It works the same for
|
|
278
|
+
`tokens create --agent` and for `oxyc login`, with one difference: a token
|
|
279
|
+
approved by device code **never carries staff or partner access**, since
|
|
280
|
+
whoever started the request collects it and the approver cannot see who that
|
|
281
|
+
is. `--standing --no-browser` is a usage error (exit `2`), and a login made
|
|
282
|
+
this way reaches only what its approver's organization memberships do.
|
|
283
|
+
|
|
284
|
+
**`--approver <email>`** (env `OXY_APPROVER`, valid only with `--no-browser`;
|
|
285
|
+
anything else is a usage error, exit `2`) names who must approve, so nobody
|
|
286
|
+
has to hand them a URL: the platform tells that person itself, by push,
|
|
287
|
+
email and Slack DM where their organization connected it, and in their
|
|
288
|
+
Oxygen inbox. They type the code from your output on the approval page. The
|
|
289
|
+
notice never holds the code and links to `/cli-auth/device` without it, and
|
|
290
|
+
the request is then theirs alone: anyone else who finds the page is told
|
|
291
|
+
there is no such request. `oxyc` prints `Approval request sent to <email>`
|
|
292
|
+
beside the code and the URL; it prints that whether or not the deployment
|
|
293
|
+
knew the address, since the deployment never says. An address nobody holds
|
|
294
|
+
leaves a request nobody can approve: it lapses after ten minutes, so a typo
|
|
295
|
+
means running the command again with the right one. An agent still never
|
|
296
|
+
opens or approves the page.
|
|
297
|
+
|
|
248
298
|
What comes back is checked before anything is printed: it must be an
|
|
249
299
|
`oxy_sbx_`, described as `sandbox_agent` and not all-access, for exactly the
|
|
250
300
|
apps named, expiring no later than the hours asked for. Anything the check
|
|
@@ -257,14 +307,17 @@ what was minted, and exits `8`.
|
|
|
257
307
|
With that token in `OXY_TOKEN`, `oxyc` behaves differently in five ways:
|
|
258
308
|
|
|
259
309
|
- **An app resolves against the token's own list** (`GET /api/auth/token`),
|
|
260
|
-
never
|
|
310
|
+
never the app listing (`GET /api/customer-apps`), which it may not call. An
|
|
311
|
+
app it was not minted for is exit `5`.
|
|
261
312
|
- **Read-back uses `/api/customer-apps/{id}/…`** — `invocations list`,
|
|
262
|
-
`invocations held` and `checks run` —
|
|
313
|
+
`invocations held` and `checks run` — the same routes every other credential
|
|
314
|
+
uses; the token never reaches `/admin`.
|
|
263
315
|
- **Refused before any request, exit `2`:** an `--app-env` that is not
|
|
264
316
|
`dev-<handle>`; `publish` without `--app-env`, or with `--promote`;
|
|
265
|
-
`fn call`, `logs`, `invocations list` and `checks run` without
|
|
266
|
-
`env show` of production or staging; `
|
|
267
|
-
`tokens revoke <id>`; and `oxyc api` against
|
|
317
|
+
`fn call`, `fn run`, `logs`, `invocations list` and `checks run` without
|
|
318
|
+
`--app-env`; `env show` of production or staging; `env schedules`;
|
|
319
|
+
`oxyc apps`, `tokens list` and `tokens revoke <id>`; and `oxyc api` against
|
|
320
|
+
any path.
|
|
268
321
|
- **`whoami`** prints the token's description — `kind`, `expires_at`, the
|
|
269
322
|
`apps` it reaches and its `minter` — and `--json` is that document as the
|
|
270
323
|
server sent it.
|
|
@@ -276,6 +329,35 @@ A refusal from the server carries a hint an agent can act on — never "try
|
|
|
276
329
|
`oxyc login` again". Exit `4` under this token means it expired, was revoked,
|
|
277
330
|
or its minter lost access: stop and report.
|
|
278
331
|
|
|
332
|
+
**`tokens create --agent`** mints an **agent token** for everything that is not
|
|
333
|
+
a sandbox: a personal access token (`oxy_pat_…`) that reaches what its approver
|
|
334
|
+
reaches through their organization memberships, for 8 hours by default
|
|
335
|
+
(`--hours`, 1 to 168). It cannot be extended, edited or regenerated, and it
|
|
336
|
+
cannot create, extend or revoke tokens; it can open a browser session as itself
|
|
337
|
+
(`login-link`).
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
eval "$(oxyc tokens create --agent --env dev)"
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
The same browser loopback again, with the same guarantees: stdout is the single
|
|
344
|
+
line `export OXY_TOKEN=oxy_pat_…`, printed once, everything else is on stderr,
|
|
345
|
+
it holds no credential while it runs, never writes the credentials file and
|
|
346
|
+
revokes no other token. Arguments are checked before the browser opens (exit
|
|
347
|
+
`2`); `--agent` and `--sandbox-agent` together are a usage error.
|
|
348
|
+
|
|
349
|
+
`--standing` asks for the approver's staff or partner access as well. The
|
|
350
|
+
approver decides on the page, where it starts **off**: a token that comes back
|
|
351
|
+
without it is accepted, and stderr says so in one line. Without `--standing`, a
|
|
352
|
+
token that carries either is refused.
|
|
353
|
+
|
|
354
|
+
What comes back is checked before anything is printed: it must be a whole
|
|
355
|
+
`oxy_pat_`; it must describe itself (`GET /api/auth/token`) as `personal` with
|
|
356
|
+
`source` `oxyc_agent`; it must expire no later than the hours asked for; and it
|
|
357
|
+
must carry no standing that was not asked for. A deployment that predates agent
|
|
358
|
+
tokens would otherwise hand back an ordinary ninety-day login. On any mismatch
|
|
359
|
+
the command prints nothing, revokes what was minted, and exits `8`.
|
|
360
|
+
|
|
279
361
|
Credentials live in the OS config directory under **`oxy`** — the same file the
|
|
280
362
|
Rust `oxy login` wrote before it was removed, so an existing login still works:
|
|
281
363
|
|
|
@@ -493,6 +575,8 @@ the current directory while granting access to the customer's repo.
|
|
|
493
575
|
|
|
494
576
|
```bash
|
|
495
577
|
oxyc publish [--env <e>] [--dir <path>] [--promote] [--build-only | --prebuilt] [--json] [--allow-function-lint] [--app-env <dev-handle>]
|
|
578
|
+
oxyc promote <app> [--from staging|dev-<handle>] [--build <id>] [--remove-schedule <fn>]… [--dry-run] [--yes] [--json]
|
|
579
|
+
oxyc rollback <app> --app-env staging|production [--to <build>] [--remove-schedule <fn>]… [--dry-run] [--yes] [--json]
|
|
496
580
|
oxyc init-ci [--app <org>/<app>] [--environment <name>] [--promote] [--force] [--no-register] [--setup-action]
|
|
497
581
|
oxyc proxy [--port <n>] [--allow-writes] [--allow-events] [--yes]
|
|
498
582
|
```
|
|
@@ -501,11 +585,32 @@ oxyc proxy [--port <n>] [--allow-writes] [--allow-events] [--yes]
|
|
|
501
585
|
`pnpm install`, `pnpm build`, output `out/`), bundles each declared Oxy Function
|
|
502
586
|
with `pnpm exec esbuild` into `functions/<name>.js`, resolves the project from
|
|
503
587
|
the target's public `build-config`, and uploads the bundle — to the **draft**
|
|
504
|
-
channel
|
|
505
|
-
|
|
588
|
+
channel (staging). `--env` defaults to **production**; name it. Ship it with
|
|
589
|
+
`oxyc promote`, below. `--promote` still publishes and promotes in one request,
|
|
590
|
+
through the same server-side path, and the server answers it with a
|
|
591
|
+
deprecation warning: it shows nobody the plan.
|
|
592
|
+
|
|
593
|
+
- **The manifest** is read strictly: `oxy-app.json` is validated against the
|
|
594
|
+
JSON Schema `@oxy-hq/sdk` generates from its `OxyAppManifest` type (the same
|
|
595
|
+
check `oxyc validate` and `@oxy-hq/vite-plugin` make, in the same words). An
|
|
596
|
+
error — a wrong type, a malformed `slug` or function name, a missing
|
|
597
|
+
required field, a notification catalogue the server refuses — stops the
|
|
598
|
+
publish before the build with exit `2`, listing every problem; a field the
|
|
599
|
+
schema does not know is a warning, since the platform ignores what it does
|
|
600
|
+
not read.
|
|
506
601
|
- **Identity**: `--org` / `--app`, then `OXY_ORG` / `OXY_APP`, then the manifest's
|
|
507
602
|
`orgSlug` / `slug`, then an `apps/<org>/<app>/` working directory. `--org`
|
|
508
603
|
takes a slug or a UUID. `--project` pins the workspace (and implies its org).
|
|
604
|
+
- **First publish**: an app the target does not know yet makes the
|
|
605
|
+
build-config lookup 404. With a stored token, `publish` lists the org's
|
|
606
|
+
workspaces: exactly one, and it publishes there, saying so on stderr;
|
|
607
|
+
several, and it exits `5` listing them with their uuids beside the
|
|
608
|
+
`--project <uuid>` to pass. Under trusted publishing (no token) it exits `5`
|
|
609
|
+
with the two-step hint, as before.
|
|
610
|
+
- **Kit versions**: an app whose `@oxy-hq/sdk` or `@oxy-hq/vite-plugin` is more
|
|
611
|
+
than one minor behind the versions this `oxyc` was built beside gets one
|
|
612
|
+
warning per package before the build, naming the `pnpm add`. Read from the
|
|
613
|
+
app's `package.json` (the installed copy when there is one); no network.
|
|
509
614
|
- **`--dir`** publishes a pre-built directory instead of building; functions are
|
|
510
615
|
still bundled into it. **`--build-only`** stops after building and bundling —
|
|
511
616
|
no credential, no network unless the org must come from `--project`.
|
|
@@ -548,7 +653,15 @@ lets it publish.
|
|
|
548
653
|
*The workflow* is `.github/workflows/oxy-publish.yml` at the repo root: a
|
|
549
654
|
`build` job (no id-token) that runs `publish --build-only`, and a `publish` job
|
|
550
655
|
bound to a GitHub `environment:` (`--environment`, default `oxy-publish`) whose
|
|
551
|
-
only work is `publish --prebuilt`.
|
|
656
|
+
only work is `publish --prebuilt`. `build` installs pnpm with
|
|
657
|
+
`pnpm/action-setup@v6` — the pnpm the repo root's package.json pins
|
|
658
|
+
(`packageManager` or `devEngines.packageManager`), with no `version:` of its
|
|
659
|
+
own, since the action refuses a second, different one; pnpm 11 when nothing
|
|
660
|
+
pins it — and both jobs run the Node in `.nvmrc`, else 26 (pnpm 11 needs
|
|
661
|
+
Node 22.13 or later). It warns when nothing pins pnpm, and when
|
|
662
|
+
`pnpm-workspace.yaml` approves build scripts only with `onlyBuiltDependencies`,
|
|
663
|
+
which pnpm 11 ignores (the build's install would fail with
|
|
664
|
+
`ERR_PNPM_IGNORED_BUILDS`; move the entries to `allowBuilds`). The publish job uses no third-party action: each
|
|
552
665
|
step runs `npx @oxy-hq/cli@<this version>` and `oxyc` exchanges the job's OIDC
|
|
553
666
|
token for itself, acting as the service account `--service-account <org>/<name>`
|
|
554
667
|
names — by default `<app's org>/deployer`. **Here, and only here, the account is
|
|
@@ -589,13 +702,42 @@ policies. Registration never fails the command.
|
|
|
589
702
|
**`--app-env <dev-handle>`** moves one sandbox's build pointer instead of the
|
|
590
703
|
draft/live channel — see [Sandboxes](#sandboxes). It needs a staff credential
|
|
591
704
|
(never a publish token or OIDC) and is refused together with `--promote`: a
|
|
592
|
-
sandbox build
|
|
593
|
-
|
|
705
|
+
sandbox build goes to production only through staging — `oxyc promote <app>
|
|
706
|
+
--from dev-<handle>`, then `oxyc promote <app>`.
|
|
707
|
+
|
|
708
|
+
**`promote`** moves a build up one environment: staging's to production (the
|
|
709
|
+
default), or a sandbox's to staging (`--from dev-<handle>`). Nothing else —
|
|
710
|
+
production is never promoted, and moving it backwards is `rollback`. It asks the
|
|
711
|
+
server for the **plan** first and prints it: the build (commit, branch,
|
|
712
|
+
publisher, how long the source has served it), what the target serves now,
|
|
713
|
+
functions and production schedules that change, the migrations production
|
|
714
|
+
applies **before** it moves, and `required` secrets production has no value for.
|
|
715
|
+
Then it asks on a terminal (off one it needs `--yes`, else exit `8`) and acts,
|
|
716
|
+
sending the plan's build and `expect_current` back — `409 target_moved` if the
|
|
717
|
+
target moved in between. `--dry-run` stops at the plan. A plan with blockers is
|
|
718
|
+
refused before any act (exit `6`): `staging_never_served` (production only
|
|
719
|
+
serves a build staging has served), `validation_not_passed`,
|
|
720
|
+
`required_secrets_unset`, and `schedule_removal_unconfirmed` — name each retired
|
|
721
|
+
schedule with `--remove-schedule <function>`. `--build` names an older build the
|
|
722
|
+
source served (publish id or UUID). Promoting the build already served is
|
|
723
|
+
`409 already_serving`.
|
|
724
|
+
|
|
725
|
+
**`rollback`** moves `--app-env` (staging or production; never a sandbox) back
|
|
726
|
+
to the build it served before its current one — twice swaps back — or `--to`
|
|
727
|
+
one it served earlier. Same plan, prompt and `--yes`. A rollback never reverses
|
|
728
|
+
a migration: the target's pending files are applied like any production move,
|
|
729
|
+
and the plan warns when the target predates files already applied. An unset
|
|
730
|
+
`required` secret only warns here (it is the emergency path).
|
|
731
|
+
|
|
732
|
+
Both need a staff credential: a publish token, a service account's and a sandbox
|
|
733
|
+
agent token are refused before any request (exit `2`). `--json` prints the
|
|
734
|
+
server's plan verbatim.
|
|
594
735
|
|
|
595
736
|
## Apps
|
|
596
737
|
|
|
597
|
-
Read-only
|
|
598
|
-
(the app-admin role). `<app>` is
|
|
738
|
+
Read-only except `domains add | verify | remove`: every other request these make
|
|
739
|
+
is a GET, against `/api/customer-apps/**` (the app-admin role). `<app>` is
|
|
740
|
+
`<org-slug>/<app-slug>` or an app UUID.
|
|
599
741
|
|
|
600
742
|
```bash
|
|
601
743
|
oxyc apps list [--org <slug>] [--published | --draft] [--builds] [--json]
|
|
@@ -604,6 +746,10 @@ oxyc apps builds <app> [--json]
|
|
|
604
746
|
oxyc apps health [<app>] [--needs-attention] [--json]
|
|
605
747
|
oxyc apps usage <app> [--json]
|
|
606
748
|
oxyc apps drift [<app>] [--org <slug>] [--dir <path>] [--refresh] [--json]
|
|
749
|
+
oxyc apps domains [list] <app> [--json]
|
|
750
|
+
oxyc apps domains add <app> <domain> [--json]
|
|
751
|
+
oxyc apps domains verify <app> <domain> [--json]
|
|
752
|
+
oxyc apps domains remove <app> <domain> [--json]
|
|
607
753
|
```
|
|
608
754
|
|
|
609
755
|
- **`list`** — every app you can see, across organizations, walking the paged
|
|
@@ -611,12 +757,17 @@ oxyc apps drift [<app>] [--org <slug>] [--dir <path>] [--refresh] [--json]
|
|
|
611
757
|
`draft` when it has none. `SOURCE` is the repository the app is *registered*
|
|
612
758
|
against. `--builds` adds the live build's id, the repository and commit it was
|
|
613
759
|
built from, who published it and when — one extra request per app, six at a
|
|
614
|
-
time.
|
|
760
|
+
time. `--json` rows also carry the server's `verdict` and, for a published
|
|
761
|
+
app that was measured, `availability`: request success over six windows and
|
|
762
|
+
the burn verdict. An absent `availability` means "not read", never healthy.
|
|
615
763
|
- **`show`** — one app on one screen: the registry row, the live build, the
|
|
616
764
|
draft build when it is newer than the live one, deployment integrity,
|
|
617
765
|
availability, and the 7-day usage summary. `last promote` is when the promote
|
|
618
766
|
or rollback action was last used; `oxyc publish --promote` does not update it,
|
|
619
|
-
so it can be older than the live build.
|
|
767
|
+
so it can be older than the live build. Availability is the registry row's
|
|
768
|
+
reading, not a request of its own: with none, `show` prints `no reading`, and
|
|
769
|
+
it is never in `failed`. `--json` has the registry row under `app` and its
|
|
770
|
+
`availability` (or `null`) under `availability`.
|
|
620
771
|
- **`builds`** — the build history, newest first. `CHANNEL` marks the build each
|
|
621
772
|
channel points at: `live`, `draft`, or both for one build.
|
|
622
773
|
- **`health`** — with no argument, the fleet table (published apps only):
|
|
@@ -626,6 +777,7 @@ oxyc apps drift [<app>] [--org <slug>] [--dir <path>] [--refresh] [--json]
|
|
|
626
777
|
of the last 24 hours.
|
|
627
778
|
- **`usage`** — the 7-day summary, the visitors, and the tracked events by name.
|
|
628
779
|
- **`drift`** — see below.
|
|
780
|
+
- **`domains`** — see [Custom domains](#custom-domains).
|
|
629
781
|
|
|
630
782
|
Times are UTC. `--json` prints one document on stdout; a table goes to stdout and
|
|
631
783
|
the count line to stderr.
|
|
@@ -642,6 +794,39 @@ a drifted app is an answer and exits `0` — read it from the output.
|
|
|
642
794
|
The health route answers **503 with its report** when a check fails; `oxyc`
|
|
643
795
|
prints that as a failing report, not as an outage.
|
|
644
796
|
|
|
797
|
+
### Custom domains
|
|
798
|
+
|
|
799
|
+
Serve an app on a domain the customer owns (`app.acme.com`) instead of its Oxy
|
|
800
|
+
subdomain. `<domain>` is a bare host name; `verify` and `remove` find the row by
|
|
801
|
+
that name (case-insensitive), so you never handle the domain's id.
|
|
802
|
+
|
|
803
|
+
```bash
|
|
804
|
+
oxyc apps domains acme/store # list (the default verb)
|
|
805
|
+
oxyc apps domains add acme/store app.acme.com # then create the two DNS records it prints
|
|
806
|
+
oxyc apps domains verify acme/store app.acme.com
|
|
807
|
+
oxyc apps domains remove acme/store app.acme.com
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
- **`list`** — each domain's `STATUS` (`pending` or `verified`), its URL and
|
|
811
|
+
when it was last checked. For every `pending` domain it prints the DNS records
|
|
812
|
+
to create — a `CNAME` from the domain to the deployment's target (a note to
|
|
813
|
+
ask Oxy ops when the deployment has none configured) and a `TXT` ownership
|
|
814
|
+
record — plus the checker's last error, if any.
|
|
815
|
+
- **`add`** — registers the domain as `pending` and prints the same two records
|
|
816
|
+
and the `verify` command to run next. A malformed name is `400
|
|
817
|
+
invalid_domain` and a duplicate `409 domain_exists`; both exit `6` with the
|
|
818
|
+
server's message.
|
|
819
|
+
- **`verify`** — checks DNS now. `verified` prints the URL and exits `0`. Still
|
|
820
|
+
`pending` is **not** success: it prints the records again and the checker's
|
|
821
|
+
reason, and exits `1` — DNS may not have propagated, so re-run it. `409
|
|
822
|
+
domain_taken` (another app already serves the domain) exits `6`.
|
|
823
|
+
- **`remove`** — stops serving the app on that domain. A name the app does not
|
|
824
|
+
have exits `5` and deletes nothing.
|
|
825
|
+
|
|
826
|
+
`--json` prints one document on stdout: `{app, app_id, domains, cname_target}`
|
|
827
|
+
for `list`, and `{app, app_id, domain, cname_target}` for `add` and `verify`
|
|
828
|
+
(`verify` still exits `1` while pending).
|
|
829
|
+
|
|
645
830
|
### `apps drift`
|
|
646
831
|
|
|
647
832
|
Compares what is live against source on **this machine**. For each app it takes
|
|
@@ -705,8 +890,28 @@ object to stdout instead:
|
|
|
705
890
|
{ "app": "acme/dashboards", "appId": "…", "checks": [{ "name": "canary", "runId": "…", "status": "done", "passed": true, "durationMs": 4123 }] }
|
|
706
891
|
```
|
|
707
892
|
|
|
893
|
+
**`--browser`** also loads the app's page in headless Chromium and proves it
|
|
894
|
+
mounted: the platform runtime's `oxy-app-ready` beacon fires, nothing on the
|
|
895
|
+
page throws, and `--expect-selector <css>` (optional) becomes visible. With
|
|
896
|
+
`--app-env` the page must also report that environment and the build it serves
|
|
897
|
+
(`oxyc env show`'s `build_uuid`) in `window.__OXY_APP__`. The credential and
|
|
898
|
+
`X-Oxy-App-Env` ride only on requests under the app's own base path
|
|
899
|
+
(`/customer-apps/<org>/<app>/`: the document, assets, beacon and `/fn` calls).
|
|
900
|
+
The rest of the deployment's API is on the same origin, and the code under test
|
|
901
|
+
is often a build someone else published, so its `/api/…` calls go without the
|
|
902
|
+
credential, as an anonymous visitor's would. The result is one more entry in `checks[]`, with `"kind": "browser"` and
|
|
903
|
+
the `/fn` calls the page made. Playwright is not a dependency: it is resolved
|
|
904
|
+
from the working directory (`playwright`, `playwright-core` or
|
|
905
|
+
`@playwright/test`), so run it where one is installed. The page loads as a
|
|
906
|
+
user, so a publish, service-account, CI or sandbox agent token is refused
|
|
907
|
+
before any request.
|
|
908
|
+
|
|
909
|
+
```bash
|
|
910
|
+
oxyc checks run acme/store --app-env dev-a1 --browser --expect-selector 'main'
|
|
911
|
+
```
|
|
912
|
+
|
|
708
913
|
**Auth** follows the same rule as `oxyc api`'s bearer surface, with one
|
|
709
|
-
difference: `checks run` talks to `/api/
|
|
914
|
+
difference: `checks run` talks to `/api/customer-apps/**`, which is not the
|
|
710
915
|
`/external/api/*` surface, so a configured API key is sent explicitly as
|
|
711
916
|
`X-API-Key` only when **no bearer resolves** — never both. A bearer from
|
|
712
917
|
`oxyc login` (or `--token-env`) always wins when one is present.
|
|
@@ -715,11 +920,12 @@ difference: `checks run` talks to `/api/admin/**`, which is not the
|
|
|
715
920
|
holding `id-token: write` signs itself in from its GitHub OIDC token — the same
|
|
716
921
|
two exchanges `oxyc publish` tries, in the same order: a service account's
|
|
717
922
|
trust policy first, the app's registered publisher second — and drives
|
|
718
|
-
`/api/customer-apps
|
|
719
|
-
|
|
720
|
-
`
|
|
721
|
-
|
|
722
|
-
|
|
923
|
+
`/api/customer-apps/**`, the same three routes a person's credential uses. A
|
|
924
|
+
service account's token (`oxy_sat_…`, `oxy_ci_…`) or a publish token set in
|
|
925
|
+
`OXY_TOKEN` is used without minting. What differs is the app lookup: a
|
|
926
|
+
personal token, a legacy API key or a session can list apps to resolve
|
|
927
|
+
`<org>/<app>`; a machine credential may not, so it names the app by UUID or
|
|
928
|
+
through its own grants. `OXY_API_KEY` is read **before** OIDC here: a job that set one has said
|
|
723
929
|
which credential it means.
|
|
724
930
|
|
|
725
931
|
The app id comes from the credential where it can. The publisher exchange
|
|
@@ -776,20 +982,33 @@ oxyc env create <app> <name> # name is dev-<handle>, 1-12 chars
|
|
|
776
982
|
oxyc env list <app>
|
|
777
983
|
oxyc env show <app> <name>
|
|
778
984
|
oxyc env delete <app> <name> [--yes] [--wait [seconds]]
|
|
985
|
+
oxyc env schedules <app> <name> [--on|--off] # staging's or a sandbox's switch; off by default
|
|
779
986
|
|
|
780
987
|
oxyc publish --app-env <name> [--env <e>] # see "Custom apps" above
|
|
988
|
+
oxyc fn dev <function> [--input <json|@file|->] [--env-file <path>] [--watch] [--read-through [--env <e>]] # local, in-process
|
|
781
989
|
oxyc fn call <app> <function> [--app-env <name>] [--data <json|@file|->]
|
|
990
|
+
oxyc fn run <app> <function> [--app-env <name>] [--as-schedule] [--data …] [--timeout <s>]
|
|
782
991
|
oxyc checks run <app> --app-env <name> # see "Checks" above
|
|
783
992
|
|
|
784
993
|
oxyc invocations list <app> [--app-env] [--build] [--function] [--limit]
|
|
785
994
|
oxyc invocations held <app> <invocation-id>
|
|
786
995
|
oxyc logs <app> [--app-env] [--invocation] [--request] [--hours] [--limit]
|
|
996
|
+
oxyc logs <app> [--app-env <name>] --follow [--timeout <seconds>] [--json]
|
|
787
997
|
|
|
788
998
|
oxyc env secret list <app> --app-env <name> # keys and flags, never a value
|
|
789
999
|
oxyc env secret set <app> <key> --app-env <name> (--value <text> | --value-env <VAR>)
|
|
790
1000
|
oxyc env secret delete <app> <key> --app-env <name>
|
|
791
1001
|
```
|
|
792
1002
|
|
|
1003
|
+
`logs --follow` is the live tail: each line as a function prints it,
|
|
1004
|
+
wherever the function runs, with no observability capture needed (the polled
|
|
1005
|
+
`logs` reads ClickHouse and answers `501` without it). `--json` prints one
|
|
1006
|
+
event per line — `open`, then `start` / `log` / `finish` per invocation,
|
|
1007
|
+
`dropped` when lines were lost, and `done` last, on which it exits `0`. A
|
|
1008
|
+
dropped connection is reopened with `Last-Event-ID`, and the server replays
|
|
1009
|
+
the outcomes of what finished meanwhile, never the lines. Production without
|
|
1010
|
+
`--app-env`; staging and sandboxes need staff with non-production reach.
|
|
1011
|
+
|
|
793
1012
|
A **sandbox** is a named, short-lived `dev-<handle>` environment of one custom
|
|
794
1013
|
app: its own build pointer, storage silo, secrets, Airhouse sibling and — in
|
|
795
1014
|
an org with an OLTP staging branch — its own copy of the app's database, with
|
|
@@ -817,8 +1036,8 @@ to sit in an agent's context.
|
|
|
817
1036
|
validated client-side — exit `2` on a malformed name) is a *different axis*
|
|
818
1037
|
from `--env`, which is the deployment: both can appear on the same command
|
|
819
1038
|
(`oxyc fn call acme/store f --env dev --app-env dev-a1`). It is not a global
|
|
820
|
-
flag — only `publish`, `fn call`, `checks run`, `invocations list`
|
|
821
|
-
take it. A publish-token credential is refused before any request for
|
|
1039
|
+
flag — only `publish`, `fn call`, `fn run`, `checks run`, `invocations list`
|
|
1040
|
+
and `logs` take it. A publish-token credential is refused before any request for
|
|
822
1041
|
`--app-env` other than `production` (exit `2`); `env`, `invocations` and
|
|
823
1042
|
`logs` refuse one outright, for any environment, because sandbox management
|
|
824
1043
|
is a staff console surface.
|
|
@@ -852,6 +1071,56 @@ distinct from a non-2xx on the POST itself (exit per the usual status mapping
|
|
|
852
1071
|
— e.g. `EnvironmentHasNoBuild` before the stream starts). The
|
|
853
1072
|
`x-oxy-invocation-id` response header becomes `invocationId` in the result.
|
|
854
1073
|
|
|
1074
|
+
**`fn dev`** runs a handler locally, from the app directory, with no server and
|
|
1075
|
+
no credentials: it bundles `functions/<name>.ts` with the esbuild call `oxyc
|
|
1076
|
+
publish` uses, runs it in-process on `createTestContext` from
|
|
1077
|
+
`@oxy-hq/sdk/testing`, and prints the response and every host op the handler
|
|
1078
|
+
made (`host ops: email.send`, one line per call). The context is seeded from
|
|
1079
|
+
`oxy-app.json` — the function's capabilities and destinations — and `ctx.env`
|
|
1080
|
+
from `--env-file` (default `.env`; never your shell). `--input` is the request
|
|
1081
|
+
body, as `fn call --data`. `--watch` re-runs on a change under the app
|
|
1082
|
+
directory. It exits `0` for a 2xx `Response`, `1` when the handler throws or
|
|
1083
|
+
answers non-2xx, `2` for a function the manifest does not declare. **A local run
|
|
1084
|
+
is not the isolate:** no timeout, memory or log limits, Node globals the isolate
|
|
1085
|
+
lacks, and every warehouse, semantic, storage, fetch and email call is stubbed
|
|
1086
|
+
in memory — nothing reaches a deployment. Check it where it really runs with
|
|
1087
|
+
`fn call` against a sandbox.
|
|
1088
|
+
|
|
1089
|
+
`fn dev --read-through [--env <name|url>]` sends exactly `ctx.query`,
|
|
1090
|
+
`ctx.warehouse.query` and `ctx.semantic.query` to that deployment, through
|
|
1091
|
+
`POST /api/projects/{project}/query` and `/semantic-query` (the routes a
|
|
1092
|
+
published app calls), authenticated as `oxyc api` is (`OXY_TOKEN` or your
|
|
1093
|
+
`oxyc login`). The server decides what you may read. The project is `--project`
|
|
1094
|
+
/ `OXY_PROJECT`, else looked up from `oxy-app.json` `slug` + `orgSlug`. Every
|
|
1095
|
+
other call — writes, transactions, storage, fetch, email — stays stubbed, the
|
|
1096
|
+
report marks each call `real` or `stubbed`, and a local run is still not the
|
|
1097
|
+
isolate.
|
|
1098
|
+
|
|
1099
|
+
**`env schedules`** reads, or with `--on` / `--off` flips, the schedules
|
|
1100
|
+
switch of staging or a sandbox (`GET`/`PUT
|
|
1101
|
+
/api/customer-apps/{id}/environments/{name}/schedules`). Off by default: a
|
|
1102
|
+
schedule outside production reads production data on a timer and calls third
|
|
1103
|
+
parties with staging's credentials, so an environment fires its functions'
|
|
1104
|
+
`schedule`s only once staff turn it on. On, it has one schedule row per
|
|
1105
|
+
function its build schedules, kept in step by every publish to it; `--off`
|
|
1106
|
+
removes them. Production has no switch (exit `2`) — its schedules always run.
|
|
1107
|
+
Staff only: a sandbox agent token is refused before any request (exit `2`).
|
|
1108
|
+
A scheduled fire, and a delivery to the environment's own webhook route
|
|
1109
|
+
(`/api/webhooks/apps-env/{environment}/{org}/{app}/{function}`, verified with
|
|
1110
|
+
that environment's own secret), are not a sandbox's activity.
|
|
1111
|
+
|
|
1112
|
+
**`fn run`** queues a function as a background run — the console's **Run
|
|
1113
|
+
now** — and polls it to a verdict (`--timeout`, default 300 s), unlike `fn
|
|
1114
|
+
call`, which is the function's own route. It runs as the system (the org
|
|
1115
|
+
owner, no caller), with the function's retry policy; outside production only a
|
|
1116
|
+
`"check": true` function runs. **`--as-schedule`** runs a function that
|
|
1117
|
+
declares a `schedule` once, as its cron would: no input (`--data` is exit
|
|
1118
|
+
`2`), the environment's build and policy, whether or not its switch is on —
|
|
1119
|
+
the way to test a scheduled function in a sandbox without turning schedules
|
|
1120
|
+
on. A function with no schedule is `409 not_scheduled` (exit `6`), production
|
|
1121
|
+
included. `--json` prints `{function, environment, runId, status, ok, trigger,
|
|
1122
|
+
answer, error?, invocationId}`; exit `1` when the run did not end `done`.
|
|
1123
|
+
|
|
855
1124
|
**`invocations held`** is what the non-production write policy did *not* do —
|
|
856
1125
|
production's would-be effect, recorded rather than performed. It is `[]` for a
|
|
857
1126
|
production invocation, since nothing is held there.
|
|
@@ -1021,9 +1290,12 @@ them, and a tool that can only fail still costs its schema every turn. A dead
|
|
|
1021
1290
|
token ends the server at startup with exit `4`.
|
|
1022
1291
|
|
|
1023
1292
|
**`guide`** prints a page to paste into `AGENTS.md` / `CLAUDE.md`.
|
|
1024
|
-
**`skills install`** symlinks the
|
|
1293
|
+
**`skills install`** symlinks the bundled Oxy platform skills into
|
|
1025
1294
|
`~/.claude/skills`; it refuses to run from an `npx` cache, whose symlinks dangle
|
|
1026
|
-
once npm reclaims it.
|
|
1295
|
+
once npm reclaims it. The same skills are published to
|
|
1296
|
+
[`oxy-hq/skills`](https://github.com/oxy-hq/skills) for every other agent —
|
|
1297
|
+
`npx skills add oxy-hq/skills`, or the Claude Code plugin `oxy@oxy-hq`. They are
|
|
1298
|
+
written in `skills/` here; see `internal-docs/platform-skills.md`.
|
|
1027
1299
|
|
|
1028
1300
|
## Exit codes
|
|
1029
1301
|
|
|
@@ -1039,7 +1311,10 @@ once npm reclaims it.
|
|
|
1039
1311
|
9 a check ran and failed or timed out (`oxyc checks run`)
|
|
1040
1312
|
```
|
|
1041
1313
|
|
|
1042
|
-
`4` almost always means the wrong `--env`.
|
|
1314
|
+
`4` almost always means the wrong `--env`. A person logs in again. An agent on
|
|
1315
|
+
its own token (`tokens create --agent` or `--sandbox-agent`) does not: there,
|
|
1316
|
+
`4` means the token expired or was revoked, and the agent stops and reports.
|
|
1317
|
+
`7` is worth retrying; `6` never is.
|
|
1043
1318
|
|
|
1044
1319
|
## Environment variables
|
|
1045
1320
|
|
|
@@ -1067,6 +1342,12 @@ once npm reclaims it.
|
|
|
1067
1342
|
| `OXYC_QUIET` | — | `1` is `--quiet` |
|
|
1068
1343
|
| `OXYC_DRY_RUN` | — | `1` makes `launch` print the command instead of running it |
|
|
1069
1344
|
| `NO_COLOR` / `FORCE_COLOR` | — | force plain / coloured output |
|
|
1345
|
+
| `HTTPS_PROXY` / `HTTP_PROXY` | — | requests go through it. On Node 22.21+ and 24.5+, `oxyc` turns on Node's proxy support itself (`NODE_USE_ENV_PROXY`), re-running once if it has to. Older Node cannot proxy `fetch`, and the error says so |
|
|
1346
|
+
|
|
1347
|
+
**In a Claude Code cloud session,** the repo's SessionStart hook installs
|
|
1348
|
+
`oxyc` and keeps an agent token across shell calls. The environment must allow
|
|
1349
|
+
the Oxy hosts. Setup: the `oxy-custom-apps` skill, "Headless and cloud
|
|
1350
|
+
sessions".
|
|
1070
1351
|
|
|
1071
1352
|
**An agent should set `OXY_AGENT`.** Every request `oxyc` makes to a deployment
|
|
1072
1353
|
carries the user agent `oxyc/<version>`, with `agent/<label>` after it when an
|