@zenera/cli 1.1.15 → 1.1.16

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 (30) hide show
  1. package/README.md +183 -119
  2. package/dist/banner.d.ts +5 -1
  3. package/dist/banner.js +2 -2
  4. package/dist/command.d.ts +5 -0
  5. package/dist/main.js +10 -6
  6. package/package.json +2 -2
  7. package/templates/editor/.github/copilot-instructions.md +253 -218
  8. package/templates/editor/.github/prompts/new-agent.prompt.md +6 -6
  9. package/templates/editor/.github/prompts/new-skill.prompt.md +7 -7
  10. package/templates/editor/.github/prompts/review-project.prompt.md +1 -1
  11. package/templates/editor/.github/prompts/sync-with-spec.prompt.md +26 -26
  12. package/templates/editor/.github/skills/zen-cli/SKILL.md +62 -6
  13. package/templates/editor/.github/skills/zen-cli/references/check.md +3 -3
  14. package/templates/editor/.github/skills/zen-cli/references/faker.md +14 -14
  15. package/templates/editor/.github/skills/zen-cli/references/frame.md +6 -6
  16. package/templates/editor/.github/skills/zen-cli/references/inspect.md +3 -3
  17. package/templates/editor/.github/skills/zen-cli/references/keys.md +9 -9
  18. package/templates/editor/.github/skills/zen-cli/references/memory.md +11 -11
  19. package/templates/editor/.github/skills/zen-cli/references/models.md +8 -8
  20. package/templates/editor/.github/skills/zen-cli/references/projects.md +3 -3
  21. package/templates/editor/.github/skills/zen-cli/references/rag.md +191 -56
  22. package/templates/editor/.github/skills/zen-cli/references/run.md +3 -3
  23. package/templates/editor/.github/skills/zen-cli/references/sandbox.md +8 -8
  24. package/templates/editor/.github/skills/zen-memory/SKILL.md +50 -50
  25. package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +71 -48
  26. package/templates/editor/.github/skills/zen-rag-schema/SKILL.md +67 -67
  27. package/templates/editor/.vscode/settings.json +5 -2
  28. package/templates/project/INSTRUCTIONS.md +1 -1
  29. package/templates/project/assets/README.md +1 -1
  30. package/templates/project/sandbox/Dockerfile.tmpl +3 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: zen-rag-schema
3
- description: What a schema index is, how it finds the right call inside a large OpenAPI/Swagger document, and how to build and query one with `zen rag schema` (or `npx @zenera/cli`) — searching it by meaning, listing and grepping it exactly instead of reaching for shell `grep`/`rg`, tracing a field up to the operations that carry it, giving it to an agent as tools, and writing the project skill that a wired-in index requires. For a pile of markdown instead of an API description, see `zen-rag-docs`.
3
+ description: What a schema index is, how it finds the right call inside a large OpenAPI/Swagger document, and how to build and query one with `zen rag schema` (or `npx @zenera/cli`) - searching it by meaning, listing and grepping it exactly instead of reaching for shell `grep`/`rg`, tracing a field up to the operations that carry it, giving it to an agent as tools, and writing the project skill that a wired-in index requires. For a pile of markdown instead of an API description, see `zen-rag-docs`.
4
4
  ---
5
5
 
6
6
  # The schema index
@@ -31,7 +31,7 @@ zen rag schema <index|search|list|grep|trace|show|stats> [spec...]
31
31
  | `show` | _print exactly these things_ | no | instant |
32
32
  | `stats` | _what is in this index?_ | no | instant |
33
33
 
34
- Only `search` ranks, and only `search` costs a network round trip — it embeds
34
+ Only `search` ranks, and only `search` costs a network round trip - it embeds
35
35
  the query before it can compare anything. The other five read `graph.json` off
36
36
  the disk and answer in milliseconds, so reach for `search` when the question is
37
37
  vague and for `list`/`grep`/`trace` when it is precise. If a search feels slow,
@@ -39,7 +39,7 @@ it is that one embedding call, not the index: near-zero CPU for several seconds
39
39
  is the tell.
40
40
 
41
41
  > **`search` takes bare words as the query, not as a subcommand.**
