@zenera/cli 1.1.4 → 1.1.5

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.
@@ -49,7 +49,7 @@ export const EXTERNAL = {
49
49
  rag: {
50
50
  package: '@zenera/rag',
51
51
  summary: 'Search an openapi/swagger document as a graph.',
52
- usage: 'zen rag schema <index|search|show|stats> [spec...]',
52
+ usage: 'zen rag schema <index|search|list|grep|show|stats> [spec...]',
53
53
  install: 'npm i -g @zenera/rag',
54
54
  banner: { head: 'Zenera', accent: 'Rag', subtitle: 'Api Retrieval' },
55
55
  },
package/dist/scaffold.js CHANGED
@@ -26,6 +26,14 @@ import { fileURLToPath } from 'node:url';
26
26
  const TEMPLATES = fileURLToPath(new URL('../templates', import.meta.url));
27
27
  /** The suffix on a file with `{{...}}` in it, dropped when the file lands. */
28
28
  const TEMPLATE = '.tmpl';
29
+ /**
30
+ * This `zen`'s own version, which the scaffold pins the sandbox's tools to.
31
+ * The publishable packages move in lockstep, so one number covers them all.
32
+ */
33
+ function ownVersion() {
34
+ const manifest = fileURLToPath(new URL('../package.json', import.meta.url));
35
+ return JSON.parse(readFileSync(manifest, 'utf8')).version;
36
+ }
29
37
  /**
30
38
  * Fills the `{{name}}` in a template, in the two shapes templates use.
31
39
  *
@@ -169,6 +177,7 @@ export function scaffold(opts) {
169
177
  vars: {
170
178
  model: modelSection(opts.model, opts.modelOptions),
171
179
  exa: opts.web ? part('exa.yaml') : '',
180
+ version: ownVersion(),
172
181
  },
173
182
  });
174
183
  // The directories with no file to put in them: a skill is a folder someone
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenera/cli",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
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.4",
54
+ "@zenera/neo": "^1.1.5",
55
55
  "@anthropic-ai/sdk": "^0.120.0",
56
56
  "@google/genai": "^2.18.0",
57
57
  "@openrouter/sdk": "^1.2.80",
@@ -1,13 +1,13 @@
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`) — including 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, and giving it to an agent as tools.
4
4
  ---
5
5
 
6
6
  # The schema index
7
7
 
8
8
  A schema index is an OpenAPI/Swagger description turned into something that can
9
9
  be **asked a question**. It is built once, on disk, and answered from without a
10
- model: `zen rag schema index` writes it, `zen rag schema search` queries it.
10
+ model: `zen rag schema index` writes it, and five commands read it.
11
11
 
12
12
  It exists because a real specification does not fit in a context window, and
13
13
  grepping it does not help. The parts that answer "how do I reset a password?"
@@ -15,6 +15,33 @@ are scattered on purpose: the field is on a schema, the schema is a request
15
15
  body, the request body belongs to one operation out of three hundred, and the
16
16
  word "password" appears in forty places that are not the one you want.
17
17
 
18
+ ## The commands
19
+
20
+ ```
21
+ zen rag schema <index|search|list|grep|show|stats> [spec...]
22
+ ```
23
+
24
+ | Command | Answers | Embedder? | Typical |
25
+ | -------- | -------------------------------------- | --------- | ------- |
26
+ | `index` | builds the thing | yes | minutes |
27
+ | `search` | _what is this API's way to do X?_ | **yes** | seconds |
28
+ | `list` | _what methods/types/fields are there?_ | no | instant |
29
+ | `grep` | _does the string X appear anywhere?_ | no | instant |
30
+ | `show` | _print exactly these things_ | no | instant |
31
+ | `stats` | _what is in this index?_ | no | instant |
32
+
33
+ Only `search` ranks, and only `search` costs a network round trip — it embeds
34
+ the query before it can compare anything. The other four read `graph.json` off
35
+ the disk and answer in milliseconds, so reach for `search` when the question is
36
+ vague and for `list`/`grep` when it is precise. If a search feels slow, it is
37
+ that one embedding call, not the index: near-zero CPU for several seconds is
38
+ the tell.
39
+
40
+ > **`search` takes bare words as the query, not as a subcommand.**
41
+ > `zen rag schema search list methods` does not list anything — it runs a
42
+ > semantic search for the phrase _"list methods"_ and returns ten ranked
43
+ > guesses. The listing command is `zen rag schema list methods`.
44
+
18
45
  ## Why it is a graph and not a search box
19
46
 
20
47
  Two structures, kept together, because neither answers alone:
@@ -236,7 +263,8 @@ quit
236
263
 
237
264
  ## Reading it without searching
238
265
 
239
- Both need no embedder and no credential — they are plain reads.
266
+ None of these need an embedder or a credential — they are plain reads of the
267
+ graph on disk.
240
268
 
241
269
  ```sh
242
270
  zen rag schema show Type:Invoice Method:listInvoices --format ts
@@ -248,9 +276,96 @@ an index and what built it — counts by kind, the embedding model, the document
248
276
  it came from. `stats` is the fastest way to answer "is this index the one I
249
277
  think it is?".
250
278
 
279
+ ### Which one to reach for
280
+
281
+ | The question | The command |
282
+ | ------------------------------------------------ | ---------------------------------------- |
283
+ | "how do I reset a password with this API?" | `search --method "reset a password"` |
284
+ | "what does the create-user request look like?" | `search --input-type "create user"` |
285
+ | "what operations exist under /users?" | `list methods --path "*/users*"` |
286
+ | "how many operations are there at all?" | `list methods` (or `--json` for `found`) |
287
+ | "is there a field called `mfa_secret` anywhere?" | `grep mfa_secret` |
288
+ | "which schemas mention tenancy?" | `grep tenancy --kind type` |
289
+ | "give me `GetUser` as OpenAPI" | `show --method GetUser --format openapi` |
290
+ | "is this index the right one?" | `stats` |
291
+
292
+ The rule: **a question about meaning is a `search`; a question about presence,
293
+ count or spelling is a `list` or a `grep`.** Search cannot answer the second
294
+ kind, because a ranking always returns its best guesses whether or not any of
295
+ them are right.
296
+
297
+ ### Exact matching, when the question is whether something exists
298
+
299
+ Search **ranks**. A ranking returns the top of a list, which means it can never
300
+ tell you that something is absent — "no results" and "not there" look the same.
301
+ When that is the actual question, do not search:
302
+
303
+ ```
304
+ zen rag schema list <methods|types|properties> [-d <dir>] [filters…]
305
+ zen rag schema grep <pattern> [-d <dir>] [filters…]
306
+ ```
307
+
308
+ | Flag | For | Meaning |
309
+ | ------------------- | ------ | ---------------------------------------------- |
310
+ | `--name <p>` | `list` | Match the name. Repeatable |
311
+ | `--path <p>` | `list` | Match the route (methods). Repeatable |
312
+ | `--method-type <t>` | `list` | `read_only`, `read_write` or `any` |
313
+ | `--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
+ | `--kind <k>` | `grep` | `method`, `type` or `property`. Repeatable |
317
+ | `--ids-only` | `grep` | Bare ids, one per line, for piping |
318
+ | `--source <name>` | both | Only one document, as `stats` names it |
319
+ | `--limit <n>` | both | Print at most n; `found` still counts them all |
320
+ | `--json` | both | `{found, truncated, rows}` / `…, matches}` |
321
+ | `--quiet` | both | No narration |
322
+
323
+ ```sh
324
+ zen rag schema list methods # all of them, sorted by route
325
+ zen rag schema list methods --path "*/users*" # every route under /users
326
+ zen rag schema list methods --method-type read_only
327
+ zen rag schema list types --name "*Password*" # every schema so named
328
+ zen rag schema list types --direction output # everything a call can return
329
+ zen rag schema list properties --name password # every field so named
330
+ zen rag schema grep password # every literal occurrence
331
+ zen rag schema grep "pass(word|phrase)" --regex
332
+ zen rag schema grep password --kind type --ids-only
333
+ ```
334
+
335
+ `list` walks one kind of node and matches its structured fields; `grep` matches
336
+ the text of every node in the index — the same text the search was built from,
337
+ so the two agree on what the API says. A pattern with `*` or `?` is a glob
338
+ matched against the whole string; a plain word is a substring, so `--name
339
+ password` finds `ResetPasswordPayload` and `--name "Password*"` finds nothing.
340
+
341
+ Both report `found` as the true total even when `--limit` shortens what is
342
+ printed, so a cut answer never misreports how much there is. Nothing matching
343
+ exits 0 with empty stdout — and that emptiness is trustworthy, which is the
344
+ whole point of them.
345
+
346
+ `grep --ids-only` composes:
347
+
348
+ ```sh
349
+ zen rag schema grep token --ids-only | xargs zen rag schema show --format ts
350
+ ```
351
+
352
+ ### Naming what you want in `show`
353
+
354
+ ```sh
355
+ zen rag schema show --method GetCurrentUserInfo --format openapi --exact
356
+ zen rag schema show --type "*Invoice*" --format ts
357
+ zen rag schema show --source billing-api --format openapi
358
+ ```
359
+
360
+ Ids are one way in, but `--method` and `--type` take the names you already
361
+ 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.
365
+
251
366
  ## Giving it to an agent
252
367
 
253
- The same engine, as four tools in the group `schema`. An agent takes them all
368
+ The same engine, as five tools in the group `schema`. An agent takes them all
254
369
  with `schema:*` in its `tools:`.
255
370
 
256
371
  ```ts
