@zenera/cli 1.1.6 → 1.1.9

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.
@@ -48,8 +48,8 @@ export const EXTERNAL = {
48
48
  },
49
49
  rag: {
50
50
  package: '@zenera/rag',
51
- summary: 'Search an openapi/swagger document as a graph.',
52
- usage: 'zen rag schema <index|search|list|grep|show|stats> [spec...]',
51
+ summary: 'Retrieval over a corpus: index it, then ask it something.',
52
+ usage: 'zen rag <subject> <command> [args...]',
53
53
  install: 'npm i -g @zenera/rag',
54
54
  banner: { head: 'Zenera', accent: 'Rag', subtitle: 'Api Retrieval' },
55
55
  },
package/dist/podman.d.ts CHANGED
@@ -34,15 +34,6 @@ export interface PodmanStatus {
34
34
  imagePresent?: boolean;
35
35
  }
36
36
  export declare function ensurePodmanReady(opts?: PodmanOptions): Promise<void>;
37
- /**
38
- * Builds the project's Dockerfile under its tag.
39
- *
40
- * Run every time rather than skipped when the tag already exists, because the
41
- * tag is a hash of the Dockerfile and its context and the engine's layer cache
42
- * is a hash of the same thing plus the base image. Asking it is cheap, and it
43
- * is the only thing that notices when `FROM node:24` starts meaning a different
44
- * node:24.
45
- */
46
37
  /**
47
38
  * A build that ran and failed, rather than a host that could not be asked.
48
39
  *
package/dist/podman.js CHANGED
@@ -167,15 +167,6 @@ function parseMachines(stdout) {
167
167
  // ---------------------------------------------------------------------------
168
168
  // The image
169
169
  // ---------------------------------------------------------------------------
170
- /**
171
- * Builds the project's Dockerfile under its tag.
172
- *
173
- * Run every time rather than skipped when the tag already exists, because the
174
- * tag is a hash of the Dockerfile and its context and the engine's layer cache
175
- * is a hash of the same thing plus the base image. Asking it is cheap, and it
176
- * is the only thing that notices when `FROM node:24` starts meaning a different
177
- * node:24.
178
- */
179
170
  /**
180
171
  * A build that ran and failed, rather than a host that could not be asked.
181
172
  *
@@ -204,7 +195,9 @@ async function build(engine, spec, run, force = false) {
204
195
  return;
205
196
  }
206
197
  }
207
- note(dim(`building ${spec.tag} from ${spec.dockerfile}`));
198
+ note(dim(`${force ? 'rebuilding' : 'building'} the sandbox image from ${spec.dockerfile}`));
199
+ note(dim(' the agent runs its commands inside a container, so the image is needed first'));
200
+ note(dim(' this takes a few minutes; later runs reuse it until the Dockerfile changes'));
208
201
  const built = await stream(engine, ['build', '--tag', spec.tag, '--file', spec.dockerfile, spec.context], run, BUILD_MS);
209
202
  if (built.code !== 0) {
210
203
  throw new BuildError(`could not build ${spec.dockerfile}`, EXIT.sandbox, last(built) || 'run the build by hand to see what the engine says');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenera/cli",
3
- "version": "1.1.6",
3
+ "version": "1.1.9",
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.6",
54
+ "@zenera/neo": "^1.1.9",
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`) — searching it by meaning, listing and grepping it exactly instead of reaching for shell `grep`/`rg`, 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`, 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.
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, and five commands read it.
10
+ model: `zen rag schema index` writes it, and six 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?"
@@ -18,7 +18,7 @@ word "password" appears in forty places that are not the one you want.
18
18
  ## The commands
19
19
 
20
20
  ```
21
- zen rag schema <index|search|list|grep|show|stats> [spec...]
21
+ zen rag schema <index|search|list|grep|trace|show|stats> [spec...]
22
22
  ```
23
23
 
24
24
  | Command | Answers | Embedder? | Typical |
@@ -27,15 +27,16 @@ zen rag schema <index|search|list|grep|show|stats> [spec...]
27
27
  | `search` | _what is this API's way to do X?_ | **yes** | seconds |
28
28
  | `list` | _what methods/types/fields are there?_ | no | instant |
29
29
  | `grep` | _does the string X appear anywhere?_ | no | instant |
30
+ | `trace` | _which call can reach this field?_ | no | instant |
30
31
  | `show` | _print exactly these things_ | no | instant |
31
32
  | `stats` | _what is in this index?_ | no | instant |
32
33
 
33
34
  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 query before it can compare anything. The other five read `graph.json` off
35
36
  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.
37
+ vague and for `list`/`grep`/`trace` when it is precise. If a search feels slow,
38
+ it is that one embedding call, not the index: near-zero CPU for several seconds
39
+ is the tell.
39
40
 
40
41
  > **`search` takes bare words as the query, not as a subcommand.**
41
42
  > `zen rag schema search list methods` does not list anything — it runs a
@@ -138,6 +139,7 @@ zen rag schema index <spec...> [--embedding <ref>] [-o <dir>] [--batch <n>]
138
139
  | `--embedding <ref>` | — | Which embedder makes the vectors. Omit it to see the choices |
139
140
  | `-o`, `--out <dir>` | `./schema-db` | Where the index goes; `$ZEN_SCHEMA_DB` if that is set |
140
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 |
141
143
  | `--quiet` | — | No narration |
142
144
 
143
145
  ```sh
@@ -316,6 +318,7 @@ think it is?".
316
318
  | ------------------------------------------------ | ---------------------------------------- |
317
319
  | "how do I reset a password with this API?" | `search --method "reset a password"` |
318
320
  | _anything you would have run `grep` for_ | `grep` / `list` — never the shell |
321
+ | "which call can reach this field?" | `trace <field>` |
319
322
  | "what does the create-user request look like?" | `search --input-type "create user"` |
320
323
  | "what operations exist under /users?" | `list methods --path "*/users*"` |