42
- > `zen rag schema search list methods` does not list anything — it runs a
42
+ > `zen rag schema search list methods` does not list anything - it runs a
43
43
  > semantic search for the phrase _"list methods"_ and returns ten ranked
44
44
  > guesses. The listing command is `zen rag schema list methods`.
45
45
 
@@ -67,8 +67,8 @@ Two structures, kept together, because neither answers alone:
67
67
  Finding the field is retrieval. Getting from the field to `POST
68
68
  /auth/reset-password` and the exact shape of its body is traversal. So a search
69
69
  does both: it seeds on the vector hits, walks the graph outward from them, and
70
- answers with **the connected piece of the API that matched** — the operations,
71
- the schemas they carry and the fields inside them — rather than a ranked list of
70
+ answers with **the connected piece of the API that matched** - the operations,
71
+ the schemas they carry and the fields inside them - rather than a ranked list of
72
72
  fragments naming types nobody printed.
73
73
 
74
74
  ## What is in one
@@ -85,10 +85,10 @@ Three kinds of node, joined by the `$ref`s between them. Documents are
85
85
  | `property` | `Method:listUsers#page_size` | a query/path/header **parameter** |
86
86
 
87
87
  A parameter is a property like any other, deliberately. Nobody should have to
88
- know in advance whether `page_size` lives in a query string or a body — that is
88
+ know in advance whether `page_size` lives in a query string or a body - that is
89
89
  the thing they came here to find out.
90
90
 
91
- Every node carries a **direction** — `input`, `output` or `both` — propagated
91
+ Every node carries a **direction** - `input`, `output` or `both` - propagated
92
92
  from the operations down through composition, so a DTO used on both sides is
93
93
  honestly both rather than whichever side was read last. That is what makes
94
94
  "a field in a **response**" a filter and not a hope.
@@ -98,7 +98,7 @@ TypeScript compiler can narrow.
98
98
 
99
99
  ```
100
100
  schema-db/
101
- ├── manifest.json written LAST — its absence means "not indexed"
101
+ ├── manifest.json written LAST - its absence means "not indexed"
102
102
  ├── graph.json topology, read whole
103
103
  ├── schemas.json the raw schemas, read on first hydrate
104
104
  ├── operations.json likewise
@@ -111,7 +111,7 @@ different model is refused rather than answered with noise.
111
111
  ## Installing
112
112
 
113
113
  `zen rag` ships in `@zenera/rag`, a sibling of the CLI. It has no binary of its
114
- own — installing it adds the `rag` subcommand to `zen`, which is also where the
114
+ own - installing it adds the `rag` subcommand to `zen`, which is also where the
115
115
  credentials already live.
116
116
 
117
117
  ```sh
@@ -136,11 +136,11 @@ zen rag schema index <spec...> [--embedding <ref>] [-o <dir>] [--batch <n>]
136
136
 
137
137
  | Flag | Default | Meaning |
138
138
  | ------------------- | ------------- | ------------------------------------------------------------ |
139
- | `--embedding <ref>` | — | Which embedder makes the vectors. Omit it to see the choices |
139
+ | `--embedding <ref>` | - | Which embedder makes the vectors. Omit it to see the choices |
140
140
  | `-o`, `--out <dir>` | `./schema-db` | Where the index goes; `$ZEN_SCHEMA_DB` if that is set |
141
141
  | `--batch <n>` | `96` | Texts per embedding request, and how often progress prints |
142
- | `--no-sources` | — | Keep no copy of the documents; `show --source` rebuilds them |
143
- | `--quiet` | — | No narration |
142
+ | `--no-sources` | - | Keep no copy of the documents; `show --source` rebuilds them |
143
+ | `--quiet` | - | No narration |
144
144
 
