@zenera/cli 1.1.26 → 1.1.28

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 CHANGED
@@ -1,8 +1,15 @@
1
+ <div align="center">
2
+ <img
3
+ src="https://raw.githubusercontent.com/andreyryabov/ZeneraNeo/main/docs/imgs/zenera-logo-with-media-queries.svg"
4
+ alt="Zenera Logo"
5
+ width="20"
6
+ />
7
+ <div>Powered By Zenera</div>
8
+ </div>
9
+ <br>
10
+
1
11
  <div align="center">
2
- <picture>
3
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/andreyryabov/ZeneraNeo/main/docs/imgs/banner-cli-dark.svg">
4
- <img src="https://raw.githubusercontent.com/andreyryabov/ZeneraNeo/main/docs/imgs/banner-cli-light.svg" alt="ZENERA CLI" width="720">
5
- </picture>
12
+ <img src="https://raw.githubusercontent.com/andreyryabov/ZeneraNeo/main/docs/imgs/banner-cli-with-media-queries.svg" alt="ZENERA CLI" width="720">
6
13
 
7
14
  [![npm](https://img.shields.io/npm/v/@zenera/cli.svg)](https://www.npmjs.com/package/@zenera/cli)
8
15
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/andreyryabov/ZeneraNeo/blob/main/LICENSE)
@@ -121,16 +128,15 @@ zen init my-project # scaffolds a project and registers it
121
128
  zen open my-project # open the specification and project in your editor
122
129
  ```
123
130
 
124
- In the editor, describe the job in `SPECIFICATION.md`, then send this in the
125
- agent chat:
131
+ In the editor, describe the job in `SPECIFICATION.md`. Then, back in the
132
+ terminal, let the meta agent build the system from it:
126
133
 
127
- ```
128
- /spec-sync-project
134
+ ```sh
135
+ zen meta run my-project /spec-sync-project
129
136
  ```
130
137
 
131
138
  It creates the specialists, responsibilities, tool access and handoffs the job
132
- needs. Back in the terminal, validate the generated system and run it against a
133
- real workspace:
139
+ needs. Validate the generated system and run it against a real workspace:
134
140
 
135
141
  ```sh
136
142
  zen check my-project
@@ -170,10 +176,11 @@ dashboard rewritten every second: what each item is doing right now, what the
170
176
  finished ones cost - per model, since agents may each use a different one - and
171
177
  what is still queued.
172
178
 
173
- To change what the system does, update `SPECIFICATION.md` and send
174
- `/spec-sync-project` again. The next section explains that workflow in detail.
179
+ To change what the system does, update `SPECIFICATION.md` and run
180
+ `zen meta run /spec-sync-project` again. The next section explains that workflow
181
+ in detail.
175
182
 
176
- ## You write the specification; a coding agent writes the system
183
+ ## You write the specification; the meta agent writes the system
177
184
 
178
185
  `zen init` writes `SPECIFICATION.md` first, and everything else in the folder
179
186
  implements it. Where the two disagree the specification wins, so a change to
@@ -201,9 +208,9 @@ nobody to hand work to, because there is no second job to hand on.
201
208
 
202
209
  ### How you actually edit it
203
210
 
204
- `SPECIFICATION.md` is ordinary Markdown, so any editor will do - but the loop is
205
- meant to be driven from a coding agent's chat, and `zen init` installs the
206
- prompts that make each step a single command:
211
+ `SPECIFICATION.md` is ordinary Markdown, so any editor will do. `zen init`
212
+ installs the prompts that make each step of the loop a single `zen meta run`
213
+ command:
207
214
 
208
215
  ```sh
209
216
  zen open my-project # or, standing in the folder: zen open
@@ -213,30 +220,29 @@ That launches the editor you already use: the one this terminal belongs to, or
213
220
  `$ZENERA_EDITOR`, or the first of VS Code, Cursor, Windsurf, Zed, Sublime Text
214
221
  or IntelliJ it can find. `--editor <cmd>` names one outright.
215
222
 
216
- In the window that opens:
217
-
218
223
  1. **Edit `SPECIFICATION.md`** - what the system is for, which agents exist,
219
224
  what each may reach for, and what _done_ means.
220
- 2. **Open the chat panel and send `/spec-sync-project`.** It reads the
221
- specification and every file implementing it, works out the difference in
222
- both directions, and changes the smallest thing that closes each gap.
225
+ 2. **Run `zen meta run /spec-sync-project`.** It reads the specification and
226
+ every file implementing it, works out the difference in both directions, and
227
+ changes the smallest thing that closes each gap.
223
228
  3. **Answer `SPECIFICATION-FEEDBACK.md`**, which it writes for anything it could
224
229
  not do without guessing. Every question carries two to four written-out
225
230
  answers, each with a `[ ]` box: tick one, delete the ones you do not want, or
226
231
  write your own in their place.
227
- 4. **Send `/spec-apply-feedback`.** It folds the answers you chose into
228
- `SPECIFICATION.md` - never into a prompt - and then `/spec-sync-project` again.
232
+ 4. **Run `zen meta run /spec-apply-feedback`.** It folds the answers you chose
233
+ into `SPECIFICATION.md` - never into a prompt - and then
234
+ `/spec-sync-project` again.
229
235
 
230
- Back in the terminal: `zen check`, then `zen run`.
236
+ Then: `zen check`, then `zen run`.
231
237
 
232
238
  <details>
233
- <summary>The chat commands <code>zen init</code> installs</summary>
239
+ <summary>The prompts <code>zen init</code> installs</summary>
234
240
 
235
- They are prompt files under `.github/prompts/`, which VS Code and its forks
236
- offer as chat slash-commands. Both `zen init` and `zen open` write them fresh,
237
- so they never go stale - and edits to them do not survive.
241
+ They are prompt files under `.github/prompts/`, and `zen meta run /<name>` runs
242
+ one (`zen meta prompts` lists them). Both `zen init` and `zen open` write them
243
+ fresh, so they never go stale - and edits to them do not survive.
238
244
 
239
- | In chat | Does |
245
+ | Prompt | Does |
240
246
  | ---------------------- | ---------------------------------------------------------------- |
241
247
  | `/spec-sync-project` | Makes every file match `SPECIFICATION.md`, both directions. |
242
248
  | `/spec-apply-feedback` | Folds your answers in `SPECIFICATION-FEEDBACK.md` into the spec. |
@@ -245,10 +251,8 @@ so they never go stale - and edits to them do not survive.
245
251
  | `/new-agent` | Adds an agent - prompt, wiring and hand-offs. |
246
252
  | `/new-skill` | Adds a skill under `agents/skills/`. |
247
253
 
248
- In an editor that does not support prompt files, paste the contents of
249
- `.github/prompts/spec-sync-project.prompt.md` into its chat instead - it is only a
250
- prompt. Alongside them, `.github/copilot-instructions.md` is the standing brief
251
- that explains this runtime to whatever agent is reading, and
254
+ Alongside them, `.github/copilot-instructions.md` is the standing brief that
255
+ explains this runtime to whatever coding agent opens the folder, and
252
256
  `.github/skills/zen-cli/` is the CLI's own reference for it.
253
257
 
254
258
  </details>
@@ -256,9 +260,9 @@ that explains this runtime to whatever agent is reading, and
256
260
  ### The self-improving loop
257
261
 
258
262
  ```
259
- edit SPECIFICATION.md → /spec-sync-project → zen check → zen run
260
- ↑ ↓
261
- └───── refine the spec <-──── zen inspect <-───────┘
263
+ edit SPECIFICATION.md → zen meta run /spec-sync-project → zen check → zen run
264
+ ↑ ↓
265
+ └─────────── refine the spec <-──── zen inspect <-─────────────┘
262
266
  ```
263
267
 
264
268
  `zen inspect` makes the record visible: the run trace, timing and token
@@ -266,7 +270,7 @@ statistics, agent architecture, and memory used during the run.
266
270
 
267
271
  ![Run inspection: trace, agent architecture, and memory](https://raw.githubusercontent.com/andreyryabov/ZeneraNeo/main/docs/imgs/0910_480.gif)
268
272
 
269
- The agent in your editor can close the same loop without the page. `zen inspect
273
+ The meta agent can close the same loop without the page. `zen inspect
270
274
  graph` writes the whole run as one Mermaid flowchart - a line per node, short
271
275
  sequential ids, the tools counted in a header - which is small enough to read
272
276
  whole. Having spotted the loop or the branch that failed, it opens the two or
@@ -287,7 +291,7 @@ prompt that reads correctly and behaves wrongly explains itself.
287
291
  `ask` has two modes, decided by whether the question is given:
288
292
 
289
293
  - **One-shot** - a question on the line, in `--question-file`, or `--json`.
290
- For a script or the agent in your editor: no banner, no prompt, no picker.
294
+ For a script or the meta agent: no banner, no prompt, no picker.
291
295
  stdout is the answer and nothing else, and the command exits.
292
296
  - **Interactive** - no question. For you, at a terminal: `zen inspect ask`
293
297
  walks through the session, run and recorded LLM call (whichever are not
@@ -298,8 +302,8 @@ prompt that reads correctly and behaves wrongly explains itself.
298
302
  Every step is a command, and everything each one reads or writes is a plain
299
303
  file: the specification, the findings `zen check` prints with a code, a location
300
304
  and a fix, the record `zen inspect` renders of what the run actually did. So the
301
- loop does not need you standing in it. Hand the whole cycle to the agent in your
302
- editor and it runs on the system it just built - test it, read the failure,
305
+ loop does not need you standing in it. Hand the whole cycle to the meta agent
306
+ and it runs on the system it just built - test it, read the failure,
303
307
  change the sentence in the specification that caused it, rebuild, run again.
304
308
  "Fix my project" is one instruction. (The first time round, `scripts/_setup.sh`
305
309
  prepares the container the tools run in.)
@@ -344,7 +348,7 @@ Read a codebase and leave a short written note about it in the workspace.
344
348
  - Every claim in it comes from a file that was actually read.
345
349
  ```
346
350
 
347
- Send `/spec-sync-project` in the editor's chat. It writes `agents.yaml` - who
351
+ Run `zen meta run /spec-sync-project`. It writes `agents.yaml` - who
348
352
  exists, and what each may reach for:
349
353
 
350
354
  ```yaml
@@ -645,8 +649,8 @@ an accident.
645
649
  the project: a coding agent rooted at the project directory, which reads
646
650
  `SPECIFICATION.md`, changes `agents.yaml`, the prompts and the skills, and runs
647
651
  whatever commands it needs to check itself - on the keys already in your
648
- keyring, and not on anybody's subscription. It is the loop your editor's chat
649
- panel drives, as a command you can put in a script.
652
+ keyring, and not on anybody's subscription. It is the spec-to-system loop as a
653
+ command you can put in a script.
650
654
 
651
655
  ```sh
652
656
  zen meta run "what does this project do?"
@@ -663,9 +667,9 @@ Every prompt goes through `run`, whether you typed it or it is a file: the
663
667
  alternative was a verb that appeared only sometimes, and a bare word after
664
668
  `zen meta` that was a subcommand on Tuesdays and a question on Wednesdays.
665
669
 
666
- It uses every tool without asking. The prompts it runs were written for an
667
- editor, where reading files, writing them and running commands is the whole
668
- point, and a `-p` run has nobody sitting there to answer the question - so the
670
+ It uses every tool without asking. The prompts it runs rely on reading files,
671
+ writing them and running commands, and a `-p` run has nobody sitting there to
672
+ answer the question - so the
669
673
  alternative is not a safer run but a stalled one. `--allow-tool read`,
670
674
  `--allow-tool 'shell(git:*)'` narrow it to what a particular run needs, and
671
675
  `--ask` puts the question back. It is your own checkout and your own key either
@@ -683,11 +687,10 @@ to `.tmp/logs/meta.<when>.log`, whose path is printed before the run starts, so
683
687
  kept beside it as `.tmp/logs/meta.<when>.md`, named again once it has printed -
684
688
  a terminal turns that into something you click.
685
689
 
686
- `zen meta run /<name>` is the part an editor cannot do for you from a script.
687
- The prompts under `.github/prompts/` - `/spec-sync-project`, `/project-review` -
688
- are what a chat panel offers as slash commands, and nothing outside an editor
689
- reads them. zen reads one, drops the frontmatter and sends the body - so the
690
- same prompt runs from the editor, from a terminal and from CI.
690
+ `zen meta run /<name>` runs a prompt file. The prompts under
691
+ `.github/prompts/` - `/spec-sync-project`, `/project-review` - are plain
692
+ Markdown with frontmatter. zen reads one, drops the frontmatter and sends the
693
+ body - so the same prompt runs from a terminal, a script and CI.
691
694
 
692
695
  A terminal has no menu dropping down as you type a slash, so
693
696
  `zen meta prompts` lists what this project holds, each with its description.
@@ -10,12 +10,14 @@ import { copilotEventLog } from "../eventlog.js";
10
10
  import { ensureHome } from "../home.js";
11
11
  import { assertOwner, KeyStore, PROVIDERS } from "../keys.js";
12
12
  import { probeChat, probeModel } from "../liveness.js";
13
- import { answerBox, chooseModel, DATASET_DETAILS, DATASET_SUMMARY, DATASET_USAGE, datasetProjectFlag, DEFAULT_RESUMES, defaultRef, editorFiles, FINETUNE_DETAILS, FINETUNE_SUMMARY, FINETUNE_USAGE, finetuneProjectFlag, lastSession, launch, listPrompts, loadPrompt, locate, masked, misspelledProvider, MODEL_ENV, openLog, ORDER, PROMPT_DIR, providersWarning, readMeta, RECOMMENDED, recordSession, RESUME_PROMPT, resumeDelayMs, runDataset, runFinetune, sameModel, sessionModel, sessionTotals, SOURCE_LABELS, startVertexRelay, tailSpans, Tally, terminalSink, transient, wire, writeMeta, } from "../meta/index.js";
13
+ import { answerBox, chooseModel, DATASET_DETAILS, DATASET_SUMMARY, DATASET_USAGE, datasetProjectFlag, DEFAULT_RESUMES, defaultRef, editorFiles, FINETUNE_DETAILS, FINETUNE_SUMMARY, FINETUNE_USAGE, finetuneProjectFlag, lastSession, launch, listPrompts, loadPrompt, locate, masked, misspelledProvider, MODEL_ENV, openLog, ORDER, PROMPT_DIR, providersWarning, readMeta, recordSession, RESUME_PROMPT, resumeDelayMs, runDataset, runFinetune, sameModel, sessionModel, sessionTotals, SOURCE_LABELS, startVertexRelay, tailSpans, Tally, terminalSink, transient, wire, writeMeta, } from "../meta/index.js";
14
14
  import { splitRef } from "../modelref.js";
15
15
  import { duration } from "../narrate.js";
16
16
  import * as Projects from "../projects.js";
17
17
  import { project as resolveProject } from "../resolve.js";
18
- import { bold, choose, CliError, credentialError, cyan, dim, EXIT, green, isInteractive, json, note, progress, readStdin, table, usageError, warn, write, writeAll, } from "../term.js";
18
+ import { TEMPLATES } from "../scaffold.js";
19
+ import { bold, choose, CliError, credentialError, cyan, dim, EXIT, green, isInteractive, json, note, pad, progress, readStdin, table, usageError, warn, write, writeAll, } from "../term.js";
20
+ import { wrap } from "../tui/wrap.js";
19
21
  import { appendUsage, LEDGER_ENV, ledgerPath, META_PROMPT_ENV, META_SESSION_ENV, } from "../usage.js";
20
22
  const USAGE = 'zen meta run [project] [prompt] [options]';
21
23
  const VERBS = new Set(['model', 'run', 'prompts', 'resume', 'dataset', 'finetune']);
@@ -55,15 +57,46 @@ function page(p) {
55
57
  write(line ? ` ${line}` : '');
56
58
  }
57
59
  }
60
+ const RUN_FORMS = [
61
+ ' zen meta run [project] "<question>" ask it something',
62
+ ' zen meta run [project] /<name> [words] run a stored prompt',
63
+ ' zen meta run [project] pick one of its stored prompts',
64
+ ];
65
+ const RUN_ARGUMENTS = [
66
+ 'Arguments:',
67
+ ' [project] Name of a project. Default: the one you are in.',
68
+ ' [prompt] Your question, in quotes. Also --prompt, or piped in.',
69
+ ' /<name> A prompt file in .github/prompts/<name>.prompt.md.',
70
+ ' [words] Appended to that prompt as a final line.',
71
+ ];
72
+ const RUN_OPTIONS = [
73
+ 'Options:',
74
+ ' --project <name|dir> Which project to work in. The agent is rooted there.',
75
+ ' --model <ref> Model for this run, e.g. vertex/gemini-3.8-flash.',
76
+ ' --provider <name> Force the key to use. Default: the model ref says.',
77
+ ' --prompt-file <file> Append this file to the prompt, after any words.',
78
+ ' --agent <name> A .github/agents/<name>.agent.md.',
79
+ ' --effort <level> none, minimal, low, medium, high, xhigh or max.',
80
+ ' --allow-tool <list> Only these: read, shell(git:*), write(README.md).',
81
+ ' --ask Have it ask before each tool. Default: it does not.',
82
+ ' --add-dir <dir> Another directory it may touch. Repeatable.',
83
+ ' --resume <id> Continue a copilot session. --continue takes the last.',
84
+ ' --session-id <uuid> Start a new session under this id. Default: a random one.',
85
+ ` --retries <n> Resume after a rate limit or outage. Default ${DEFAULT_RESUMES}, 0 = off.`,
86
+ ' --share <file> Write the transcript to a markdown file.',
87
+ ' --events <file> Append what the agent does to this file as it does it:',
88
+ ' one JSON line per model call, tool call and message.',
89
+ ' --dry-run Print what would run, secrets masked, and stop.',
90
+ ' --no-refresh Leave the editor files as they are (parallel runs).',
91
+ ' --json Print one object: answer, answerFile, sessionId, exitCode, tokens.',
92
+ ];
58
93
  export const meta = {
59
94
  summary: 'Run the meta agent over this project, on your own keys.',
60
95
  usage: USAGE,
61
96
  banner: { head: 'Zenera', accent: 'Meta', subtitle: 'Meta Agent', hue: 'cyan' },
62
97
  details: [
63
98
  'Forms:',
64
- ' zen meta run [project] "<question>" ask it something',
65
- ' zen meta run [project] /<name> [words] run a stored prompt',
66
- ' zen meta run [project] pick one of its stored prompts',
99
+ ...RUN_FORMS,
67
100
  ' zen meta prompts [project] list the stored prompts',
68
101
  ' zen meta resume [project] [session] carry on a run that stopped',
69
102
  ' zen meta model [ref] show or set the model it uses',
@@ -71,33 +104,12 @@ export const meta = {
71
104
  ' zen meta finetune <verb> tune the project on those cases',
72
105
  '',
73
106
  'The project may come before the verb instead: `zen meta acme run`.',
74
- '`zen meta dataset --help` and `zen meta finetune --help` have their own pages.',
107
+ '`zen meta dataset --help`, `zen meta finetune --help` and `zen meta run --help`',
108
+ 'have their own pages; the last lists the stored prompts.',
75
109
  '',
76
- 'Arguments:',
77
- ' [project] Name of a project. Default: the one you are in.',
78
- ' [prompt] Your question, in quotes. Also --prompt, or piped in.',
79
- ' /<name> A prompt file in .github/prompts/<name>.prompt.md.',
80
- ' [words] Appended to that prompt as a final line.',
110
+ ...RUN_ARGUMENTS,
81
111
  '',
82
- 'Options:',
83
- ' --project <name|dir> Which project to work in. The agent is rooted there.',
84
- ' --model <ref> Model for this run, e.g. vertex/gemini-3.8-flash.',
85
- ' --provider <name> Force the key to use. Default: the model ref says.',
86
- ' --prompt-file <file> Append this file to the prompt, after any words.',
87
- ' --agent <name> A .github/agents/<name>.agent.md.',
88
- ' --effort <level> none, minimal, low, medium, high, xhigh or max.',
89
- ' --allow-tool <list> Only these: read, shell(git:*), write(README.md).',
90
- ' --ask Have it ask before each tool. Default: it does not.',
91
- ' --add-dir <dir> Another directory it may touch. Repeatable.',
92
- ' --resume <id> Continue a copilot session. --continue takes the last.',
93
- ' --session-id <uuid> Start a new session under this id. Default: a random one.',
94
- ` --retries <n> Resume after a rate limit or outage. Default ${DEFAULT_RESUMES}, 0 = off.`,
95
- ' --share <file> Write the transcript to a markdown file.',
96
- ' --events <file> Append what the agent does to this file as it does it:',
97
- ' one JSON line per model call, tool call and message.',
98
- ' --dry-run Print what would run, secrets masked, and stop.',
99
- ' --no-refresh Leave the editor files as they are (parallel runs).',
100
- ' --json Print one object: answer, answerFile, sessionId, exitCode, tokens.',
112
+ ...RUN_OPTIONS,
101
113
  '',
102
114
  'It always uses your own keys — `zen key add` — and never a coding-agent',
103
115
  'subscription. The answer goes to stdout and the progress to stderr, so',
@@ -131,6 +143,9 @@ export const meta = {
131
143
  'Setting one asks the provider a one-word question first, so a model that',
132
144
  'will not answer is never stored. --force skips that.',
133
145
  '',
146
+ '`zen meta model --pick` browses every model your keys reach: / filters,',
147
+ 't asks one a question, p takes it.',
148
+ '',
134
149
  'Examples:',
135
150
  ' zen meta run "what does this project do?"',
136
151
  ' zen meta run acme "review the last commit"',
@@ -144,9 +159,16 @@ export const meta = {
144
159
  ' zen meta model --pick',
145
160
  ' zen meta run --dry-run "hello"',
146
161
  ],
147
- help: (ctx) => {
162
+ help: async (ctx) => {
148
163
  const sub = subcommand(ctx.args);
149
- page(sub ? SUBCOMMANDS[sub.name].page : { ...meta, details: meta.details ?? [] });
164
+ if (sub) {
165
+ return page(SUBCOMMANDS[sub.name].page);
166
+ }
167
+ const words = ctx.args.filter((a) => !a.startsWith('-'));
168
+ if (words[0] === 'run' || words[1] === 'run') {
169
+ return await runHelp(ctx, words);
170
+ }
171
+ page({ ...meta, details: meta.details ?? [] });
150
172
  },
151
173
  run: async (ctx) => {
152
174
  // These own their flags, so they are routed before the meta agent's are parsed.
@@ -216,6 +238,45 @@ export const meta = {
216
238
  throw usageError(first === undefined ? 'nothing to run' : 'a prompt goes through `run`', `try: zen meta run ${positionals.map(requote).join(' ') || '"what does this project do?"'}`);
217
239
  },
218
240
  };
241
+ // ---------------------------------------------------------------------------
242
+ // zen meta run --help
243
+ //
244
+ // The prompts are the reason to type `run`, so its page names them. The
245
+ // shipped ones come from the templates, which `run` refreshes into the project
246
+ // first; a project's own additions are read from the project.
247
+ // ---------------------------------------------------------------------------
248
+ async function runHelp(ctx, words) {
249
+ const at = ctx.args.indexOf('--project');
250
+ const flag = ctx.args.find((a) => a.startsWith('--project='))?.slice('--project='.length) ??
251
+ (at >= 0 ? ctx.args[at + 1] : undefined);
252
+ const named = flag ?? (words[1] === 'run' ? words[0] : words[1]);
253
+ const project = (named && !named.startsWith('/') ? await Projects.find(named) : undefined) ??
254
+ (await Projects.current(ctx.cwd));
255
+ const shipped = join(TEMPLATES, 'editor');
256
+ const ours = new Set(await listPrompts(shipped));
257
+ const theirs = project ? await listPrompts(project.dir) : [];
258
+ const names = [...new Set([...ours, ...theirs])].sort();
259
+ const found = await Promise.all(names.map((n) => loadPrompt(ours.has(n) ? shipped : project.dir, n)));
260
+ const lines = [
261
+ 'Forms:',
262
+ ...RUN_FORMS,
263
+ '',
264
+ project ? `Prompts in ${project.name}:` : 'Prompts:',
265
+ ];
266
+ const nameWidth = Math.max(...found.map((p) => p.name.length + 1));
267
+ const room = Math.max(30, Math.min(process.stdout.columns ?? 80, 100) - nameWidth - 6);
268
+ for (const p of found) {
269
+ const [first = '', ...more] = wrap(p.description ?? '', room);
270
+ lines.push(` ${pad(`/${p.name}`, nameWidth)} ${first}`.trimEnd());
271
+ lines.push(...more.map((l) => ` ${' '.repeat(nameWidth)} ${l}`));
272
+ }
273
+ lines.push('', ...RUN_ARGUMENTS, '', ...RUN_OPTIONS, '', 'Examples:', ' zen meta run "what does this project do?"', ' zen meta run acme /project-review', ' zen meta run acme /spec-sync-project agents/triage.md', ' zen meta run acme /analyze --json');
274
+ page({
275
+ usage: 'zen meta run [project] [/<name> [words] | "<question>"] [options]',
276
+ summary: 'Ask the meta agent a question, or run one of the stored prompts.',
277
+ details: lines,
278
+ });
279
+ }
219
280
  /** Words the shell would have split are shown back the way they were typed. */
220
281
  const requote = (s) => (/\s/.test(s) ? JSON.stringify(s) : s);
221
282
  // ---------------------------------------------------------------------------
@@ -898,8 +959,15 @@ async function pickModel(ctx) {
898
959
  if (known.length === 0) {
899
960
  throw credentialError('no keys to choose from', 'add one: zen key add openai');
900
961
  }
901
- const suggestions = [...new Set(known)].flatMap((p) => (RECOMMENDED[p] ?? []).map((id) => `${p}/${id}`));
902
- note(dim(bold('Models zen knows how to wire, for providers you hold a key for')));
903
- return await choose('Which model?', suggestions.map((ref) => ({ label: ref, value: ref })));
962
+ const held = [...new Set(known)];
963
+ const { browseModels } = await import("./models.js");
964
+ const chosen = await browseModels({
965
+ initial: held.length === 1 ? held[0] : undefined,
966
+ role: 'chat',
967
+ });
968
+ if (!chosen) {
969
+ throw usageError('nothing picked', 'name the model instead: zen meta model <provider>/<id>');
970
+ }
971
+ return `${chosen.provider}/${chosen.id}`;
904
972
  }
905
973
  //# sourceMappingURL=meta.js.map
@@ -1,3 +1,15 @@
1
+ import { type CatalogEntry, type Role } from '../catalog.ts';
1
2
  import type { Command } from '../command.ts';
3
+ import { type Provider } from '../keys.ts';
4
+ export interface BrowseRequest {
5
+ /** opens straight into this provider's listing */
6
+ initial?: Provider;
7
+ /** the role filter each listing opens on; tab still cycles */
8
+ role?: Role;
9
+ refresh?: boolean;
10
+ theme?: string;
11
+ }
12
+ /** The browser, for any command that wants a person to pick a model. */
13
+ export declare function browseModels(request?: BrowseRequest): Promise<CatalogEntry | undefined>;
2
14
  export declare const models: Command;
3
15
  //# sourceMappingURL=models.d.ts.map
@@ -573,12 +573,29 @@ const browse = async (ctx, args) => {
573
573
  if (ctx.json || !isInteractive()) {
574
574
  throw usageError('browsing needs a person at a terminal', 'from a script: zen models ls --json, or zen models pick --chat');
575
575
  }
576
+ const chosen = await browseModels({
577
+ initial,
578
+ theme: values.theme,
579
+ refresh: values.refresh,
580
+ });
581
+ if (!chosen) {
582
+ // Leaving is a fine way to end a browse, but not an answer to a
583
+ // substitution that was waiting for one.
584
+ if (!process.stdout.isTTY) {
585
+ throw new CliError('nothing picked', EXIT.failed);
586
+ }
587
+ return;
588
+ }
589
+ write(chosen.ref);
590
+ };
591
+ /** The browser, for any command that wants a person to pick a model. */
592
+ export async function browseModels(request = {}) {
576
593
  const where = await credentials();
577
594
  // Offline, like `providers`: the overview should not cost five listings.
578
595
  // A provider is fetched when it is opened.
579
596
  const cats = await loadCatalogs(PROVIDERS, { offline: true });
580
597
  const { browse: draw } = await import("../tui/models.js");
581
- const chosen = await draw({
598
+ return await draw({
582
599
  providers: cats.map((c) => ({
583
600
  provider: c.provider,
584
601
  credential: source(where, c.provider),
@@ -586,9 +603,10 @@ const browse = async (ctx, args) => {
586
603
  origin: c.origin,
587
604
  fetchedAt: c.fetchedAt,
588
605
  })),
589
- initial,
590
- theme: values.theme,
591
- load: (provider, refresh) => loadCatalog(provider, { refresh: refresh || values.refresh }),
606
+ initial: request.initial,
607
+ role: request.role,
608
+ theme: request.theme,
609
+ load: (provider, refresh) => loadCatalog(provider, { refresh: refresh || request.refresh }),
592
610
  balance: async (provider) => provider === 'openrouter' && source(where, provider) ? routerBalance() : undefined,
593
611
  test: async (entry) => {
594
612
  const role = entry.roles.includes('chat')
@@ -606,16 +624,7 @@ const browse = async (ctx, args) => {
606
624
  };
607
625
  },
608
626
  });
609
- if (!chosen) {
610
- // Leaving is a fine way to end a browse, but not an answer to a
611
- // substitution that was waiting for one.
612
- if (!process.stdout.isTTY) {
613
- throw new CliError('nothing picked', EXIT.failed);
614
- }
615
- return;
616
- }
617
- write(chosen.ref);
618
- };
627
+ }
619
628
  // ---------------------------------------------------------------------------
620
629
  // Dispatch
621
630
  // ---------------------------------------------------------------------------
package/dist/main.js CHANGED
File without changes
@@ -23,7 +23,7 @@ export interface ModelSources {
23
23
  export declare const ORDER: readonly ModelSource[];
24
24
  export declare function chooseModel(sources: ModelSources): ModelChoice | undefined;
25
25
  export declare const SOURCE_LABELS: Record<ModelSource, string>;
26
- /** First of each row is the default; the rest are what `--pick` offers beside it. */
26
+ /** First of each row is the default; the rest are the next best. */
27
27
  export declare const RECOMMENDED: Partial<Record<Provider, readonly string[]>>;
28
28
  /** The recommended ref for the first provider with a key, or for `only`. */
29
29
  export declare function defaultRef(store: KeyStore, only?: Provider): string | undefined;
@@ -47,7 +47,7 @@ export const SOURCE_LABELS = {
47
47
  // error. Deep tier deliberately: this agent is pointed at the project itself,
48
48
  // and a cheap model reading a specification is a false economy.
49
49
  // ---------------------------------------------------------------------------
50
- /** First of each row is the default; the rest are what `--pick` offers beside it. */
50
+ /** First of each row is the default; the rest are the next best. */
51
51
  export const RECOMMENDED = {
52
52
  openai: ['gpt-5.6-sol', 'gpt-5.6-terra'],
53
53
  anthropic: ['claude-opus-5', 'claude-sonnet-5'],
@@ -1,4 +1,4 @@
1
- import { type Balance, type Catalog, type CatalogEntry } from '../catalog.ts';
1
+ import { type Balance, type Catalog, type CatalogEntry, type Role } from '../catalog.ts';
2
2
  import type { Provider } from '../keys.ts';
3
3
  export interface ProviderRow {
4
4
  provider: Provider;
@@ -21,6 +21,8 @@ export interface BrowseOptions {
21
21
  providers: readonly ProviderRow[];
22
22
  /** opens straight into this provider's listing */
23
23
  initial?: Provider;
24
+ /** the role filter a listing opens on, when it has any such model */
25
+ role?: Role;
24
26
  theme?: string;
25
27
  load(provider: Provider, refresh: boolean): Promise<Catalog>;
26
28
  /** what the account has left to spend, for providers that say */
@@ -103,7 +103,7 @@ function Browser({ options, theme, onPick }) {
103
103
  const [failure, setFailure] = useState();
104
104
  const [query, setQuery] = useState('');
105
105
  const [typing, setTyping] = useState(false);
106
- const [role, setRole] = useState(0);
106
+ const [role, setRole] = useState(options.role);
107
107
  const [selected, setSelected] = useState(0);
108
108
  const [verdicts, setVerdicts] = useState(new Map());
109
109
  const [balances, setBalances] = useState(new Map());
@@ -147,7 +147,7 @@ function Browser({ options, theme, onPick }) {
147
147
  undefined,
148
148
  ...ROLES.filter((r) => catalog?.entries.some((e) => e.roles.includes(r))),
149
149
  ], [catalog]);
150
- const only = present[role % present.length];
150
+ const only = present.includes(role) ? role : undefined;
151
151
  const rows = useMemo(() => (catalog?.entries ?? [])
152
152
  .filter((e) => matches(e, query, { roles: only ? [only] : [] }))
153
153
  .sort(byGroup), [catalog, query, only]);
@@ -159,7 +159,7 @@ function Browser({ options, theme, onPick }) {
159
159
  setScreen('models');
160
160
  setQuery('');
161
161
  setTyping(false);
162
- setRole(0);
162
+ setRole(options.role);
163
163
  setSelected(0);
164
164
  top.current = 0;
165
165
  if (!catalogs.has(p)) {
@@ -287,7 +287,7 @@ function Browser({ options, theme, onPick }) {
287
287
  setSelected(0);
288
288
  }
289
289
  else if (key.tab) {
290
- setRole((r) => (r + 1) % present.length);
290
+ setRole(present[(present.indexOf(only) + 1) % present.length]);
291
291
  setSelected(0);
292
292
  }
293
293
  else if ((key.return || key.rightArrow || input === 'l') && current) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenera/cli",
3
- "version": "1.1.26",
3
+ "version": "1.1.28",
4
4
  "description": "Command-line front end for @zenera/neo: agentic projects you can run, share and commit.",
5
5
  "keywords": [
6
6
  "agents",
@@ -52,8 +52,8 @@
52
52
  "@google/genai": "^2.18.0",
53
53
  "@inkjs/ui": "^2.0.0",
54
54
  "@openrouter/sdk": "^1.2.80",
55
- "@zenera/neo": "^1.1.26",
56
- "@zenera/view": "^1.1.26",
55
+ "@zenera/neo": "^1.1.28",
56
+ "@zenera/view": "^1.1.28",
57
57
  "google-auth-library": "^10.0.0",
58
58
  "ink": "^7.1.1",
59
59
  "openai": "^6.0.0",
@@ -61,6 +61,11 @@ standard above using its nodes. So, during an analysis:
61
61
  - The only commands that reach outside the run are `zen memory search`,
62
62
  `zen rag docs search` and `zen rag schema search` - to check whether
63
63
  something the run needed was findable.
64
+ - You run on the host, in the project root - not in the run's sandbox. A path
65
+ the run used as `/assets/<x>` is `assets/<x>` here (or under the folder
66
+ `assets:` names in `agents.yaml`). So call
67
+ `zen rag docs search -d assets/<x> "<query>"`, never `-d /assets/...`, and
68
+ always pass `-d`: `$ZEN_DOCS_DB` / `$ZEN_SCHEMA_DB` may name the sandbox path.
64
69
  - Never run `python3`, `node`, `curl`, `wget` or anything under the run's
65
70
  `workspace/`, never call the project's APIs or services, never run
66
71
  `zen run`, and never read the project's `resources/` or reference
@@ -1,29 +0,0 @@
1
- import type { Case } from './dataset/types.ts';
2
- import { type Tuning } from './tuning.ts';
3
- export interface GuardTry {
4
- dir: string;
5
- verdict: string;
6
- passed: boolean;
7
- summary?: string;
8
- }
9
- export interface GuardResult {
10
- case: string;
11
- tries: GuardTry[];
12
- broken: boolean;
13
- }
14
- /** A guard case as the live view and the status page show it, updated as it goes. */
15
- export interface GuardSeat {
16
- case: string;
17
- try: number;
18
- step: 'run' | 'analyze' | 'passed' | 'broken';
19
- /** the try's directory */
20
- dir: string;
21
- since: number;
22
- }
23
- /** A case is broken only after failing this many tries in a row. */
24
- export declare const GUARD_TRIES = 2;
25
- /** Completed cases outside `exclude`, in an order fixed by the seed and the apply. */
26
- export declare function guardCases(t: Tuning, apply: string, exclude: Set<string>): Case[];
27
- /** Runs the guard for one attempt of an apply; `patch` is that attempt's diff. */
28
- export declare function guard(t: Tuning, dir: string, attempt: number, exclude: Set<string>, patch: string): Promise<GuardResult[]>;
29
- //# sourceMappingURL=guard.d.ts.map