@zenera/cli 1.1.5 → 1.1.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenera/cli",
3
- "version": "1.1.5",
3
+ "version": "1.1.6",
4
4
  "description": "Command-line front end for @zenera/neo: agentic projects you can run, share and commit.",
5
5
  "keywords": [
6
6
  "agents",
@@ -51,7 +51,7 @@
51
51
  "@inkjs/ui": "^2.0.0",
52
52
  "ink": "^7.1.1",
53
53
  "react": "^19.2.8",
54
- "@zenera/neo": "^1.1.5",
54
+ "@zenera/neo": "^1.1.6",
55
55
  "@anthropic-ai/sdk": "^0.120.0",
56
56
  "@google/genai": "^2.18.0",
57
57
  "@openrouter/sdk": "^1.2.80",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: api-schema-index
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, and giving it to an agent as tools.
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`, and giving it to an agent as tools.
4
4
  ---
5
5
 
6
6
  # The schema index
@@ -42,6 +42,18 @@ the tell.
42
42
  > semantic search for the phrase _"list methods"_ and returns ten ranked
43
43
  > guesses. The listing command is `zen rag schema list methods`.
44
44
 
45
+ > **Never reach for shell `grep`, `rg`, `find`, `cat` or `jq` here.**
46
+ > Not on the specification, not on `graph.json`, not on anything under the
47
+ > index directory. `zen rag schema grep` and `zen rag schema list` are the
48
+ > exact-matching commands, they are local, they need no credential, and they
49
+ > answer in milliseconds. Shell tools on the same files are strictly worse:
50
+ > they match raw YAML/JSON lines rather than nodes, so they cannot tell a
51
+ > field from a `$ref` from a description, cannot say which operation a hit
52
+ > belongs to, cannot filter by kind or direction, and they miss every name
53
+ > the index normalised. A `grep -r password openapi.yaml` returns forty lines
54
+ > of text; `zen rag schema grep password` returns the nodes, with their ids,
55
+ > ready to hand back to `show`.
56
+
45
57
  ## Why it is a graph and not a search box
46
58
 
47
59
  Two structures, kept together, because neither answers alone:
@@ -124,7 +136,7 @@ zen rag schema index <spec...> [--embedding <ref>] [-o <dir>] [--batch <n>]
124
136
  | Flag | Default | Meaning |
125
137
  | ------------------- | ------------- | ------------------------------------------------------------ |
126
138
  | `--embedding <ref>` | — | Which embedder makes the vectors. Omit it to see the choices |
127
- | `-o`, `--out <dir>` | `./schema-db` | Where the index goes |
139
+ | `-o`, `--out <dir>` | `./schema-db` | Where the index goes; `$ZEN_SCHEMA_DB` if that is set |
128
140
  | `--batch <n>` | `96` | Texts per embedding request, and how often progress prints |
129
141
  | `--quiet` | — | No narration |
130
142
 
@@ -149,6 +161,27 @@ a real environment variable always wins.
149
161
  Rebuild the index when the specification changes. Nothing watches it, and a
150
162
  stale index is a confident wrong answer.
151
163
 
164
+ ## Which index gets read
165
+
166
+ Every reading command takes `-d`, `--dir`. Without one:
167
+
168
+ 1. `$ZEN_SCHEMA_DB`, if it is set. **Set this once** instead of typing `-d` on
169
+ every command — `export ZEN_SCHEMA_DB=/assets/schema-db`.
170
+ 2. Otherwise the **nearest index** to the working directory: here, then a short
171
+ way down into it, then up a level and again, stopping at your home
172
+ directory. The one chosen is named on stderr as it is used, so an answer is
173
+ never anonymous.
174
+ 3. Otherwise `./schema-db`, which is only so the error names the directory you
175
+ were expecting.
176
+
177
+ What is looked for is a `manifest.json` — an index is self-describing, so
178
+ nothing searches for a directory _called_ `schema-db` and one called anything
179
+ else is found the same way. `schema-db` is just the name a new one is given.
180
+
181
+ Two indexes the same distance away is refused rather than guessed at: the wrong
182
+ index does not fail, it answers confidently about a different API. Name one
183
+ with `-d`, or set `ZEN_SCHEMA_DB`.
184
+
152
185
  ## Searching it
153
186
 
154
187
  ```
@@ -181,23 +214,24 @@ response field in `--output-property`, an action in `--method`.
181
214
 
182
215
  ### Shaping the answer
183
216
 
184
- | Flag | Default | Meaning |
185
- | --------------------------- | ------------- | ------------------------------------------------------- |
186
- | `-d`, `--dir <dir>` | `./schema-db` | Which index |
187
- | `--embedding <ref>` | the index's | Must be the one the index was built with |
188
- | `--direction <d>` | `any` | `input`, `output` or `any` |
189
- | `--method-type <t>` | `any` | `read_only` (GET/HEAD/OPTIONS) or `read_write` |
190
- | `--exclude-id <id>` | — | Drop a node. Repeatable |
191
- | `--exclude-method <name>` | — | Drop an operation by name. Repeatable |
192
- | `--exclude-type <name>` | — | Drop a schema by name. Repeatable |
193
- | `--exclude-property <name>` | — | Drop a field by name. Repeatable |
194
- | `--limit <n>` | `5` | Seeds kept per term |
195
- | `--max-hops <n>` | `3` | How far apart two hits may be and still join |
196
- | `--max-nodes <n>` | `200` | Nodes per result |
197
- | `--format <f>` | `text` | `text`, `mermaid`, `mermaid-flowchart`, `ts`, `openapi` |
198
- | `--no-docs` | — | Leave the descriptions out |
199
- | `--interactive` | — | Prompt, search, refine. Needs a terminal |
200
- | `--quiet` | — | No narration |
217
+ | Flag | Default | Meaning |
218
+ | --------------------------- | ----------- | ------------------------------------------------------- |
219
+ | `-d`, `--dir <dir>` | found | Which index — see "Which index gets read" |
220
+ | `--embedding <ref>` | the index's | Must be the one the index was built with |
221
+ | `--direction <d>` | `any` | `input`, `output` or `any` |
222
+ | `--method-type <t>` | `any` | `read_only` (GET/HEAD/OPTIONS) or `read_write` |
223
+ | `--exclude-id <id>` | — | Drop a node. Repeatable |
224
+ | `--exclude-method <name>` | — | Drop an operation by name. Repeatable |
225
+ | `--exclude-type <name>` | — | Drop a schema by name. Repeatable |
226
+ | `--exclude-property <name>` | — | Drop a field by name. Repeatable |
227
+ | `--limit <n>` | `5` | Seeds kept per term |
228
+ | `--max-hops <n>` | `3` | How far apart two hits may be and still join |
229
+ | `--max-nodes <n>` | `200` | Nodes per result |
230
+ | `--format <f>` | `text` | `text`, `mermaid`, `mermaid-flowchart`, `ts`, `openapi` |
231
+ | `--show-source` | — | Tag each operation and schema with its document |
232
+ | `--no-docs` | — | Leave the descriptions out |
233
+ | `--interactive` | — | Prompt, search, refine. Needs a terminal |
234
+ | `--quiet` | — | No narration |
201
235
 
202
236
  ```sh
203
237
  zen rag schema search --method "reset a user password" --format ts
@@ -281,6 +315,7 @@ think it is?".
281
315
  | The question | The command |
282
316
  | ------------------------------------------------ | ---------------------------------------- |
283
317
  | "how do I reset a password with this API?" | `search --method "reset a password"` |
318
+ | _anything you would have run `grep` for_ | `grep` / `list` — never the shell |
284
319
  | "what does the create-user request look like?" | `search --input-type "create user"` |
285
320
  | "what operations exist under /users?" | `list methods --path "*/users*"` |
286
321
  | "how many operations are there at all?" | `list methods` (or `--json` for `found`) |
@@ -307,15 +342,16 @@ zen rag schema grep <pattern> [-d <dir>] [filters…]
307
342
 
308
343
  | Flag | For | Meaning |
309
344
  | ------------------- | ------ | ---------------------------------------------- |
310
- | `--name <p>` | `list` | Match the name. Repeatable |
311
- | `--path <p>` | `list` | Match the route (methods). Repeatable |
345
+ | `--name <p>` | both | Match the name. Repeatable |
346
+ | `--path <p>` | both | Match the route it sits on. Repeatable |
347
+ | `--regex` | both | Read every pattern as a regular expression |
348
+ | `--case-sensitive` | both | Stop ignoring case |
349
+ | `--source <name>` | both | Only one document, as `stats` names it |
350
+ | `--show-source` | both | Print which document each row came from |
312
351
  | `--method-type <t>` | `list` | `read_only`, `read_write` or `any` |
313
352
  | `--direction <d>` | `list` | `input`, `output` or `any` |
314
- | `--regex` | `grep` | Read the pattern as a regular expression |
315
- | `--case-sensitive` | `grep` | Stop ignoring case |
316
353
  | `--kind <k>` | `grep` | `method`, `type` or `property`. Repeatable |
317
354
  | `--ids-only` | `grep` | Bare ids, one per line, for piping |
318
- | `--source <name>` | both | Only one document, as `stats` names it |
319
355
  | `--limit <n>` | both | Print at most n; `found` still counts them all |
320
356
  | `--json` | both | `{found, truncated, rows}` / `…, matches}` |
321
357
  | `--quiet` | both | No narration |
@@ -330,6 +366,7 @@ zen rag schema list properties --name password # every field so named
330
366
  zen rag schema grep password # every literal occurrence
331
367
  zen rag schema grep "pass(word|phrase)" --regex
332
368
  zen rag schema grep password --kind type --ids-only
369
+ zen rag schema grep status --path "/invoices/*" # the word, in one corner
333
370
  ```