145
145
  ```sh
146
146
  zen rag schema index openapi.yaml --embedding openai:text-embedding-3-small
@@ -154,7 +154,7 @@ what you want when an API is split across files. Swagger 2.0 and OpenAPI
154
154
  This is the one command here that spends money and time: it embeds every
155
155
  operation, schema and field. It prints a per-document table (paths, operations,
156
156
  schemas, fields) and a progress line per batch, and **stdout is the output
157
- directory and nothing else** — so `DIR=$(zen rag schema index …)` works.
157
+ directory and nothing else** - so `DIR=$(zen rag schema index …)` works.
158
158
 
159
159
  The embedding reference names a provider first: `openai:text-embedding-3-small`,
160
160
  not a bare model id. Credentials come from the `zen` keyring (`zen key ls`), and
@@ -168,7 +168,7 @@ stale index is a confident wrong answer.
168
168
  Every reading command takes `-d`, `--dir`. Without one:
169
169
 
170
170
  1. `$ZEN_SCHEMA_DB`, if it is set. **Set this once** instead of typing `-d` on
171
- every command — `export ZEN_SCHEMA_DB=/assets/schema-db`.
171
+ every command - `export ZEN_SCHEMA_DB=/assets/schema-db`.
172
172
  2. Otherwise the **nearest index** to the working directory: here, then a short
173
173
  way down into it, then up a level and again, stopping at your home
174
174
  directory. The one chosen is named on stderr as it is used, so an answer is
@@ -176,7 +176,7 @@ Every reading command takes `-d`, `--dir`. Without one:
176
176
  3. Otherwise `./schema-db`, which is only so the error names the directory you
177
177
  were expecting.
178
178
 
179
- What is looked for is a `manifest.json` — an index is self-describing, so
179
+ What is looked for is a `manifest.json` - an index is self-describing, so
180
180
  nothing searches for a directory _called_ `schema-db` and one called anything
181
181
  else is found the same way. `schema-db` is just the name a new one is given.
182
182
 
@@ -193,13 +193,13 @@ zen rag schema search [terms…] [filters…]
193
193
  ### The field is the point
194
194
 
195
195
  A query is not one string. It is a handful of **fields**, and the field a phrase
196
- arrives in decides the filter it runs under — `--output-property "invoice total"`
196
+ arrives in decides the filter it runs under - `--output-property "invoice total"`
197
197
  means _kind=property, on the response side_, and none of that has to be said
198
198
  twice.
199
199
 
200
200
  | Term | Searches |
201
201
  | ----------------------- | ------------------------------------------------- |
202
- | `<text>` | Everything — the same as `--all` |
202
+ | `<text>` | Everything - the same as `--all` |
203
203
  | `--all <q>` | Everything, unfiltered |
204
204
  | `--method <q>` | Operations |
205
205
  | `--type <q>` | Schemas, on the side `--direction` names |
@@ -210,7 +210,7 @@ twice.
210
210
  | `--output-property <q>` | Fields a call returns |
211
211
  | `--query <json\|->` | A whole query object; `-` reads stdin |
212
212
 
213
- Every term is repeatable. **`--all` is the weakest of them** — it cannot filter,
213
+ Every term is repeatable. **`--all` is the weakest of them** - it cannot filter,
214
214
  so put the intent where it belongs: a request field in `--input-property`, a
215
215
  response field in `--output-property`, an action in `--method`.
216
216
 
@@ -218,23 +218,23 @@ response field in `--output-property`, an action in `--method`.
218
218
 
219
219
  | Flag | Default | Meaning |
220
220
  | --------------------------- | ----------- | ---------------------------------------------------------- |
221
- | `-d`, `--dir <dir>` | found | Which index — see "Which index gets read" |
221
+ | `-d`, `--dir <dir>` | found | Which index - see "Which index gets read" |
222
222
  | `--embedding <ref>` | the index's | Must be the one the index was built with |
223
223
  | `--direction <d>` | `any` | `input`, `output` or `any` |
224
224
  | `--method-type <t>` | `any` | `read_only` (GET/HEAD/OPTIONS) or `read_write` |
225
- | `--exclude-id <id>` | — | Drop a node. Repeatable |
226
- | `--exclude-method <name>` | — | Drop an operation by name. Repeatable |
227
- | `--exclude-type <name>` | — | Drop a schema by name. Repeatable |
228
- | `--exclude-property <name>` | — | Drop a field by name. Repeatable |
225
+ | `--exclude-id <id>` | - | Drop a node. Repeatable |
226
+ | `--exclude-method <name>` | - | Drop an operation by name. Repeatable |
227
+ | `--exclude-type <name>` | - | Drop a schema by name. Repeatable |
228
+ | `--exclude-property <name>` | - | Drop a field by name. Repeatable |
229
229
  | `--source <name>` | every one | Search only this document, as `stats` names it. Repeatable |
230
230
  | `--limit <n>` | `5` | Seeds kept per term |
231
231
  | `--max-hops <n>` | `3` | How far apart two hits may be and still join |
232
232
  | `--max-nodes <n>` | `200` | Nodes per result |
233
233
  | `--format <f>` | `text` | `text`, `mermaid`, `mermaid-flowchart`, `ts`, `openapi` |
234
- | `--show-source` | — | Tag each operation and schema with its document |
235
- | `--no-docs` | — | Leave the descriptions out |
236
- | `--interactive` | — | Prompt, search, refine. Needs a terminal |
237
- | `--quiet` | — | No narration |
234
+ | `--show-source` | - | Tag each operation and schema with its document |
235
+ | `--no-docs` | - | Leave the descriptions out |
236
+ | `--interactive` | - | Prompt, search, refine. Needs a terminal |
237
+ | `--quiet` | - | No narration |
238
238
 
239
239
  ```sh