321
324
  | "how many operations are there at all?" | `list methods` (or `--json` for `found`) |
@@ -325,9 +328,9 @@ think it is?".
325
328
  | "is this index the right one?" | `stats` |
326
329
 
327
330
  The rule: **a question about meaning is a `search`; a question about presence,
328
- count or spelling is a `list` or a `grep`.** Search cannot answer the second
329
- kind, because a ranking always returns its best guesses whether or not any of
330
- them are right.
331
+ count or spelling is a `list` or a `grep`; a question about reachability is a
332
+ `trace`.** Search cannot answer the last two, because a ranking always returns
333
+ its best guesses whether or not any of them are right.
331
334
 
332
335
  ### Exact matching, when the question is whether something exists
333
336
 
@@ -344,7 +347,7 @@ zen rag schema grep <pattern> [-d <dir>] [filters…]
344
347
  | ------------------- | ------ | ---------------------------------------------- |
345
348
  | `--name <p>` | both | Match the name. Repeatable |
346
349
  | `--path <p>` | both | Match the route it sits on. Repeatable |
347
- | `--regex` | both | Read every pattern as a regular expression |
350
+ | `--regex` | both | Read the patterns as regular expressions |
348
351
  | `--case-sensitive` | both | Stop ignoring case |
349
352
  | `--source <name>` | both | Only one document, as `stats` names it |
350
353
  | `--show-source` | both | Print which document each row came from |
@@ -375,12 +378,15 @@ so the two agree on what the API says. A pattern with `*` or `?` is a glob
375
378
  matched against the whole string; a plain word is a substring, so `--name
376
379
  password` finds `ResetPasswordPayload` and `--name "Password*"` finds nothing.
377
380
 
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:
381
+ **`--regex` is the only way to say "one of these"** a glob has no alternation.
382
+ On `list` it turns every pattern into a regular expression. On `grep` it turns
383
+ the **pattern** into one; `--name` and `--path` stay globs-or-substrings there,
384
+ because they are always names:
380
385
 
381
386
  ```sh
382
387
  zen rag schema list methods --regex --path "^/(users|teams)/"
383
388
  zen rag schema list types --regex --name "(Request|Response)$"
389
+ zen rag schema grep "pass(word|phrase)" --regex --path "*/users*"
384
390
  ```
385
391
 
386
392
  `--name` and `--path` are constraints on `grep` as well, which is what makes a
@@ -404,6 +410,63 @@ whole point of them.
404
410
  zen rag schema grep token --ids-only | xargs zen rag schema show --format ts