@@ -269,12 +384,16 @@ const project = await loadProject('./my-project', { tools: schemaTools(index) })
269
384
  | `search_api` | the search above, with the same fields |
270
385
  | `describe_types` | named schemas as TypeScript, closed over what they refer to |
271
386
  | `find_types_with_property` | every schema with a field of this name — exact lookup, no searching |
272
- | `list_methods` | operations by path, to see the shape of the API before asking |
387
+ | `list_api` | methods, types or fields by name complete, and counted in full |
388
+ | `grep_api` | every literal occurrence of a string — the way to prove absence |
273
389
 
274
- `find_types_with_property` is the one for the repair loop. When `tsc` says
275
- `'password' does not exist in type 'PublicUserProfile'`, the model does not need
276
- the word explained again it needs the list of types that _do_ have one, and
277
- embedding the word will only rank the guess it already made near the top.
390
+ Only `search_api` ranks; the other four are exact. `find_types_with_property`
391
+ is the one for the repair loop. When `tsc` says `'password' does not exist in
392
+ type 'PublicUserProfile'`, the model does not need the word explained again
393
+ it needs the list of types that _do_ have one, and embedding the word will only
394
+ rank the guess it already made near the top. `grep_api` is the same instinct
395
+ widened: it is how a model checks that a search returning nothing really means
396
+ there is nothing.
278
397
 
