@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
package/README.md
CHANGED
|
@@ -22,12 +22,11 @@ carries your keys with it.
|
|
|
22
22
|
|
|
23
23
|
## Install
|
|
24
24
|
|
|
25
|
-
Node.js 24+.
|
|
26
|
-
|
|
25
|
+
Node.js 24+. One command — the OpenAI, Anthropic, Google and OpenRouter SDKs
|
|
26
|
+
all ship with the CLI, so any provider works out of the box.
|
|
27
27
|
|
|
28
28
|
```sh
|
|
29
|
-
npm i -g @zenera/cli
|
|
30
|
-
# or @anthropic-ai/sdk, @google/genai, @openrouter/sdk — any mix of them
|
|
29
|
+
npm i -g @zenera/cli
|
|
31
30
|
```
|
|
32
31
|
|
|
33
32
|
Or without installing anything:
|
|
@@ -41,8 +40,8 @@ npx @zenera/cli --help
|
|
|
41
40
|
Four commands, from nothing to an answer:
|
|
42
41
|
|
|
43
42
|
```sh
|
|
44
|
-
npm i -g @zenera/cli
|
|
45
|
-
zen key add openai #
|
|
43
|
+
npm i -g @zenera/cli # every vendor SDK comes with it
|
|
44
|
+
zen key add openai # asks for the key without showing it; stored in ~/.zenera
|
|
46
45
|
zen init my-project # scaffolds a project and registers it
|
|
47
46
|
cd my-project && zen run "introduce yourself"
|
|
48
47
|
```
|
|
@@ -50,7 +49,7 @@ cd my-project && zen run "introduce yourself"
|
|
|
50
49
|
Then the rest of the loop:
|
|
51
50
|
|
|
52
51
|
```sh
|
|
53
|
-
zen run # nothing to say yet — a
|
|
52
|
+
zen run # nothing to say yet — a full-screen terminal app (a TUI)
|
|
54
53
|
zen check # validate the project and every file it names
|
|
55
54
|
zen inspect # open the last run's report.html
|
|
56
55
|
zen list --sessions # every project, its sessions and last run
|
|
@@ -151,17 +150,18 @@ you are not expected to hand-author `agents.yaml`.
|
|
|
151
150
|
|
|
152
151
|
## Commands
|
|
153
152
|
|
|
154
|
-
| Command | Does
|
|
155
|
-
| --------- |
|
|
156
|
-
| `init` | Creates a project here, or in `<dir>`, and registers it.
|
|
157
|
-
| `list` | Every known project: sessions, last run, whether one is live.
|
|
158
|
-
| `open` | Opens a project in your editor.
|
|
159
|
-
| `key` | The credential keyring — add, check, switch, remove.
|
|
160
|
-
| `
|
|
161
|
-
| `
|
|
162
|
-
| `
|
|
163
|
-
| `
|
|
164
|
-
| `
|
|
153
|
+
| Command | Does |
|
|
154
|
+
| --------- | -------------------------------------------------------------------- |
|
|
155
|
+
| `init` | Creates a project here, or in `<dir>`, and registers it. |
|
|
156
|
+
| `list` | Every known project: sessions, last run, whether one is live. |
|
|
157
|
+
| `open` | Opens a project in your editor. |
|
|
158
|
+
| `key` | The credential keyring — add, check, switch, remove. |
|
|
159
|
+
| `models` | What this machine can use — list, search, test, pick. |
|
|
160
|
+
| `run` | Runs the project — the TUI on a terminal, a single answer otherwise. |
|
|
161
|
+
| `inspect` | Opens or rebuilds a run's `report.html`. |
|
|
162
|
+
| `check` | Validates the project and every file it names, and asks the models. |
|
|
163
|
+
| `sandbox` | Checks and prepares the container that command-line tools run in. |
|
|
164
|
+
| `version` | CLI, library and Node versions. |
|
|
165
165
|
|
|
166
166
|
Commands can also come from a package installed alongside this one, so a new
|
|
167
167
|
capability is a subcommand rather than a new binary to remember — one thing on
|
|
@@ -199,39 +199,236 @@ command — so `zen run … | jq` is a supported way to use it, not an accident.
|
|
|
199
199
|
|
|
200
200
|
The binary is installed under three names: `zen`, `zn` and `zenera`.
|
|
201
201
|
|
|
202
|
+
## Credentials
|
|
203
|
+
|
|
204
|
+
One keyring serves every provider, and a key goes in the same way whatever it
|
|
205
|
+
is for:
|
|
206
|
+
|
|
207
|
+
```sh
|
|
208
|
+
zen key add <provider> # asks for the key without showing it
|
|
209
|
+
zen key add <provider> < key.txt # or pipe it in
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The value is never given as an argument — a command line is visible to anyone
|
|
213
|
+
listing running processes, is saved in your shell history and is captured in CI
|
|
214
|
+
logs — so piping it in and the hidden prompt are the only two ways. Entries live
|
|
215
|
+
in `~/.zenera/neo/keys.json`, in a file only you can read, and are copied into
|
|
216
|
+
the environment just before a run, so an environment variable you set yourself
|
|
217
|
+
always wins and a project checked out on a machine without `zen` still runs.
|
|
218
|
+
|
|
219
|
+
| Provider | The value is | Exported as |
|
|
220
|
+
| ------------ | ------------------------------------- | -------------------------------- |
|
|
221
|
+
| `openai` | a secret | `OPENAI_API_KEY` |
|
|
222
|
+
| `anthropic` | a secret | `ANTHROPIC_API_KEY` |
|
|
223
|
+
| `google` | a secret — AI Studio | `GEMINI_API_KEY` |
|
|
224
|
+
| `vertex` | a path to a service-account JSON file | `GOOGLE_APPLICATION_CREDENTIALS` |
|
|
225
|
+
| `openrouter` | a secret | `OPENROUTER_API_KEY` |
|
|
226
|
+
| `exa` | a secret — for the search tool | `EXA_API_KEY` |
|
|
227
|
+
|
|
228
|
+
`zen key add` verifies the credential against the provider before it finishes,
|
|
229
|
+
but stores it either way: a key that cannot be checked right now — offline,
|
|
230
|
+
behind a proxy — is not a key that is wrong. `--no-check` skips the call.
|
|
231
|
+
|
|
232
|
+
### Which key a model uses
|
|
233
|
+
|
|
234
|
+
A model reference is `[provider[/api]:]model`, and the first segment names a
|
|
235
|
+
**provider, not a vendor**. So `vertex:gemini-3.5-flash` and
|
|
236
|
+
`google:gemini-3.5-flash` are the same model reached through two different
|
|
237
|
+
services, needing two different credentials — and a bare `gpt-5.4-mini` goes to
|
|
238
|
+
the default provider, `openai`. `zen check` resolves every reference in a
|
|
239
|
+
project against what is stored, says which credential each one needs, and spends
|
|
240
|
+
a few tokens asking each of them to answer — the only way to catch a model id
|
|
241
|
+
this account is not served. `--no-models` stops before the asking.
|
|
242
|
+
|
|
243
|
+
### Which models you can use
|
|
244
|
+
|
|
245
|
+
`zen check` answers _does my project work_. `zen models` answers _what can I
|
|
246
|
+
use_, needs no project, and asks the providers themselves:
|
|
247
|
+
|
|
248
|
+
```sh
|
|
249
|
+
zen models # who has a credential, and what is cached
|
|
250
|
+
zen models openai # everything OpenAI serves this account
|
|
251
|
+
zen models search haiku --tools --free # narrow it
|
|
252
|
+
zen models show openrouter:anthropic/claude-haiku-4.5
|
|
253
|
+
zen models test vertex:gemini-embedding-001 # one real call, one verdict
|
|
254
|
+
zen models pick --embedding # the first ref that answers, on stdout
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Listings are cached for a day in `~/.zenera/neo/catalog`. When a provider cannot
|
|
258
|
+
be asked the last listing is used and said to be stale; only if there was never
|
|
259
|
+
one does a short built-in list stand in.
|
|
260
|
+
|
|
261
|
+
`test` distinguishes three failures, because they want three different actions.
|
|
262
|
+
A **refused** model means the credential was rejected. A **blocked** one means
|
|
263
|
+
the credential was accepted and the account then said no — an API switched off,
|
|
264
|
+
an empty balance, a model this key was never granted — and the fix comes with
|
|
265
|
+
it:
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
$ zen models test vertex:gemini-embedding-001
|
|
269
|
+
vertex:gemini-embedding-001 blocked Vertex AI API has not been used in project my-proj …
|
|
270
|
+
vertex:gemini-embedding-001: gcloud services enable aiplatform.googleapis.com --project my-proj
|
|
271
|
+
error 1 of 1 did not answer
|
|
272
|
+
find one that does: zen models pick --embedding
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
`pick` tries a short list of candidates one at a time and stops at the first
|
|
276
|
+
that works, printing the bare ref on stdout — so recovering from the above is
|
|
277
|
+
one substitution, whether a person or an agent is doing it:
|
|
278
|
+
|
|
279
|
+
```sh
|
|
280
|
+
zen rag schema index --embedding "$(zen models pick --embedding)" ./specs/*.yaml
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Vertex AI
|
|
284
|
+
|
|
285
|
+
Vertex takes two kinds of credential, and you never say which you are giving:
|
|
286
|
+
if the value is a path to a file that exists it is a service-account key, and
|
|
287
|
+
otherwise it is treated as an API key. The name you choose for the entry has no
|
|
288
|
+
say in it — `vertex/express` is simply an entry called `express`, exactly as
|
|
289
|
+
`vertex/prod` is one called `prod`, and either name can hold either kind.
|
|
290
|
+
|
|
291
|
+
The usual one is a **service-account JSON file** — give its path, not its
|
|
292
|
+
contents. Run the command with nothing piped and it asks:
|
|
293
|
+
|
|
294
|
+
```sh
|
|
295
|
+
zen key add vertex --location us-central1
|
|
296
|
+
# Paste the key, or a path to the file: /Users/you/keys/vertex-sa.json
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
The prompt is read by `zen`, not by your shell, so give a full path there — `~`
|
|
300
|
+
is not expanded. In a script, pipe the path in instead:
|
|
301
|
+
|
|
302
|
+
```sh
|
|
303
|
+
echo ~/keys/vertex-sa.json | zen key add vertex --location us-central1
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
The file is copied into `~/.zenera/neo/keys/`, where only you can read it, so
|
|
307
|
+
moving or cleaning up the original later cannot break it.
|
|
308
|
+
|
|
309
|
+
- `--location <region>` is worth setting. It must be `global` or a **concrete
|
|
310
|
+
region**; multi-region names like `us` are rejected with a 404. `global`
|
|
311
|
+
routes across regions and pays about ten seconds of cold start on the first
|
|
312
|
+
request each process makes — a region answers in about two.
|
|
313
|
+
- `--project <id>` is only needed when the `project_id` inside the file is not
|
|
314
|
+
the project you want.
|
|
315
|
+
|
|
316
|
+
The alternative is an **express-mode API key** — a single secret, stored under
|
|
317
|
+
`VERTEX_API_KEY`. It is the Vertex console's way of handing out access without a
|
|
318
|
+
service account, and it needs neither a project nor a region, so `--project` and
|
|
319
|
+
`--location` mean nothing there and are not stored.
|
|
320
|
+
|
|
321
|
+
### Gemini, three ways
|
|
322
|
+
|
|
323
|
+
The same Gemini models are reachable through three different credentials, and
|
|
324
|
+
which one you hold decides the prefix a model reference needs.
|
|
325
|
+
|
|
326
|
+
**AI Studio** — one key and nothing else to configure, the shortest way to a
|
|
327
|
+
working `gemini-3.5-flash`:
|
|
328
|
+
|
|
329
|
+
```sh
|
|
330
|
+
zen key add google # asks for the key without showing it
|
|
331
|
+
zen check # google:gemini-3.5-flash now resolves
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**Vertex, service account** — what production usually runs on. Give the path
|
|
335
|
+
and a region, because the file says which project it belongs to but never which
|
|
336
|
+
region to call:
|
|
337
|
+
|
|
338
|
+
```sh
|
|
339
|
+
echo ~/keys/vertex-sa.json | zen key add vertex --location us-central1
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Add `--project` only when the `project_id` inside the file is not the one you
|
|
343
|
+
want to bill:
|
|
344
|
+
|
|
345
|
+
```sh
|
|
346
|
+
echo ~/keys/vertex-sa.json \
|
|
347
|
+
| zen key add vertex --project other-project --location europe-west4
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**Vertex, express mode** — paste the key at the prompt; no flags apply:
|
|
351
|
+
|
|
352
|
+
```sh
|
|
353
|
+
zen key add vertex
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Holding several at once is the ordinary case. Name them and switch:
|
|
357
|
+
|
|
358
|
+
```sh
|
|
359
|
+
echo ~/keys/prod-sa.json | zen key add vertex/prod --location us-central1
|
|
360
|
+
echo ~/keys/dev-sa.json | zen key add vertex/dev --location global
|
|
361
|
+
zen key add vertex/express # the express key, same provider
|
|
362
|
+
|
|
363
|
+
zen key use vertex/dev # which one the next run uses
|
|
364
|
+
zen key ls --check # all three, and whether they still work
|
|
365
|
+
zen key show vertex/prod # masked; --reveal prints the path
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
`google` and `vertex` can both be configured — they are separate entries for
|
|
369
|
+
separate services, and the reference picks:
|
|
370
|
+
|
|
371
|
+
```sh
|
|
372
|
+
zen run --model google:gemini-3.5-flash "summarise this repo"
|
|
373
|
+
zen run --model vertex:gemini-3.5-flash "summarise this repo"
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
If you have already run `gcloud auth application-default login`, that login is
|
|
377
|
+
itself a usable credential: `zen key ls` shows it as `adc`, marked `~` because
|
|
378
|
+
it came from outside the keyring, and Vertex works with nothing stored at all.
|
|
379
|
+
Anything already set in `GOOGLE_APPLICATION_CREDENTIALS`, `VERTEX_API_KEY` or
|
|
380
|
+
`GEMINI_API_KEY` is listed the same way and wins over the keyring, so it is
|
|
381
|
+
always visible which credential a run will actually use.
|
|
382
|
+
|
|
383
|
+
### More than one key per provider
|
|
384
|
+
|
|
385
|
+
Entries are named, so a provider can hold several and one of them is active:
|
|
386
|
+
|
|
387
|
+
```sh
|
|
388
|
+
zen key add openai/work # a second entry
|
|
389
|
+
zen key use openai/work # which one runs use
|
|
390
|
+
zen key ls --check # everything stored, and whether it still works
|
|
391
|
+
zen key show vertex/default # masked — --reveal prints the secret
|
|
392
|
+
zen key env openai # shell exports, for other tools
|
|
393
|
+
zen key rm openai/work
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
`zen key ls` marks the active entry with `*`, and anything it found outside the
|
|
397
|
+
keyring — in your environment, or in a `gcloud` login — with `~`, so it is always
|
|
398
|
+
clear where a working provider actually comes from.
|
|
399
|
+
|
|
202
400
|
## Concepts
|
|
203
401
|
|
|
204
402
|
- **Project** — a named directory holding a complete agent definition and the
|
|
205
403
|
sessions that ran against it. Self-describing: `agents.yaml` is what makes it
|
|
206
404
|
one, so moving or cloning the directory loses nothing.
|
|
207
|
-
- **Session** — a context that persists: one workspace, one memory, one
|
|
208
|
-
|
|
405
|
+
- **Session** — a context that persists: one workspace, one memory, one store
|
|
406
|
+
for large files, and a record of everything that happened, added to as it
|
|
407
|
+
goes. Resumable.
|
|
209
408
|
- **Run** — one prompt in, one answer out, inside a session. Recorded in full,
|
|
210
409
|
whether or not you were watching.
|
|
211
410
|
- **Workspace** — the directory the agents may read and write. A prompt given on
|
|
212
411
|
the command line uses the current directory; the TUI offers the session's own
|
|
213
412
|
empty folder and confirms anything outside it.
|
|
214
|
-
- **Keyring** — `~/.zenera/neo`,
|
|
215
|
-
environment just before a run, so
|
|
216
|
-
checked out on a machine without `zen` still runs.
|
|
413
|
+
- **Keyring** — `~/.zenera/neo`, readable only by you. Keys are copied into the
|
|
414
|
+
environment just before a run, so an environment variable you set yourself
|
|
415
|
+
always wins and a project checked out on a machine without `zen` still runs.
|
|
217
416
|
|
|
218
417
|
## The library underneath
|
|
219
418
|
|
|
220
419
|
This is a shell over
|
|
221
420
|
[`@zenera/neo`](https://www.npmjs.com/package/@zenera/neo) — agents, models,
|
|
222
|
-
tools, skills, memory and
|
|
223
|
-
want the runtime inside your own application rather than on a
|
|
421
|
+
tools, skills, memory and a running record of everything that happened. Use it
|
|
422
|
+
directly when you want the runtime inside your own application rather than on a
|
|
423
|
+
terminal:
|
|
224
424
|
[its README](https://github.com/andreyryabov/ZeneraNeo/blob/main/packages/neo/README.md).
|
|
225
425
|
|
|
226
426
|
## Documentation
|
|
227
427
|
|
|
228
|
-
Full specification:
|
|
229
|
-
[packages/cli/DESIGN.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/packages/cli/DESIGN.md).
|
|
230
|
-
Also
|
|
231
|
-
[DESIGN.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/DESIGN.md) ·
|
|
232
428
|
[docs/projects.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/projects.md)
|
|
233
|
-
·
|
|
234
|
-
[docs/agents-yaml.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/agents-yaml.md)
|
|
429
|
+
— the folder a project is ·
|
|
430
|
+
[docs/agents-yaml.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/agents-yaml.md)
|
|
431
|
+
— every key in the configuration file.
|
|
235
432
|
|
|
236
433
|
## License
|
|
237
434
|
|
package/dist/audit.d.ts
CHANGED
|
@@ -11,10 +11,13 @@ export interface ModelIssue {
|
|
|
11
11
|
provider: string;
|
|
12
12
|
/** the variable that would carry the credential */
|
|
13
13
|
env: string;
|
|
14
|
-
/** `missing` — nothing to authenticate with. `dead` — rejected when checked.
|
|
15
|
-
|
|
14
|
+
/** `missing` — nothing to authenticate with. `dead` — rejected when checked.
|
|
15
|
+
* `blocked` — accepted, and then refused by the account behind it. */
|
|
16
|
+
reason: 'missing' | 'dead' | 'blocked';
|
|
16
17
|
/** the provider's own words, when it was the one to say no */
|
|
17
18
|
detail?: string;
|
|
19
|
+
/** what to do about a refusal that a new key would not fix */
|
|
20
|
+
fix?: string;
|
|
18
21
|
/** the keyring provider the fix names, when the kind is one of them */
|
|
19
22
|
add?: Provider;
|
|
20
23
|
}
|
|
@@ -22,13 +25,15 @@ export interface ModelIssue {
|
|
|
22
25
|
* Whether a requirement is actually met, and under which variable.
|
|
23
26
|
*
|
|
24
27
|
* `satisfied` is the library's answer and it is about api keys, which is the
|
|
25
|
-
* wrong question for Vertex: that kind is `keyOptional` because it
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
+
* wrong question for Vertex: that kind is `keyOptional` because it can
|
|
29
|
+
* authenticate from a service-account file instead. So both of its variables
|
|
30
|
+
* are looked for, along with the file `gcloud` leaves behind.
|
|
28
31
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
+
* The variable named back is the one that is actually set, because a report
|
|
33
|
+
* that named `VERTEX_API_KEY` while the audit silently checked
|
|
34
|
+
* `GOOGLE_APPLICATION_CREDENTIALS` would be two answers to one question, and
|
|
35
|
+
* the wrong one is the one people would act on. With nothing set there is no
|
|
36
|
+
* such answer, so it names the form the provider is usually reached by.
|
|
32
37
|
*/
|
|
33
38
|
export declare function credentialFor(need: ModelRequirement): {
|
|
34
39
|
env: string;
|
package/dist/audit.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
1
|
import { projectRegistry, readProjectConfig, } from '@zenera/neo';
|
|
5
|
-
import {
|
|
2
|
+
import { envNames, form, gcloudAdc, isProvider, SHAPES, } from "./keys.js";
|
|
6
3
|
import { bold, dim } from "./term.js";
|
|
7
4
|
/**
|
|
8
5
|
* Every model the project names, keyed by how one would refer to it.
|
|
@@ -36,32 +33,27 @@ function declared(config) {
|
|
|
36
33
|
['embedding', embeddings],
|
|
37
34
|
]);
|
|
38
35
|
}
|
|
39
|
-
/**
|
|
40
|
-
* `gcloud auth application-default login` writes here, and the GenAI SDK finds
|
|
41
|
-
* it with no variable set — so without this check every developer using ADC
|
|
42
|
-
* would be told their working Vertex setup is broken.
|
|
43
|
-
*/
|
|
44
|
-
function hasGcloudAdc() {
|
|
45
|
-
const dir = process.env.CLOUDSDK_CONFIG ?? join(homedir(), '.config', 'gcloud');
|
|
46
|
-
return existsSync(join(dir, 'application_default_credentials.json'));
|
|
47
|
-
}
|
|
48
36
|
/**
|
|
49
37
|
* Whether a requirement is actually met, and under which variable.
|
|
50
38
|
*
|
|
51
39
|
* `satisfied` is the library's answer and it is about api keys, which is the
|
|
52
|
-
* wrong question for Vertex: that kind is `keyOptional` because it
|
|
53
|
-
*
|
|
54
|
-
*
|
|
40
|
+
* wrong question for Vertex: that kind is `keyOptional` because it can
|
|
41
|
+
* authenticate from a service-account file instead. So both of its variables
|
|
42
|
+
* are looked for, along with the file `gcloud` leaves behind.
|
|
55
43
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
44
|
+
* The variable named back is the one that is actually set, because a report
|
|
45
|
+
* that named `VERTEX_API_KEY` while the audit silently checked
|
|
46
|
+
* `GOOGLE_APPLICATION_CREDENTIALS` would be two answers to one question, and
|
|
47
|
+
* the wrong one is the one people would act on. With nothing set there is no
|
|
48
|
+
* such answer, so it names the form the provider is usually reached by.
|
|
59
49
|
*/
|
|
60
50
|
export function credentialFor(need) {
|
|
61
51
|
const provider = isProvider(need.kind) ? need.kind : undefined;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
52
|
+
// A provider with a file form can authenticate without an api key, which is
|
|
53
|
+
// the only thing `satisfied` knows how to look for.
|
|
54
|
+
if (provider && SHAPES[provider].forms.some((f) => f.holds === 'file')) {
|
|
55
|
+
const set = envNames(provider).find((name) => process.env[name]);
|
|
56
|
+
return { env: set ?? form(provider).env, present: Boolean(set) || Boolean(gcloudAdc()) };
|
|
65
57
|
}
|
|
66
58
|
return { env: need.apiKeyEnv, present: need.satisfied };
|
|
67
59
|
}
|
|
@@ -111,14 +103,15 @@ export function auditModels(projectDir, store) {
|
|
|
111
103
|
// reinstated between the check and the run, and a stale verdict must
|
|
112
104
|
// not be the thing that stops a run from being attempted.
|
|
113
105
|
const check = provider ? store.active(provider)?.check : undefined;
|
|
114
|
-
if (check?.state === 'dead') {
|
|
106
|
+
if (check?.state === 'dead' || check?.state === 'blocked') {
|
|
115
107
|
issues.push({
|
|
116
108
|
name,
|
|
117
109
|
role,
|
|
118
110
|
provider: need.provider,
|
|
119
111
|
env,
|
|
120
|
-
reason:
|
|
112
|
+
reason: check.state,
|
|
121
113
|
detail: check.detail,
|
|
114
|
+
fix: check.fix,
|
|
122
115
|
add: provider,
|
|
123
116
|
});
|
|
124
117
|
}
|
|
@@ -138,6 +131,10 @@ export function describeIssue(issue) {
|
|
|
138
131
|
return `${what} has no credential — ${issue.env} is not set; ${dim(fix)}`;
|
|
139
132
|
}
|
|
140
133
|
const why = issue.detail ? `: ${issue.detail}` : '';
|
|
134
|
+
if (issue.reason === 'blocked') {
|
|
135
|
+
const pick = `zen models pick --${issue.role === 'embedding' ? 'embedding' : 'chat'}`;
|
|
136
|
+
return `${what} authenticated and was then refused${why} — ${dim(issue.fix ?? pick)}`;
|
|
137
|
+
}
|
|
141
138
|
const fix = `zen key check ${issue.add ?? ''}`.trim();
|
|
142
139
|
return `${what} was rejected when last checked${why} — ${dim(fix)}`;
|
|
143
140
|
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { type KeyCheck, type Provider } from './keys.ts';
|
|
2
|
+
/**
|
|
3
|
+
* What a model is *for*. A model can hold more than one — Gemini's embedding
|
|
4
|
+
* endpoint and its chat endpoint are the same catalog row on some providers —
|
|
5
|
+
* so this is a set rather than a field.
|
|
6
|
+
*/
|
|
7
|
+
export type Role = 'chat' | 'embedding' | 'image' | 'audio';
|
|
8
|
+
export interface CatalogEntry {
|
|
9
|
+
/** `provider:id` — paste-able straight into `agents.yaml` */
|
|
10
|
+
ref: string;
|
|
11
|
+
/** the id that goes on the wire, with any resource prefix already stripped */
|
|
12
|
+
id: string;
|
|
13
|
+
provider: Provider;
|
|
14
|
+
roles: Role[];
|
|
15
|
+
name?: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
/** total tokens the model will accept in one request */
|
|
18
|
+
contextLength?: number;
|
|
19
|
+
maxOutputTokens?: number;
|
|
20
|
+
/** embeddings only, when the vendor publishes it */
|
|
21
|
+
dimensions?: number;
|
|
22
|
+
modalities?: {
|
|
23
|
+
input?: string[];
|
|
24
|
+
output?: string[];
|
|
25
|
+
};
|
|
26
|
+
supports?: {
|
|
27
|
+
tools?: boolean;
|
|
28
|
+
reasoning?: boolean;
|
|
29
|
+
vision?: boolean;
|
|
30
|
+
};
|
|
31
|
+
/** USD per token, as the vendor writes it — strings, because they are tiny */
|
|
32
|
+
pricing?: {
|
|
33
|
+
prompt?: string;
|
|
34
|
+
completion?: string;
|
|
35
|
+
free?: boolean;
|
|
36
|
+
};
|
|
37
|
+
/** ISO date the model was published, when known */
|
|
38
|
+
created?: string;
|
|
39
|
+
source: 'live' | 'curated';
|
|
40
|
+
}
|
|
41
|
+
/** A provider's listing, and an honest account of where it came from. */
|
|
42
|
+
export interface Catalog {
|
|
43
|
+
provider: Provider;
|
|
44
|
+
entries: CatalogEntry[];
|
|
45
|
+
/** `live` asked just now; `cache` a fresh file; `stale` an expired one; `curated` the fallback */
|
|
46
|
+
origin: 'live' | 'cache' | 'stale' | 'curated';
|
|
47
|
+
/** when the entries were actually fetched, not when they were read */
|
|
48
|
+
fetchedAt: string;
|
|
49
|
+
/** why the live call was not used, when it was tried and failed */
|
|
50
|
+
problem?: KeyCheck;
|
|
51
|
+
}
|
|
52
|
+
/** A day. Model lists change on the scale of weeks; a stale row costs a retry. */
|
|
53
|
+
export declare const CATALOG_TTL_MS: number;
|
|
54
|
+
/**
|
|
55
|
+
* Enough to work with when the provider cannot be asked — offline, no
|
|
56
|
+
* credential, or a listing endpoint that is down. Deliberately short: this is
|
|
57
|
+
* the set worth typing, not the set that exists.
|
|
58
|
+
*
|
|
59
|
+
* Anthropic publishes no embeddings API at all, which is why it has no
|
|
60
|
+
* embedding row here and why the registry throws rather than guessing.
|
|
61
|
+
*/
|
|
62
|
+
export declare const CURATED: Record<Provider, readonly Omit<CatalogEntry, 'ref' | 'provider' | 'source'>[]>;
|
|
63
|
+
/**
|
|
64
|
+
* The order `zen models pick` walks, per provider and per role.
|
|
65
|
+
*
|
|
66
|
+
* Cheap and fast first. `pick` exists to answer "give me something that works"
|
|
67
|
+
* in one round trip where it can, and the small models answer soonest and cost
|
|
68
|
+
* least when the answer is thrown away — which it always is.
|
|
69
|
+
*/
|
|
70
|
+
export declare const PREFERRED: Record<Provider, {
|
|
71
|
+
chat: readonly string[];
|
|
72
|
+
embedding: readonly string[];
|
|
73
|
+
}>;
|
|
74
|
+
/**
|
|
75
|
+
* Asks one provider what it serves. Throws whatever the SDK throws.
|
|
76
|
+
*
|
|
77
|
+
* `client` is a seam, not a feature: the four adapters are the part most likely
|
|
78
|
+
* to break when a vendor reshapes a payload, and they are untestable if the
|
|
79
|
+
* only way to reach them is a credential and a network. It mirrors
|
|
80
|
+
* `ProviderSpec.client`, which exists in the library for the same reason.
|
|
81
|
+
*/
|
|
82
|
+
export declare function fetchCatalog(provider: Provider, client?: unknown): Promise<CatalogEntry[]>;
|
|
83
|
+
export interface CatalogOptions {
|
|
84
|
+
/** ignore a fresh cache and ask the provider again */
|
|
85
|
+
refresh?: boolean;
|
|
86
|
+
/** do not make a network call at all — cache, however old, then curated */
|
|
87
|
+
offline?: boolean;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The listing for one provider, from the cheapest source that can answer.
|
|
91
|
+
*
|
|
92
|
+
* Fresh cache, else the provider, else a *stale* cache, else the curated table.
|
|
93
|
+
* Stale-before-curated is the important ordering: yesterday's real answer from
|
|
94
|
+
* this account beats today's guess about accounts in general, and a listing
|
|
95
|
+
* that failed because the wifi dropped should not silently shrink someone's
|
|
96
|
+
* model list to four rows.
|
|
97
|
+
*/
|
|
98
|
+
export declare function loadCatalog(provider: Provider, opts?: CatalogOptions): Promise<Catalog>;
|
|
99
|
+
/** Every provider asked at once — they are independent and mostly latency. */
|
|
100
|
+
export declare function loadCatalogs(providers: readonly Provider[], opts?: CatalogOptions): Promise<Catalog[]>;
|
|
101
|
+
export interface Filters {
|
|
102
|
+
roles?: readonly Role[];
|
|
103
|
+
tools?: boolean;
|
|
104
|
+
vision?: boolean;
|
|
105
|
+
free?: boolean;
|
|
106
|
+
minContext?: number;
|
|
107
|
+
}
|
|
108
|
+
export declare function matches(row: CatalogEntry, query: string, filters?: Filters): boolean;
|
|
109
|
+
/** The providers a search covers when none was named. */
|
|
110
|
+
export declare const catalogProviders: () => readonly Provider[];
|
|
111
|
+
//# sourceMappingURL=catalog.d.ts.map
|