405
411
  ```
406
412
 
413
+ ### Upwards, from a field to the calls that carry it
414
+
415
+ Finding the field is half the job. The other half — which operation can
416
+ actually reach it — is a walk up the `$ref`s.
417
+
418
+ `search` does part of it: it stitches its seeds into one connected piece and
419
+ prints what each operation accepts and returns, so a lucky search does show the
420
+ call. But it joins only what **ranked**, only within `--max-hops` (3 by
421
+ default), and it never names the chain — and the call almost never repeats the
422
+ word, so `GET /users/{userId}` and `city` have nothing in common except the
423
+ edges between them. `trace` follows those edges instead of guessing at them:
424
+ exhaustive, and certain.
425
+
426
+ ```
427
+ zen rag schema trace <pattern|id...> [-d <dir>] [filters…]
428
+ ```
429
+
430
+ ```sh
431
+ zen rag schema trace city
432
+ ```
433
+
434
+ ```
435
+ Property:Address.city
436
+ GET /users/{userId} getUser output PublicUserProfile.address → Address.city
437
+ ```
438
+
439
+ One command instead of three lookups and a guess: find the field, find what
440
+ holds `Address`, find what accepts _that_, and hope you followed every branch.
441
+ The last column is the whole route, so the shape of the call can be read off
442
+ the answer.
443
+
444
+ | Flag | Default | Meaning |
445
+ | ----------------- | ------------------ | ---------------------------------------------------- |
446
+ | `--kind <k>` | types + properties | `method`, `type` or `property`. Repeatable |
447
+ | `--direction <d>` | `any` | Only the calls that accept it, or that return it |
448
+ | `--max-hops <n>` | `8` | How far up to walk |
449
+ | `--limit <n>` | — | Trace at most n matching nodes |
450
+ | `--routes <n>` | — | Operations printed per node; `found` counts them all |
451
+ | `--ids-only` | — | Bare operation ids, one per line, for piping |
452
+ | `--regex` | — | Read the pattern as a regex; `--case-sensitive` too |
453
+ | `--source <name>` | — | Only nodes from one document |
454
+ | `--show-source` | — | Print which document each operation came from |
455
+
456
+ A bare word matches the way `list --name` does — a substring, or a glob when it
457
+ has `*` or `?`. A node id (`Type:User`) is taken as that node rather than as a
458
+ pattern. Operations are left out of a name match on purpose: they are where a
459
+ trace ends, not where one starts.
460
+
461
+ ```sh
462
+ zen rag schema trace password --direction input
463
+ zen rag schema trace "*Settings" --kind type
464
+ zen rag schema trace mfa_secret --ids-only | xargs zen rag schema show --format openapi
465
+ ```
466
+
467
+ `no operation reaches it` is a real answer, and one worth having: the schema is
468
+ unreachable in this document, so no request will ever carry it.
469
+
407
470
  ### Instead of the shell
408
471
 
409
472
  Every reflex that reaches for a shell tool has a command here that answers the
@@ -456,7 +519,7 @@ client or a mock payload from.
456
519
 
457
520
  ## Giving it to an agent
458
521
 
459
- The same engine, as five tools in the group `schema`. An agent takes them all
522
+ The same engine, as six tools in the group `schema`. An agent takes them all
460
523
  with `schema:*` in its `tools:`.
461
524
 
462
525
  ```ts
@@ -477,14 +540,17 @@ const project = await loadProject('./my-project', { tools: schemaTools(index) })
477
540
  | `find_types_with_property` | every schema with a field of this name — exact lookup, no searching |
478
541
  | `list_api` | methods, types or fields by name — complete, and counted in full |
479
542
  | `grep_api` | every literal occurrence of a string — the way to prove absence |
543
+ | `trace_api` | up from a field or schema to the operations that carry it |
480
544
 
481
- Only `search_api` ranks; the other four are exact. `find_types_with_property`
545
+ Only `search_api` ranks; the other five are exact. `find_types_with_property`
482
546
  is the one for the repair loop. When `tsc` says `'password' does not exist in
483
547
  type 'PublicUserProfile'`, the model does not need the word explained again —
484
548
  it needs the list of types that _do_ have one, and embedding the word will only
485
549
  rank the guess it already made near the top. `grep_api` is the same instinct
486
550
  widened: it is how a model checks that a search returning nothing really means
487
- there is nothing.
551
+ there is nothing. `trace_api` is the step after either of them: a field is of
552
+ no use until the call that carries it is known, and no ranking will find that
553
+ call — the operation and the field share no words, only edges.
488
554
 
