@zenera/cli 1.1.2 → 1.1.4
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 +228 -31
- package/dist/audit.d.ts +13 -8
- package/dist/audit.js +21 -24
- package/dist/catalog.d.ts +111 -0
- package/dist/catalog.js +439 -0
- package/dist/commands/check.js +72 -17
- package/dist/commands/index.d.ts +2 -2
- package/dist/commands/index.js +3 -2
- package/dist/commands/init.js +71 -11
- package/dist/commands/key.js +144 -36
- package/dist/commands/models.d.ts +0 -6
- package/dist/commands/models.js +546 -101
- package/dist/commands/open.js +2 -2
- package/dist/commands/run.js +3 -0
- package/dist/engine.d.ts +2 -0
- package/dist/engine.js +1 -0
- package/dist/home.d.ts +2 -0
- package/dist/home.js +2 -0
- package/dist/keys.d.ts +104 -13
- package/dist/keys.js +175 -34
- package/dist/lib.d.ts +2 -1
- package/dist/lib.js +2 -1
- package/dist/liveness.d.ts +48 -6
- package/dist/liveness.js +268 -28
- package/dist/sandbox.d.ts +2 -0
- package/dist/sandbox.js +58 -7
- package/dist/scaffold.d.ts +21 -21
- package/dist/scaffold.js +132 -204
- package/dist/validate.d.ts +17 -1
- package/dist/validate.js +100 -10
- package/package.json +2 -18
- package/templates/{.github → editor/.github}/copilot-instructions.md +37 -9
- package/templates/editor/.github/skills/api-schema-index/SKILL.md +292 -0
- package/templates/editor/.github/skills/zen-cli/SKILL.md +77 -0
- package/templates/editor/.github/skills/zen-cli/references/check.md +88 -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 +119 -0
- package/templates/editor/.github/skills/zen-cli/references/models.md +108 -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/{sandbox → project/sandbox}/Dockerfile +2 -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/new-skill.prompt.md +0 -0
- /package/templates/{.github → editor/.github}/prompts/review-project.prompt.md +0 -0
|
@@ -382,8 +382,8 @@ Google's `embedContent` takes one document per request for every
|
|
|
382
382
|
hide that. `maxBatch` therefore defaults to `1`; raise it only for a
|
|
383
383
|
`text-embedding-*` model, which accepts more.
|
|
384
384
|
|
|
385
|
-
`zen check`
|
|
386
|
-
|
|
385
|
+
`zen check` reports every declared embedding beside the models, with the
|
|
386
|
+
credential each one would need.
|
|
387
387
|
|
|
388
388
|
### 3.2 `INSTRUCTIONS.md`
|
|
389
389
|
|
|
@@ -621,12 +621,13 @@ container involved there is no second name and everything stays relative.
|
|
|
621
621
|
|
|
622
622
|
**Selecting them.** A `tools:` entry is a selector, not only a name:
|
|
623
623
|
|
|
624
|
-
| Selector
|
|
625
|
-
|
|
|
626
|
-
| `read_file`
|
|
627
|
-
| `workspace
|
|
628
|
-
| `
|
|
629
|
-
|
|
|
624
|
+
| Selector | Selects |
|
|
625
|
+
| --------------------- | ------------------------------------------------- |
|
|
626
|
+
| `read_file` | that one tool |
|
|
627
|
+
| `workspace:read_file` | the same tool, written out in full |
|
|
628
|
+
| `workspace:*` | every tool in the group |
|
|
629
|
+
| `'*'` | every tool the runtime provides |
|
|
630
|
+
| `-<any>` | removes what it matches from the selection so far |
|
|
630
631
|
|
|
631
632
|
```yaml
|
|
632
633
|
agents:
|
|
@@ -1539,8 +1540,34 @@ There is no compiler for prose. Substitutes, in order of value:
|
|
|
1539
1540
|
credential resolves, not that the route serves images or honours `tools`; on a
|
|
1540
1541
|
gateway that gap is a request-time 404 (§7.5).
|
|
1541
1542
|
|
|
1543
|
+
### When a model or embedder will not answer
|
|
1544
|
+
|
|
1545
|
+
`zen check` names the model and the verdict. Act on the verdict, not on the
|
|
1546
|
+
first thing that looks like a credential problem:
|
|
1547
|
+
|
|
1548
|
+
```sh
|
|
1549
|
+
zen models test <ref> # one real call, one verdict
|
|
1550
|
+
```
|
|
1551
|
+
|
|
1552
|
+
- **refused** — the credential was rejected. `zen key check <provider>`.
|
|
1553
|
+
- **blocked** — the credential was _accepted_ and the account said no: an API
|
|
1554
|
+
switched off, an empty balance, a model this key was never granted. **Do not
|
|
1555
|
+
rotate the key** — a new one is refused identically. The fix is printed under
|
|
1556
|
+
the verdict; for a disabled Google API it is the exact `gcloud services
|
|
1557
|
+
enable …` line.
|
|
1558
|
+
- **no answer** — the network. Try again.
|
|
1559
|
+
|
|
1560
|
+
Either run the printed fix, or take a model that works and put the ref it prints
|
|
1561
|
+
into `agents.yaml`:
|
|
1562
|
+
|
|
1563
|
+
```sh
|
|
1564
|
+
zen models pick --embedding # or --chat; the ref goes to stdout, alone
|
|
1565
|
+
```
|
|
1566
|
+
|
|
1567
|
+
Then re-run `zen check`.
|
|
1568
|
+
|
|
1542
1569
|
CLI (`zen --help` for the authoritative list): `zen init`, `zen run`, `zen check`,
|
|
1543
|
-
`zen inspect`, `zen
|
|
1570
|
+
`zen inspect`, `zen key`, `zen models`, `zen list`. **stdout is the answer, stderr
|
|
1544
1571
|
is the narration**; every command takes `--json`. Exit codes: `0` ok, `1` failed,
|
|
1545
1572
|
`2` usage, `3` invalid project, `4` no usable credential.
|
|
1546
1573
|
|
|
@@ -1630,6 +1657,7 @@ Before finishing any change here:
|
|
|
1630
1657
|
| Answers from stale knowledge of the world | Grant `web_search` + `web_read`, and say when — §3.8 |
|
|
1631
1658
|
| Cites a page it only saw the excerpt of | A prompt line: `web_read` before quoting — §3.8 |
|
|
1632
1659
|
| Every web call refuses | No Exa key: `zen key add exa` — `zen check` warns — §3.8 |
|
|
1660
|
+
| A model or embedder refuses every call | `zen models test <ref>` — if `blocked`, `zen models pick` — §8 |
|
|
1633
1661
|
| Answers instead of routing | Router prompt prohibition; check `handoffs:` |
|
|
1634
1662
|
| Routes to the wrong specialist | The target agents' `description:` fields |
|
|
1635
1663
|
| Loses a detail after a handoff | Say it in the handoff; check the collapse policy |
|
|
@@ -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,77 @@
|
|
|
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 <providers|ls|search|show|test|pick> [ref] [options]
|
|
25
|
+
zen check [name|dir] [--project <name|dir>] [--no-sandbox] [--no-models] [--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`, what it can and cannot see | [check.md](./references/check.md) |
|
|
46
|
+
| API keys, providers, the keyring, "no credential" errors | [keys.md](./references/keys.md) |
|
|
47
|
+
| Which models exist, whether one works, recovering from a refusal | [models.md](./references/models.md) |
|
|
48
|
+
| The container shell commands run in, images, `persist` | [sandbox.md](./references/sandbox.md) |
|
|
49
|
+
| Run reports, trajectories, what a session directory holds | [inspect.md](./references/inspect.md) |
|
|
50
|
+
| `zen faker` — a mock API from an OpenAPI/Swagger document | [faker.md](./references/faker.md) |
|
|
51
|
+
| `zen rag` — searching an OpenAPI/Swagger document as a graph | [rag.md](./references/rag.md) |
|
|
52
|
+
|
|
53
|
+
## The short version
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
zen init scaffold a project here, pick a model
|
|
57
|
+
zen check validate everything before spending a turn
|
|
58
|
+
zen run the TUI
|
|
59
|
+
zen run "what changed?" one answer, this directory as the workspace
|
|
60
|
+
zen inspect --open what the model was actually given
|
|
61
|
+
zen key ls --check which credentials still work
|
|
62
|
+
zen models test <ref> whether one model actually answers
|
|
63
|
+
zen models pick --embedding the first embedder that does, on stdout
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Run `zen check` after any edit to `agents.yaml`, a prompt or a skill: it reads
|
|
67
|
+
the project the way a run does, reports everything wrong at once, and calls no
|
|
68
|
+
model. It is the cheapest possible test.
|
|
69
|
+
|
|
70
|
+
When behaviour is wrong and the prompt looks right, open the run report. It
|
|
71
|
+
shows what the model was actually given, which is rarely what you assumed.
|
|
72
|
+
|
|
73
|
+
Flags always beat the file: the repository states intent, the invocation
|
|
74
|
+
overrides it. Failure messages name the offending key or file — a load error
|
|
75
|
+
names the exact path, as in `agents.yaml: agents[1].skills.discovery — …` — so
|
|
76
|
+
read it rather than guessing. The loader is strict on purpose, and an unknown
|
|
77
|
+
key is an error rather than a value quietly ignored.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Validating — `zen check`
|
|
2
|
+
|
|
3
|
+
It reads the project without running it, and spends a few tokens asking each
|
|
4
|
+
model to answer once; `--no-models` gives an answer that costs nothing at all.
|
|
5
|
+
|
|
6
|
+
## `zen check`
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
zen check [name|dir] [--project <name|dir>] [--no-sandbox] [--no-models] [--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
|
+
| `--no-models` | Skip asking each model to answer |
|
|
23
|
+
| `--strict` | Warnings count as failure |
|
|
24
|
+
| `--quiet` | The findings and nothing else |
|
|
25
|
+
|
|
26
|
+
The bare argument is a directory if one is there and a registered project name
|
|
27
|
+
otherwise, and it needs no `zenera.json`, so an unregistered directory can be
|
|
28
|
+
checked too. A word that is neither is a usage error (exit 2), not a report.
|
|
29
|
+
|
|
30
|
+
### What it checks
|
|
31
|
+
|
|
32
|
+
- `agents.yaml` parses and satisfies the schema, with unknown keys reported
|
|
33
|
+
rather than ignored.
|
|
34
|
+
- Every file the configuration names is on disk and non-empty: `INSTRUCTIONS.md`,
|
|
35
|
+
each agent's prompt, each skill's `SKILL.md`, each asset glob.
|
|
36
|
+
- Hand-offs and forks name agents that exist, and no agent hands off to itself.
|
|
37
|
+
- Tool selectors resolve against the real tool set, including a skill's own
|
|
38
|
+
`tools:` frontmatter.
|
|
39
|
+
- Skills bind to a catalog that holds them, and every declared skill is
|
|
40
|
+
reachable from some agent.
|
|
41
|
+
- Every declared model and embedding resolves to a provider, and that provider
|
|
42
|
+
has a credential on this machine.
|
|
43
|
+
- Unless `--no-models`, every model that has a credential is **asked to answer**
|
|
44
|
+
once. A key that authenticates says nothing about the id it is spent on, so
|
|
45
|
+
this is the only way to catch a misspelt, retired or ungranted model. A refusal
|
|
46
|
+
is an error (`model.refused`, `embedding.refused`); a model that never answered
|
|
47
|
+
is a warning (`.unreachable`), because that is the network's problem and not
|
|
48
|
+
the project's.
|
|
49
|
+
- The sandbox: paths stay inside the project, the Dockerfile and its context
|
|
50
|
+
exist, and — unless `--no-sandbox` — the image **builds** and one command runs
|
|
51
|
+
in it, against a temporary directory rather than your workspace. No container
|
|
52
|
+
engine at all is a warning, not an error.
|
|
53
|
+
|
|
54
|
+
### Findings
|
|
55
|
+
|
|
56
|
+
Each is `severity` (error, warning, note), a `code`, a `where` and a `message`,
|
|
57
|
+
with the fix alongside. Codes are namespaced by what went wrong:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
root.missing project.unregistered config.missing / .invalid / .shadowed
|
|
61
|
+
house-rules.missing agent.duplicate agent.no-instructions
|
|
62
|
+
entry.unknown entry.ambiguous prompt.missing / .empty / .outside
|
|
63
|
+
tools.unresolved tools.empty / .none handoff.self / .unknown
|
|
64
|
+
fork.unknown skills.missing skill.unloadable / .no-skill-md
|
|
65
|
+
skills.no-catalog skills.unreachable skill.unused
|
|
66
|
+
assets.missing assets.overbroad sandbox.dockerfile.missing
|
|
67
|
+
sandbox.build sandbox.smoke sandbox.start / .unchecked
|
|
68
|
+
provider.invalid model.none model.unresolvable
|
|
69
|
+
model.refused model.unreachable model.unusable
|
|
70
|
+
embedding.refused embedding.unreachable embedding.unusable
|
|
71
|
+
credential.* service.credential
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The report goes to **stdout** — it is the answer. `--json` gives the same
|
|
75
|
+
findings as data.
|
|
76
|
+
|
|
77
|
+
Exit codes: `0` nothing wrong, `3` at least one error, or with `--strict` at
|
|
78
|
+
least one warning. `5` if the container engine itself is missing when something
|
|
79
|
+
required it.
|
|
80
|
+
|
|
81
|
+
### What it cannot catch
|
|
82
|
+
|
|
83
|
+
Combinations that are only rejected by the provider at the first call. The
|
|
84
|
+
known one: **OpenAI reasoning and tools only meet on the responses API.** A
|
|
85
|
+
model with `reasoningEffort` and tools on the default chat-completions API is a
|
|
86
|
+
valid configuration that fails at runtime with _"Function tools with
|
|
87
|
+
reasoning_effort are not supported … in /v1/chat/completions"_. Set
|
|
88
|
+
`api: responses` alongside it.
|
|
@@ -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.
|