279
398
  Tell the agent in its prompt to search before it writes a call, and to put the
280
399
  intent in the narrow field. A model left to itself puts everything in `all`.
@@ -290,3 +409,5 @@ intent in the narrow field. A model left to itself puts everything in `all`.
290
409
  | A usage error before any credential is asked for | Deliberate: everything about the invocation is checked first, so a typo is a typo |
291
410
  | Nothing matched | Exit 0 with an empty answer. Try fewer words, or `--all` instead of a narrow field |
292
411
  | Answers about the wrong version of the API | Nothing watches the document. Re-index after it changes |
412
+ | A search took ten seconds, using no CPU | One embedding round trip, not the index. `list`/`grep` make none |
413
+ | `search <word> <word>` gave ranked nonsense | Bare words after `search` are the QUERY, not a subcommand. You meant `list`/`grep` |
@@ -28,7 +28,7 @@ zen sandbox [status|up|pull|clean|disk] [options]
28
28
  zen version
29
29
 
30
30
  zen faker <serve|build|cache> [spec...]
31
- zen rag schema <index|search|show|stats> [spec...]
31
+ zen rag schema <index|search|list|grep|show|stats> [spec...]
32
32
  ```
33
33
 
34
34
  ## Read the reference before answering
@@ -1,7 +1,7 @@
1
1
  # API search — `zen rag`
2
2
 
3
3
  ```
4
- zen rag schema <index|search|show|stats> [spec...]
4
+ zen rag schema <index|search|list|grep|show|stats> [spec...]
5
5
  ```
6
6
 
7
7
  Provided by `@zenera/rag` — `npm i -g @zenera/rag` if `zen rag` says it is not
@@ -58,6 +58,11 @@ zen rag schema search [terms…] [filters…]
58
58
  Every argument is validated before an embedder is constructed, so a typo is a
59
59
  usage error rather than a credential error.
60
60
 
61
+ **Bare words are the query, not a subcommand.** `zen rag schema search list
62
+ methods` searches for the phrase _"list methods"_ and returns ranked guesses;
63
+ `zen rag schema list methods` is the listing. `search` is also the only read
64
+ command that embeds, so it is the only slow one.
65
+
61
66
  ### Terms — repeatable, and the field is the point
62
67
 
63
68
  | Term | Searches |
@@ -126,15 +131,67 @@ reset forget it, exclusions included
126
131
  quit
127
132
  ```