489
555
  `list_api` and `grep_api` take `name`, `path`, `regex` and `source`, so a
490
556
  common word can be narrowed to one route or one document rather than read out
@@ -495,6 +561,82 @@ together, which one answered is part of the answer.
495
561
  Tell the agent in its prompt to search before it writes a call, and to put the
496
562
  intent in the narrow field. A model left to itself puts everything in `all`.
497
563
 
564
+ ## Wiring it into a project means writing the project a skill
565
+
566
+ **Whenever an index is used by a Zenera project — as `schema:*` tools, or as
567
+ `zen rag` reachable from the agent's sandbox — write a skill for it in that
568
+ project.** Not optional, and not the same thing as passing the tools in.
569
+
570
+ Wiring alone leaves the model to infer everything that matters. A tool
571
+ description says what `grep_api` does; it cannot say that this index holds the
572
+ NSX policy API, that names are `snake_case`, that every route is under
573
+ `/policy/api/v1`, or that `list_api` is the right first move here because the
574
+ API has three hundred operations and search will hand back five. That is
575
+ project knowledge, and project knowledge belongs in a skill — where it is
576
+ loaded only when the model is actually working on this API, instead of sitting
577
+ in the system prompt of every run.
578
+
579
+ ```
580
+ <project>/agents/skills/<api>-api/SKILL.md
581
+ ```
582
+
583
+ Frontmatter is `name` and `description`; the description is what the model
584
+ reads when choosing, so it must name the API and the questions it answers.
585
+ Add `tools: [search_api, list_api, grep_api, trace_api, describe_types,
586
+ find_types_with_property]` if the skill should be what unlocks them.
587
+
588
+ ### What the skill has to say
589
+
590
+ | Section | Because |
591
+ | --------------- | ------------------------------------------------------------------------------------------------------------------ |
592
+ | Which API | The name, the version, and the document it was built from |
593
+ | Where the index | `ZEN_SCHEMA_DB`, or the `-d` to pass — an agent cannot guess a path |
594
+ | Which command | Meaning → `search`; presence, spelling or a count → `list`/`grep`; which endpoint carries a field → `trace` |
595
+ | Never the shell | State it outright. `grep`/`rg`/`jq` on the spec is the default reflex |
596
+ | The conventions | Auth, base path, pagination, casing, error envelope — none of it is in the graph |
597
+ | Worked examples | Two or three, with **real operation and schema names from this index** |
598
+ | The repair loop | Compiler said the field is not on the type → `find_types_with_property`, then `trace` for the call that carries it |
599
+
600
+ Best practice, in order of how often it is got wrong:
601
+
602
+ 1. **Use real names.** `list_api types --name "*Policy*"` with output the model
603
+ will actually see beats a generic `<TypeName>` placeholder, because the
604
+ names are the anchors it steers by.
605
+ 2. **Say which command answers which question**, and say that a ranking cannot
606
+ prove absence. Left alone a model searches for everything, gets five ranked
607
+ guesses, and writes a call against the best of them.
608
+ 3. **Keep it short.** A skill is prompt. One screen of routing rules and
609
+ conventions beats a transcription of this document — link to `zen rag
610
+ schema --help` for the flags.
611
+ 4. **Re-index, then re-read the skill.** Both go stale against the same
612
+ change, and a skill quoting operations that no longer exist is worse than
613
+ none.
614
+ 5. **One skill per API**, named after it. Two APIs in one skill and the model
615
+ mixes their conventions.
616
+
617
+ ```md
618
+ ---
619
+ name: billing-api
620
+ description: How to find the right call in the Acme Billing API (v2) — which schema
621
+ carries which field, and which endpoint accepts it. Use before writing any request.
622
+ ---
623
+
624
+ # The Billing API
625
+
626
+ Indexed at `/assets/schema-db` (already in `$ZEN_SCHEMA_DB`). 214 operations,
627
+ all under `/v2`. Bearer token in `Authorization`; cursors, never page numbers.
628
+
629
+ - Vague question ("how do I cancel a subscription?") → `search_api`, intent in
630
+ the narrowest field: `methods`, not `all`.
631
+ - Does X exist, how is it spelled, how many are there → `list_api` / `grep_api`.
632
+ These are complete; a search is not, and cannot prove absence.
633
+ - Which endpoint carries this field → `trace_api`. Never guess the owner.
634
+ - Never `grep`/`rg`/`jq` the spec — the tools above are local and exact.
635
+
636
+ Worked: the invoice total is `Invoice.amount_due` (minor units), returned by
637
+ `GetInvoice` and `ListInvoices`; `trace_api of: amount_due` shows both.
638
+ ```
639
+
498
640
  ## When it goes wrong
