@oxy-hq/cli 0.1.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.
Files changed (39) hide show
  1. package/README.md +163 -0
  2. package/dist/main.mjs +6688 -0
  3. package/dist/main.mjs.map +1 -0
  4. package/dist/mcp-VdontkUr.mjs +316 -0
  5. package/dist/mcp-VdontkUr.mjs.map +1 -0
  6. package/json-schemas/agent-test.json +104 -0
  7. package/json-schemas/agentic.json +406 -0
  8. package/json-schemas/app.json +1884 -0
  9. package/json-schemas/config.json +2215 -0
  10. package/json-schemas/flow-test.json +124 -0
  11. package/json-schemas/global-semantics.json +782 -0
  12. package/json-schemas/workflow.json +1907 -0
  13. package/package.json +65 -0
  14. package/skills/oxy-airway-pipelines/SKILL.md +338 -0
  15. package/skills/oxy-automations/SKILL.md +67 -0
  16. package/skills/oxy-cli/SKILL.md +134 -0
  17. package/skills/oxy-custom-apps/SKILL.md +330 -0
  18. package/skills/oxy-data-architecture/SKILL.md +225 -0
  19. package/skills/oxy-semantic-modeling/SKILL.md +216 -0
  20. package/template/.gitattributes +2 -0
  21. package/template/.github/scripts/ci-publish.sh +77 -0
  22. package/template/.github/scripts/discover-apps.sh +175 -0
  23. package/template/.github/scripts/publish-env.sh +55 -0
  24. package/template/.github/workflows/publish.yaml +653 -0
  25. package/template/.github/workflows/validate.yaml +264 -0
  26. package/template/.nvmrc +1 -0
  27. package/template/.oxyc-managed +297 -0
  28. package/template/README.md +414 -0
  29. package/template/_gitignore +32 -0
  30. package/template/apps/.gitkeep +0 -0
  31. package/template/config.yml +53 -0
  32. package/template/memory/.gitkeep +0 -0
  33. package/template/package.json +17 -0
  34. package/template/pipelines/.gitkeep +0 -0
  35. package/template/pnpm-workspace.yaml +32 -0
  36. package/template/scripts/dev.sh +443 -0
  37. package/template/semantics/topics/.gitkeep +0 -0
  38. package/template/semantics/views/.gitkeep +0 -0
  39. package/template/workflows/.gitkeep +0 -0