240
240
  zen rag schema search --method "reset a user password" --format ts
@@ -244,14 +244,14 @@ zen rag schema search --method "apply a tag" --source policy_api --show-source
244
244
  ```
245
245
 
246
246
  When an index holds two revisions of one API, `--source` is what keeps the
247
- answer inside the one you mean — the same words rank in both, so without it the
247
+ answer inside the one you mean - the same words rank in both, so without it the
248
248
  two have to be told apart by eye after the fact. It is repeatable, and the names
249
249
  it takes are the ones `stats` prints; anything else is refused, and the refusal
250
250
  lists what the index does hold.
251
251
 
252
252
  `--format ts` emits TypeScript closed over its own `$ref`s: everything named is
253
253
  also declared, so the output compiles on its own. `--format openapi` emits a
254
- standalone document holding just the matched slice — the one to hand to a code
254
+ standalone document holding just the matched slice - the one to hand to a code
255
255
  generator or a mock server. The Mermaid formats are for looking at.
256
256
 
257
257
  ### Turning one search into a session
@@ -267,7 +267,7 @@ zen rag schema search --all "subscription" --exclude-type Subscription --exclude
267
267
 
268
268
  Non-interactive search is a tool, not an afterthought. Every field is a flag,
269
269
  the whole query can arrive as one JSON object, `--json` is a stable shape, no
270
- terminal is needed, and **an empty result exits 0** — a caller must never have
270
+ terminal is needed, and **an empty result exits 0** - a caller must never have
271
271
  to tell "nothing matched" from "the index is missing" by parsing stderr.
272
272
 
273
273
  ```sh
@@ -307,7 +307,7 @@ quit
307
307
 
308
308
  ## Reading it without searching
309
309
 
310
- None of these need an embedder or a credential — they are plain reads of the
310
+ None of these need an embedder or a credential - they are plain reads of the
311
311
  graph on disk.
312
312
 
313
313
  ```sh
@@ -316,7 +316,7 @@ zen rag schema stats
316
316
  ```
317
317
 
318
318
  `show` prints named nodes with no retrieval in between; `stats` says what is in
319
- an index and what built it — counts by kind, the embedding model, the documents
319
+ an index and what built it - counts by kind, the embedding model, the documents
320
320
  it came from. `stats` is the fastest way to answer "is this index the one I
321
321
  think it is?".
322
322
 
@@ -325,7 +325,7 @@ think it is?".
325
325
  | The question | The command |
326
326
  | ------------------------------------------------ | ---------------------------------------- |
327
327
  | "how do I reset a password with this API?" | `search --method "reset a password"` |
328
- | _anything you would have run `grep` for_ | `grep` / `list` — never the shell |
328
+ | _anything you would have run `grep` for_ | `grep` / `list` - never the shell |
329
329
  | "which call can reach this field?" | `trace <field>` |
330
330
  | "what does the create-user request look like?" | `search --input-type "create user"` |
331
331
  | "what operations exist under /users?" | `list methods --path "*/users*"` |
@@ -343,7 +343,7 @@ its best guesses whether or not any of them are right.
343
343
  ### Exact matching, when the question is whether something exists