499
641
 
500
642
  | Symptom | Cause |
@@ -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|list|grep|show|stats> [spec...]
31
+ zen rag schema <index|search|list|grep|trace|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|list|grep|show|stats> [spec...]
4
+ zen rag schema <index|search|list|grep|trace|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
@@ -27,6 +27,8 @@ zen rag schema index <spec...> [--embedding <ref>] [-o <dir>] [--batch <n>]
27
27
  | `--embedding <ref>` | — | Which embedder makes the vectors. Omit it to see the choices |
28
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
+ | `--no-sources` | — | Keep no copy of the documents in the index |
31
+ | `--quiet` | — | No narration |
30
32
 
31
33
  ```
32
34
  zen rag schema index openapi.yaml --embedding openai:text-embedding-3-small
@@ -162,7 +164,7 @@ read `graph.json` directly, with no embedder, no credential and no network.
162
164
  | ------------------- | ------ | -------------------------------------------- |
163
165
  | `--name <p>` | both | Match the name. Repeatable |
164
166
  | `--path <p>` | both | Match the route it sits on. Repeatable |
165
- | `--regex` | both | Read every pattern as a regular expression |
167
+ | `--regex` | both | Read the patterns as regular expressions |
166
168
  | `--case-sensitive` | both | Stop ignoring case |
167
169
  | `--source <name>` | both | Only nodes from one document |
168
170
  | `--show-source` | both | Print which document each row came from |
@@ -171,11 +173,18 @@ read `graph.json` directly, with no embedder, no credential and no network.
171
173
  | `--kind <k>` | `grep` | `method`, `type` or `property`. Repeatable |
172
174
  | `--ids-only` | `grep` | Just the ids, one per line, for piping |
173
175
  | `--limit <n>` | both | Rows to print. `found` still counts them all |
176
+ | `--quiet` | both | No narration |
177
+
178
+ Under `--json`: `{found, truncated, rows}` from `list`, `{found, truncated,
179
+ matches}` from `grep`. `grep` takes **one** pattern — quote it if it has
180
+ spaces — and `list` **one** subject.
174
181
 
175
182
  A pattern with `*` or `?` is a glob matched against the whole string; a plain
176
183
  word is a substring. So `--name password` finds `ResetPasswordPayload`, and
177
184
  `--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":
185
+ makes it a regular expression instead — the only way to say "one of these". On
186
+ `list` it applies to every pattern; on `grep` it applies to the pattern, while
187
+ `--name` and `--path` stay globs-or-substrings:
179
188
 