334
371
 
335
372
  `list` walks one kind of node and matches its structured fields; `grep` matches
@@ -338,6 +375,24 @@ so the two agree on what the API says. A pattern with `*` or `?` is a glob
338
375
  matched against the whole string; a plain word is a substring, so `--name
339
376
  password` finds `ResetPasswordPayload` and `--name "Password*"` finds nothing.
340
377
 
378
+ **`--regex` applies to both commands and to every pattern**, which is the only
379
+ way to say "one of these" — a glob has no alternation:
380
+
381
+ ```sh
382
+ zen rag schema list methods --regex --path "^/(users|teams)/"
383
+ zen rag schema list types --regex --name "(Request|Response)$"
384
+ ```
385
+
386
+ `--name` and `--path` are constraints on `grep` as well, which is what makes a
387
+ common word usable: `grep status` across a whole API is unreadable,
388
+ `grep status --path "/invoices/*" --kind property` is an answer. On `list
389
+ properties` and on `grep`, `--path` matches the route a parameter's operation
390
+ sits on; a schema belongs to no one route, so `--path` never selects one.
391
+
392
+ When an index holds several documents, `--show-source` puts
393
+ `[source: billing_api_v2]` on every row, so which document answered does not
394
+ have to be recovered from `--json`.
395
+
341
396
  Both report `found` as the true total even when `--limit` shortens what is