344
344
 
345
345
  Search **ranks**. A ranking returns the top of a list, which means it can never
346
- tell you that something is absent — "no results" and "not there" look the same.
346
+ tell you that something is absent - "no results" and "not there" look the same.
347
347
  When that is the actual question, do not search:
348
348
 
349
349
  ```
@@ -381,12 +381,12 @@ zen rag schema grep status --path "/invoices/*" # the word, in one corner
381
381
  ```
382
382
 
383
383
  `list` walks one kind of node and matches its structured fields; `grep` matches
384
- the text of every node in the index — the same text the search was built from,
384
+ the text of every node in the index - the same text the search was built from,
385
385
  so the two agree on what the API says. A pattern with `*` or `?` is a glob
386
386
  matched against the whole string; a plain word is a substring, so `--name
387
387
  password` finds `ResetPasswordPayload` and `--name "Password*"` finds nothing.
388
388
 
389
- **`--regex` is the only way to say "one of these"** — a glob has no alternation.
389
+ **`--regex` is the only way to say "one of these"** - a glob has no alternation.
390
390
  On `list` it turns every pattern into a regular expression. On `grep` it turns
391
391
  the **pattern** into one; `--name` and `--path` stay globs-or-substrings there,
392
392
  because they are always names:
@@ -409,7 +409,7 @@ have to be recovered from `--json`.
409
409
 
410
410
  Both report `found` as the true total even when `--limit` shortens what is
411
411
  printed, so a cut answer never misreports how much there is. Nothing matching
412
- exits 0 with empty stdout — and that emptiness is trustworthy, which is the
412
+ exits 0 with empty stdout - and that emptiness is trustworthy, which is the
413
413
  whole point of them.
414
414
 
415
415
  `grep --ids-only` composes:
@@ -420,13 +420,13 @@ zen rag schema grep token --ids-only | xargs zen rag schema show --format ts
420
420
 
421
421
  ### Upwards, from a field to the calls that carry it
422
422
 
423
- Finding the field is half the job. The other half — which operation can
424
- actually reach it — is a walk up the `$ref`s.
423
+ Finding the field is half the job. The other half - which operation can
424
+ actually reach it - is a walk up the `$ref`s.
425
425
 
426
426
  `search` does part of it: it stitches its seeds into one connected piece and
427
427
  prints what each operation accepts and returns, so a lucky search does show the
428
428
  call. But it joins only what **ranked**, only within `--max-hops` (3 by
429
- default), and it never names the chain — and the call almost never repeats the
429
+ default), and it never names the chain - and the call almost never repeats the
430
430
  word, so `GET /users/{userId}` and `city` have nothing in common except the
431
431
  edges between them. `trace` follows those edges instead of guessing at them:
432
432
  exhaustive, and certain.
@@ -454,14 +454,14 @@ the answer.
454
454
  | `--kind <k>` | types + properties | `method`, `type` or `property`. Repeatable |
455
455
  | `--direction <d>` | `any` | Only the calls that accept it, or that return it |
456
456
  | `--max-hops <n>` | `8` | How far up to walk |
457
- | `--limit <n>` | — | Trace at most n matching nodes |
458
- | `--routes <n>` | — | Operations printed per node; `found` counts them all |
459
- | `--ids-only` | — | Bare operation ids, one per line, for piping |
460
- | `--regex` | — | Read the pattern as a regex; `--case-sensitive` too |
461
- | `--source <name>` | — | Only nodes from one document |
462
- | `--show-source` | — | Print which document each operation came from |
463
-
464
- A bare word matches the way `list --name` does — a substring, or a glob when it
457
+ | `--limit <n>` | - | Trace at most n matching nodes |
458
+ | `--routes <n>` | - | Operations printed per node; `found` counts them all |
459
+ | `--ids-only` | - | Bare operation ids, one per line, for piping |
460
+ | `--regex` | - | Read the pattern as a regex; `--case-sensitive` too |
461
+ | `--source <name>` | - | Only nodes from one document |
462
+ | `--show-source` | - | Print which document each operation came from |
463
+
464
+ A bare word matches the way `list --name` does - a substring, or a glob when it
465
465
  has `*` or `?`. A node id (`Type:User`) is taken as that node rather than as a
