@zenera/cli 1.1.10 → 1.1.11
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/README.md +49 -15
- package/dist/cache.d.ts +98 -0
- package/dist/cache.js +301 -0
- package/dist/catalog.d.ts +3 -0
- package/dist/catalog.js +35 -11
- package/dist/commands/cache.d.ts +7 -0
- package/dist/commands/cache.js +245 -0
- package/dist/commands/check.js +6 -3
- package/dist/commands/index.js +2 -0
- package/dist/commands/key.js +68 -14
- package/dist/commands/models.js +17 -1
- package/dist/commands/run.js +11 -3
- package/dist/commands/sandbox.js +70 -23
- package/dist/home.d.ts +2 -2
- package/dist/home.js +2 -2
- package/dist/keys.d.ts +22 -0
- package/dist/keys.js +105 -2
- package/dist/lib.d.ts +1 -0
- package/dist/lib.js +1 -0
- package/dist/liveness.js +11 -0
- package/dist/resolve.d.ts +4 -0
- package/dist/resolve.js +43 -17
- package/dist/term.d.ts +23 -2
- package/dist/term.js +215 -8
- package/dist/tui/app.d.ts +10 -0
- package/dist/tui/app.js +470 -54
- package/dist/tui/theme.d.ts +6 -2
- package/dist/tui/theme.js +14 -8
- package/dist/tui/wrap.d.ts +92 -0
- package/dist/tui/wrap.js +147 -2
- package/dist/validate.d.ts +2 -0
- package/dist/validate.js +87 -2
- package/package.json +2 -2
- package/templates/editor/.github/copilot-instructions.md +50 -13
- package/templates/editor/.github/prompts/new-agent.prompt.md +5 -2
- package/templates/editor/.github/prompts/sync-with-spec.prompt.md +4 -0
- package/templates/editor/.github/skills/zen-cli/references/faker.md +18 -8
- package/templates/editor/.github/skills/zen-cli/references/keys.md +7 -7
- package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +575 -0
- package/templates/editor/.github/skills/{api-schema-index → zen-rag-schema}/SKILL.md +2 -2
- package/templates/editor/.vscode/settings.json +1 -1
|
@@ -91,17 +91,27 @@ Probes are synthetic on purpose — real request bodies never reach a prompt.
|
|
|
91
91
|
|
|
92
92
|
## The cache
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
94
|
+
Generators live in the machine's shared cache, `~/.zenera/neo/cache/faker-generator/`,
|
|
95
|
+
keyed by the operation's shape — so the same spec served from two directories is
|
|
96
|
+
written once, and a spec edit produces a new key rather than a stale answer. A
|
|
97
|
+
generator that a model gave up on is remembered for the life of the process, so
|
|
98
|
+
a hopeless operation is not re-asked on every request; a _transient_ failure — a
|
|
99
|
+
429, a dropped socket — is not, because it is about this minute rather than this
|
|
100
|
+
operation.
|
|
101
|
+
|
|
102
|
+
`~/.zenera/neo/faker/` is the container's workspace, and scratch: a cache hit is
|
|
103
|
+
copied into it, because the container can only run what is under its mount.
|
|
104
|
+
|
|
105
|
+
`zen faker cache ls` lists what has been generated. `zen faker cache clear`
|
|
106
|
+
removes the generators, the workspace and the container together. They have to
|
|
107
|
+
go together: the container's name is a hash of its configuration, so deleting
|
|
108
|
+
the workspace alone would leave a stopped container bind-mounted onto a
|
|
102
109
|
directory that no longer exists, and every generator would fail with
|
|
103
110
|
`python3: can't open file '/workspace/generators/…/gen.py'`.
|
|
104
111
|
|
|
112
|
+
`zen cache ls --kind faker-generator` reaches the same entries from the other
|
|
113
|
+
side, along with everything else this machine has cached.
|
|
114
|
+
|
|
105
115
|
## Documents it accepts
|
|
106
116
|
|
|
107
117
|
Swagger 2 and OpenAPI 3.0/3.1, JSON or YAML, `$ref`s resolved. Several documents
|
|
@@ -25,8 +25,8 @@ keys; one of them is active.
|
|
|
25
25
|
| `zen key show <ref> [--reveal]` | Masked by default |
|
|
26
26
|
| `zen key env [provider …]` | Shell exports, for other tools |
|
|
27
27
|
|
|
28
|
-
`zen key add` also takes `--project` and `--location`, for a Vertex
|
|
29
|
-
account.
|
|
28
|
+
`zen key add` also takes `--gcp-project` and `--gcp-location`, for a Vertex
|
|
29
|
+
service account.
|
|
30
30
|
|
|
31
31
|
```
|
|
32
32
|
zen key add openai # prompts, echo off
|
|
@@ -56,18 +56,18 @@ echo-off prompt and from nowhere else.
|
|
|
56
56
|
|
|
57
57
|
A **service-account file** is absorbed into `~/.zenera/neo/keys/` and
|
|
58
58
|
`GOOGLE_APPLICATION_CREDENTIALS` points at it. It wants a project too, from
|
|
59
|
-
`--project`, or `GOOGLE_CLOUD_PROJECT`, or the `project_id` inside the file,
|
|
60
|
-
a region from `--location` (`global` otherwise). Application Default
|
|
59
|
+
`--gcp-project`, or `GOOGLE_CLOUD_PROJECT`, or the `project_id` inside the file,
|
|
60
|
+
and a region from `--gcp-location` (`global` otherwise). Application Default
|
|
61
61
|
Credentials from `gcloud auth application-default login` work with no entry at
|
|
62
62
|
all.
|
|
63
63
|
|
|
64
64
|
An **express-mode key** is an ordinary secret under `VERTEX_API_KEY`, and
|
|
65
65
|
addresses no project: a key and a project are alternatives, and sending both
|
|
66
|
-
gets a `403` that mentions neither. `--project` and `--location` are
|
|
67
|
-
refused alongside a key.
|
|
66
|
+
gets a `403` that mentions neither. `--gcp-project` and `--gcp-location` are
|
|
67
|
+
therefore refused alongside a key.
|
|
68
68
|
|
|
69
69
|
```
|
|
70
|
-
zen key add vertex --project acme-prod --location europe-west4
|
|
70
|
+
zen key add vertex --gcp-project acme-prod --gcp-location europe-west4
|
|
71
71
|
# paste a path → the file shape
|
|
72
72
|
# paste a key → the express shape
|
|
73
73
|
```
|
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: zen-rag-docs
|
|
3
|
+
description: What a document index is, how to build one from a folder of `.md` files with `zen rag docs index` (or `npx @zenera/cli`), and how to ask it something — searching by meaning and narrowing to a release, a heading or a table, listing and grepping it exactly instead of reaching for shell `grep`/`rg`, reading a section back verbatim, giving it to an agent as tools, and writing the project skill that a wired-in index requires. For an OpenAPI description instead of prose, see `zen-rag-schema`.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# The document index
|
|
7
|
+
|
|
8
|
+
A document index is a pile of markdown turned into something that can be
|
|
9
|
+
**asked a question**. It is built once, on disk, and answered from — and what
|
|
10
|
+
comes back is not a summary but the documents themselves: the passages that
|
|
11
|
+
matched, quoted verbatim, with their line numbers.
|
|
12
|
+
|
|
13
|
+
It exists because a documentation tree does not fit in a context window and
|
|
14
|
+
grepping it does not help. The paragraph that answers "how are rate limits
|
|
15
|
+
counted?" does not contain the word "counted", sits four headings deep, and the
|
|
16
|
+
number you actually need is in a table two screens further down. Forty `grep`
|
|
17
|
+
hits for `limit` are not an answer; the eight lines around one of them are.
|
|
18
|
+
|
|
19
|
+
## The commands
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
zen rag docs <index|search|list|grep|show|stats> [path...]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
| Command | Answers | Embedder? | Typical |
|
|
26
|
+
| -------- | ------------------------------------------ | --------- | ------- |
|
|
27
|
+
| `index` | builds the thing | yes | minutes |
|
|
28
|
+
| `search` | _where does this corpus talk about X?_ | **yes** | seconds |
|
|
29
|
+
| `list` | _what documents/headings/tables are here?_ | no | instant |
|
|
30
|
+
| `grep` | _does the string X appear anywhere?_ | no | instant |
|
|
31
|
+
| `show` | _print this section, verbatim_ | no | instant |
|
|
32
|
+
| `stats` | _what is in this index?_ | no | instant |
|
|
33
|
+
|
|
34
|
+
Only `search` ranks, and only `search` costs a network round trip — it embeds
|
|
35
|
+
the query before it can compare anything. The other four read `outline.json`
|
|
36
|
+
and the copies in `sources/` off the disk and answer in milliseconds. Reach for
|
|
37
|
+
`search` when the question is vague and for `list`/`grep`/`show` when it is
|
|
38
|
+
precise. If a search feels slow, it is that one embedding call, not the index:
|
|
39
|
+
near-zero CPU for several seconds is the tell.
|
|
40
|
+
|
|
41
|
+
> **Never reach for shell `grep`, `rg`, `find`, `cat` or `head` here.**
|
|
42
|
+
> Not on the source tree, not on anything under the index directory.
|
|
43
|
+
> `zen rag docs grep` and `zen rag docs list` are the exact-matching commands,
|
|
44
|
+
> they are local, they need no credential, and they answer in milliseconds.
|
|
45
|
+
> Shell tools on the same files are strictly worse: they match raw lines, so
|
|
46
|
+
> they cannot say which heading a hit sits under, cannot narrow to one release
|
|
47
|
+
> of a tree by name, cannot tell a table row from the prose around it, and
|
|
48
|
+
> cannot report how many matches there were past the ones they printed.
|
|
49
|
+
> `rg -n 'Retry-After' docs/` gives you paths and offsets; `zen rag docs grep
|
|
50
|
+
"Retry-After"` gives you the line, the document, and the section it belongs
|
|
51
|
+
> to, counted in full.
|
|
52
|
+
|
|
53
|
+
## What is in one
|
|
54
|
+
|
|
55
|
+
Three things, and the third is the point.
|
|
56
|
+
|
|
57
|
+
| Piece | Is |
|
|
58
|
+
| -------------- | --------------------------------------------------------- |
|
|
59
|
+
| `outline.json` | every heading and every table, with the lines they cover |
|
|
60
|
+
| `sources/` | the documents themselves, verbatim, exactly as indexed |
|
|
61
|
+
| `lance/` | one row per chunk: two texts, one vector, and the filters |
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
docs-db/
|
|
65
|
+
├── README.md what this index holds — a live progress report while it builds
|
|
66
|
+
├── manifest.json written LAST — its absence means "not indexed"
|
|
67
|
+
├── outline.json headings and tables, read whole
|
|
68
|
+
├── sources/ the documents, verbatim — where every quoted line comes from
|
|
69
|
+
└── lance/ the chunks
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`sources/` is not a convenience. A search returns **line ranges**, and the lines
|
|
73
|
+
are read back out of those copies, so what you are shown is the document and not
|
|
74
|
+
a reconstruction of it. That is also what makes an index one portable thing:
|
|
75
|
+
nothing in it names a path outside itself, so it can be committed, shipped, or
|
|
76
|
+
mounted somewhere else in an agent's sandbox and still answer. (A schema index
|
|
77
|
+
has `--no-sources`; a document index does not, because there the copies _are_
|
|
78
|
+
the answer.)
|
|
79
|
+
|
|
80
|
+
`manifest.json` records **which embedder made the vectors**, so a search with a
|
|
81
|
+
different model is refused rather than answered with noise.
|
|
82
|
+
|
|
83
|
+
### How a document is cut up
|
|
84
|
+
|
|
85
|
+
Chunking follows the markdown rather than a character count: a paragraph, a
|
|
86
|
+
list, a fenced code block, the frontmatter. Every chunk knows the heading it
|
|
87
|
+
sits under, which is what makes `--section` a filter and not a hope.
|
|
88
|
+
|
|
89
|
+
Tables are indexed **twice over** — once as a descriptor carrying the caption
|
|
90
|
+
and the column names, and once per row, with the header row travelling
|
|
91
|
+
alongside so the columns are still named wherever a row lands. A row too wide
|
|
92
|
+
to be one chunk is cut into column groups with the key column repeated. That is
|
|
93
|
+
why `--kind table_row` is a useful thing to ask for: a limits table is a
|
|
94
|
+
hundred facts, not one paragraph.
|
|
95
|
+
|
|
96
|
+
Every chunk gets a `kind`: `paragraph`, `list`, `table`, `table_row`, `code`,
|
|
97
|
+
`frontmatter`, `html`.
|
|
98
|
+
|
|
99
|
+
## Installing
|
|
100
|
+
|
|
101
|
+
`zen rag` ships in `@zenera/rag`, a sibling of the CLI. It has no binary of its
|
|
102
|
+
own — installing it adds the `rag` subcommand to `zen`, which is also where the
|
|
103
|
+
credentials already live.
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
npm i -g @zenera/cli @zenera/rag # then: zen rag docs …
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
For a one-off, without installing anything, **both** packages must be in the
|
|
110
|
+
same temporary install or `zen` will report `rag` as not installed:
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
npx --package @zenera/cli --package @zenera/rag -- zen rag docs index ./docs --embedding openai:text-embedding-3-small
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Every example below is spelled `zen …`; prefix it with that `npx` form if you
|
|
117
|
+
have not installed globally.
|
|
118
|
+
|
|
119
|
+
## Building an index from `.md` files
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
zen rag docs index <path...> [--embedding <ref>] [-o <dir>] [--batch <n>] [--chunk-tokens <n>]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
zen rag docs index ./docs --embedding openai:text-embedding-3-small
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
That is the whole thing: point it at a directory and it walks it.
|
|
130
|
+
|
|
131
|
+
### What gets read
|
|
132
|
+
|
|
133
|
+
| You name | What happens |
|
|
134
|
+
| --------------- | ----------------------------------------------------------------- |
|
|
135
|
+
| a **file** | that file |
|
|
136
|
+
| a **directory** | walked, recursively |
|
|
137
|
+
| a **glob** | anchored at the deepest directory it names outright, then matched |
|
|
138
|
+
|
|
139
|
+
`.md`, `.markdown`, `.txt` and `.text` are read; `.txt` and `.text` are treated
|
|
140
|
+
as prose with no headings. Anything else is not a document and is not indexed.
|
|
141
|
+
Hidden directories and `node_modules` are skipped — a file nobody can see is
|
|
142
|
+
not one anybody meant to index. A file over 16 MB is skipped as a data dump and
|
|
143
|
+
said so on stderr, next to the per-document table.
|
|
144
|
+
|
|
145
|
+
Several paths can go into one index, and usually should: one index over a whole
|
|
146
|
+
documentation tree is what makes "which release says this?" answerable at all.
|
|
147
|
+
|
|
148
|
+
```sh
|
|
149
|
+
zen rag docs index ./docs ./README.md ./packages/*/README.md --embedding openai:text-embedding-3-small
|
|
150
|
+
zen rag docs index "releases/nsx_4.*/**/*.md" --embedding google:gemini-embedding-001 -o .index/nsx
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### The name is the identity
|
|
154
|
+
|
|
155
|
+
Every document is given a **name**: its path relative to the common root of
|
|
156
|
+
everything you named. Nothing anywhere records where the file was on the machine
|
|
157
|
+
that built the index.
|
|
158
|
+
|
|
159
|
+
That name is what `--file` patterns match, so it is worth arranging on purpose.
|
|
160
|
+
Indexing two release trees at once keeps `nsx_4.1.0/api/routing.md` and
|
|
161
|
+
`nsx_4.2.0/api/routing.md` apart, and a search can be pinned to one of them.
|
|
162
|
+
Indexing one tree from inside it gives you `api/routing.md`. Two files that
|
|
163
|
+
would land on the same name are deduped rather than merged.
|
|
164
|
+
|
|
165
|
+
### The flags
|
|
166
|
+
|
|
167
|
+
| Flag | Default | Meaning |
|
|
168
|
+
| -------------------- | ----------- | ------------------------------------------------------------ |
|
|
169
|
+
| `--embedding <ref>` | — | Which embedder makes the vectors. Omit it to see the choices |
|
|
170
|
+
| `-o`, `--out <dir>` | `./docs-db` | Where the index goes; `$ZEN_DOCS_DB` if that is set |
|
|
171
|
+
| `--batch <n>` | `96` | Texts per embedding request, and how often progress prints |
|
|
172
|
+
| `--chunk-tokens <n>` | `384` | Target chunk size. Leave it alone unless you have a reason |
|
|
173
|
+
| `--quiet` | — | No narration |
|
|
174
|
+
|
|
175
|
+
The embedding reference names a provider first — `openai:text-embedding-3-small`,
|
|
176
|
+
not a bare model id. Credentials come from the `zen` keyring (`zen key ls`), and
|
|
177
|
+
a real environment variable always wins.
|
|
178
|
+
|
|
179
|
+
### What it prints
|
|
180
|
+
|
|
181
|
+
A per-document table (lines, sections, tables, chunks) with a total row, a line
|
|
182
|
+
per skipped file with the reason, then a progress line per batch. **stdout is
|
|
183
|
+
the output directory and nothing else**, so `DIR=$(zen rag docs index …)` works;
|
|
184
|
+
everything else is stderr.
|
|
185
|
+
|
|
186
|
+
This is the one command here that spends money and time: it embeds every chunk.
|
|
187
|
+
While it runs, the output directory narrates itself — `README.md` is a live
|
|
188
|
+
progress report, rewritten at most every five seconds, replaced on completion by
|
|
189
|
+
a description of what the index turned out to hold, and left saying so if the
|
|
190
|
+
build dies. A `.lock` names the process, so a second build of the same directory
|
|
191
|
+
is refused unless the lock is stale.
|
|
192
|
+
|
|
193
|
+
Rebuild the index when the documents change. Nothing watches them, and a stale
|
|
194
|
+
index is a confident wrong answer — worse here than anywhere, because the quoted
|
|
195
|
+
lines will look exactly as authoritative as they did when they were true.
|
|
196
|
+
|
|
197
|
+
## Which index gets read
|
|
198
|
+
|
|
199
|
+
Every reading command takes `-d`, `--dir`. Without one:
|
|
200
|
+
|
|
201
|
+
1. `$ZEN_DOCS_DB`, if it is set. **Set this once** instead of typing `-d` on
|
|
202
|
+
every command — `export ZEN_DOCS_DB=/assets/docs-db`.
|
|
203
|
+
2. Otherwise the **nearest index** to the working directory: here, then a short
|
|
204
|
+
way down into it, then up a level and again, stopping at your home
|
|
205
|
+
directory. The one chosen is named on stderr as it is used, so an answer is
|
|
206
|
+
never anonymous.
|
|
207
|
+
3. Otherwise `./docs-db`, which is only so the error names the directory you
|
|
208
|
+
were expecting.
|
|
209
|
+
|
|
210
|
+
What is looked for is a `manifest.json`, and the search is **scoped by kind** —
|
|
211
|
+
a `docs` index and a `schema` index can sit in the same tree without either
|
|
212
|
+
shadowing the other, and nothing searches for a directory literally called
|
|
213
|
+
`docs-db`. That is just the name a new one is given.
|
|
214
|
+
|
|
215
|
+
Two document indexes the same distance away is refused rather than guessed at:
|
|
216
|
+
the wrong index does not fail, it answers confidently about another corpus.
|
|
217
|
+
Name one with `-d`, or set `ZEN_DOCS_DB`.
|
|
218
|
+
|
|
219
|
+
## Searching it
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
zen rag docs search [text...] [narrowings…] [shape…]
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
```sh
|
|
226
|
+
zen rag docs search "how are rate limits counted"
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The answer is the corpus quoting itself:
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
## nsx_4.2.0/api/routing.md — 9 of 148 lines
|
|
233
|
+
|
|
234
|
+
5 | ## Rate limits
|
|
235
|
+
7 | Requests are counted per tenant and rejected past the limit.
|
|
236
|
+
... 12 lines omitted (Retries, Backoff) ...
|
|
237
|
+
24 | | route | limit | window |
|
|
238
|
+
25 | | --- | --- | --- |
|
|
239
|
+
27 | | /api/users | 250 | 1m |
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Every line carries its number, and the gaps say what was left out. That is
|
|
243
|
+
deliberate: a passage you cannot point at is a passage you cannot do anything
|
|
244
|
+
with. The numbers are what `show` and `read_docs` take.
|
|
245
|
+
|
|
246
|
+
### Narrowing is the interface
|
|
247
|
+
|
|
248
|
+
Nobody finds the paragraph they want on the first ask. They search, see it is
|
|
249
|
+
the wrong release, and search again inside one. So the second call is the same
|
|
250
|
+
question with a narrowing on it — not a different command.
|
|
251
|
+
|
|
252
|
+
| Flag | Narrows to |
|
|
253
|
+
| ------------------------ | ------------------------------------------------------------------------------------ |
|
|
254
|
+
| `-f`, `--file <p>` | documents whose **name** matches. Repeatable |
|
|
255
|
+
| `--exclude-file <p>` | everything but those. Repeatable |
|
|
256
|
+
| `-s`, `--section <name>` | one heading and whatever nests inside it. Repeatable |
|
|
257
|
+
| `--kind <k>` | `paragraph`, `list`, `table`, `table_row`, `code`, `frontmatter`, `html`. Repeatable |
|
|
258
|
+
| `--mode <m>` | `hybrid` (default), `vector`, `text` |
|
|
259
|
+
| `--exclude-id <id>` | a passage already seen. Repeatable |
|
|
260
|
+
|
|
261
|
+
```sh
|
|
262
|
+
zen rag docs search --file "nsx_4.2.*/api/**" "rate limit for the users route"
|
|
263
|
+
zen rag docs search --section "Rate limits" --kind table "requests per minute"
|
|
264
|
+
zen rag docs search --mode text "X-RateLimit-Remaining"
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
A `--file` pattern with `*` or `?` is a glob matched against the whole document
|
|
268
|
+
name; a plain word is a substring. So `--file routing` finds
|
|
269
|
+
`nsx_4.2.0/api/routing.md` and `--file "routing*"` finds nothing.
|
|
270
|
+
|
|
271
|
+
`--section` takes a heading title, a structure id, or the structure path an
|
|
272
|
+
earlier answer printed. A title that appears in four documents becomes four
|
|
273
|
+
scopes, not a guess at one.
|
|
274
|
+
|
|
275
|
+
**`--mode text` is the one to remember.** `hybrid` blends meaning and wording,
|
|
276
|
+
which is right for a question; when what you have is an exact string — an error
|
|
277
|
+
message, a header name, a flag — `text` is exact wording only, and meaning can
|
|
278
|
+
only dilute it.
|
|
279
|
+
|
|
280
|
+
The narrowings are resolved against the manifest and the outline **before** the
|
|
281
|
+
store is touched, so a `--file` pattern that matches nothing says so instead of
|
|
282
|
+
quietly searching everything.
|
|
283
|
+
|
|
284
|
+
### Shaping the answer
|
|
285
|
+
|
|
286
|
+
| Flag | Default | Meaning |
|
|
287
|
+
| -------------------- | ------- | ----------------------------------------- |
|
|
288
|
+
| `-d`, `--dir <dir>` | found | Which index — see "Which index gets read" |
|
|
289
|
+
| `--embedding <ref>` | index's | Must be the one the index was built with |
|
|
290
|
+
| `--limit <n>` | `8` | Passages kept |
|
|
291
|
+
| `-B`, `--before <n>` | `0` | Extra lines quoted before each passage |
|
|
292
|
+
| `-A`, `--after <n>` | `0` | Extra lines quoted after each passage |
|
|
293
|
+
| `--max-lines <n>` | `400` | A ceiling on the whole answer |
|
|
294
|
+
| `--no-numbers` | — | Quote the lines without their numbers |
|
|
295
|
+
| `--hits` | — | One row per passage instead of the text |
|
|
296
|
+
| `--interactive` | — | Prompt, search, narrow. Needs a terminal |
|
|
297
|
+
| `--quiet` | — | No narration |
|
|
298
|
+
|
|
299
|
+
`--hits` is the fast triage: it prints where the passages are without spending
|
|
300
|
+
the lines on them, and you follow up with `show`.
|
|
301
|
+
|
|
302
|
+
### As a machine interface
|
|
303
|
+
|
|
304
|
+
Non-interactive search is a tool, not an afterthought. `--json` is a stable
|
|
305
|
+
shape — `{matches, files, scope, mode, considered, truncated}` — no terminal is
|
|
306
|
+
needed, and **an empty result exits 0**: a caller must never have to tell
|
|
307
|
+
"nothing matched" from "the index is missing" by parsing stderr.
|
|
308
|
+
|
|
309
|
+
Everything about the invocation is validated before an embedder is constructed,
|
|
310
|
+
so a typo is a usage error and not a credential error.
|
|
311
|
+
|
|
312
|
+
### `--interactive`
|
|
313
|
+
|
|
314
|
+
A prompt that keeps the query between searches, and excludes what it has
|
|
315
|
+
already shown you so asking again moves on:
|
|
316
|
+
|
|
317
|
+
```
|
|
318
|
+
<text> search
|
|
319
|
+
file <pattern> only documents matching it; blank for all
|
|
320
|
+
not <pattern> drop documents matching it
|
|
321
|
+
section <name> only under this heading, by title, id or path
|
|
322
|
+
kind <k> paragraph | list | table | table_row | code | frontmatter | html
|
|
323
|
+
mode <m> hybrid | vector | text
|
|
324
|
+
limit <n> passages per search
|
|
325
|
+
files the documents currently in scope
|
|
326
|
+
show the query as it stands
|
|
327
|
+
reset forget it, narrowings and exclusions alike
|
|
328
|
+
quit
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Reading it without searching
|
|
332
|
+
|
|
333
|
+
None of these need an embedder or a credential — they are plain reads of the
|
|
334
|
+
outline and the copies on disk.
|
|
335
|
+
|
|
336
|
+
```
|
|
337
|
+
zen rag docs list <files|sections|tables> [-d <dir>] [filters…]
|
|
338
|
+
zen rag docs grep <pattern> [-d <dir>] [filters…]
|
|
339
|
+
zen rag docs show <file> [--section <name>] [--lines <from-to>]
|
|
340
|
+
zen rag docs stats
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
| Flag | For | Meaning |
|
|
344
|
+
| ------------------------ | ------ | ---------------------------------------------- |
|
|
345
|
+
| `-f`, `--file <p>` | both | Only these documents. Repeatable |
|
|
346
|
+
| `--exclude-file <p>` | both | Drop these documents. Repeatable |
|
|
347
|
+
| `-s`, `--section <name>` | both | Only under this heading. Repeatable |
|
|
348
|
+
| `--depth <n>` | `list` | `sections` only: deepest heading level to show |
|
|
349
|
+
| `--regex` | both | Read the patterns as regular expressions |
|
|
350
|
+
| `--case-sensitive` | both | Stop ignoring case |
|
|
351
|
+
| `--limit <n>` | both | Print at most n; `found` still counts them all |
|
|
352
|
+
| `--json` | both | `{found, rows, truncated}` |
|
|
353
|
+
| `--quiet` | both | No narration |
|
|
354
|
+
|
|
355
|
+
```sh
|
|
356
|
+
zen rag docs list files # every document, and what it holds
|
|
357
|
+
zen rag docs list sections --file "api/**" # every heading, with its line span
|
|
358
|
+
zen rag docs list sections --depth 2 # just the shape of the tree
|
|
359
|
+
zen rag docs list tables # every table, with its columns
|
|
360
|
+
zen rag docs grep "Retry-After" # every matching line, and its section
|
|
361
|
+
zen rag docs grep "X-RateLimit-\w+" --regex --file "api/**"
|
|
362
|
+
zen rag docs show api/routing.md --section "Rate limits"
|
|
363
|
+
zen rag docs show api/routing.md --lines 40-80
|
|
364
|
+
zen rag docs stats
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
`list files` reports the name, title, format, and how many lines, sections,
|
|
368
|
+
tables and chunks each document holds — which is also the fastest way to learn
|
|
369
|
+
the names your `--file` patterns will be matched against. `list sections` is a
|
|
370
|
+
table of contents with line spans; `list tables` names every table's caption,
|
|
371
|
+
columns and row count, which is how you find the limits table without reading
|
|
372
|
+
the prose around it.
|
|
373
|
+
|
|
374
|
+
`grep` reports `found` as the **true total** even when `--limit` cuts the rows,
|
|
375
|
+
so unlike a search it can answer whether a string appears at all. Every hit
|
|
376
|
+
carries the innermost heading it sits under, so a match has a place. Nothing
|
|
377
|
+
matching exits 0 with empty stdout — and that emptiness is trustworthy, which is
|
|
378
|
+
the whole point of it.
|
|
379
|
+
|
|
380
|
+
`show` prints a document, a named section, or a line range, verbatim, with a
|
|
381
|
+
line-number gutter (`--no-numbers` to drop it). It is the natural follow-up to
|
|
382
|
+
every search: find the passage, then read what is actually around it.
|
|
383
|
+
|
|
384
|
+
`stats` says what is in an index and what built it — the documents, the counts,
|
|
385
|
+
the embedding model. It is the fastest way to answer "is this index the one I
|
|
386
|
+
think it is?".
|
|
387
|
+
|
|
388
|
+
### Which one to reach for
|
|
389
|
+
|
|
390
|
+
| The question | The command |
|
|
391
|
+
| ------------------------------------------------- | ----------------------------------------------- |
|
|
392
|
+
| "how does this thing handle retries?" | `search "how are retries handled"` |
|
|
393
|
+
| _anything you would have run `grep` for_ | `grep` / `list` — never the shell |
|
|
394
|
+
| "does `X-Request-Id` appear anywhere?" | `grep X-Request-Id` |
|
|
395
|
+
| "what does the rate-limits section actually say?" | `show <file> --section "Rate limits"` |
|
|
396
|
+
| "which documents are even in here?" | `list files` |
|
|
397
|
+
| "what is the shape of this manual?" | `list sections --depth 2` |
|
|
398
|
+
| "where is the table of per-route limits?" | `list tables --file "api/**"` |
|
|
399
|
+
| "the exact error string, not something like it" | `search --mode text "connection reset by peer"` |
|
|
400
|
+
| "same question, but only the 4.2 docs" | `search --file "nsx_4.2*/**" "…"` |
|
|
401
|
+
| "is this index the right one?" | `stats` |
|
|
402
|
+
|
|
403
|
+
The rule: **a question about meaning is a `search`; a question about presence,
|
|
404
|
+
spelling or a count is a `list` or a `grep`; a question about what a passage
|
|
405
|
+
actually says is a `show`.** Search cannot answer the middle one, because a
|
|
406
|
+
ranking always returns its best guesses whether or not any of them are right.
|
|
407
|
+
|
|
408
|
+
### Instead of the shell
|
|
409
|
+
|
|
410
|
+
Add `-d <dir>` when the index is not the nearest one, or name it once with
|
|
411
|
+
`ZEN_DOCS_DB`.
|
|
412
|
+
|
|
413
|
+
| The reflex | The command |
|
|
414
|
+
| ----------------------------------- | ----------------------------------------------------- |
|
|
415
|
+
| `grep -rn "Retry-After" docs/` | `zen rag docs grep "Retry-After"` |
|
|
416
|
+
| `rg -i retry --glob 'api/**'` | `zen rag docs grep retry --file "api/**"` |
|
|
417
|
+
| `grep -E "X-RateLimit-\w+"` | `zen rag docs grep "X-RateLimit-\w+" --regex` |
|
|
418
|
+
| `grep -c` / `wc -l` | `--json`, and read `found` — it counts past `--limit` |
|
|
419
|
+
| `find docs -name '*.md'` | `zen rag docs list files` |
|
|
420
|
+
| `grep '^#' file.md` | `zen rag docs list sections --file file.md` |
|
|
421
|
+
| `sed -n '40,80p' file.md` | `zen rag docs show file.md --lines 40-80` |
|
|
422
|
+
| `cat file.md` (to find one section) | `zen rag docs show file.md --section "Rate limits"` |
|
|
423
|
+
| `ls` the index directory | `zen rag docs stats` |
|
|
424
|
+
|
|
425
|
+
```sh
|
|
426
|
+
export ZEN_DOCS_DB=/assets/docs-db # once, then never again
|
|
427
|
+
zen rag docs grep "Retry-After" --limit 10
|
|
428
|
+
zen rag docs list sections --file "api/**" --depth 3
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
If none of these fits the question, the question is about meaning, and the
|
|
432
|
+
answer is `search` — still not the shell.
|
|
433
|
+
|
|
434
|
+
## Giving it to an agent
|
|
435
|
+
|
|
436
|
+
The same engine, as four tools in the group `docs`. An agent takes them all
|
|
437
|
+
with `docs:*` in its `tools:`.
|
|
438
|
+
|
|
439
|
+
```ts
|
|
440
|
+
import { createEmbedder, loadProject } from '@zenera/neo';
|
|
441
|
+
import { docs } from '@zenera/rag';
|
|
442
|
+
|
|
443
|
+
const index = await docs.DocsIndex.open(
|
|
444
|
+
'./docs-db',
|
|
445
|
+
createEmbedder('openai:text-embedding-3-small'),
|
|
446
|
+
);
|
|
447
|
+
const project = await loadProject('./my-project', { tools: docs.docsTools(index) });
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
| Tool | For |
|
|
451
|
+
| ------------- | ------------------------------------------------------------ |
|
|
452
|
+
| `search_docs` | the passages that match, quoted with their line numbers |
|
|
453
|
+
| `list_docs` | the documents, their headings, or their tables — no search |
|
|
454
|
+
| `grep_docs` | every matching line, counted in full — no search |
|
|
455
|
+
| `read_docs` | a section or a line range, verbatim and with nothing omitted |
|
|
456
|
+
|
|
457
|
+
Only `search_docs` ranks; the other three are exact, because a model told "no
|
|
458
|
+
results" by a vector search has learned nothing — a ranking returns the top of a
|
|
459
|
+
list, so an empty answer and an absent thing look identical. `grep_docs` is how
|
|
460
|
+
"it is not written down anywhere" can actually be concluded.
|
|
461
|
+
|
|
462
|
+
`search_docs` is shaped for the **second** call rather than the first. The first
|
|
463
|
+
is always a sentence and always returns some of the wrong tree; the second is
|
|
464
|
+
the same sentence with `files: ["nsx_4.2*/api/**"]`, or `section: "Rate limits"`,
|
|
465
|
+
or `kind: ["table"]`. Those are parameters and not separate tools, so narrowing
|
|
466
|
+
costs one call instead of three. `exclude_ids` takes the ids from an earlier
|
|
467
|
+
answer, so asking again moves on instead of repeating itself.
|
|
468
|
+
|
|
469
|
+
Every answer carries line numbers and `read_docs` takes them. That is the loop
|
|
470
|
+
the whole subject exists for: **find the passage, read around it, then edit the
|
|
471
|
+
file the passage came from.**
|
|
472
|
+
|
|
473
|
+
Tell the agent in its prompt to call `list_docs` once before its first search —
|
|
474
|
+
the document names are what every `files` pattern is matched against, and a
|
|
475
|
+
model guessing at them narrows to nothing and concludes the corpus is empty.
|
|
476
|
+
|
|
477
|
+
## Wiring it into a project means writing the project a skill
|
|
478
|
+
|
|
479
|
+
**Whenever an index is used by a Zenera project — as `docs:*` tools, or as
|
|
480
|
+
`zen rag` reachable from the agent's sandbox — write a skill for it in that
|
|
481
|
+
project.** Not optional, and not the same thing as passing the tools in.
|
|
482
|
+
|
|
483
|
+
Wiring alone leaves the model to infer everything that matters. A tool
|
|
484
|
+
description says what `grep_docs` does; it cannot say that this index holds the
|
|
485
|
+
NSX 4.1 and 4.2 manuals side by side, that every answer must be pinned to a
|
|
486
|
+
release with `files`, that the API reference lives under `*/api/**` and the
|
|
487
|
+
task guides under `*/guides/**`, or that the numbers anybody actually wants are
|
|
488
|
+
in tables and so `kind: ["table_row"]` is the right first move. That is project
|
|
489
|
+
knowledge, and project knowledge belongs in a skill — where it is loaded only
|
|
490
|
+
when the model is working on this corpus, instead of sitting in the system
|
|
491
|
+
prompt of every run.
|
|
492
|
+
|
|
493
|
+
```
|
|
494
|
+
<project>/agents/skills/<corpus>-docs/SKILL.md
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
Frontmatter is `name` and `description`; the description is what the model reads
|
|
498
|
+
when choosing, so it must name the corpus and the questions it answers. Add
|
|
499
|
+
`tools: [search_docs, list_docs, grep_docs, read_docs]` if the skill should be
|
|
500
|
+
what unlocks them.
|
|
501
|
+
|
|
502
|
+
### What the skill has to say
|
|
503
|
+
|
|
504
|
+
| Section | Because |
|
|
505
|
+
| ------------------ | ----------------------------------------------------------------------------------- |
|
|
506
|
+
| Which corpus | What it is, which version(s), and when it was indexed |
|
|
507
|
+
| Where the index | `ZEN_DOCS_DB`, or the `-d` to pass — an agent cannot guess a path |
|
|
508
|
+
| The document names | The shape `--file`/`files` patterns match, with two real examples |
|
|
509
|
+
| Which command | Meaning → `search`; presence or a count → `grep`; what it says → `show`/`read_docs` |
|
|
510
|
+
| Never the shell | State it outright. `grep`/`rg`/`cat` on the tree is the default reflex |
|
|
511
|
+
| How to narrow | Which `files` prefixes matter here, and when the answer is a table |
|
|
512
|
+
| Worked examples | Two or three, with **real document names and headings from this index** |
|
|
513
|
+
| Citing | Answer with the document name and line numbers, because the tools give them |
|
|
514
|
+
|
|
515
|
+
Best practice, in order of how often it is got wrong:
|
|
516
|
+
|
|
517
|
+
1. **Use real names.** `list_docs` output the model will actually see beats a
|
|
518
|
+
generic `<file>` placeholder, because the names are the anchors it steers by.
|
|
519
|
+
2. **Say which command answers which question**, and say that a ranking cannot
|
|
520
|
+
prove absence. Left alone a model searches for everything, gets eight ranked
|
|
521
|
+
guesses, and answers from the best of them.
|
|
522
|
+
3. **Name the narrowing that matters here.** Two releases in one index is the
|
|
523
|
+
common case, and an unpinned search silently mixes them.
|
|
524
|
+
4. **Keep it short.** A skill is prompt. One screen of routing rules beats a
|
|
525
|
+
transcription of this document — link to `zen rag docs --help` for the flags.
|
|
526
|
+
5. **Re-index, then re-read the skill.** Both go stale against the same change,
|
|
527
|
+
and a skill quoting headings that no longer exist is worse than none.
|
|
528
|
+
6. **One skill per corpus**, named after it. Two corpora in one skill and the
|
|
529
|
+
model mixes their conventions.
|
|
530
|
+
|
|
531
|
+
```md
|
|
532
|
+
---
|
|
533
|
+
name: nsx-docs
|
|
534
|
+
description: How to find an answer in the NSX 4.1 and 4.2 manuals — which document says it,
|
|
535
|
+
what it says verbatim, and which release it is true of. Use before answering anything
|
|
536
|
+
about NSX behaviour, limits or API routes.
|
|
537
|
+
---
|
|
538
|
+
|
|
539
|
+
# The NSX manuals
|
|
540
|
+
|
|
541
|
+
Indexed at `/assets/docs-db` (already in `$ZEN_DOCS_DB`). Two releases side by
|
|
542
|
+
side; document names begin `nsx_4.1.0/` or `nsx_4.2.0/`, then `api/` for the
|
|
543
|
+
reference and `guides/` for the task documentation.
|
|
544
|
+
|
|
545
|
+
- Always pin the release: `files: ["nsx_4.2.0/**"]`. An unpinned search mixes them.
|
|
546
|
+
- Vague question ("how does edge failover work?") → `search_docs`, then ask again
|
|
547
|
+
with `section` or `files` once you can see which half of the tree it is in.
|
|
548
|
+
- Does X exist, how is it spelled, how many are there → `grep_docs`. It is
|
|
549
|
+
complete; a search is not, and cannot prove absence.
|
|
550
|
+
- Limits and defaults are in tables → add `kind: ["table_row"]`.
|
|
551
|
+
- Then `read_docs` the lines around the hit before answering. Never answer from
|
|
552
|
+
the excerpt alone.
|
|
553
|
+
- Never `grep`/`rg`/`cat` the tree — the tools above are local and exact.
|
|
554
|
+
|
|
555
|
+
Worked: per-route rate limits are the table under "Rate limits" in
|
|
556
|
+
`nsx_4.2.0/api/routing.md` (lines 24-31); the retry envelope is described two
|
|
557
|
+
sections down. Cite the document name and the line numbers.
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
## When it goes wrong
|
|
561
|
+
|
|
562
|
+
| Symptom | Cause |
|
|
563
|
+
| ------------------------------------------------ | ------------------------------------------------------------------------------------- |
|
|
564
|
+
| "not installed" | `@zenera/rag` is not resolvable — install it, or use the two-`--package` npx form |
|
|
565
|
+
| `nothing to index` | No `.md`/`.markdown`/`.txt`/`.text` under those paths, or they are all hidden |
|
|
566
|
+
| A document you expected is missing | Hidden directory, `node_modules`, an unlisted extension, or over 16 MB — check stderr |
|
|
567
|
+
| Refused for a different embedding | The index records the model that built it; re-index or pass the right `--embedding` |
|
|
568
|
+
| No manifest / not an index | A build that did not finish. `manifest.json` is written last on purpose |
|
|
569
|
+
| A build is refused | `.lock` — another build is running. It is taken over when the process is gone |
|
|
570
|
+
| `provider "openai": no api key` | `zen key ls` — the keyring, or a real environment variable |
|
|
571
|
+
| A usage error before any credential is asked for | Deliberate: everything about the invocation is checked first, so a typo is a typo |
|
|
572
|
+
| `no document matched --file` | The pattern is matched against the document **name**; `list files` prints them |
|
|
573
|
+
| Nothing matched | Exit 0 with an empty answer. Try fewer words, or `--mode text` for an exact string |
|
|
574
|
+
| Answers quoting text that is no longer there | Nothing watches the documents. Re-index after they change |
|
|
575
|
+
| A search took ten seconds, using no CPU | One embedding round trip, not the index. `list`/`grep`/`show` make none |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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.
|
|
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`.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# The schema index
|