342
397
  printed, so a cut answer never misreports how much there is. Nothing matching
343
398
  exits 0 with empty stdout — and that emptiness is trustworthy, which is the
@@ -349,19 +404,55 @@ whole point of them.
349
404
  zen rag schema grep token --ids-only | xargs zen rag schema show --format ts
350
405
  ```
351
406
 
407
+ ### Instead of the shell
408
+
409
+ Every reflex that reaches for a shell tool has a command here that answers the
410
+ same question better. Add `-d <dir>` when the index is not the nearest one, or
411
+ name it once with `ZEN_SCHEMA_DB`.
412
+
413
+ | The reflex | The command |
414
+ | --------------------------------------- | ----------------------------------------------------- |
415
+ | `grep -ri password spec.yaml` | `zen rag schema grep password` |
416
+ | `grep -r password` \| _only in schemas_ | `zen rag schema grep password --kind type` |
417
+ | `grep -E "pass(word\|phrase)"` | `zen rag schema grep "pass(word\|phrase)" --regex` |
418
+ | `grep password` (case matters) | `zen rag schema grep password --case-sensitive` |
419
+ | `grep -c` / `wc -l` | `--json`, and read `found` — it counts past `--limit` |
420
+ | `grep -l` / `grep -o` for piping | `zen rag schema grep password --ids-only` |
421
+ | `grep "/users" spec.yaml` | `zen rag schema list methods --path "*/users*"` |
422
+ | `grep -i "updateuser"` | `zen rag schema list methods --name "*Update*"` |
423
+ | `grep "UserSettings"` | `zen rag schema list types --name "*UserSettings*"` |
424
+ | `grep -A5 password` for the field | `zen rag schema list properties --name "*password*"` |
425
+ | `grep -rl password specs/` (which one?) | `zen rag schema grep password --show-source` |
426
+ | `cat`/`yq` a schema out of the document | `zen rag schema show --type UserSettings --format ts` |
427
+ | `ls` the index directory | `zen rag schema stats` |
428
+
429
+ ```sh
430
+ export ZEN_SCHEMA_DB=/assets/schema-db # once, then never again
431
+ zen rag schema grep "password" --limit 10
432
+ zen rag schema list methods --path "*user*"
433
+ zen rag schema list types --name "*UserSettings*"
434
+ zen rag schema list properties --name "*password*" --show-source
435
+ ```
436
+
437
+ If none of these fits the question, the question is about meaning, and the
438
+ answer is `search` — still not the shell.
439
+
352
440
  ### Naming what you want in `show`
353
441
 
354
442
  ```sh