@@ -0,0 +1,414 @@
1
+ # __NAME__
2
+
3
+ The oxy workspace for **__NAME__** — everything we build for this customer in
4
+ one repo: their semantic model, pipelines, automations, custom apps, and the
5
+ agent memory the team shares.
6
+
7
+ Start a session scoped to this customer with:
8
+
9
+ oxyc __SLUG__
10
+
11
+ ## Layout
12
+
13
+ | Path | What goes here |
14
+ | --- | --- |
15
+ | `memory/` | Agent memory, written by Claude Code during `oxyc` sessions. `MEMORY.md` is **generated** from the frontmatter of the fact files beside it — do not edit it by hand; regenerate on conflict. |
16
+ | `semantics/views/` | `*.view.yml` — one per table: entities, dimensions, measures. The `description` is a contract with the agent, not a label. |
17
+ | `semantics/topics/` | `*.topic.yml` — question areas: a base view plus the views it may join to. Not one per table. |
18
+ | `pipelines/` | `*.airway.yml` — Airway ELT pipelines. Credentials live in the secret manager, never in the YAML. |
19
+ | `workflows/` | `*.automation.yml` — automations. `.automation.yml` is the canonical extension; `.procedure.yml` is legacy and still accepted, so do not start new files with it. |
20
+ | `apps/` | Custom app bundles (React + Vite, shipped with `oxy publish`). |
21
+ | `config.yml` | Workspace root config — models, databases, defaults. |
22
+ | `.github/workflows/` | GitHub Actions CI for this repo. Not the same thing as `workflows/` above, which holds oxy automations — the two are one letter of context apart and easy to confuse. |
23
+ | `.github/scripts/` | Shell the workflows call. Kept out of the YAML because a `run:` block can only ever be executed on a runner, while a script can be run — and tested — anywhere. |
24
+ | `scripts/` | Shell **you** run. `dev.sh` is the local dev entry point — see "Developing an app locally" below; `pnpm dev` is how you invoke it. |
25
+ | `.oxyc-managed` | Which files here are the tooling's and which are yours, so `oxyc update` can refresh one without overwriting the other. It is the only thing that decides what a sync may rewrite. Add a line when you add a file to the template — not when you add a view or an app. |
26
+ | `.gitignore` | What never gets committed: `node_modules/`, build output (`out/`, `dist/`, `build/`), `.oxy_state/`, `.env*`. The same directory names CI prunes when it walks the tree. |
27
+
28
+ The scaffold ships empty directories on purpose. There are no example views or
29
+ pipelines to copy: a stale example carried forward is how a whole workspace
30
+ ends up on a deprecated file extension.
31
+
32
+ ## What commits itself, and what does not
33
+
34
+ A session started with `oxyc` regenerates the memory index and then commits and
35
+ pushes **`memory/` only**, at the end of the session, with no review step.
36
+
37
+ Everything else here — the semantic model, pipelines, automations, apps — you
38
+ commit yourself, deliberately, after review. That split is deliberate: a
39
+ customer's semantic model should never land in `main` because a session ended.
40
+
41
+ The practical consequence: do not put anything in `memory/` that needs review
42
+ before the team sees it.
43
+
44
+ ## What CI checks
45
+
46
+ Every pull request runs `.github/workflows/validate.yaml`: a Postgres 16
47
+ service container, then `oxy compile` over this whole tree. It needs no
48
+ warehouse credentials — compiling is a pure YAML-to-rows transform — so the
49
+ gate works before a single database is connected.
50
+
51
+ It catches:
52
+
53
+ - **YAML that does not parse**, anywhere in the tree, `config.yml` included.
54
+ The failing path is emitted as a GitHub error annotation, so the failure
55
+ lands on the diff rather than only in the log. It anchors to the **top of
56
+ the file**, not to the offending line: `oxy compile --json` reports a path, a
57
+ kind and a message per failure, and no line or column for the workflow to
58
+ pass on.
59
+ - **Two files of one kind declaring the same `name:`** — two views both called
60
+ `orders`, say, which is what a copy-pasted file usually produces.
61
+
62
+ It does **not** catch, and each of these compiles green today:
63
+
64
+ - **A view whose `datasource:` names a database that is not in `config.yml`.**
65
+ There is no referential check at compile time; the typo surfaces at query
66
+ time instead.
67
+ - **A view with no `name:` field at all.** The compiler derives one from the
68
+ file path rather than failing, so the view exists under a name nobody chose.
69
+ - **Anything about whether the model is right** — a measure with the wrong SQL,
70
+ a join on the wrong key, a description that misdescribes the column. Nothing
71
+ mechanical checks those.
72
+
73
+ So a green check means "this parses, and the identifiers are unique". It does
74
+ not mean the model is correct, and it is not a substitute for reading the diff.
75
+
76
+ The `oxy` CLI version is **pinned** in that workflow, and the job fails if the
77
+ installed binary is older. Both halves matter: the installer does not fail on a
78
+ version tag that does not exist, and this CLI answers a subcommand it does not
79
+ have with its own help text and exit 0 — so an unpinned, unguarded job can go
80
+ green while doing less than you think.
81
+
82
+ ## Developing an app locally
83
+
84
+ An app in `apps/` is a React + Vite bundle. Running one on your machine means
85
+ running it against **a real oxy in the cloud** — there is no local warehouse,
86
+ no fixture data, and no local `oxy serve` to point at. From the repo root:
87
+
88
+ ```bash
89
+ oxy login --env dev # once per env; opens a browser, caches a token
90
+ pnpm dev --env dev # starts everything
91
+ ```
92
+
93
+ `pnpm dev` runs `scripts/dev.sh`, and that script is the whole answer: it finds
94
+ the apps in this repo, starts both processes, prints what it is doing, and
95
+ stops both together when you press Ctrl-C.
96
+
97
+ **`--env` is required, and that is deliberate.** It says which oxy the app
98
+ reads from — `dev`, `staging`, `production`, a full URL
99
+ (`--env https://acme.oxygen-hq.com`), or any name the app's own `oxy-app.json`
100
+ declares under `environments`. The underlying `oxy proxy` defaults to
101
+ **production** if nothing says otherwise, and a `pnpm dev` that quietly serves
102
+ a customer's live data is not something this repo will do. `--env production`
103
+ additionally needs `--yes`.
104
+
105
+ If the repo holds more than one app, name the one you want — by its directory
106
+ or just its name:
107
+
108
+ ```bash
109
+ pnpm dev --env dev sales-dashboard
110
+ ```
111
+
112
+ ### The two processes
113
+
114
+ | Process | Port | What it is |
115
+ | --- | --- | --- |
116
+ | `oxy proxy --env <env>` | `3000` | Background. Every `/api` call the app makes goes through here, signed with the token `oxy login` cached, and out to the cloud. It stands in for a local `oxy serve`, which is why the port is not configurable — the Vite plugin already proxies `/api` to `3000`. |
117
+ | `pnpm dev` in the app | `5173` | Foreground. Vite, with hot reload. **This is the URL you open**: <http://localhost:5173>. |
118
+
119
+ Ctrl-C stops both, and it always returns: a proxy that ignores the polite
120
+ signal is given five seconds and then killed outright, because a dev script
121
+ whose Ctrl-C sometimes hangs is worse than one that leaks. If a run ever fails
122
+ with *port 3000 is already taken*, a proxy from an earlier session outlived its
123
+ terminal — the message names the process holding it.
124
+
125
+ ### The guardrails, and how to lift them
126
+
127
+ The proxy is not a transparent tunnel. Two kinds of traffic are held back so
128
+ that an afternoon of local clicking cannot change what the customer sees:
129
+
130
+ - **Side-effecting calls are HELD** — Oxy Functions (`/fn`), agent runs,
131
+ automation runs. They do not reach the cloud at all. This is the single most
132
+ common "my function isn't running and there is no error" — pass
133
+ **`--allow-writes`** to forward them for real.
134
+ - **Tracking events are DROPPED**, so local clicking never lands in the
135
+ customer's analytics. Pass **`--allow-events`** to forward them.
136
+
137
+ `pnpm dev` prints both, every run, before it starts anything.
138
+
139
+ ### If something is wrong
140
+
141
+ - **Requests come back 401** — the cached token for that env has expired.
142
+ `oxy login --env <env>` again.
143
+ - **`the 'oxy' CLI is not on your PATH`** — install it with
144
+ `curl -sSfL https://get.oxy.tech | bash`; it lands in `~/.local/bin`.
145
+ - **`this repo has no custom apps yet`** — expected on a fresh repo, and it is
146
+ not an error. The message says how to add one.
147
+ - **A change to `apps/` does not show up** — Vite serves the app, but the data
148
+ comes from the cloud through the proxy; a stale *answer* is a semantic-model
149
+ or warehouse question, not a dev-server one.
150
+
151
+ ## Publishing custom apps
152
+
153
+ **Publishing is self-serve here, and CI publishes nothing unless this repo
154
+ turns it on.** That is the default deliberately: it is how Oxy ships its own
155
+ custom apps, and it means this repo is useful on the day it is created without
156
+ a long-lived credential sitting in it.
157
+
158
+ ### The default: you publish, from your machine
159
+
160
+ From the app's own directory under `apps/`:
161
+
162
+ ```bash
163
+ oxy login --env dev # once per env; opens a browser, caches a token
164
+ oxy publish --env dev # a draft, to the dev environment
165
+ ```
166
+
167
+ `--env` is not optional in practice. The CLI defaults it to **production**, so
168
+ an invocation that leaves the flag off does not publish "nowhere in
169
+ particular" — it publishes at the customer's live environment. Name it every
170
+ time. The draft channel is the default; `--promote` publishes live instead.
171
+
172
+ **A push to `main` still runs `.github/workflows/publish.yaml`, and it is
173
+ still worth having.** Its `build` job installs the workspace and builds every
174
+ bundle under `apps/` — which needs no credential — so a bundle that stopped
175
+ compiling fails on the merge, rather than in front of you an hour later when
176
+ you sit down to publish it. The `publish` job is then **skipped**, the run is
177
+ **green**, and the log carries a notice saying publishing here is self-serve
178
+ and naming the two commands above.
179
+
180
+ ### Turning CI publishing on
181
+
182
+ One repository **variable**:
183
+
184
+ > Settings → Secrets and variables → Actions → **Variables** →
185
+ > `OXY_CI_PUBLISH` = `true`
186
+
187
+ A variable rather than a secret, for two reasons. It is read in the `build`
188
+ job — the only job that runs when publishing is off, and so the only one that
189
+ can say anything about it — and that job must reference no secret at all,
190
+ because it executes the postinstall and build scripts of every dependency your
191
+ apps pull in. The other reason is plainer: a switch whose whole job is to tell
192
+ "off on purpose" apart from "misconfigured" has to be legible, in Settings and
193
+ in a run log. A secret is neither.
194
+
195
+ `true`, `yes`, `on` and `1` all mean on; `false`, `no`, `off`, `0` and a
196
+ variable that is not set at all mean off. **Anything else fails the run**
197
+ instead of being read as one of them: `OXY_CI_PUBLISH=ture` guessed as off
198
+ stops publishing with nobody told, and guessed as on publishes when nobody
199
+ asked, so it is not guessed.
200
+
201
+ Note what the switch does **not** do. It decides whether the publish job runs;
202
+ it never softens what happens once it has. With CI publishing on, a missing
203
+ `OXY_TOKEN` is a **hard failure** exactly as it always was. That is the reason
204
+ there is a switch at all rather than the obvious shortcut of skipping whenever
205
+ no token turns up: inferred from the token, "this repo does not publish from
206
+ CI" and "somebody forgot production's token" are the same observation — and
207
+ the second one ships nothing, quietly, for as long as nobody looks.
208
+
209
+ Everything from here to the end of this section describes a repo that has
210
+ opted in.
211
+
212
+ ### Where an opted-in CI publishes
213
+
214
+ `.github/workflows/publish.yaml` builds every bundle under `apps/` and ships
215
+ it with `oxy publish`. It runs on a push to `main` that touches `apps/`, the
216
+ lockfile, or the workflow itself, and it can be run by hand from the Actions
217
+ tab.
218
+
219
+ **Where it publishes is decided by how it was triggered, and it is always
220
+ said out loud:**
221
+
222
+ | Trigger | Event | `oxy publish --env` | Channel | Reviewers |
223
+ | --- | --- | --- | --- | --- |
224
+ | push to `main` | `push` | `dev` | draft | none |
225
+ | Actions tab (**Run workflow**) | `workflow_dispatch` | `production` | draft, or live with **Promote** | whatever `production` requires |
226
+
227
+ The **Event** column is the raw GitHub event name, and it is here rather than
228
+ implied because it is the key the mapping is written against — this table and
229
+ `.github/scripts/publish-env.sh` are checked against each other, row by row,
230
+ by `customer-tooling`'s own suite.
231
+
232
+ That mapping lives in `.github/scripts/publish-env.sh`, and a trigger it has
233
+ no entry for **fails the run** rather than picking one. The reason is worth
234
+ knowing, because it was a live bug in this workflow: `oxy publish` defaults
235
+ `--env` to **production**, so an invocation that leaves the flag off does not
236
+ publish "nowhere in particular" — it publishes at the customer's live
237
+ environment. Every publish here names its environment explicitly, and there
238
+ is no code path that can fall back to a default.
239
+
240
+ There is deliberately **no environment picker** on the manual run. An input
241
+ would need a default, which is the thing being removed; and the same resolved
242
+ name also selects the GitHub environment whose `OXY_TOKEN` the job publishes
243
+ with, so leaving it to a form field would mean the credential and the
244
+ destination agree only by an operator's care. To publish a dev draft by hand,
245
+ re-run the push-triggered run from the Actions tab — a re-run keeps the
246
+ original event, so it still resolves to `dev`.
247
+
248
+ It is **two jobs**, and the split is the security boundary rather than a
249
+ tidiness one. `build` runs `pnpm install` and `pnpm -r build` — which is to
250
+ say it executes the postinstall and build scripts of every dependency your
251
+ apps pull in — and holds no publish credential at all. It hands the built
252
+ output to `publish` as a workflow artifact. `publish` holds `OXY_TOKEN`, and
253
+ runs no package script: `oxy publish --dir` uploads a pre-built directory
254
+ as-is instead of building anything. So a compromised dependency in an app's
255
+ tree never runs on a runner where the token exists.
256
+
257
+ **On a fresh repo it does nothing at all, and that is the intended
258
+ behaviour.** The scaffold ships no apps and no `pnpm-lock.yaml` — there is
259
+ nothing to lock yet — so the first step looks for `apps/**/oxy-app.json`,
260
+ finds none, skips the install and the build, skips the whole publish job, and
261
+ the run goes **green**. The install is skipped along with the publish
262
+ deliberately: `pnpm install --frozen-lockfile` fails without a lockfile, and
263
+ a repo whose CI is red the day it is created is a repo where nobody reads CI.
264
+
265
+ Before it can publish anything, each of the two **GitHub environments** —
266
+ `dev` and `production` — needs its own **`OXY_TOKEN`** (Settings →
267
+ Environments → *environment* → Environment secrets), a publish-scoped API key
268
+ minted in oxy against that workspace. This is the credential the self-serve
269
+ model does without, and it is worth knowing what it is before you create one:
270
+ a personal API key records the project it was minted against, but that project
271
+ never reaches validation — a leaked key carries the minter's **user-level**
272
+ access, not "this app's project only".
273
+
274
+ **A missing one fails the run, loudly, and that is the intended behaviour.**
275
+ It is also why CI publishing is a switch rather than something inferred from
276
+ the token's presence: with the switch on, no token means a red run naming the
277
+ environment it was looked for in, never a green run that published nothing.
278
+
279
+ **And any repository-level `OXY_TOKEN` must be DELETED, not left in place.**
280
+ GitHub's order is environment → repository → organisation, and that is
281
+ *precedence, not exclusion*: an environment secret overrides a repository one
282
+ of the same name, it does not suppress it. A repo still carrying an old
283
+ repository-level token therefore serves **both** environments from it wherever
284
+ the environment has none — every run looks healthy, the missing-token check
285
+ never fires, and production ships under whatever that token was scoped to.
286
+
287
+ Nothing in CI can catch it. GitHub does not tell a job which level a secret
288
+ came from, so the two cases are identical from inside the run. It is a
289
+ one-time human check, and it is the single thing to get right when moving an
290
+ existing repo onto environments:
291
+
292
+ ```bash
293
+ gh secret list --repo <owner>/<repo> # repository level
294
+ gh secret delete OXY_TOKEN --repo <owner>/<repo> # if one is listed
295
+ ```
296
+
297
+ Nothing else needs configuring: each app's `oxy-app.json` carries its own
298
+ `slug` and `orgSlug`, and the project is resolved from the target at publish
299
+ time. If an environment's oxy is not where `--env` would resolve it, set
300
+ `OXY_TARGET` as an **environment** variable on that environment — never as a
301
+ repository one, since it *overrides* `--env` and a repository-level value
302
+ points both environments at the same oxy.
303
+
304
+ So adding the first app is two things — three, if this repo publishes from CI:
305
+
306
+ 1. the bundle at `apps/<app>/` (or `apps/<org>/<app>/`), with an
307
+ `oxy-app.json` carrying `slug` and `orgSlug`;
308
+ 2. a committed `pnpm-lock.yaml` — run `pnpm install` once and commit the
309
+ lockfile it writes. Only the lockfile: `node_modules/` and every app's
310
+ build output are covered by `.gitignore`, so `git add -A` after an install
311
+ stages the one file CI needs and none of the ones it does not;
312
+ 3. **only with `OXY_CI_PUBLISH` set** — an `OXY_TOKEN` in each environment.
313
+
314
+ Miss one and the workflow fails loudly and names it: the discovery step
315
+ points at the manifest, or the missing lockfile, or the missing secret. It
316
+ does not guess and it does not half-publish. (The first two are needed even
317
+ with CI publishing off: the `build` job installs and builds either way.)
318
+
319
+ A push publishes to the **draft** channel of the **dev** environment — draft
320
+ is the CLI's own default, not a policy this workflow invented; dev is this
321
+ repo's. Going live in production is a separate, deliberate act, and it is
322
+ worth knowing exactly what the two ways of doing it are:
323
+
324
+ - **`/admin/apps`** promotes *the build you are looking at*. If you have
325
+ reviewed a draft and want that exact bundle live, this is the one you want.
326
+ - **The Actions tab**, running this workflow with a **Ref** and **Promote**
327
+ ticked, **rebuilds that ref and publishes the result live**. It does not
328
+ promote an existing draft — no command in the `oxy` CLI can, so the
329
+ workflow cannot either. If `main` has moved since the draft you reviewed,
330
+ this ships the newer commit, under a new build id.
331
+
332
+ That is why the ref is an explicit, required input rather than something the
333
+ run infers: pin it to the SHA you reviewed and the two routes agree. Leaving
334
+ it at its default, `main`, is fine and publishes main's current tip — the run
335
+ resolves the commit it actually checked out and prints it, so what shipped is
336
+ never left to inference. Do not read the commit off the run's title or the
337
+ branch you launched it from: on a manual run those track GitHub's "Use
338
+ workflow from" dropdown, which is a separate control from the Ref input here.
339
+
340
+ ### Putting a human in front of a publish
341
+
342
+ The publish job runs in the GitHub Actions **environment** it is publishing
343
+ to — `dev` or `production`, the same string it passes to `--env`. On a fresh
344
+ repo those are labels and nothing else: GitHub creates a referenced
345
+ environment that does not exist, with no protection rules. (Environments in a
346
+ private repo need GitHub Team or Pro; the `oxy-hq` org is on Team.)
347
+
348
+ Either becomes a gate when someone wants one — Settings → Environments →
349
+ `production` → **Required reviewers** — and no workflow edit is needed to add
350
+ it. Because the trigger already decides the environment, reviewers on
351
+ `production` hold up exactly the manual runs and none of the dev drafts a
352
+ merge produces. Putting them on `dev` instead would hold up every merge, which
353
+ is almost never what anyone means.
354
+
355
+ It does **not** run tests, typecheck, or check that an app works. If the
356
+ build produces output, the bundle ships. One case is worth knowing about
357
+ because it is silent everywhere else: an app whose `package.json` has no
358
+ `build` script is skipped by pnpm **without failing**, so the workflow
359
+ refuses to publish an empty output directory rather than uploading nothing
360
+ successfully. Nothing catches a bundle that builds and is simply wrong.
361
+
362
+ ## This repo is the customer's oxy workspace
363
+
364
+ The semantic model in here does nothing until the platform compiles it, so this
365
+ repo is registered as __NAME__'s oxy workspace — **twice**, once as dev and
366
+ once as production. Both track this repo and this same `main`.
367
+
368
+ Registration happens through the **org onboarding UI** and needs a GitHub App
369
+ installation for the org. There is no CLI command for it — do not go looking for
370
+ one. Registration supports pointing at a subdirectory, so the workspace root can
371
+ sit inside a larger repo if that is ever needed.
372
+
373
+ Until it is registered, the model here is still worth writing and still worth
374
+ validating; it just is not queryable by agents or Ask Oxygen yet.
375
+
376
+ ### Two workspaces, one branch — and who promotes what
377
+
378
+ There is no `dev` branch, and that is the design rather than an omission. A
379
+ semantic model is not application code: a long-lived divergent branch means a
380
+ measure exists in one environment and not the other, and the symptom is a
381
+ query that worked yesterday quietly returning nothing. One definition, two
382
+ pointers at it.
383
+
384
+ It works because **merging to `main` promotes nothing on its own** — the
385
+ platform compiles on an explicit action (the IDE button, admin's *Run compile
386
+ now*, the CLI) and never on a push. So the two workspaces differ only in when
387
+ someone points each at the branch:
388
+
389
+ | What | Who moves it |
390
+ | --- | --- |
391
+ | Custom apps → dev | CI, on every merge to `main` (draft) |
392
+ | Custom apps → production | CI, on a manual run from the Actions tab |
393
+ | Semantic model → dev | a human, or a scheduled compile if one is set up |
394
+ | **Semantic model → production** | **a human, always** |
395
+
396
+ Promoting the production semantic model is a deliberate, human step: open the
397
+ production workspace in Oxygen and pull the latest commit. **CI does not do
398
+ it**, and holds no credential that could — the `OXY_TOKEN` in each environment
399
+ is publish-scoped and cannot compile or promote a workspace at all. So a merge
400
+ to `main` never changes what production answers until someone decides it
401
+ should.
402
+
403
+ ## Node and pnpm
404
+
405
+ Node **20** (`.nvmrc`), matching the working app monorepo this build config is
406
+ derived from. Use **pnpm**, never npm or yarn — there is no `package-lock.json`
407
+ here and there should not be one.
408
+
409
+ pnpm is floored at **>= 10**, one major above that monorepo's `>= 9`, and the
410
+ difference is deliberate: pnpm 9 reads no settings at all from
411
+ `pnpm-workspace.yaml`, so on pnpm 9 the `onlyBuiltDependencies` block there is
412
+ silently inert and an app's `esbuild` / `sharp` postinstall is blocked with no
413
+ diagnostic. A floor that admits a version where the config does nothing is
414
+ worse than no floor.
@@ -0,0 +1,32 @@
1
+ # What never belongs in this repo.
2
+ #
3
+ # Not housekeeping — load-bearing. Onboarding step 1 runs `git add -A`, and
4
+ # the README tells you to run `pnpm install` and commit the lockfile it
5
+ # writes; both of those stage everything in the tree that git is not told to
6
+ # ignore. Without this file the first commit of a customer workspace carries
7
+ # node_modules/ and every app's build output.
8
+ #
9
+ # Both CI workflows already prune these same directory names when they walk
10
+ # the tree (.github/scripts/discover-apps.sh, and the compile floor check in
11
+ # validate.yaml), so this list and those prunes are two views of one rule:
12
+ # these directories are generated, and nothing here reads them as source.
13
+
14
+ # Dependencies. Restored by `pnpm install` from pnpm-lock.yaml, which IS
15
+ # committed.
16
+ node_modules/
17
+
18
+ # Build output. `out/` is oxy publish's default outDir; `dist/` and `build/`
19
+ # are what a Vite or Next app writes when its oxy-app.json names one instead.
20
+ out/
21
+ dist/
22
+ build/
23
+
24
+ # The oxy CLI's local state directory.
25
+ .oxy_state/
26
+
27
+ # Local secrets. Credentials for pipelines and apps belong in the platform's
28
+ # secret manager, never in a file in this repo — an ignored .env is where
29
+ # people put them anyway, so make sure an accident stays local.
30
+ .env*
31
+
32
+ .DS_Store
File without changes
@@ -0,0 +1,53 @@
1
+ # yaml-language-server: $schema=https://raw.githubusercontent.com/oxy-hq/oxy/refs/heads/main/json-schemas/config.json
2
+ #
3
+ # Workspace config for __NAME__.
4
+ #
5
+ # Minimal and valid on purpose: this compiles clean on day one, before a single
6
+ # view, topic, pipeline or automation exists. `models:` must be present even
7
+ # when empty — `oxy compile` fails with "missing field `models`" without the
8
+ # key. `databases:` may be omitted entirely (the compiler synthesizes an empty
9
+ # list), and is spelled out here only so there is an obvious place to add one.
10
+ #
11
+ # EVERYTHING COMMENTED OUT BELOW IS A SHAPE, NOT A WORKING DEFAULT. Any value
12
+ # written as <replace-me-...> is a placeholder and will not work as written.
13
+ #
14
+ # Secrets are never written in this file. A `*_var` field names an ENVIRONMENT
15
+ # VARIABLE, resolved from the workspace secret store at runtime — never the
16
+ # secret itself.
17
+
18
+ models: []
19
+ # models:
20
+ # - vendor: anthropic
21
+ # name: <replace-me-model-name> # how this workspace refers to it
22
+ # model_ref: claude-sonnet-4-6 # the vendor's own model id
23
+ # key_var: ANTHROPIC_API_KEY
24
+ # api_url: https://api.anthropic.com/v1
25
+
26
+ databases: []
27
+ # databases:
28
+ # - name: <replace-me-database-name>
29
+ # type: clickhouse
30
+ # host_var: CLICKHOUSE_HOST
31
+ # user_var: CLICKHOUSE_USERNAME
32
+ # password_var: CLICKHOUSE_PASSWORD
33
+ # database_var: CLICKHOUSE_DATABASE
34
+ #
35
+ # # A managed Airhouse warehouse takes no *_var fields at all — its
36
+ # # credentials are brokered at runtime.
37
+ # - name: <replace-me-database-name>
38
+ # type: airhouse_managed
39
+
40
+ # defaults:
41
+ # database: <replace-me-database-name> # must name an entry above; note
42
+ # # that `oxy compile` does NOT
43
+ # # check this reference, so a typo
44
+ # # here surfaces at query time
45
+
46
+ # No `health_check:` block, deliberately. An absent block means this workspace
47
+ # is never evaluated by Workspace Health, which is the honest state of one
48
+ # nobody has tuned. It does not mean "healthy" — an unevaluated workspace is
49
+ # absent from the admin health table rather than shown green. Add a block when
50
+ # someone owns the thresholds; a guessed interval reads as a real opinion.
51
+
52
+ # No `integrations:` either. Every entry is one specific vendor's credentials,
53
+ # and this workspace has no vendor yet.
File without changes
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "__SLUG__",
3
+ "private": true,
4
+ "engines": {
5
+ "node": ">=20",
6
+ "pnpm": ">=10"
7
+ },
8
+ "scripts": {
9
+ "dev": "./scripts/dev.sh",
10
+ "build": "pnpm -r build",
11
+ "lint": "pnpm -r lint",
12
+ "typecheck": "pnpm -r typecheck"
13
+ },
14
+ "devDependencies": {
15
+ "typescript": "^5.9.3"
16
+ }
17
+ }
File without changes
@@ -0,0 +1,32 @@
1
+ # Custom app bundles live under `apps/`. Both globs are carried from the
2
+ # customer-apps monorepo's working workspace file, and both earn their place:
3
+ #
4
+ # apps/* one app per directory — what a single-customer repo uses
5
+ # apps/*/* an org-nested layout — kept so a bundle lifted out of the
6
+ # shared monorepo, where apps sit at apps/<org>/<app>/, works
7
+ # here unchanged
8
+ #
9
+ # The URL an app is served at comes from its own oxy-app.json slug, not from
10
+ # its folder depth, so either layout is valid.
11
+ packages:
12
+ - "apps/*"
13
+ - "apps/*/*"
14
+
15
+ # pnpm 10 does not run a dependency's install/postinstall script unless the
16
+ # package is named here. These three are what the real app bundles need
17
+ # (esbuild and unrs-resolver fetch a platform binary, sharp builds one), so
18
+ # without this block the first app added here fails to build. Add to the list
19
+ # rather than replacing it.
20
+ #
21
+ # The key is `onlyBuiltDependencies`, NOT the `allowBuilds` that customer-apps
22
+ # carries. Verified against pnpm 10.8.0: a wrong-typed `onlyBuiltDependencies`
23
+ # crashes inside `createAllowBuildFunction`, proving pnpm reads it, while a
24
+ # wrong-typed `allowBuilds` installs cleanly — pnpm never looks at that key at
25
+ # all, with no warning. customer-apps' block is therefore inert; ours is not.
26
+ #
27
+ # pnpm 9 ignores this file's settings entirely (same probe, pnpm 9.15.9:
28
+ # no crash), which is why package.json floors pnpm at >=10 rather than >=9.
29
+ onlyBuiltDependencies:
30
+ - esbuild
31
+ - sharp
32
+ - unrs-resolver