@zenera/cli 1.1.0 → 1.1.3
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 +88 -11
- package/dist/audit.d.ts +8 -6
- package/dist/audit.js +14 -22
- package/dist/commands/check.js +79 -19
- package/dist/commands/init.js +71 -11
- package/dist/commands/key.js +126 -36
- package/dist/commands/models.js +3 -3
- package/dist/commands/open.js +2 -2
- package/dist/commands/run.js +3 -0
- package/dist/commands/sandbox.js +226 -22
- package/dist/engine.d.ts +3 -1
- package/dist/engine.js +10 -2
- package/dist/image.d.ts +16 -0
- package/dist/image.js +85 -0
- package/dist/keys.d.ts +95 -12
- package/dist/keys.js +175 -34
- package/dist/lib.d.ts +2 -2
- package/dist/lib.js +2 -2
- package/dist/liveness.d.ts +16 -6
- package/dist/liveness.js +74 -23
- package/dist/main.js +0 -0
- package/dist/podman.d.ts +57 -1
- package/dist/podman.js +177 -12
- package/dist/projects.d.ts +18 -0
- package/dist/projects.js +60 -1
- package/dist/sandbox.d.ts +14 -1
- package/dist/sandbox.js +88 -8
- package/dist/scaffold.d.ts +21 -15
- package/dist/scaffold.js +133 -167
- package/dist/term.d.ts +2 -0
- package/dist/term.js +14 -0
- package/dist/validate.d.ts +20 -3
- package/dist/validate.js +309 -14
- package/package.json +2 -18
- package/templates/{.github → editor/.github}/copilot-instructions.md +161 -48
- package/templates/{.github → editor/.github}/prompts/new-skill.prompt.md +13 -6
- package/templates/editor/.github/skills/api-schema-index/SKILL.md +292 -0
- package/templates/editor/.github/skills/zen-cli/SKILL.md +74 -0
- package/templates/editor/.github/skills/zen-cli/references/check.md +92 -0
- package/templates/editor/.github/skills/zen-cli/references/faker.md +111 -0
- package/templates/editor/.github/skills/zen-cli/references/frame.md +119 -0
- package/templates/editor/.github/skills/zen-cli/references/inspect.md +61 -0
- package/templates/editor/.github/skills/zen-cli/references/keys.md +114 -0
- package/templates/editor/.github/skills/zen-cli/references/projects.md +99 -0
- package/templates/editor/.github/skills/zen-cli/references/rag.md +159 -0
- package/templates/editor/.github/skills/zen-cli/references/run.md +104 -0
- package/templates/editor/.github/skills/zen-cli/references/sandbox.md +91 -0
- package/templates/editor/.vscode/settings.json +6 -0
- package/templates/parts/exa.yaml.tmpl +5 -0
- package/templates/parts/model.yaml.tmpl +4 -0
- package/templates/parts/models.yaml.tmpl +10 -0
- package/templates/project/INSTRUCTIONS.md +7 -0
- package/templates/project/SPECIFICATION.md +6 -0
- package/templates/project/agents/prompts/default.md +15 -0
- package/templates/project/agents.yaml.tmpl +44 -0
- package/templates/project/assets/README.md +12 -0
- package/templates/project/gitignore +9 -0
- package/templates/project/sandbox/Dockerfile +21 -0
- package/templates/.github/skills/zen-cli/SKILL.md +0 -110
- /package/templates/{.github → editor/.github}/prompts/new-agent.prompt.md +0 -0
- /package/templates/{.github → editor/.github}/prompts/review-project.prompt.md +0 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
---
|
|
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.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# The schema index
|
|
7
|
+
|
|
8
|
+
A schema index is an OpenAPI/Swagger description turned into something that can
|
|
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.
|
|
11
|
+
|
|
12
|
+
It exists because a real specification does not fit in a context window, and
|
|
13
|
+
grepping it does not help. The parts that answer "how do I reset a password?"
|
|
14
|
+
are scattered on purpose: the field is on a schema, the schema is a request
|
|
15
|
+
body, the request body belongs to one operation out of three hundred, and the
|
|
16
|
+
word "password" appears in forty places that are not the one you want.
|
|
17
|
+
|
|
18
|
+
## Why it is a graph and not a search box
|
|
19
|
+
|
|
20
|
+
Two structures, kept together, because neither answers alone:
|
|
21
|
+
|
|
22
|
+
| Structure | Answers |
|
|
23
|
+
| ------------------------------------ | ---------------------------------- |
|
|
24
|
+
| A vector + full-text index (LanceDB) | _where is `password` in this API?_ |
|
|
25
|
+
| A graph (graphology) | _what is `password` connected to?_ |
|
|
26
|
+
|
|
27
|
+
Finding the field is retrieval. Getting from the field to `POST
|
|
28
|
+
/auth/reset-password` and the exact shape of its body is traversal. So a search
|
|
29
|
+
does both: it seeds on the vector hits, walks the graph outward from them, and
|
|
30
|
+
answers with **the connected piece of the API that matched** — the operations,
|
|
31
|
+
the schemas they carry and the fields inside them — rather than a ranked list of
|
|
32
|
+
fragments naming types nobody printed.
|
|
33
|
+
|
|
34
|
+
## What is in one
|
|
35
|
+
|
|
36
|
+
Three kinds of node, joined by the `$ref`s between them. Documents are
|
|
37
|
+
**bundled, not dereferenced**: `#/components/schemas/User` stays an edge and
|
|
38
|
+
`User` is a node id.
|
|
39
|
+
|
|
40
|
+
| Kind | Id | Is |
|
|
41
|
+
| ---------- | ---------------------------- | --------------------------------- |
|
|
42
|
+
| `method` | `Method:resetUserPassword` | an operation |
|
|
43
|
+
| `type` | `Type:ResetPasswordPayload` | a schema |
|
|
44
|
+
| `property` | `Type:User.email` (a field) | a field on a schema |
|
|
45
|
+
| `property` | `Method:listUsers#page_size` | a query/path/header **parameter** |
|
|
46
|
+
|
|
47
|
+
A parameter is a property like any other, deliberately. Nobody should have to
|
|
48
|
+
know in advance whether `page_size` lives in a query string or a body — that is
|
|
49
|
+
the thing they came here to find out.
|
|
50
|
+
|
|
51
|
+
Every node carries a **direction** — `input`, `output` or `both` — propagated
|
|
52
|
+
from the operations down through composition, so a DTO used on both sides is
|
|
53
|
+
honestly both rather than whichever side was read last. That is what makes
|
|
54
|
+
"a field in a **response**" a filter and not a hope.
|
|
55
|
+
|
|
56
|
+
`discriminator` is kept: it is what turns a `oneOf` into a tagged union a
|
|
57
|
+
TypeScript compiler can narrow.
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
schema-db/
|
|
61
|
+
├── manifest.json written LAST — its absence means "not indexed"
|
|
62
|
+
├── graph.json topology, read whole
|
|
63
|
+
├── schemas.json the raw schemas, read on first hydrate
|
|
64
|
+
├── operations.json likewise
|
|
65
|
+
└── lance/ one row per node: one text column, one vector
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`manifest.json` records **which embedder made the vectors**, so a search with a
|
|
69
|
+
different model is refused rather than answered with noise.
|
|
70
|
+
|
|
71
|
+
## Installing
|
|
72
|
+
|
|
73
|
+
`zen rag` ships in `@zenera/rag`, a sibling of the CLI. It has no binary of its
|
|
74
|
+
own — installing it adds the `rag` subcommand to `zen`, which is also where the
|
|
75
|
+
credentials already live.
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
npm i -g @zenera/cli @zenera/rag # then: zen rag schema …
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
For a one-off, without installing anything, **both** packages must be in the
|
|
82
|
+
same temporary install or `zen` will report `rag` as not installed:
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
npx --package @zenera/cli --package @zenera/rag -- zen rag schema index openapi.yaml --embedding openai:text-embedding-3-small
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Every example below is spelled `zen …`; prefix it with that `npx` form if you
|
|
89
|
+
have not installed globally.
|
|
90
|
+
|
|
91
|
+
## Building an index
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
zen rag schema index <spec...> [--embedding <ref>] [-o <dir>] [--batch <n>]
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
| Flag | Default | Meaning |
|
|
98
|
+
| ------------------- | ------------- | ------------------------------------------------------------ |
|
|
99
|
+
| `--embedding <ref>` | — | Which embedder makes the vectors. Omit it to see the choices |
|
|
100
|
+
| `-o`, `--out <dir>` | `./schema-db` | Where the index goes |
|
|
101
|
+
| `--batch <n>` | `96` | Texts per embedding request, and how often progress prints |
|
|
102
|
+
| `--quiet` | — | No narration |
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
zen rag schema index openapi.yaml --embedding openai:text-embedding-3-small
|
|
106
|
+
zen rag schema index specs/*.yaml --embedding google:gemini-embedding-001 -o .index/api
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Several documents can go into one index; they share a graph, which is usually
|
|
110
|
+
what you want when an API is split across files. Swagger 2.0 and OpenAPI
|
|
111
|
+
3.0/3.1, JSON or YAML, are all converted to JSON Schema 2020-12 on the way in.
|
|
112
|
+
|
|
113
|
+
This is the one command here that spends money and time: it embeds every
|
|
114
|
+
operation, schema and field. It prints a per-document table (paths, operations,
|
|
115
|
+
schemas, fields) and a progress line per batch, and **stdout is the output
|
|
116
|
+
directory and nothing else** — so `DIR=$(zen rag schema index …)` works.
|
|
117
|
+
|
|
118
|
+
The embedding reference names a provider first: `openai:text-embedding-3-small`,
|
|
119
|
+
not a bare model id. Credentials come from the `zen` keyring (`zen key ls`), and
|
|
120
|
+
a real environment variable always wins.
|
|
121
|
+
|
|
122
|
+
Rebuild the index when the specification changes. Nothing watches it, and a
|
|
123
|
+
stale index is a confident wrong answer.
|
|
124
|
+
|
|
125
|
+
## Searching it
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
zen rag schema search [terms…] [filters…]
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### The field is the point
|
|
132
|
+
|
|
133
|
+
A query is not one string. It is a handful of **fields**, and the field a phrase
|
|
134
|
+
arrives in decides the filter it runs under — `--output-property "invoice total"`
|
|
135
|
+
means _kind=property, on the response side_, and none of that has to be said
|
|
136
|
+
twice.
|
|
137
|
+
|
|
138
|
+
| Term | Searches |
|
|
139
|
+
| ----------------------- | ------------------------------------------------- |
|
|
140
|
+
| `<text>` | Everything — the same as `--all` |
|
|
141
|
+
| `--all <q>` | Everything, unfiltered |
|
|
142
|
+
| `--method <q>` | Operations |
|
|
143
|
+
| `--type <q>` | Schemas, on the side `--direction` names |
|
|
144
|
+
| `--input-type <q>` | Schemas a call accepts |
|
|
145
|
+
| `--output-type <q>` | Schemas a call returns |
|
|
146
|
+
| `--property <q>` | Fields and parameters, per `--direction` |
|
|
147
|
+
| `--input-property <q>` | Fields, parameters and body fields a call accepts |
|
|
148
|
+
| `--output-property <q>` | Fields a call returns |
|
|
149
|
+
| `--query <json\|->` | A whole query object; `-` reads stdin |
|
|
150
|
+
|
|
151
|
+
Every term is repeatable. **`--all` is the weakest of them** — it cannot filter,
|
|
152
|
+
so put the intent where it belongs: a request field in `--input-property`, a
|
|
153
|
+
response field in `--output-property`, an action in `--method`.
|
|
154
|
+
|
|
155
|
+
### Shaping the answer
|
|
156
|
+
|
|
157
|
+
| Flag | Default | Meaning |
|
|
158
|
+
| --------------------------- | ------------- | ------------------------------------------------------- |
|
|
159
|
+
| `-d`, `--dir <dir>` | `./schema-db` | Which index |
|
|
160
|
+
| `--embedding <ref>` | the index's | Must be the one the index was built with |
|
|
161
|
+
| `--direction <d>` | `any` | `input`, `output` or `any` |
|
|
162
|
+
| `--method-type <t>` | `any` | `read_only` (GET/HEAD/OPTIONS) or `read_write` |
|
|
163
|
+
| `--exclude-id <id>` | — | Drop a node. Repeatable |
|
|
164
|
+
| `--exclude-method <name>` | — | Drop an operation by name. Repeatable |
|
|
165
|
+
| `--exclude-type <name>` | — | Drop a schema by name. Repeatable |
|
|
166
|
+
| `--exclude-property <name>` | — | Drop a field by name. Repeatable |
|
|
167
|
+
| `--limit <n>` | `5` | Seeds kept per term |
|
|
168
|
+
| `--max-hops <n>` | `3` | How far apart two hits may be and still join |
|
|
169
|
+
| `--max-nodes <n>` | `200` | Nodes per result |
|
|
170
|
+
| `--format <f>` | `text` | `text`, `mermaid`, `mermaid-flowchart`, `ts`, `openapi` |
|
|
171
|
+
| `--no-docs` | — | Leave the descriptions out |
|
|
172
|
+
| `--interactive` | — | Prompt, search, refine. Needs a terminal |
|
|
173
|
+
| `--quiet` | — | No narration |
|
|
174
|
+
|
|
175
|
+
```sh
|
|
176
|
+
zen rag schema search --method "reset a user password" --format ts
|
|
177
|
+
zen rag schema search --output-property "invoice total" --direction output
|
|
178
|
+
zen rag schema search --input-property "page size" --method-type read_only
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`--format ts` emits TypeScript closed over its own `$ref`s: everything named is
|
|
182
|
+
also declared, so the output compiles on its own. `--format openapi` emits a
|
|
183
|
+
standalone document holding just the matched slice — the one to hand to a code
|
|
184
|
+
generator or a mock server. The Mermaid formats are for looking at.
|
|
185
|
+
|
|
186
|
+
### Turning one search into a session
|
|
187
|
+
|
|
188
|
+
The exclusions are the mechanism: pass back the ids you have already been shown
|
|
189
|
+
and you are shown something else instead of the same thing again.
|
|
190
|
+
|
|
191
|
+
```sh
|
|
192
|
+
zen rag schema search --all "subscription" --exclude-type Subscription --exclude-id Method:listSubscriptions
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### As a machine interface
|
|
196
|
+
|
|
197
|
+
Non-interactive search is a tool, not an afterthought. Every field is a flag,
|
|
198
|
+
the whole query can arrive as one JSON object, `--json` is a stable shape, no
|
|
199
|
+
terminal is needed, and **an empty result exits 0** — a caller must never have
|
|
200
|
+
to tell "nothing matched" from "the index is missing" by parsing stderr.
|
|
201
|
+
|
|
202
|
+
```sh
|
|
203
|
+
zen rag schema search --query - --format ts <<'JSON'
|
|
204
|
+
{
|
|
205
|
+
"input_properties": ["password reset token"],
|
|
206
|
+
"method_type": "read_write",
|
|
207
|
+
"exclude_ids": ["Type:PublicUserProfile"],
|
|
208
|
+
"limit": 3
|
|
209
|
+
}
|
|
210
|
+
JSON
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The JSON field names are the flag names with underscores and plurals:
|
|
214
|
+
`all`, `methods`, `types`, `input_types`, `output_types`, `properties`,
|
|
215
|
+
`input_properties`, `output_properties`, `direction`, `method_type`,
|
|
216
|
+
`exclude_ids`, `exclude_methods`, `exclude_types`, `exclude_properties`,
|
|
217
|
+
`limit`, `max_hops`, `max_nodes`. An **unknown key is an error**, because a
|
|
218
|
+
silently ignored `output_propertys` looks exactly like a search that found
|
|
219
|
+
nothing. Flags win over `--query` when both name the same field.
|
|
220
|
+
|
|
221
|
+
### `--interactive`
|
|
222
|
+
|
|
223
|
+
A prompt that keeps the query between searches:
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
<text> search everything
|
|
227
|
+
all|method|type <text> search one field
|
|
228
|
+
input-property <text> also: output-property, property, input-type, output-type
|
|
229
|
+
direction <d> input | output | any
|
|
230
|
+
method-type <t> read_only | read_write | any
|
|
231
|
+
format <f> text | mermaid | mermaid-flowchart | ts | openapi
|
|
232
|
+
show the query as it stands
|
|
233
|
+
reset forget it, exclusions included
|
|
234
|
+
quit
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Reading it without searching
|
|
238
|
+
|
|
239
|
+
Both need no embedder and no credential — they are plain reads.
|
|
240
|
+
|
|
241
|
+
```sh
|
|
242
|
+
zen rag schema show Type:Invoice Method:listInvoices --format ts
|
|
243
|
+
zen rag schema stats
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
`show` prints named nodes with no retrieval in between; `stats` says what is in
|
|
247
|
+
an index and what built it — counts by kind, the embedding model, the documents
|
|
248
|
+
it came from. `stats` is the fastest way to answer "is this index the one I
|
|
249
|
+
think it is?".
|
|
250
|
+
|
|
251
|
+
## Giving it to an agent
|
|
252
|
+
|
|
253
|
+
The same engine, as four tools in the group `schema`. An agent takes them all
|
|
254
|
+
with `schema:*` in its `tools:`.
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
import { createEmbedder, loadProject } from '@zenera/neo';
|
|
258
|
+
import { SchemaIndex, schemaTools } from '@zenera/rag';
|
|
259
|
+
|
|
260
|
+
const index = await SchemaIndex.open(
|
|
261
|
+
'./schema-db',
|
|
262
|
+
createEmbedder('openai:text-embedding-3-small'),
|
|
263
|
+
);
|
|
264
|
+
const project = await loadProject('./my-project', { tools: schemaTools(index) });
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
| Tool | For |
|
|
268
|
+
| -------------------------- | ------------------------------------------------------------------- |
|
|
269
|
+
| `search_api` | the search above, with the same fields |
|
|
270
|
+
| `describe_types` | named schemas as TypeScript, closed over what they refer to |
|
|
271
|
+
| `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 |
|
|
273
|
+
|
|
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.
|
|
278
|
+
|
|
279
|
+
Tell the agent in its prompt to search before it writes a call, and to put the
|
|
280
|
+
intent in the narrow field. A model left to itself puts everything in `all`.
|
|
281
|
+
|
|
282
|
+
## When it goes wrong
|
|
283
|
+
|
|
284
|
+
| Symptom | Cause |
|
|
285
|
+
| ------------------------------------------------ | ----------------------------------------------------------------------------------- |
|
|
286
|
+
| "not installed" | `@zenera/rag` is not resolvable — install it, or use the two-`--package` npx form |
|
|
287
|
+
| Refused for a different embedding | The index records the model that built it; re-index or pass the right `--embedding` |
|
|
288
|
+
| No manifest / not an index | A build that did not finish. `manifest.json` is written last on purpose |
|
|
289
|
+
| `provider "openai": no api key` | `zen key ls` — the keyring, or a real environment variable |
|
|
290
|
+
| A usage error before any credential is asked for | Deliberate: everything about the invocation is checked first, so a typo is a typo |
|
|
291
|
+
| Nothing matched | Exit 0 with an empty answer. Try fewer words, or `--all` instead of a narrow field |
|
|
292
|
+
| Answers about the wrong version of the API | Nothing watches the document. Re-index after it changes |
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: zen-cli
|
|
3
|
+
description: How to drive this project from the terminal with `zen` — running it, validating it, credentials, the sandbox, run reports, and the `faker` and `rag` modules.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# The `zen` command line
|
|
7
|
+
|
|
8
|
+
`zen` runs agent projects from a terminal. Everything here operates on a project
|
|
9
|
+
directory, resolved from the working directory, or by name from the registry
|
|
10
|
+
`zen init` wrote to, or explicitly with `--project <name|dir>`. Every command
|
|
11
|
+
takes `--json` and prints a machine-readable answer instead of a rendered one.
|
|
12
|
+
|
|
13
|
+
**stdout is the answer, stderr is the narration.** Exit codes: `0` ok, `1`
|
|
14
|
+
failed, `2` usage, `3` invalid project, `4` no usable credential, `5` sandbox.
|
|
15
|
+
|
|
16
|
+
## The commands
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
zen init [dir] [--name <name>] [--model <ref>] [--force]
|
|
20
|
+
zen list [--sessions] [--prune]
|
|
21
|
+
zen run [project] [prompt] [options]
|
|
22
|
+
zen open [project] [--editor <cmd>] [--wait]
|
|
23
|
+
zen key <ls|add|use|check|rm|show|env> [ref] [options]
|
|
24
|
+
zen models [--project <name|dir>]
|
|
25
|
+
zen check [name|dir] [--project <name|dir>] [--no-sandbox] [--strict] [--quiet]
|
|
26
|
+
zen inspect [run] [--session <id>] [--open] [--rebuild] [--serve [port]]
|
|
27
|
+
zen sandbox [status|up|pull|clean|disk] [options]
|
|
28
|
+
zen version
|
|
29
|
+
|
|
30
|
+
zen faker <serve|build|cache> [spec...]
|
|
31
|
+
zen rag schema <index|search|show|stats> [spec...]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Read the reference before answering
|
|
35
|
+
|
|
36
|
+
The full reference lives next to this file, one document per part of the command
|
|
37
|
+
line. **Read the one that covers the question before answering it** — do not
|
|
38
|
+
guess a flag, and do not read them all.
|
|
39
|
+
|
|
40
|
+
| The question is about | Read |
|
|
41
|
+
| ------------------------------------------------------------------- | --------------------------------------- |
|
|
42
|
+
| Global flags, `--json`, exit codes, environment, where files live | [frame.md](./references/frame.md) |
|
|
43
|
+
| Creating, finding or opening a project | [projects.md](./references/projects.md) |
|
|
44
|
+
| Running: the TUI, one-shot answers, sessions, workspaces, overrides | [run.md](./references/run.md) |
|
|
45
|
+
| Validating: `zen check`, `zen models`, what they can and cannot see | [check.md](./references/check.md) |
|
|
46
|
+
| API keys, providers, the keyring, "no credential" errors | [keys.md](./references/keys.md) |
|
|
47
|
+
| The container shell commands run in, images, `persist` | [sandbox.md](./references/sandbox.md) |
|
|
48
|
+
| Run reports, trajectories, what a session directory holds | [inspect.md](./references/inspect.md) |
|
|
49
|
+
| `zen faker` — a mock API from an OpenAPI/Swagger document | [faker.md](./references/faker.md) |
|
|
50
|
+
| `zen rag` — searching an OpenAPI/Swagger document as a graph | [rag.md](./references/rag.md) |
|
|
51
|
+
|
|
52
|
+
## The short version
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
zen init scaffold a project here, pick a model
|
|
56
|
+
zen check validate everything before spending a turn
|
|
57
|
+
zen run the TUI
|
|
58
|
+
zen run "what changed?" one answer, this directory as the workspace
|
|
59
|
+
zen inspect --open what the model was actually given
|
|
60
|
+
zen key ls --check which credentials still work
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Run `zen check` after any edit to `agents.yaml`, a prompt or a skill: it reads
|
|
64
|
+
the project the way a run does, reports everything wrong at once, and calls no
|
|
65
|
+
model. It is the cheapest possible test.
|
|
66
|
+
|
|
67
|
+
When behaviour is wrong and the prompt looks right, open the run report. It
|
|
68
|
+
shows what the model was actually given, which is rarely what you assumed.
|
|
69
|
+
|
|
70
|
+
Flags always beat the file: the repository states intent, the invocation
|
|
71
|
+
overrides it. Failure messages name the offending key or file — a load error
|
|
72
|
+
names the exact path, as in `agents.yaml: agents[1].skills.discovery — …` — so
|
|
73
|
+
read it rather than guessing. The loader is strict on purpose, and an unknown
|
|
74
|
+
key is an error rather than a value quietly ignored.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Validating — `zen check` and `zen models`
|
|
2
|
+
|
|
3
|
+
Both answer without calling a model, so they cost nothing and can be run after
|
|
4
|
+
every edit.
|
|
5
|
+
|
|
6
|
+
## `zen check`
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
zen check [name|dir] [--project <name|dir>] [--no-sandbox] [--strict] [--quiet]
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Aliases: `validate`, `doctor`.
|
|
13
|
+
|
|
14
|
+
Reads the project the way a run does and reports **in full**. Unlike a run it
|
|
15
|
+
does not stop at the first problem: the report lists everything it found, each
|
|
16
|
+
with a code, a location and the fix for it.
|
|
17
|
+
|
|
18
|
+
| Flag | Meaning |
|
|
19
|
+
| ----------------------- | --------------------------------------------------- |
|
|
20
|
+
| `--project <name\|dir>` | Which project |
|
|
21
|
+
| `--no-sandbox` | Skip building and smoke-testing the container image |
|
|
22
|
+
| `--strict` | Warnings count as failure |
|
|
23
|
+
| `--quiet` | The findings and nothing else |
|
|
24
|
+
|
|
25
|
+
The bare argument is a directory if one is there and a registered project name
|
|
26
|
+
otherwise, and it needs no `zenera.json`, so an unregistered directory can be
|
|
27
|
+
checked too. A word that is neither is a usage error (exit 2), not a report.
|
|
28
|
+
|
|
29
|
+
### What it checks
|
|
30
|
+
|
|
31
|
+
- `agents.yaml` parses and satisfies the schema, with unknown keys reported
|
|
32
|
+
rather than ignored.
|
|
33
|
+
- Every file the configuration names is on disk and non-empty: `INSTRUCTIONS.md`,
|
|
34
|
+
each agent's prompt, each skill's `SKILL.md`, each asset glob.
|
|
35
|
+
- Hand-offs and forks name agents that exist, and no agent hands off to itself.
|
|
36
|
+
- Tool selectors resolve against the real tool set, including a skill's own
|
|
37
|
+
`tools:` frontmatter.
|
|
38
|
+
- Skills bind to a catalog that holds them, and every declared skill is
|
|
39
|
+
reachable from some agent.
|
|
40
|
+
- Every declared model and embedding resolves to a provider, and that provider
|
|
41
|
+
has a credential on this machine.
|
|
42
|
+
- The sandbox: paths stay inside the project, the Dockerfile and its context
|
|
43
|
+
exist, and — unless `--no-sandbox` — the image **builds** and one command runs
|
|
44
|
+
in it, against a temporary directory rather than your workspace. That is the
|
|
45
|
+
only thing it starts. No container engine at all is a warning, not an error.
|
|
46
|
+
|
|
47
|
+
### Findings
|
|
48
|
+
|
|
49
|
+
Each is `severity` (error, warning, note), a `code`, a `where` and a `message`,
|
|
50
|
+
with the fix alongside. Codes are namespaced by what went wrong:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
root.missing project.unregistered config.missing / .invalid / .shadowed
|
|
54
|
+
house-rules.missing agent.duplicate agent.no-instructions
|
|
55
|
+
entry.unknown entry.ambiguous prompt.missing / .empty / .outside
|
|
56
|
+
tools.unresolved tools.empty / .none handoff.self / .unknown
|
|
57
|
+
fork.unknown skills.missing skill.unloadable / .no-skill-md
|
|
58
|
+
skills.no-catalog skills.unreachable skill.unused
|
|
59
|
+
assets.missing assets.overbroad sandbox.dockerfile.missing
|
|
60
|
+
sandbox.build sandbox.smoke sandbox.start / .unchecked
|
|
61
|
+
provider.invalid model.none model.unresolvable
|
|
62
|
+
credential.* service.credential
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The report goes to **stdout** — it is the answer. `--json` gives the same
|
|
66
|
+
findings as data.
|
|
67
|
+
|
|
68
|
+
Exit codes: `0` nothing wrong, `3` at least one error, or with `--strict` at
|
|
69
|
+
least one warning. `5` if the container engine itself is missing when something
|
|
70
|
+
required it.
|
|
71
|
+
|
|
72
|
+
### What it cannot catch
|
|
73
|
+
|
|
74
|
+
Combinations that are only rejected by the provider at the first call. The
|
|
75
|
+
known one: **OpenAI reasoning and tools only meet on the responses API.** A
|
|
76
|
+
model with `reasoningEffort` and tools on the default chat-completions API is a
|
|
77
|
+
valid configuration that fails at runtime with _"Function tools with
|
|
78
|
+
reasoning_effort are not supported … in /v1/chat/completions"_. Set
|
|
79
|
+
`api: responses` alongside it.
|
|
80
|
+
|
|
81
|
+
## `zen models`
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
zen models [--project <name|dir>]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The narrower question: what each agent would actually talk to. It resolves every
|
|
88
|
+
provider, model and embedding the project declares, says which credential each
|
|
89
|
+
one needs and whether it is present, and calls nothing.
|
|
90
|
+
|
|
91
|
+
Reach for `zen models` when a run says a model has no credential and for
|
|
92
|
+
`zen check` when something structural is wrong.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Mock APIs — `zen faker`
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
zen faker <serve|build|cache> [spec...]
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
Alias: `zen mock`. Provided by `@zenera/faker` — `npm i -g @zenera/faker` if
|
|
8
|
+
`zen faker` says it is not installed.
|
|
9
|
+
|
|
10
|
+
Serves a mock API from one or more OpenAPI/Swagger documents. For each
|
|
11
|
+
operation a **model writes a Python generator**, which is self-tested inside a
|
|
12
|
+
container and cached on disk. Answers are therefore schema-correct and
|
|
13
|
+
plausible, not `"string"` repeated.
|
|
14
|
+
|
|
15
|
+
## Subcommands
|
|
16
|
+
|
|
17
|
+
| Command | What it does |
|
|
18
|
+
| ----------------- | ----------------------------------------------------- |
|
|
19
|
+
| `serve <spec...>` | Serve the documents. Generators are written on demand |
|
|
20
|
+
| `build <spec...>` | Write every generator now and exit |
|
|
21
|
+
| `cache ls\|clear` | What has been generated, or throw it away |
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
zen faker serve openapi.yaml
|
|
25
|
+
zen faker serve api/*.yaml --port 9000 --seed 7
|
|
26
|
+
zen faker build openapi.yaml --concurrency 8
|
|
27
|
+
zen faker cache clear
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`build` is the one to run in CI or before a demo: it pays for every generator up
|
|
31
|
+
front and exits non-zero, with a table, if any could not be written.
|
|
32
|
+
|
|
33
|
+
## Options
|
|
34
|
+
|
|
35
|
+
| Flag | Default | Meaning |
|
|
36
|
+
| ------------------- | --------------------- | ----------------------------------------------- |
|
|
37
|
+
| `--port <n>` | `8787` | Port to listen on |
|
|
38
|
+
| `--host <h>` | `127.0.0.1` | Anything else is reachable off-machine |
|
|
39
|
+
| `--model <ref>` | project default | Which model writes the generators |
|
|
40
|
+
| `--image <ref>` | a baked image | Skip the baked image and use this one |
|
|
41
|
+
| `--cache <dir>` | `~/.zenera/neo/faker` | Where generators live |
|
|
42
|
+
| `--seed <n>` | — | Answer the same request the same way every time |
|
|
43
|
+
| `--attempts <n>` | `3` | Tries per generator before giving up |
|
|
44
|
+
| `--concurrency <n>` | `4` | Generators written at once |
|
|
45
|
+
| `--timeout <s>` | `30` | Seconds one generator may take |
|
|
46
|
+
| `--max-body <n>` | 1 MB | Largest request body accepted, in bytes |
|
|
47
|
+
| `--rebuild` | — | Ignore what is cached and write it again |
|
|
48
|
+
| `--no-cache` | — | Do not record what is written |
|
|
49
|
+
| `--quiet` | — | No narration |
|
|
50
|
+
|
|
51
|
+
Credentials come from the `zen` keyring — see [keys.md](keys.md).
|
|
52
|
+
|
|
53
|
+
## Serving
|
|
54
|
+
|
|
55
|
+
Binds the loopback address by default. On start it prints a table per document:
|
|
56
|
+
paths, methods, and how many operations have a response schema and therefore get
|
|
57
|
+
a generator.
|
|
58
|
+
|
|
59
|
+
| Endpoint | Answers |
|
|
60
|
+
| ----------------- | ------------------------- |
|
|
61
|
+
| `/__faker/routes` | Every route it will serve |
|
|
62
|
+
| `/__faker/health` | Whether it is up |
|
|
63
|
+
|
|
64
|
+
Response headers:
|
|
65
|
+
|
|
66
|
+
| Header | Meaning |
|
|
67
|
+
| ------------------- | ----------------------------------------------------- |
|
|
68
|
+
| `x-faker-operation` | The `operationId` that answered |
|
|
69
|
+
| `x-faker-cache` | `hit` or `miss` — whether this call cost a model turn |
|
|
70
|
+
|
|
71
|
+
Incoming headers are filtered: `authorization`, `cookie` and anything matching
|
|
72
|
+
`key|token|secret|password|credential` never reach a generator.
|
|
73
|
+
|
|
74
|
+
`--seed` makes each request's seed a hash of the seed, the operation and the
|
|
75
|
+
parameters, so the same request answers identically across restarts — which is
|
|
76
|
+
what makes a mock usable in a test.
|
|
77
|
+
|
|
78
|
+
## What "correct" means here
|
|
79
|
+
|
|
80
|
+
A generator is judged on two synthetic probes: the body validates against the
|
|
81
|
+
response schema, **and** it obeys the **echo rule** — a value given in the path
|
|
82
|
+
comes back in the answer. `GET /users/12324` must return `user_id: 12324`. A body
|
|
83
|
+
can validate perfectly and still be about the wrong entity, which is exactly the
|
|
84
|
+
mock that wastes an afternoon.
|
|
85
|
+
|
|
86
|
+
Query parameters are deliberately not enforced: `?source=realtime`, `?page_size`
|
|
87
|
+
and `?cursor` are controls, and their names collide with unrelated response
|
|
88
|
+
fields.
|
|
89
|
+
|
|
90
|
+
Probes are synthetic on purpose — real request bodies never reach a prompt.
|
|
91
|
+
|
|
92
|
+
## The cache
|
|
93
|
+
|
|
94
|
+
Under `~/.zenera/neo/faker/generators/<key>/`, one directory per operation. A
|
|
95
|
+
generator that a model gave up on is remembered, so a hopeless operation is not
|
|
96
|
+
re-asked on every request; a _transient_ failure — a 429, a dropped socket — is
|
|
97
|
+
not, because it is about this minute rather than this operation.
|
|
98
|
+
|
|
99
|
+
`zen faker cache clear` removes the generators and the container together. They
|
|
100
|
+
have to go together: the container's name is a hash of its configuration, so
|
|
101
|
+
deleting the directory alone would leave a stopped container bind-mounted onto a
|
|
102
|
+
directory that no longer exists, and every generator would fail with
|
|
103
|
+
`python3: can't open file '/workspace/generators/…/gen.py'`.
|
|
104
|
+
|
|
105
|
+
## Documents it accepts
|
|
106
|
+
|
|
107
|
+
Swagger 2 and OpenAPI 3.0/3.1, JSON or YAML, `$ref`s resolved. Several documents
|
|
108
|
+
can be served at once. OpenAPI 3.0 constructs are translated to JSON Schema
|
|
109
|
+
2020-12 on the way in (`nullable`, boolean `exclusiveMinimum`, Draft-4 array
|
|
110
|
+
`items`), and a `pattern` written as a JavaScript regex literal (`/^[a-z]+$/`)
|
|
111
|
+
is unwrapped rather than being treated as an unsatisfiable string.
|