355
443
  zen rag schema show --method GetCurrentUserInfo --format openapi --exact
356
444
  zen rag schema show --type "*Invoice*" --format ts
357
445
  zen rag schema show --source billing-api --format openapi
446
+ zen rag schema show --type "*Invoice*" --show-source
358
447
  ```
359
448
 
360
449
  Ids are one way in, but `--method` and `--type` take the names you already
361
450
  have. A bare name means exactly that name; add `*` to take more than one.
362
- `--exact` prints only what was named instead of the neighbourhood around it,
363
- which with `--format openapi` gives a valid self-contained slice of the
364
- specification enough to generate a client or a mock payload from.
451
+ `--show-source` names the document each node came from, which is the quick way
452
+ to tell two versions of the same API apart. `--exact` prints only what was
453
+ named instead of the neighbourhood around it, which with `--format openapi`
454
+ gives a valid self-contained slice of the specification — enough to generate a
455
+ client or a mock payload from.
365
456
 
366
457
  ## Giving it to an agent
367
458
 
@@ -395,6 +486,12 @@ rank the guess it already made near the top. `grep_api` is the same instinct
395
486
  widened: it is how a model checks that a search returning nothing really means
396
487
  there is nothing.
397
488
 
489
+ `list_api` and `grep_api` take `name`, `path`, `regex` and `source`, so a
490
+ common word can be narrowed to one route or one document rather than read out
491
+ in full. When an index holds more than one document, both tools name the source
492
+ of every row without being asked — with two versions of the same API indexed
493
+ together, which one answered is part of the answer.
494
+
398
495
  Tell the agent in its prompt to search before it writes a call, and to put the
399
496
  intent in the narrow field. A model left to itself puts everything in `all`.
400
497
 
@@ -25,7 +25,7 @@ zen rag schema index <spec...> [--embedding <ref>] [-o <dir>] [--batch <n>]
25
25
  | Flag | Default | Meaning |
26
26
  | ------------------- | ------------- | ------------------------------------------------------------ |
27
27
  | `--embedding <ref>` | — | Which embedder makes the vectors. Omit it to see the choices |
28
- | `-o`, `--out <dir>` | `./schema-db` | Where the index goes |
28
+ | `-o`, `--out <dir>` | `./schema-db` | Where the index goes; `$ZEN_SCHEMA_DB` if set |
29
29
  | `--batch <n>` | `96` | Texts per embedding request, and how often progress prints |
30
30
 
31
31
  ```
@@ -49,6 +49,21 @@ lance/ the vector and full-text indexes
49
49
  The manifest records the embedding ref **and** the embedder's own id, so a