466
466
  pattern. Operations are left out of a name match on purpose: they are where a
467
467
  trace ends, not where one starts.
@@ -487,7 +487,7 @@ name it once with `ZEN_SCHEMA_DB`.
487
487
  | `grep -r password` \| _only in schemas_ | `zen rag schema grep password --kind type` |
488
488
  | `grep -E "pass(word\|phrase)"` | `zen rag schema grep "pass(word\|phrase)" --regex` |
489
489
  | `grep password` (case matters) | `zen rag schema grep password --case-sensitive` |
490
- | `grep -c` / `wc -l` | `--json`, and read `found` — it counts past `--limit` |
490
+ | `grep -c` / `wc -l` | `--json`, and read `found` - it counts past `--limit` |
491
491
  | `grep -l` / `grep -o` for piping | `zen rag schema grep password --ids-only` |
492
492
  | `grep "/users" spec.yaml` | `zen rag schema list methods --path "*/users*"` |
493
493
  | `grep -i "updateuser"` | `zen rag schema list methods --name "*Update*"` |
@@ -506,7 +506,7 @@ zen rag schema list properties --name "*password*" --show-source
506
506
  ```
507
507
 
508
508
  If none of these fits the question, the question is about meaning, and the
509
- answer is `search` — still not the shell.
509
+ answer is `search` - still not the shell.
510
510
 
511
511
  ### Naming what you want in `show`
512
512
 
@@ -522,7 +522,7 @@ have. A bare name means exactly that name; add `*` to take more than one.
522
522
  `--show-source` names the document each node came from, which is the quick way
523
523
  to tell two versions of the same API apart. `--exact` prints only what was
524
524
  named instead of the neighbourhood around it, which with `--format openapi`
525
- gives a valid self-contained slice of the specification — enough to generate a
525
+ gives a valid self-contained slice of the specification - enough to generate a
526
526
  client or a mock payload from.
527
527
 
528
528
  ## Giving it to an agent
@@ -545,25 +545,25 @@ const project = await loadProject('./my-project', { tools: schemaTools(index) })
545
545
  | -------------------------- | ------------------------------------------------------------------- |
546
546
  | `search_api` | the search above, with the same fields |
547
547
  | `describe_types` | named schemas as TypeScript, closed over what they refer to |
548
- | `find_types_with_property` | every schema with a field of this name — exact lookup, no searching |
549
- | `list_api` | methods, types or fields by name — complete, and counted in full |
550
- | `grep_api` | every literal occurrence of a string — the way to prove absence |
548
+ | `find_types_with_property` | every schema with a field of this name - exact lookup, no searching |
549
+ | `list_api` | methods, types or fields by name - complete, and counted in full |
550
+ | `grep_api` | every literal occurrence of a string - the way to prove absence |
551
551
  | `trace_api` | up from a field or schema to the operations that carry it |
552
552
 
553
553
  Only `search_api` ranks; the other five are exact. `find_types_with_property`
554
554
  is the one for the repair loop. When `tsc` says `'password' does not exist in
555
- type 'PublicUserProfile'`, the model does not need the word explained again —
555
+ type 'PublicUserProfile'`, the model does not need the word explained again -
556
556
  it needs the list of types that _do_ have one, and embedding the word will only
557
557
  rank the guess it already made near the top. `grep_api` is the same instinct
558
558
  widened: it is how a model checks that a search returning nothing really means
559
559
  there is nothing. `trace_api` is the step after either of them: a field is of
560
560
  no use until the call that carries it is known, and no ranking will find that
561
- call — the operation and the field share no words, only edges.
561
+ call - the operation and the field share no words, only edges.
562
562
 
563
563
  `list_api` and `grep_api` take `name`, `path`, `regex` and `source`, so a
564
564
  common word can be narrowed to one route or one document rather than read out
565
565
  in full. When an index holds more than one document, both tools name the source
566
- of every row without being asked — with two versions of the same API indexed
566
+ of every row without being asked - with two versions of the same API indexed
567
567
  together, which one answered is part of the answer.
568
568
 
569
569
  Tell the agent in its prompt to search before it writes a call, and to put the