128
133
 
134
+ ## `list` and `grep`
135
+
136
+ ```
137
+ 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]
139
+ ```
140
+
141
+ Exact, and therefore complete. `search` ranks, so it can only hand back the top
142
+ of a list — it cannot tell you that something is _not_ there. These can: they
143
+ read `graph.json` directly, with no embedder, no credential and no network.
144
+
145
+ | Flag | For | Meaning |
146
+ | ------------------- | ------ | -------------------------------------------- |
147
+ | `--name <p>` | `list` | Match the name. Repeatable |
148
+ | `--path <p>` | `list` | Match the route. Repeatable |
149
+ | `--source <name>` | both | Only nodes from one document |
150
+ | `--method-type <t>` | `list` | `read_only`, `read_write` or `any` |
151
+ | `--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
+ | `--kind <k>` | `grep` | `method`, `type` or `property`. Repeatable |
155
+ | `--ids-only` | `grep` | Just the ids, one per line, for piping |
156
+ | `--limit <n>` | both | Rows to print. `found` still counts them all |
157
+
158
+ A pattern with `*` or `?` is a glob matched against the whole string; a plain
159
+ word is a substring. So `--name password` finds `ResetPasswordPayload`, and
160
+ `--name "Password*"` finds nothing, because nothing starts with it.
161
+
162
+ ```
163
+ zen rag schema list methods --path "*/users*"
164
+ zen rag schema list types --name "*Password*"
165
+ zen rag schema grep password
166
+ zen rag schema grep "pass(word|phrase)" --regex
167
+ zen rag schema grep token --ids-only | xargs zen rag schema show --format ts
168
+ ```
169
+
170
+ No match exits 0 with nothing on stdout — that is the answer, and unlike an
171
+ empty search it is a reliable one. Under `--limit`, `found` is still the true
172
+ total, so a shortened answer never misreports how much there is.
173
+
129
174
  ## `show`
130
175
 
131
176
  ```
132
- zen rag schema show <id...> [-d <dir>] [--format <f>]
177
+ zen rag schema show [id...] [-d <dir>] [--format <f>]
178
+ [--method <name>] [--type <name>] [--source <name>] [--exact]
133
179
  ```
134
180
 
135
181
  Prints named nodes with no search in between. Needs no embedder and no
136
182
  credential — it is a read of the graph.
137
183
 
184
+ Ids are one way in; `--method` and `--type` name things directly, which is
185
+ 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.
189
+
190
+ ```
191
+ zen rag schema show --method GetCurrentUserInfo --format openapi --exact
192
+ zen rag schema show --type "*Invoice*" --format ts
193
+ ```
194
+
138
195
  ## `stats`
139
196
 
140
197
  ```
@@ -153,7 +210,12 @@ documents it came from. Also needs no embedder.
153
210
  | `search_api` | The search above, with the same fields |
154
211
  | `describe_types` | Named schemas as TypeScript, closed over what they refer to |
155
212
  | `find_types_with_property` | Every schema with a field of this name — exact lookup, no searching |
156
- | `list_methods` | Operations by path, to see the shape of the API before asking |
213
+ | `list_api` | Methods, types or fields by name complete, and counted in full |
214
+ | `grep_api` | Every literal occurrence of a string — the way to prove absence |
215
+
216
+ Only `search_api` ranks. Reach for the others whenever the question is whether
217
+ something exists, because a search that returns nothing and a thing that is not
218
+ there look exactly the same.
157
219
 
158
220
  They share the group `schema`, so an agent takes them with `schema:*` in its
159
221
  `tools:`.
@@ -18,4 +18,6 @@ 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
- RUN npm install -g @zenera/cli @zenera/rag
21
+ # Pinned to the `zen` that wrote this file, so the tools in the container are
22
+ # the ones the session outside it was built by. Bump both together.
23
+ RUN npm install -g @zenera/cli@{{version}} @zenera/rag@{{version}}