50
50
  search with a different model is refused rather than quietly returning nonsense.
51
51
 
52
+ ## Which index gets read
53
+
54
+ Every reading command takes `-d`, `--dir`. Without one:
55
+
56
+ 1. `$ZEN_SCHEMA_DB`, if it is set.
57
+ 2. Otherwise the **nearest index** to the working directory — here, then a
58
+ short way down, then up a level and again, stopping at your home directory.
59
+ The one used is named on stderr.
60
+ 3. Otherwise `./schema-db`, so the error names the directory you expected.
61
+
62
+ What is looked for is a `manifest.json`, not a directory called `schema-db`, so
63
+ an index called anything else is found the same way. Two the same distance away
64
+ is refused rather than guessed at — name one with `-d`, or set `ZEN_SCHEMA_DB`
65
+ once and stop typing it.
66
+
52
67
  ## `search`
53
68
 
54
69
  ```
@@ -84,23 +99,24 @@ search good. A request field belongs in `--input-property`, a response field in
84
99
 
85
100
  ### Filters and shape
86
101
 
87
- | Flag | Default | Meaning |
88
- | --------------------------- | ------------- | ------------------------------------------------------- |
89
- | `-d`, `--dir <dir>` | `./schema-db` | Which index |
90
- | `--embedding <ref>` | the index's | Must be the one the index was built with |
91
- | `--direction <d>` | `any` | `input`, `output` or `any` |
92
- | `--method-type <t>` | `any` | `read_only`, `read_write` or `any` |
93
- | `--exclude-id <id>` | — | Drop a node. Repeatable |
94
- | `--exclude-method <name>` | — | Drop an operation by name. Repeatable |
95
- | `--exclude-type <name>` | — | Drop a schema by name. Repeatable |
96
- | `--exclude-property <name>` | — | Drop a field by name. Repeatable |
97
- | `--limit <n>` | `5` | Seeds kept per term |
98
- | `--max-hops <n>` | `3` | How far apart two hits may be |
99
- | `--max-nodes <n>` | `200` | Nodes per result |
100
- | `--format <f>` | `text` | `text`, `mermaid`, `mermaid-flowchart`, `ts`, `openapi` |
101
- | `--no-docs` | — | Leave the descriptions out |
102
- | `--interactive` | — | Prompt, search, refine. Needs a terminal |
103
- | `--quiet` | — | No narration |
102
+ | Flag | Default | Meaning |
103
+ | --------------------------- | ----------- | ------------------------------------------------------- |
104
+ | `-d`, `--dir <dir>` | found | Which index — see "Which index gets read" |
105
+ | `--embedding <ref>` | the index's | Must be the one the index was built with |
106
+ | `--direction <d>` | `any` | `input`, `output` or `any` |
107
+ | `--method-type <t>` | `any` | `read_only`, `read_write` or `any` |
108
+ | `--exclude-id <id>` | — | Drop a node. Repeatable |
109
+ | `--exclude-method <name>` | — | Drop an operation by name. Repeatable |
110
+ | `--exclude-type <name>` | — | Drop a schema by name. Repeatable |
111
+ | `--exclude-property <name>` | — | Drop a field by name. Repeatable |
112
+ | `--limit <n>` | `5` | Seeds kept per term |
113
+ | `--max-hops <n>` | `3` | How far apart two hits may be |
114
+ | `--max-nodes <n>` | `200` | Nodes per result |
115
+ | `--format <f>` | `text` | `text`, `mermaid`, `mermaid-flowchart`, `ts`, `openapi` |
116
+ | `--show-source` | — | Tag each operation and schema with its document |
117
+ | `--no-docs` | — | Leave the descriptions out |
118
+ | `--interactive` | — | Prompt, search, refine. Needs a terminal |
119
+ | `--quiet` | — | No narration |
104
120
 
105
121
  ```
106
122
  zen rag schema search --method "reset a user password" --format ts
@@ -135,7 +151,7 @@ quit
135
151
 