@@ -571,8 +571,8 @@ intent in the narrow field. A model left to itself puts everything in `all`.
571
571
 
572
572
  ## Wiring it into a project means writing the project a skill
573
573
 
574
- **Whenever an index is used by a Zenera project — as `schema:*` tools, or as
575
- `zen rag` reachable from the agent's sandbox — write a skill for it in that
574
+ **Whenever an index is used by a Zenera project - as `schema:*` tools, or as
575
+ `zen rag` reachable from the agent's sandbox - write a skill for it in that
576
576
  project.** Not optional, and not the same thing as passing the tools in.
577
577
 
578
578
  Wiring alone leaves the model to infer everything that matters. A tool
@@ -580,7 +580,7 @@ description says what `grep_api` does; it cannot say that this index holds the
580
580
  ACME policy API, that names are `snake_case`, that every route is under
581
581
  `/api/v1/policy`, or that `list_api` is the right first move here because the
582
582
  API has three hundred operations and search will hand back five. That is
583
- project knowledge, and project knowledge belongs in a skill — where it is
583
+ project knowledge, and project knowledge belongs in a skill - where it is
584
584
  loaded only when the model is actually working on this API, instead of sitting
585
585
  in the system prompt of every run.
586
586
 
@@ -598,10 +598,10 @@ find_types_with_property]` if the skill should be what unlocks them.
598
598
  | Section | Because |
599
599
  | --------------- | ------------------------------------------------------------------------------------------------------------------ |
600
600
  | Which API | The name, the version, and the document it was built from |
601
- | Where the index | `ZEN_SCHEMA_DB`, or the `-d` to pass — an agent cannot guess a path |
601
+ | Where the index | `ZEN_SCHEMA_DB`, or the `-d` to pass - an agent cannot guess a path |
602
602
  | Which command | Meaning → `search`; presence, spelling or a count → `list`/`grep`; which endpoint carries a field → `trace` |
603
603
  | Never the shell | State it outright. `grep`/`rg`/`jq` on the spec is the default reflex |
604
- | The conventions | Auth, base path, pagination, casing, error envelope — none of it is in the graph |
604
+ | The conventions | Auth, base path, pagination, casing, error envelope - none of it is in the graph |
605
605
  | Worked examples | Two or three, with **real operation and schema names from this index** |
606
606
  | The repair loop | Compiler said the field is not on the type → `find_types_with_property`, then `trace` for the call that carries it |
607
607
 
@@ -614,7 +614,7 @@ Best practice, in order of how often it is got wrong:
614
614
  prove absence. Left alone a model searches for everything, gets five ranked
615
615
  guesses, and writes a call against the best of them.
616
616
  3. **Keep it short.** A skill is prompt. One screen of routing rules and
617
- conventions beats a transcription of this document — link to `zen rag
617
+ conventions beats a transcription of this document - link to `zen rag
618
618
  schema --help` for the flags.
619
619
  4. **Re-index, then re-read the skill.** Both go stale against the same
620
620
  change, and a skill quoting operations that no longer exist is worse than
@@ -625,7 +625,7 @@ schema --help` for the flags.
625
625
  ```md
626
626
  ---
627
627
  name: billing-api
628
- description: How to find the right call in the Acme Billing API (v2) — which schema
628
+ description: How to find the right call in the Acme Billing API (v2) - which schema
629
629
  carries which field, and which endpoint accepts it. Use before writing any request.
630
630
  ---
631
631
 
@@ -639,7 +639,7 @@ all under `/v2`. Bearer token in `Authorization`; cursors, never page numbers.
639
639
  - Does X exist, how is it spelled, how many are there → `list_api` / `grep_api`.
640
640
  These are complete; a search is not, and cannot prove absence.
641
641
  - Which endpoint carries this field → `trace_api`. Never guess the owner.
642
- - Never `grep`/`rg`/`jq` the spec — the tools above are local and exact.
642
+ - Never `grep`/`rg`/`jq` the spec - the tools above are local and exact.
643
643
 
644
644
  Worked: the invoice total is `Invoice.amount_due` (minor units), returned by
645
645
  `GetInvoice` and `ListInvoices`; `trace_api of: amount_due` shows both.
