@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.
Files changed (61) hide show
  1. package/README.md +88 -11
  2. package/dist/audit.d.ts +8 -6
  3. package/dist/audit.js +14 -22
  4. package/dist/commands/check.js +79 -19
  5. package/dist/commands/init.js +71 -11
  6. package/dist/commands/key.js +126 -36
  7. package/dist/commands/models.js +3 -3
  8. package/dist/commands/open.js +2 -2
  9. package/dist/commands/run.js +3 -0
  10. package/dist/commands/sandbox.js +226 -22
  11. package/dist/engine.d.ts +3 -1
  12. package/dist/engine.js +10 -2
  13. package/dist/image.d.ts +16 -0
  14. package/dist/image.js +85 -0
  15. package/dist/keys.d.ts +95 -12
  16. package/dist/keys.js +175 -34
  17. package/dist/lib.d.ts +2 -2
  18. package/dist/lib.js +2 -2
  19. package/dist/liveness.d.ts +16 -6
  20. package/dist/liveness.js +74 -23
  21. package/dist/main.js +0 -0
  22. package/dist/podman.d.ts +57 -1
  23. package/dist/podman.js +177 -12
  24. package/dist/projects.d.ts +18 -0
  25. package/dist/projects.js +60 -1
  26. package/dist/sandbox.d.ts +14 -1
  27. package/dist/sandbox.js +88 -8
  28. package/dist/scaffold.d.ts +21 -15
  29. package/dist/scaffold.js +133 -167
  30. package/dist/term.d.ts +2 -0
  31. package/dist/term.js +14 -0
  32. package/dist/validate.d.ts +20 -3
  33. package/dist/validate.js +309 -14
  34. package/package.json +2 -18
  35. package/templates/{.github → editor/.github}/copilot-instructions.md +161 -48
  36. package/templates/{.github → editor/.github}/prompts/new-skill.prompt.md +13 -6
  37. package/templates/editor/.github/skills/api-schema-index/SKILL.md +292 -0
  38. package/templates/editor/.github/skills/zen-cli/SKILL.md +74 -0
  39. package/templates/editor/.github/skills/zen-cli/references/check.md +92 -0
  40. package/templates/editor/.github/skills/zen-cli/references/faker.md +111 -0
  41. package/templates/editor/.github/skills/zen-cli/references/frame.md +119 -0
  42. package/templates/editor/.github/skills/zen-cli/references/inspect.md +61 -0
  43. package/templates/editor/.github/skills/zen-cli/references/keys.md +114 -0
  44. package/templates/editor/.github/skills/zen-cli/references/projects.md +99 -0
  45. package/templates/editor/.github/skills/zen-cli/references/rag.md +159 -0
  46. package/templates/editor/.github/skills/zen-cli/references/run.md +104 -0
  47. package/templates/editor/.github/skills/zen-cli/references/sandbox.md +91 -0
  48. package/templates/editor/.vscode/settings.json +6 -0
  49. package/templates/parts/exa.yaml.tmpl +5 -0
  50. package/templates/parts/model.yaml.tmpl +4 -0
  51. package/templates/parts/models.yaml.tmpl +10 -0
  52. package/templates/project/INSTRUCTIONS.md +7 -0
  53. package/templates/project/SPECIFICATION.md +6 -0
  54. package/templates/project/agents/prompts/default.md +15 -0
  55. package/templates/project/agents.yaml.tmpl +44 -0
  56. package/templates/project/assets/README.md +12 -0
  57. package/templates/project/gitignore +9 -0
  58. package/templates/project/sandbox/Dockerfile +21 -0
  59. package/templates/.github/skills/zen-cli/SKILL.md +0 -110
  60. /package/templates/{.github → editor/.github}/prompts/new-agent.prompt.md +0 -0
  61. /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.