136
152
  ```
137
153
  zen rag schema list <methods|types|properties> [-d <dir>] [--name <p>] [--path <p>]
138
- zen rag schema grep <pattern> [-d <dir>] [--regex] [--kind <k>] [--ids-only]
154
+ zen rag schema grep <pattern> [-d <dir>] [--name <p>] [--path <p>] [--kind <k>]
139
155
  ```
140
156
 
141
157
  Exact, and therefore complete. `search` ranks, so it can only hand back the top
@@ -144,26 +160,37 @@ read `graph.json` directly, with no embedder, no credential and no network.
144
160
 
145
161
  | Flag | For | Meaning |
146
162
  | ------------------- | ------ | -------------------------------------------- |
147
- | `--name <p>` | `list` | Match the name. Repeatable |
148
- | `--path <p>` | `list` | Match the route. Repeatable |
163
+ | `--name <p>` | both | Match the name. Repeatable |
164
+ | `--path <p>` | both | Match the route it sits on. Repeatable |
165
+ | `--regex` | both | Read every pattern as a regular expression |
166
+ | `--case-sensitive` | both | Stop ignoring case |
149
167
  | `--source <name>` | both | Only nodes from one document |
168
+ | `--show-source` | both | Print which document each row came from |
150
169
  | `--method-type <t>` | `list` | `read_only`, `read_write` or `any` |
151
170
  | `--direction <d>` | `list` | `input`, `output` or `any` |
152
- | `--regex` | `grep` | Read the pattern as a regular expression |
153
- | `--case-sensitive` | `grep` | Stop ignoring case |
154
171
  | `--kind <k>` | `grep` | `method`, `type` or `property`. Repeatable |
155
172
  | `--ids-only` | `grep` | Just the ids, one per line, for piping |
156
173
  | `--limit <n>` | both | Rows to print. `found` still counts them all |
157
174
 
158
175
  A pattern with `*` or `?` is a glob matched against the whole string; a plain
159
176
  word is a substring. So `--name password` finds `ResetPasswordPayload`, and
160
- `--name "Password*"` finds nothing, because nothing starts with it.
177
+ `--name "Password*"` finds nothing, because nothing starts with it. `--regex`
178
+ makes it a regular expression instead — the only way to say "one of these":
179
+
180
+ ```
181
+ zen rag schema list methods --regex --path "^/(users|teams)/"
182
+ ```
183
+
184
+ `--path` selects on the route an operation sits on, and on the route a
185
+ parameter's operation sits on. A schema belongs to no one route, so `--path`
186
+ never selects one.
161
187
 
162
188
  ```
163
189
  zen rag schema list methods --path "*/users*"
164
190
  zen rag schema list types --name "*Password*"
165
191
  zen rag schema grep password
166
192
  zen rag schema grep "pass(word|phrase)" --regex
193
+ zen rag schema grep status --path "/invoices/*" --kind property
167
194
  zen rag schema grep token --ids-only | xargs zen rag schema show --format ts
168
195
  ```
169
196
 
@@ -175,7 +202,8 @@ total, so a shortened answer never misreports how much there is.
175
202
 
176
203
  ```
177
204
  zen rag schema show [id...] [-d <dir>] [--format <f>]
178
- [--method <name>] [--type <name>] [--source <name>] [--exact]
205
+ [--method <name>] [--type <name>] [--source <name>]
206
+ [--show-source] [--exact]
179
207
  ```
180
208
 
181
209
  Prints named nodes with no search in between. Needs no embedder and no
@@ -183,9 +211,10 @@ credential — it is a read of the graph.
183
211
 
184
212
  Ids are one way in; `--method` and `--type` name things directly, which is
185
213
  usually what you have. A bare name means exactly that name; add `*` to select
186
- more than one. `--source <name>` takes a whole document. `--exact` prints only
187
- what was named instead of the neighbourhood around it with `--format openapi`
188
- that is a valid, self-contained slice of the specification.
214
+ more than one. `--source <name>` takes a whole document, and `--show-source`
215
+ tags each node with the document it came from. `--exact` prints only what was
216
+ named instead of the neighbourhood around it with `--format openapi` that is
217
+ a valid, self-contained slice of the specification.
189
218
 
190
219
  ```
191
220
  zen rag schema show --method GetCurrentUserInfo --format openapi --exact