@@ -649,10 +649,10 @@ Worked: the invoice total is `Invoice.amount_due` (minor units), returned by
649
649
 
650
650
  | Symptom | Cause |
651
651
  | ------------------------------------------------ | ----------------------------------------------------------------------------------- |
652
- | "not installed" | `@zenera/rag` is not resolvable — install it, or use the two-`--package` npx form |
652
+ | "not installed" | `@zenera/rag` is not resolvable - install it, or use the two-`--package` npx form |
653
653
  | Refused for a different embedding | The index records the model that built it; re-index or pass the right `--embedding` |
654
654
  | No manifest / not an index | A build that did not finish. `manifest.json` is written last on purpose |
655
- | `provider "openai": no api key` | `zen key ls` — the keyring, or a real environment variable |
655
+ | `provider "openai": no api key` | `zen key ls` - the keyring, or a real environment variable |
656
656
  | A usage error before any credential is asked for | Deliberate: everything about the invocation is checked first, so a typo is a typo |
657
657
  | Nothing matched | Exit 0 with an empty answer. Try fewer words, or `--all` instead of a narrow field |
658
658
  | Answers about the wrong version of the API | Nothing watches the document. Re-index after it changes |
@@ -2,7 +2,10 @@
2
2
  "chat.useNestedAgentsMdFiles": false,
3
3
  "chat.tools.terminal.enableAutoApprove": true,
4
4
  "chat.tools.terminal.ignoreDefaultAutoApproveRules": true,
5
- "chat.tools.edits.autoApprove": {
6
- "**": true
5
+ "chat.tools.edits.autoApprove": true,
6
+ "chat.tools.terminal.autoApprove": {
7
+ "^(?:(?:export\\s+[A-Za-z_][A-Za-z0-9_]*(?:=[^;\\s]+)?\\s*;\\s*)|(?:[A-Za-z_][A-Za-z0-9_]*=[^;\\s]+\\s+))*(?:cat|echo|head|ls|pwd|rg|tail|which)(?:\\s|$)": true,
8
+ "^(?:(?:export\\s+[A-Za-z_][A-Za-z0-9_]*(?:=[^;\\s]+)?\\s*;\\s*)|(?:[A-Za-z_][A-Za-z0-9_]*=[^;\\s]+\\s+))*(?:git\\s+(?:branch|diff|log|show|status)|npm\\s+(?:run\\s+(?:build|format:check|lint|test|typecheck)|test)|tsc)(?:\\s|$)": true,
9
+ "^(?:(?:export\\s+[A-Za-z_][A-Za-z0-9_]*(?:=[^;\\s]+)?\\s*;\\s*)|(?:[A-Za-z_][A-Za-z0-9_]*=[^;\\s]+\\s+))*(?:zen|zn|zenera)(?:\\s|$)": true
7
10
  }
8
11
  }
@@ -12,4 +12,4 @@ Say what you did, file by file, and say what you deliberately did not do.
12
12
 
13
13
  Every claim about what a command did has to be one you can make because you ran
14
14
  it and read the output. When something cannot be established, say so and say
15
- what would settle it — a guess offered as an answer is worse than no answer.
15
+ what would settle it - a guess offered as an answer is worse than no answer.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Everything in this folder is mounted at /assets when an agent runs. Every agent
4
4
  in this project can read, list and search it, and no tool of theirs can change
5
- it — so this is where reference material goes: handbooks, specifications,
5
+ it - so this is where reference material goes: handbooks, specifications,
6
6
  schemas, worked examples, the style guide the output is supposed to follow.
7
7
 
8
8
  It is the project's own files that agents get without being asked. The
@@ -18,6 +18,9 @@ COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
18
18
  RUN ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
19
19
  && ln -s /usr/local/lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx
20
20
 
21
+ # Keep the heavyweight provider SDKs cached when the pinned CLI release changes.
22
+ RUN npm install -g @anthropic-ai/sdk @google/genai @openrouter/sdk openai
23
+
21
24
  # Pinned to the `zen` that wrote this file, so the tools in the container are
22
25
  # the ones the session outside it was built by. Bump both together.
23
26
  RUN npm install -g @zenera/cli@{{version}} @zenera/rag@{{version}}