180
189
  ```
181
190
  zen rag schema list methods --regex --path "^/(users|teams)/"
@@ -198,12 +207,71 @@ No match exits 0 with nothing on stdout — that is the answer, and unlike an
198
207
  empty search it is a reliable one. Under `--limit`, `found` is still the true
199
208
  total, so a shortened answer never misreports how much there is.
200
209
 
210
+ ## `trace`
211
+
212
+ ```
213
+ zen rag schema trace <pattern|id...> [-d <dir>] [--kind <k>] [--direction <d>]
214
+ [--max-hops <n>] [--limit <n>] [--routes <n>]
215
+ [--ids-only] [--regex] [--case-sensitive] [--source <name>]
216
+ [--show-source]
217
+ ```
218
+
219
+ The question `list` and `grep` leave you holding: you have found the field, so
220
+ **which call can reach it?** `trace` walks up the `$ref`s from every node of
221
+ that name to the operations that accept or return it, and prints the chain in
222
+ between. Also a plain graph read — no embedder, no credential.
223
+
224
+ ```
225
+ zen rag schema trace city
226
+ ```
227
+
228
+ ```
229
+ Property:Address.city
230
+ GET /users/{userId} getUser output PublicUserProfile.address → Address.city
231
+ ```
232
+
233
+ By hand that is three lookups and a guess: find the field, find what holds
234
+ `Address`, find what accepts _that_, and hope you followed every branch.
235
+
236
+ `search` does some of this already — it stitches its seeds into one connected
237
+ piece and prints what each operation accepts and returns. But it links only
238
+ what **ranked**, only within `--max-hops`, and it never names the chain; and
239
+ `getUser` and `city` share no word, so the field has to rank on its own and the
240
+ call has to land near it. `trace` follows the edges instead, which are already
241
+ there and are certain.
242
+
243
+ | Flag | Default | Meaning |
244
+ | ----------------- | ------------------ | ---------------------------------------------------- |
245
+ | `--kind <k>` | types + properties | `method`, `type` or `property`. Repeatable |
246
+ | `--direction <d>` | `any` | Keep only the calls that accept it, or return it |
247
+ | `--max-hops <n>` | `8` | How far up to walk |
248
+ | `--limit <n>` | — | Trace at most n matching nodes |
249
+ | `--routes <n>` | — | Operations printed per node; `found` counts them all |
250
+ | `--ids-only` | — | Bare operation ids, one per line, for piping |
251
+ | `--regex` | — | Read the pattern as a regex; `--case-sensitive` too |
252
+ | `--source <name>` | — | Only nodes from one document |
253
+ | `--show-source` | — | Print which document each operation came from |
254
+
255
+ A bare word is matched the way `list --name` matches it — substring, or a glob
256
+ when it has `*` or `?`. A node id (`Type:User`) is taken as that node and not
257
+ as a pattern. Operations are left out of a name match on purpose: they are
258
+ where a trace ends.
259
+
260
+ ```
261
+ zen rag schema trace password --direction input
262
+ zen rag schema trace "*Settings" --kind type
263
+ zen rag schema trace mfa_secret --ids-only | xargs zen rag schema show --format openapi
264
+ ```
265
+
266
+ A node nothing carries prints `no operation reaches it` — a real answer, and
267
+ one worth having: it means the schema is unreachable in this document.
268
+
201
269
  ## `show`
202
270
 
203
271
  ```
204
- zen rag schema show [id...] [-d <dir>] [--format <f>]
272
+ zen rag schema show [id...] [-d <dir>] [--format <f>] [--exact]
205
273
  [--method <name>] [--type <name>] [--source <name>]
206
- [--show-source] [--exact]
274
+ [--show-source] [--max-nodes <n>] [--no-docs] [--quiet]
207
275
  ```
208
276
 
209
277
  Prints named nodes with no search in between. Needs no embedder and no
@@ -214,7 +282,16 @@ usually what you have. A bare name means exactly that name; add `*` to select
214
282
  more than one. `--source <name>` takes a whole document, and `--show-source`
215
283
  tags each node with the document it came from. `--exact` prints only what was
216
284
  named instead of the neighbourhood around it — with `--format openapi` that is
217
- a valid, self-contained slice of the specification.
285
+ a valid, self-contained slice of the specification. Without it the neighbours
286
+ are stitched in, up to `--max-nodes`.
287
+
288
+ `--source <name> --format openapi` with nothing else named prints the
289
+ **verbatim** document as it was indexed, unless the index was built
290
+ `--no-sources`, in which case it is rebuilt from the graph and says so.
291
+
292
+ A name that matches nothing is an **error**, not an empty answer: naming
293
+ something is a claim that it is there, and `list --name` is the command for
294
+ asking whether it is.
218
295
 
219
296
  ```
220
297
  zen rag schema show --method GetCurrentUserInfo --format openapi --exact
@@ -241,10 +318,12 @@ documents it came from. Also needs no embedder.
241
318
  | `find_types_with_property` | Every schema with a field of this name — exact lookup, no searching |
242
319
  | `list_api` | Methods, types or fields by name — complete, and counted in full |
243
320
  | `grep_api` | Every literal occurrence of a string — the way to prove absence |
321
+ | `trace_api` | Up from a field or schema to the operations that carry it |
244
322
 
245
323
  Only `search_api` ranks. Reach for the others whenever the question is whether
246
324
  something exists, because a search that returns nothing and a thing that is not
247
- there look exactly the same.
325
+ there look exactly the same. `trace_api` is the one for after a field has been
326
+ found and the endpoint to call is what is actually wanted.
248
327
 
249
328
  They share the group `schema`, so an agent takes them with `schema:*` in its
250
329
  `tools:`.