@zenera/cli 1.1.16 → 1.1.18
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 +110 -19
- package/dist/banner.d.ts +19 -0
- package/dist/banner.js +12 -9
- package/dist/catalog.js +19 -20
- package/dist/command.d.ts +3 -0
- package/dist/commands/check.js +5 -1
- package/dist/commands/index.js +4 -1
- package/dist/commands/init.js +42 -7
- package/dist/commands/inspect.js +77 -13
- package/dist/commands/key.js +49 -1
- package/dist/commands/memory.js +16 -3
- package/dist/commands/meta.d.ts +3 -0
- package/dist/commands/meta.js +596 -0
- package/dist/commands/run.js +64 -35
- package/dist/engine.d.ts +15 -0
- package/dist/engine.js +26 -1
- package/dist/env.d.ts +20 -0
- package/dist/env.js +65 -0
- package/dist/home.d.ts +2 -0
- package/dist/home.js +2 -0
- package/dist/keys.d.ts +2 -0
- package/dist/keys.js +2 -0
- package/dist/main.js +1 -1
- package/dist/meta.d.ts +221 -0
- package/dist/meta.js +695 -0
- package/dist/podman.js +64 -2
- package/dist/projects.d.ts +9 -1
- package/dist/projects.js +19 -2
- package/dist/resolve.js +7 -1
- package/dist/sandbox.d.ts +11 -0
- package/dist/sandbox.js +24 -4
- package/dist/scaffold.d.ts +6 -0
- package/dist/scaffold.js +56 -15
- package/dist/session.d.ts +10 -0
- package/dist/session.js +19 -1
- package/dist/term.d.ts +7 -0
- package/dist/term.js +8 -1
- package/dist/tui/app.js +33 -5
- package/dist/tui/wrap.d.ts +1 -1
- package/dist/tui/wrap.js +1 -1
- package/dist/validate.js +57 -15
- package/package.json +3 -2
- package/templates/editor/.github/copilot-instructions.md +313 -94
- package/templates/editor/.github/prompts/project-review.prompt.md +74 -0
- package/templates/editor/.github/prompts/spec-apply-feedback.prompt.md +142 -0
- package/templates/editor/.github/prompts/spec-sync-project.prompt.md +392 -0
- package/templates/editor/.github/skills/zen-cli/references/check.md +6 -3
- package/templates/editor/.github/skills/zen-cli/references/frame.md +5 -3
- package/templates/editor/.github/skills/zen-cli/references/inspect.md +4 -3
- package/templates/editor/.github/skills/zen-cli/references/memory.md +14 -0
- package/templates/editor/.github/skills/zen-cli/references/projects.md +19 -2
- package/templates/editor/.github/skills/zen-cli/references/run.md +75 -16
- package/templates/editor/.github/skills/zen-cli/references/sandbox.md +6 -0
- package/templates/editor/.github/skills/zen-memory/SKILL.md +153 -33
- package/templates/editor/.github/skills/zen-memory/scripts/check-instructions.sh +115 -0
- package/templates/editor/.github/skills/zen-review/SKILL.md +123 -0
- package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +119 -0
- package/templates/editor/.github/skills/zen-review/scripts/review.sh +104 -0
- package/templates/editor/.github/skills/zen-spec-sync/SKILL.md +197 -0
- package/templates/editor/.github/skills/zen-spec-sync/scripts/snapshot.sh +200 -0
- package/templates/editor/.vscode/settings.json +44 -4
- package/templates/parts/memory-terms.yaml.tmpl +16 -0
- package/templates/parts/memory.yaml.tmpl +12 -0
- package/templates/project/SPECIFICATION.md.tmpl +32 -2
- package/templates/project/agents/memory-instructions.md +278 -0
- package/templates/project/agents.yaml.tmpl +12 -3
- package/templates/project/env +28 -0
- package/templates/project/gitignore +4 -4
- package/templates/editor/.github/prompts/review-project.prompt.md +0 -36
- package/templates/editor/.github/prompts/sync-with-spec.prompt.md +0 -205
- /package/templates/project/{INSTRUCTIONS.md → agents/instructions.md} +0 -0
package/README.md
CHANGED
|
@@ -125,7 +125,7 @@ In the editor, describe the job in `SPECIFICATION.md`, then send this in the
|
|
|
125
125
|
agent chat:
|
|
126
126
|
|
|
127
127
|
```
|
|
128
|
-
/sync-
|
|
128
|
+
/spec-sync-project
|
|
129
129
|
```
|
|
130
130
|
|
|
131
131
|
It creates the specialists, responsibilities, tool access and handoffs the job
|
|
@@ -145,7 +145,7 @@ zen run my-project # no prompt given: opens a full-screen terminal
|
|
|
145
145
|
zen check my-project # validate the project and every file it names
|
|
146
146
|
zen inspect # open the last run's report.html
|
|
147
147
|
zen list --sessions # every project, its sessions and last run
|
|
148
|
-
echo "triage this" | zen run my-project --
|
|
148
|
+
echo "triage this" | zen run my-project --json | jq
|
|
149
149
|
```
|
|
150
150
|
|
|
151
151
|
Standing inside the project, the name is optional: a bare `zen run`, `zen check`
|
|
@@ -156,7 +156,7 @@ session and uses the current directory as the workspace, with write access.
|
|
|
156
156
|
`--session`, `--workspace` and `--read-only` override that.
|
|
157
157
|
|
|
158
158
|
To change what the system does, update `SPECIFICATION.md` and send
|
|
159
|
-
`/sync-
|
|
159
|
+
`/spec-sync-project` again. The next section explains that workflow in detail.
|
|
160
160
|
|
|
161
161
|
## You write the specification; a coding agent writes the system
|
|
162
162
|
|
|
@@ -202,12 +202,15 @@ In the window that opens:
|
|
|
202
202
|
|
|
203
203
|
1. **Edit `SPECIFICATION.md`** - what the system is for, which agents exist,
|
|
204
204
|
what each may reach for, and what _done_ means.
|
|
205
|
-
2. **Open the chat panel and send `/sync-
|
|
205
|
+
2. **Open the chat panel and send `/spec-sync-project`.** It reads the
|
|
206
206
|
specification and every file implementing it, works out the difference in
|
|
207
207
|
both directions, and changes the smallest thing that closes each gap.
|
|
208
|
-
3. **
|
|
209
|
-
not do without guessing.
|
|
210
|
-
|
|
208
|
+
3. **Answer `SPECIFICATION-FEEDBACK.md`**, which it writes for anything it could
|
|
209
|
+
not do without guessing. Every question carries two to four written-out
|
|
210
|
+
answers, each with a `[ ]` box: tick one, delete the ones you do not want, or
|
|
211
|
+
write your own in their place.
|
|
212
|
+
4. **Send `/spec-apply-feedback`.** It folds the answers you chose into
|
|
213
|
+
`SPECIFICATION.md` - never into a prompt - and then `/spec-sync-project` again.
|
|
211
214
|
|
|
212
215
|
Back in the terminal: `zen check`, then `zen run`.
|
|
213
216
|
|
|
@@ -218,15 +221,16 @@ They are prompt files under `.github/prompts/`, which VS Code and its forks
|
|
|
218
221
|
offer as chat slash-commands. Both `zen init` and `zen open` write them fresh,
|
|
219
222
|
so they never go stale - and edits to them do not survive.
|
|
220
223
|
|
|
221
|
-
| In chat
|
|
222
|
-
|
|
|
223
|
-
| `/sync-
|
|
224
|
-
| `/
|
|
225
|
-
| `/
|
|
226
|
-
| `/new-
|
|
224
|
+
| In chat | Does |
|
|
225
|
+
| ---------------------- | ---------------------------------------------------------------- |
|
|
226
|
+
| `/spec-sync-project` | Makes every file match `SPECIFICATION.md`, both directions. |
|
|
227
|
+
| `/spec-apply-feedback` | Folds your answers in `SPECIFICATION-FEEDBACK.md` into the spec. |
|
|
228
|
+
| `/project-review` | Reads the project as a reviewer would, and reports. |
|
|
229
|
+
| `/new-agent` | Adds an agent - prompt, wiring and hand-offs. |
|
|
230
|
+
| `/new-skill` | Adds a skill under `agents/skills/`. |
|
|
227
231
|
|
|
228
232
|
In an editor that does not support prompt files, paste the contents of
|
|
229
|
-
`.github/prompts/sync-
|
|
233
|
+
`.github/prompts/spec-sync-project.prompt.md` into its chat instead - it is only a
|
|
230
234
|
prompt. Alongside them, `.github/copilot-instructions.md` is the standing brief
|
|
231
235
|
that explains this runtime to whatever agent is reading, and
|
|
232
236
|
`.github/skills/zen-cli/` is the CLI's own reference for it.
|
|
@@ -236,7 +240,7 @@ that explains this runtime to whatever agent is reading, and
|
|
|
236
240
|
### The self-improving loop
|
|
237
241
|
|
|
238
242
|
```
|
|
239
|
-
edit SPECIFICATION.md → /sync-
|
|
243
|
+
edit SPECIFICATION.md → /spec-sync-project → zen check → zen run
|
|
240
244
|
↑ ↓
|
|
241
245
|
└───── refine the spec <-──── zen inspect <-───────┘
|
|
242
246
|
```
|
|
@@ -295,7 +299,7 @@ Read a codebase and leave a short written note about it in the workspace.
|
|
|
295
299
|
- Every claim in it comes from a file that was actually read.
|
|
296
300
|
```
|
|
297
301
|
|
|
298
|
-
Send `/sync-
|
|
302
|
+
Send `/spec-sync-project` in the editor's chat. It writes `agents.yaml` - who
|
|
299
303
|
exists, and what each may reach for:
|
|
300
304
|
|
|
301
305
|
```yaml
|
|
@@ -378,20 +382,24 @@ is a command on their machine too.
|
|
|
378
382
|
Prompts, agent wiring, skills and tool selections are all files - Markdown and
|
|
379
383
|
YAML - rather than code buried inside an application. So the folder can be
|
|
380
384
|
committed, copied to another machine, reviewed in a pull request, and handed to
|
|
381
|
-
someone else who runs it with one command.
|
|
382
|
-
the project,
|
|
385
|
+
someone else who runs it with one command. Model credentials live in `$HOME`,
|
|
386
|
+
never in the project, and anything the project itself needs goes in a
|
|
387
|
+
git-ignored `.env` - so there is no secret to strip before sharing it.
|
|
383
388
|
|
|
384
389
|
```
|
|
385
390
|
my-project/
|
|
386
391
|
SPECIFICATION.md what this is for - the intent
|
|
387
|
-
INSTRUCTIONS.md house rules, prepended to every agent
|
|
388
392
|
agents.yaml who exists, what they may reach for
|
|
389
393
|
agents/
|
|
394
|
+
instructions.md house rules, prepended to every agent
|
|
395
|
+
<topic>-instructions.md more of them, one subject at a time
|
|
390
396
|
prompts/<name>.md each agent's own brief
|
|
391
397
|
skills/<name>/ knowledge loaded on demand, not always-on
|
|
392
398
|
assets/ reference material, read-only at /assets
|
|
393
399
|
sandbox/Dockerfile the container commands run in
|
|
394
400
|
scripts/_setup.sh the one command that initialises the project
|
|
401
|
+
.spec-sync/ what the last sync with the spec applied
|
|
402
|
+
.env this project's environment - git-ignored
|
|
395
403
|
sessions/ one workspace, memory and trajectory each
|
|
396
404
|
<id>/
|
|
397
405
|
workspace/ what the agents can read and write
|
|
@@ -457,6 +465,7 @@ remove resources beyond the current project.
|
|
|
457
465
|
| `key` | The credential keyring - add, check, switch, remove. |
|
|
458
466
|
| `models` | What this machine can use - list, search, test, pick. |
|
|
459
467
|
| `run` | Runs the project - the TUI on a terminal, a single answer otherwise. |
|
|
468
|
+
| `meta` | Runs the meta agent over the project - the one that edits it. |
|
|
460
469
|
| `inspect` | Opens or rebuilds a run's `report.html`. |
|
|
461
470
|
| `memory` | What the agents remember - size, listing, one node, or a whole page. |
|
|
462
471
|
| `check` | Validates the project and every file it names, and asks the models. |
|
|
@@ -531,6 +540,88 @@ an accident.
|
|
|
531
540
|
| `4` | no usable credential |
|
|
532
541
|
| `5` | sandbox unavailable |
|
|
533
542
|
|
|
543
|
+
## The meta agent - the agent that works on the project
|
|
544
|
+
|
|
545
|
+
`zen run` runs the agents your project describes. `zen meta` runs an agent _over_
|
|
546
|
+
the project: a coding agent rooted at the project directory, which reads
|
|
547
|
+
`SPECIFICATION.md`, changes `agents.yaml`, the prompts and the skills, and runs
|
|
548
|
+
whatever commands it needs to check itself - on the keys already in your
|
|
549
|
+
keyring, and not on anybody's subscription. It is the loop your editor's chat
|
|
550
|
+
panel drives, as a command you can put in a script.
|
|
551
|
+
|
|
552
|
+
```sh
|
|
553
|
+
zen meta run "what does this project do?"
|
|
554
|
+
zen meta run acme "review the last commit"
|
|
555
|
+
zen meta prompts # which /<name> prompts this project has
|
|
556
|
+
zen meta run /project-review # .github/prompts/project-review.prompt.md
|
|
557
|
+
zen meta run acme /spec-sync-project agents/triage.md
|
|
558
|
+
git diff | zen meta run "what broke?" --allow-tool read
|
|
559
|
+
zen meta run --dry-run "hello" # what would run, secrets masked
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
Every prompt goes through `run`, whether you typed it or it is a file: the
|
|
563
|
+
alternative was a verb that appeared only sometimes, and a bare word after
|
|
564
|
+
`zen meta` that was a subcommand on Tuesdays and a question on Wednesdays.
|
|
565
|
+
|
|
566
|
+
It uses every tool without asking. The prompts it runs were written for an
|
|
567
|
+
editor, where reading files, writing them and running commands is the whole
|
|
568
|
+
point, and a `-p` run has nobody sitting there to answer the question - so the
|
|
569
|
+
alternative is not a safer run but a stalled one. `--allow-tool read`,
|
|
570
|
+
`--allow-tool 'shell(git:*)'` narrow it to what a particular run needs, and
|
|
571
|
+
`--ask` puts the question back. It is your own checkout and your own key either
|
|
572
|
+
way, so treat it as you would `git push`: fine on a project you own, worth
|
|
573
|
+
narrowing on one you do not.
|
|
574
|
+
|
|
575
|
+
The answer goes to stdout and the progress to stderr, like everything else here,
|
|
576
|
+
so `zen meta run "…" > out.md` keeps the answer alone.
|
|
577
|
+
|
|
578
|
+
Progress is a small window that rewrites itself rather than a transcript
|
|
579
|
+
scrolling past, because only the last few steps say what it is doing now. The
|
|
580
|
+
whole of it - the prompt, every tool call, the answer - is written as it happens
|
|
581
|
+
to `.tmp/logs/meta.<when>.log`, whose path is printed before the run starts, so
|
|
582
|
+
`tail -f` in another terminal follows a run already going. The answer alone is
|
|
583
|
+
kept beside it as `.tmp/logs/meta.<when>.md`, named again once it has printed -
|
|
584
|
+
a terminal turns that into something you click.
|
|
585
|
+
|
|
586
|
+
`zen meta run /<name>` is the part an editor cannot do for you from a script.
|
|
587
|
+
The prompts under `.github/prompts/` - `/spec-sync-project`, `/project-review` -
|
|
588
|
+
are what a chat panel offers as slash commands, and nothing outside an editor
|
|
589
|
+
reads them. zen reads one, drops the frontmatter and sends the body - so the
|
|
590
|
+
same prompt runs from the editor, from a terminal and from CI.
|
|
591
|
+
|
|
592
|
+
A terminal has no menu dropping down as you type a slash, so
|
|
593
|
+
`zen meta prompts` lists what this project holds, each with its description.
|
|
594
|
+
`zen meta run` with nothing to say asks the same question interactively.
|
|
595
|
+
|
|
596
|
+
### Which model it runs on
|
|
597
|
+
|
|
598
|
+
```sh
|
|
599
|
+
zen meta model # the whole chain, with the winner marked
|
|
600
|
+
zen meta model vertex/gemini-3.8-flash # for every project on this machine
|
|
601
|
+
zen meta model --local openai/gpt-5.6-sol # for this project only, via its .env
|
|
602
|
+
zen meta model --pick # choose from providers you hold a key for
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
Highest first: `--model`, `ZENERA_META_MODEL` in the shell, the same variable in
|
|
606
|
+
the project's `.env`, `zen meta model`, then the project's `agents.yaml`
|
|
607
|
+
`model:`. When none of them answers, a key does: the best model zen knows of
|
|
608
|
+
for the first provider you hold one for, named on stderr as it starts, so a
|
|
609
|
+
fresh machine needs a key and nothing else.
|
|
610
|
+
The store is separate from `agents.yaml` on purpose - the model a coding agent
|
|
611
|
+
runs on is a personal choice about a tool, like a key, where `agents.yaml` is a
|
|
612
|
+
committed decision about the project's own agents.
|
|
613
|
+
|
|
614
|
+
Not every model works. The meta agent declares its tools in a form only the
|
|
615
|
+
reasoning APIs accept, so use a reasoning model - `gpt-5.6-sol`,
|
|
616
|
+
`claude-opus-5`, `gemini-3.8-flash`. Those three are also what it falls back
|
|
617
|
+
to, and what `zen meta model --pick` offers.
|
|
618
|
+
|
|
619
|
+
Setting one asks it a one-word question before storing it, so a model that will
|
|
620
|
+
not answer is refused with the provider's own words rather than at the start of
|
|
621
|
+
the next run. A typed prefix that names no provider - `vertes/...` - is caught
|
|
622
|
+
before that, because it would otherwise be sent whole to a vendor you never
|
|
623
|
+
named. `--force` stores a ref without asking.
|
|
624
|
+
|
|
534
625
|
## Credentials
|
|
535
626
|
|
|
536
627
|
One keyring serves every provider, and a key goes in the same way whatever it
|
package/dist/banner.d.ts
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
/** One per sub-brand, so two of them are told apart before the word is read. */
|
|
2
|
+
declare const ACCENTS: {
|
|
3
|
+
readonly orange: {
|
|
4
|
+
readonly face: "\u001B[1;38;5;208m";
|
|
5
|
+
readonly shade: "\u001B[0;38;5;130m";
|
|
6
|
+
};
|
|
7
|
+
readonly cyan: {
|
|
8
|
+
readonly face: "\u001B[1;38;5;51m";
|
|
9
|
+
readonly shade: "\u001B[0;38;5;37m";
|
|
10
|
+
};
|
|
11
|
+
readonly mint: {
|
|
12
|
+
readonly face: "\u001B[1;38;5;121m";
|
|
13
|
+
readonly shade: "\u001B[0;38;5;78m";
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export type Hue = keyof typeof ACCENTS;
|
|
1
17
|
export interface BannerText {
|
|
2
18
|
/** drawn white */
|
|
3
19
|
head: string;
|
|
@@ -5,6 +21,8 @@ export interface BannerText {
|
|
|
5
21
|
accent: string;
|
|
6
22
|
/** the line underneath, dim */
|
|
7
23
|
subtitle: string;
|
|
24
|
+
/** which neon to use. Default: orange, the `zen` mark. */
|
|
25
|
+
hue?: Hue;
|
|
8
26
|
}
|
|
9
27
|
export interface PrintBannerOptions {
|
|
10
28
|
/** Render the block face even when the terminal is narrower than it. */
|
|
@@ -14,4 +32,5 @@ export declare const NEO_BANNER: BannerText;
|
|
|
14
32
|
export declare function bannerLines(text: BannerText, columns?: number): string[];
|
|
15
33
|
/** Narration, and only for someone watching. */
|
|
16
34
|
export declare function printBanner(text: BannerText, options?: PrintBannerOptions): void;
|
|
35
|
+
export {};
|
|
17
36
|
//# sourceMappingURL=banner.d.ts.map
|
package/dist/banner.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { dim, note } from "./term.js";
|
|
1
|
+
import { dim, note, styled } from "./term.js";
|
|
3
2
|
// ---------------------------------------------------------------------------
|
|
4
3
|
// The banner
|
|
5
4
|
//
|
|
@@ -65,11 +64,14 @@ function big(word) {
|
|
|
65
64
|
}
|
|
66
65
|
/** A wordmark rather than a sentence, so the letters are set apart. */
|
|
67
66
|
const spaced = (s) => [...s.toUpperCase()].join(' ');
|
|
68
|
-
/** Whether anything should be coloured at all — `styleText`'s own answer. */
|
|
69
|
-
const styling = () => styleText('dim', '.') !== '.';
|
|
70
67
|
const RESET = '\u001b[0m';
|
|
71
68
|
const HEAD = { face: '\u001b[1;38;5;231m', shade: '\u001b[0;38;5;244m' };
|
|
72
|
-
|
|
69
|
+
/** One per sub-brand, so two of them are told apart before the word is read. */
|
|
70
|
+
const ACCENTS = {
|
|
71
|
+
orange: { face: '\u001b[1;38;5;208m', shade: '\u001b[0;38;5;130m' },
|
|
72
|
+
cyan: { face: '\u001b[1;38;5;51m', shade: '\u001b[0;38;5;37m' },
|
|
73
|
+
mint: { face: '\u001b[1;38;5;121m', shade: '\u001b[0;38;5;78m' },
|
|
74
|
+
};
|
|
73
75
|
/** Runs of stroke and runs of bevel, alternating; spaces stay unstyled. */
|
|
74
76
|
const RUNS = /[█▀▄]+|[^█▀▄ ]+/g;
|
|
75
77
|
const isStroke = (run) => /[█▀▄]/.test(run);
|
|
@@ -77,11 +79,11 @@ const isStroke = (run) => /[█▀▄]/.test(run);
|
|
|
77
79
|
* Two tones per word — without the darker bevel the letter is a flat outline
|
|
78
80
|
* and the shadow reads as part of the stroke.
|
|
79
81
|
*/
|
|
80
|
-
const paint = (text, tone) =>
|
|
82
|
+
const paint = (text, tone) => styled()
|
|
81
83
|
? text.replace(RUNS, (run) => `${isStroke(run) ? tone.face : tone.shade}${run}${RESET}`)
|
|
82
84
|
: text;
|
|
83
85
|
/** The wordmark has no bevel to shade, so it is all face. */
|
|
84
|
-
const flat = (text, tone) =>
|
|
86
|
+
const flat = (text, tone) => styled() ? `${tone.face}${text}${RESET}` : text;
|
|
85
87
|
export const NEO_BANNER = {
|
|
86
88
|
head: 'Zenera',
|
|
87
89
|
accent: 'Neo',
|
|
@@ -92,17 +94,18 @@ export function bannerLines(text, columns = process.stderr.columns || 80) {
|
|
|
92
94
|
// Trimmed: the widest accent row is the banner's right edge, and a pad left
|
|
93
95
|
// inside a styled string cannot be trimmed away later.
|
|
94
96
|
const accent = big(text.accent).map((row) => row.trimEnd());
|
|
97
|
+
const tone = ACCENTS[text.hue ?? 'orange'];
|
|
95
98
|
const margin = ' '.repeat(INDENT);
|
|
96
99
|
const width = INDENT + head[0].length + GAP + Math.max(...accent.map((row) => row.length));
|
|
97
100
|
if (width > columns) {
|
|
98
101
|
return [
|
|
99
|
-
`${margin}${flat(text.head.toUpperCase(), HEAD)} ${flat(text.accent.toUpperCase(),
|
|
102
|
+
`${margin}${flat(text.head.toUpperCase(), HEAD)} ${flat(text.accent.toUpperCase(), tone)}`,
|
|
100
103
|
`${margin}${dim(spaced(text.subtitle))}`,
|
|
101
104
|
];
|
|
102
105
|
}
|
|
103
106
|
const lines = [];
|
|
104
107
|
for (let r = 0; r < HEIGHT; r++) {
|
|
105
|
-
lines.push(`${margin}${paint(head[r], HEAD)}${' '.repeat(GAP)}${paint(accent[r],
|
|
108
|
+
lines.push(`${margin}${paint(head[r], HEAD)}${' '.repeat(GAP)}${paint(accent[r], tone)}`);
|
|
106
109
|
}
|
|
107
110
|
// Rules on both sides, centred under the art: the subtitle is a caption,
|
|
108
111
|
// and without them a short line of spaced capitals floats.
|
package/dist/catalog.js
CHANGED
|
@@ -38,29 +38,28 @@ const CATALOG_VERSION = 1;
|
|
|
38
38
|
*/
|
|
39
39
|
export const CURATED = {
|
|
40
40
|
openai: [
|
|
41
|
-
{ id: 'gpt-
|
|
42
|
-
{ id: 'gpt-
|
|
41
|
+
{ id: 'gpt-5.6-luna', roles: ['chat'], contextLength: 1_050_000 },
|
|
42
|
+
{ id: 'gpt-5.6-terra', roles: ['chat'], contextLength: 1_050_000 },
|
|
43
43
|
{ id: 'text-embedding-3-small', roles: ['embedding'], dimensions: 1536 },
|
|
44
44
|
{ id: 'text-embedding-3-large', roles: ['embedding'], dimensions: 3072 },
|
|
45
45
|
],
|
|
46
46
|
anthropic: [
|
|
47
|
-
{ id: 'claude-
|
|
48
|
-
{ id: 'claude-
|
|
47
|
+
{ id: 'claude-sonnet-5', roles: ['chat'], contextLength: 1_000_000 },
|
|
48
|
+
{ id: 'claude-opus-5', roles: ['chat'], contextLength: 1_000_000 },
|
|
49
49
|
],
|
|
50
50
|
google: [
|
|
51
|
-
{ id: 'gemini-
|
|
52
|
-
{ id: 'gemini-
|
|
53
|
-
{ id: 'gemini-embedding-
|
|
51
|
+
{ id: 'gemini-3.5-flash-lite', roles: ['chat'], contextLength: 1_048_576 },
|
|
52
|
+
{ id: 'gemini-3.8-flash', roles: ['chat'], contextLength: 1_048_576 },
|
|
53
|
+
{ id: 'gemini-embedding-2', roles: ['embedding'] },
|
|
54
54
|
],
|
|
55
55
|
vertex: [
|
|
56
|
-
{ id: 'gemini-
|
|
57
|
-
{ id: 'gemini-
|
|
58
|
-
{ id: 'gemini-embedding-
|
|
59
|
-
{ id: 'text-embedding-005', roles: ['embedding'], dimensions: 768 },
|
|
56
|
+
{ id: 'gemini-3.5-flash-lite', roles: ['chat'], contextLength: 1_048_576 },
|
|
57
|
+
{ id: 'gemini-3.8-flash', roles: ['chat'], contextLength: 1_048_576 },
|
|
58
|
+
{ id: 'gemini-embedding-2', roles: ['embedding'] },
|
|
60
59
|
],
|
|
61
60
|
openrouter: [
|
|
62
|
-
{ id: 'openai/gpt-
|
|
63
|
-
{ id: 'anthropic/claude-
|
|
61
|
+
{ id: 'openai/gpt-5.6-luna', roles: ['chat'], contextLength: 1_050_000 },
|
|
62
|
+
{ id: 'anthropic/claude-sonnet-5', roles: ['chat'], contextLength: 1_000_000 },
|
|
64
63
|
{ id: 'openai/text-embedding-3-small', roles: ['embedding'], dimensions: 1536 },
|
|
65
64
|
],
|
|
66
65
|
};
|
|
@@ -73,23 +72,23 @@ export const CURATED = {
|
|
|
73
72
|
*/
|
|
74
73
|
export const PREFERRED = {
|
|
75
74
|
openai: {
|
|
76
|
-
chat: ['gpt-
|
|
75
|
+
chat: ['gpt-5.6-luna', 'gpt-5.6-terra'],
|
|
77
76
|
embedding: ['text-embedding-3-small', 'text-embedding-3-large'],
|
|
78
77
|
},
|
|
79
78
|
anthropic: {
|
|
80
|
-
chat: ['claude-
|
|
79
|
+
chat: ['claude-sonnet-5', 'claude-opus-5'],
|
|
81
80
|
embedding: [],
|
|
82
81
|
},
|
|
83
82
|
google: {
|
|
84
|
-
chat: ['gemini-
|
|
85
|
-
embedding: ['gemini-embedding-
|
|
83
|
+
chat: ['gemini-3.5-flash-lite', 'gemini-3.5-flash'],
|
|
84
|
+
embedding: ['gemini-embedding-2'],
|
|
86
85
|
},
|
|
87
86
|
vertex: {
|
|
88
|
-
chat: ['gemini-
|
|
89
|
-
embedding: ['
|
|
87
|
+
chat: ['gemini-3.5-flash-lite', 'gemini-3.5-flash'],
|
|
88
|
+
embedding: ['gemini-embedding-2'],
|
|
90
89
|
},
|
|
91
90
|
openrouter: {
|
|
92
|
-
chat: ['openai/gpt-
|
|
91
|
+
chat: ['openai/gpt-5.6-luna', 'anthropic/claude-sonnet-5'],
|
|
93
92
|
embedding: ['openai/text-embedding-3-small'],
|
|
94
93
|
},
|
|
95
94
|
};
|
package/dist/command.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BannerText } from './banner.ts';
|
|
1
2
|
export interface Context {
|
|
2
3
|
readonly args: readonly string[];
|
|
3
4
|
readonly json: boolean;
|
|
@@ -9,6 +10,8 @@ export interface Command {
|
|
|
9
10
|
readonly usage: string;
|
|
10
11
|
/** lines printed under the usage line by `zen help <command>` */
|
|
11
12
|
readonly details?: readonly string[];
|
|
13
|
+
/** printed instead of the `zen` banner, so the sub-brand shows through */
|
|
14
|
+
readonly banner?: BannerText;
|
|
12
15
|
/**
|
|
13
16
|
* Answers `--help` itself, for a command whose arguments select a page the
|
|
14
17
|
* frame cannot see. Without it the frame prints this command's own page.
|
package/dist/commands/check.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, statSync } from 'node:fs';
|
|
2
2
|
import { basename, dirname, resolve } from 'node:path';
|
|
3
3
|
import { one, parse } from "../args.js";
|
|
4
|
+
import { loadProjectEnv } from "../env.js";
|
|
4
5
|
import { KeyStore } from "../keys.js";
|
|
5
6
|
import { duration } from "../narrate.js";
|
|
6
7
|
import { Registry } from "../projects.js";
|
|
@@ -75,7 +76,10 @@ export const check = {
|
|
|
75
76
|
const name = entry?.name ?? basename(dir);
|
|
76
77
|
// Materialised first, so the credential verdicts are the ones a run
|
|
77
78
|
// would reach: a key in the environment and a key in the keyring are
|
|
78
|
-
// the same key by the time the library asks.
|
|
79
|
+
// the same key by the time the library asks. The project's own `.env`
|
|
80
|
+
// goes first for the same reason — `zen run` reads it, so a check that
|
|
81
|
+
// did not would report a missing credential for a project that runs.
|
|
82
|
+
loadProjectEnv(dir);
|
|
79
83
|
const keys = await KeyStore.open();
|
|
80
84
|
keys.materialize();
|
|
81
85
|
const bar = progress();
|
package/dist/commands/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { inspect } from "./inspect.js";
|
|
|
5
5
|
import { key } from "./key.js";
|
|
6
6
|
import { list } from "./list.js";
|
|
7
7
|
import { memory } from "./memory.js";
|
|
8
|
+
import { meta } from "./meta.js";
|
|
8
9
|
import { models } from "./models.js";
|
|
9
10
|
import { open } from "./open.js";
|
|
10
11
|
import { run } from "./run.js";
|
|
@@ -19,6 +20,7 @@ export const COMMANDS = {
|
|
|
19
20
|
init,
|
|
20
21
|
list,
|
|
21
22
|
run,
|
|
23
|
+
meta,
|
|
22
24
|
open,
|
|
23
25
|
key,
|
|
24
26
|
models,
|
|
@@ -37,6 +39,7 @@ export const ALIASES = {
|
|
|
37
39
|
model: 'models',
|
|
38
40
|
validate: 'check',
|
|
39
41
|
doctor: 'check',
|
|
42
|
+
copilot: 'meta',
|
|
40
43
|
report: 'inspect',
|
|
41
44
|
mem: 'memory',
|
|
42
45
|
edit: 'open',
|
|
@@ -49,7 +52,7 @@ export const EXTERNAL = {
|
|
|
49
52
|
summary: 'A mock API from an openapi/swagger document.',
|
|
50
53
|
usage: 'zen faker <serve|build|cache> [spec...]',
|
|
51
54
|
install: 'npm i -g @zenera/faker',
|
|
52
|
-
banner: { head: 'Zenera', accent: 'Faker', subtitle: 'Mock API Server' },
|
|
55
|
+
banner: { head: 'Zenera', accent: 'Faker', subtitle: 'Mock API Server', hue: 'mint' },
|
|
53
56
|
},
|
|
54
57
|
rag: {
|
|
55
58
|
package: '@zenera/rag',
|
package/dist/commands/init.js
CHANGED
|
@@ -2,6 +2,7 @@ import { readProjectConfig } from '@zenera/neo';
|
|
|
2
2
|
import { existsSync, readdirSync } from 'node:fs';
|
|
3
3
|
import { basename, resolve } from 'node:path';
|
|
4
4
|
import { one, parse } from "../args.js";
|
|
5
|
+
import { PREFERRED } from "../catalog.js";
|
|
5
6
|
import { ensureHome } from "../home.js";
|
|
6
7
|
import { resolveBuild } from "../image.js";
|
|
7
8
|
import { envNames, isProvider, keyId, KeyStore, PROVIDERS } from "../keys.js";
|
|
@@ -43,6 +44,36 @@ const DEFAULT_MODEL = {
|
|
|
43
44
|
vertex: { ref: 'vertex:gemini-3.5-flash' },
|
|
44
45
|
openrouter: { ref: 'openrouter:inclusionai/ling-3.0-flash-fin:free' },
|
|
45
46
|
};
|
|
47
|
+
/**
|
|
48
|
+
* What vectorises the project's memory, given the provider it ends up on.
|
|
49
|
+
*
|
|
50
|
+
* Taken from the same table `zen models pick` ranks by rather than written out
|
|
51
|
+
* again here, so the scaffold cannot drift from what the catalogue calls
|
|
52
|
+
* current. Anthropic publishes no embeddings API, and a `--model` naming a
|
|
53
|
+
* provider we do not ship gives nothing to guess from; both scaffold without
|
|
54
|
+
* one, and memory still works by ranking recall on term overlap. An
|
|
55
|
+
* `embeddings:` entry pointing at a provider that has none is not a degraded
|
|
56
|
+
* project, it is one that fails to load.
|
|
57
|
+
*/
|
|
58
|
+
function defaultEmbedding(provider) {
|
|
59
|
+
if (provider === undefined) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
const id = PREFERRED[provider].embedding[0];
|
|
63
|
+
return id === undefined ? undefined : `${provider}:${id}`;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The provider a model ref names, when it names one we know.
|
|
67
|
+
*
|
|
68
|
+
* Every ref the scaffold writes carries its provider, so this is how the
|
|
69
|
+
* memory store ends up on the same credential as the chat model — including
|
|
70
|
+
* for an explicit `--model`, where nothing else says which vendor was meant.
|
|
71
|
+
*/
|
|
72
|
+
function providerOf(ref) {
|
|
73
|
+
const colon = ref.indexOf(':');
|
|
74
|
+
const head = colon < 0 ? '' : ref.slice(0, colon);
|
|
75
|
+
return isProvider(head) ? head : undefined;
|
|
76
|
+
}
|
|
46
77
|
/**
|
|
47
78
|
* The provider this machine can actually reach.
|
|
48
79
|
*
|
|
@@ -96,10 +127,11 @@ export const init = {
|
|
|
96
127
|
summary: 'Create a project here, or in <dir>, and register it.',
|
|
97
128
|
usage: USAGE,
|
|
98
129
|
details: [
|
|
99
|
-
'Writes SPECIFICATION.md,
|
|
100
|
-
'scripts/, then records the directory so
|
|
101
|
-
'find it by name. Editor files
|
|
102
|
-
'tree) are written alongside,
|
|
130
|
+
'Writes SPECIFICATION.md, agents.yaml, agents/ (house rules, the memory',
|
|
131
|
+
'rules and one role prompt) and scripts/, then records the directory so',
|
|
132
|
+
'`zen list` and `zen open` can find it by name. Editor files',
|
|
133
|
+
'(.vscode/settings.json and the .github/ tree) are written alongside,',
|
|
134
|
+
'replacing any already there.',
|
|
103
135
|
'',
|
|
104
136
|
'A directory that is already a project is adopted rather than rebuilt:',
|
|
105
137
|
'it is registered under its own name, and nothing in it is touched.',
|
|
@@ -108,10 +140,12 @@ export const init = {
|
|
|
108
140
|
'',
|
|
109
141
|
'SPECIFICATION.md states what the project is for and what it may do,',
|
|
110
142
|
'and describes the one that was just written. Say what you are building',
|
|
111
|
-
'there, then run /sync-
|
|
143
|
+
'there, then run /spec-sync-project in your editor to make the rest match.',
|
|
112
144
|
'',
|
|
113
145
|
'The default agent gets the file tools and a sandboxed shell, plus',
|
|
114
|
-
'`exa:*` when the keyring holds an Exa key.
|
|
146
|
+
'`exa:*` when the keyring holds an Exa key. It keeps a memory of its',
|
|
147
|
+
'own work in memory/, vectorised by the same provider as the model',
|
|
148
|
+
'when that provider publishes embeddings. Without --model, the',
|
|
115
149
|
'keyring is checked and the model is picked from a credential the',
|
|
116
150
|
'provider accepts.',
|
|
117
151
|
'',
|
|
@@ -143,7 +177,8 @@ export const init = {
|
|
|
143
177
|
: DEFAULT_MODEL[provider ?? 'openai'];
|
|
144
178
|
const model = choice.ref;
|
|
145
179
|
const web = hasExa(store);
|
|
146
|
-
const
|
|
180
|
+
const embedding = defaultEmbedding(providerOf(model));
|
|
181
|
+
const written = scaffold({ dir, model, modelOptions: choice.options, web, embedding });
|
|
147
182
|
const registry = await Registry.open();
|
|
148
183
|
registry.add(name, dir);
|
|
149
184
|
registry.save();
|