@zenera/cli 1.1.27 → 1.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +127 -75
  2. package/dist/audit.d.ts +2 -2
  3. package/dist/audit.js +11 -4
  4. package/dist/candidates.d.ts +9 -0
  5. package/dist/candidates.js +17 -0
  6. package/dist/catalog.d.ts +10 -5
  7. package/dist/catalog.js +93 -12
  8. package/dist/commands/completion.d.ts +9 -0
  9. package/dist/commands/completion.js +70 -0
  10. package/dist/commands/index.js +2 -0
  11. package/dist/commands/init.js +1 -1
  12. package/dist/commands/key.js +142 -11
  13. package/dist/commands/meta.d.ts +3 -0
  14. package/dist/commands/meta.js +235 -62
  15. package/dist/commands/models.d.ts +12 -0
  16. package/dist/commands/models.js +79 -112
  17. package/dist/completion-install.d.ts +16 -0
  18. package/dist/completion-install.js +85 -0
  19. package/dist/completion.d.ts +9 -0
  20. package/dist/completion.js +93 -0
  21. package/dist/keys.d.ts +27 -4
  22. package/dist/keys.js +63 -3
  23. package/dist/liveness.js +73 -3
  24. package/dist/main.js +22 -0
  25. package/dist/meta/model.d.ts +1 -1
  26. package/dist/meta/model.js +1 -1
  27. package/dist/postinstall.d.ts +2 -0
  28. package/dist/postinstall.js +10 -0
  29. package/dist/sandbox.js +4 -3
  30. package/dist/term.d.ts +2 -0
  31. package/dist/term.js +2 -0
  32. package/dist/tui/links.d.ts +2 -0
  33. package/dist/tui/links.js +31 -7
  34. package/dist/tui/models.d.ts +8 -1
  35. package/dist/tui/models.js +25 -18
  36. package/dist/validate.js +2 -2
  37. package/package.json +8 -6
  38. package/postinstall.mjs +7 -0
  39. package/templates/completions/zen.bash +16 -0
  40. package/templates/completions/zen.fish +22 -0
  41. package/templates/completions/zen.zsh +28 -0
  42. package/templates/editor/.github/copilot-instructions.md +5 -4
  43. package/templates/editor/.github/prompts/spec-edit.prompt.md +250 -0
  44. package/templates/editor/.github/skills/zen-analyze-run/SKILL.md +5 -0
  45. package/templates/editor/.github/skills/zen-cli/SKILL.md +2 -2
  46. package/templates/editor/.github/skills/zen-cli/references/keys.md +27 -2
  47. package/templates/editor/.github/skills/zen-cli/references/models.md +22 -23
  48. package/dist/meta/finetune/guard.d.ts +0 -29
  49. package/dist/meta/finetune/guard.js +0 -104
  50. package/dist/meta.d.ts +0 -291
  51. package/dist/meta.js +0 -981
@@ -1,104 +0,0 @@
1
- import { createHash, randomUUID } from 'node:crypto';
2
- import { mkdirSync, writeFileSync } from 'node:fs';
3
- import { basename, join } from 'node:path';
4
- import { readFeedback } from "./feedback.js";
5
- import { resultOf, selection } from "./sampler.js";
6
- import { keepAnalysis, runOnce } from "./train.js";
7
- import { readJson } from "./tuning.js";
8
- /** A case is broken only after failing this many tries in a row. */
9
- export const GUARD_TRIES = 2;
10
- const pad = (n) => String(n).padStart(2, '0');
11
- /** Completed cases outside `exclude`, in an order fixed by the seed and the apply. */
12
- export function guardCases(t, apply, exclude) {
13
- const key = (id) => createHash('sha256').update(`${t.config.seed}:${apply}:${id}`).digest('hex');
14
- return selection(t)
15
- .filter((c) => !exclude.has(c.id) && resultOf(t, c)?.state === 'completed')
16
- .sort((a, b) => key(a.id).localeCompare(key(b.id)))
17
- .slice(0, t.config.guard);
18
- }
19
- /** Runs the guard for one attempt of an apply; `patch` is that attempt's diff. */
20
- export async function guard(t, dir, attempt, exclude, patch) {
21
- const cases = guardCases(t, basename(dir), exclude);
22
- if (cases.length === 0) {
23
- return [];
24
- }
25
- const seats = cases.map((c) => ({
26
- case: c.id,
27
- try: 1,
28
- step: 'run',
29
- dir: join(dir, 'guard', `a${attempt}`, c.id),
30
- since: Date.now(),
31
- }));
32
- if (t.improvements.current) {
33
- t.improvements.current.guard = seats;
34
- }
35
- t.event({
36
- what: 'guard',
37
- detail: `${basename(dir)}: ${cases.map((c) => c.id).join(', ')} run again on the edit`,
38
- });
39
- const results = await Promise.all(cases.map((c, i) => guardOne(t, join(dir, 'guard', `a${attempt}`, c.id), c, patch, seats[i])));
40
- writeFileSync(join(dir, 'guard.json'), `${JSON.stringify(results, null, 2)}\n`);
41
- return results;
42
- }
43
- async function guardOne(t, dir, c, patch, seat) {
44
- const tries = [];
45
- for (let n = 1; n <= GUARD_TRIES; n++) {
46
- const at = join(dir, `${pad(n)}-nomem`);
47
- const one = await tryOne(t, c, at, n, patch, seat);
48
- tries.push(one);
49
- if (one.passed) {
50
- break;
51
- }
52
- }
53
- const broken = tries.length === GUARD_TRIES && tries.every((x) => !x.passed);
54
- Object.assign(seat, { step: broken ? 'broken' : 'passed', since: Date.now() });
55
- t.event({
56
- what: broken ? 'guard broken' : 'guard passed',
57
- case: c.id,
58
- rev: c.rev,
59
- detail: tries.map((x) => x.verdict).join(', '),
60
- });
61
- return { case: c.id, tries, broken };
62
- }
63
- /** A run or an analysis that says nothing about the prose counts as a pass: only a graded failure undoes an apply. */
64
- async function tryOne(t, c, dir, n, patch, seat) {
65
- mkdirSync(dir, { recursive: true });
66
- Object.assign(seat, { try: n, step: 'run', dir, since: Date.now() });
67
- const why = await runOnce(t, c, dir);
68
- if (why) {
69
- return { dir, verdict: 'void', passed: true, summary: why };
70
- }
71
- const run = readJson(join(dir, 'run.json'));
72
- const file = join(dir, 'feedback.json');
73
- Object.assign(seat, { step: 'analyze', since: Date.now() });
74
- const res = await t.zen([
75
- 'meta',
76
- 'run',
77
- '--no-refresh',
78
- '--json',
79
- '--events',
80
- join(dir, 'analyze.events.jsonl'),
81
- '--session-id',
82
- randomUUID(),
83
- '/analyze',
84
- run.dir,
85
- `case=${c.id}`,
86
- `rev=${c.rev}`,
87
- 'phase=nomem',
88
- `attempt=${n}`,
89
- `feedback=${file}`,
90
- `guard=${patch}`,
91
- ], join(dir, 'analyze.log'));
92
- keepAnalysis(dir, res.stdout);
93
- const read = readFeedback(file, { case: c.id, caseRev: c.rev, phase: 'nomem', attempt: n }, c.rubric.map((r) => r.id));
94
- if (!('feedback' in read)) {
95
- return { dir, verdict: 'unread', passed: true, summary: read.problem };
96
- }
97
- const f = read.feedback;
98
- if (f.verdict === 'void') {
99
- return { dir, verdict: 'void', passed: true, summary: f.infra };
100
- }
101
- const passed = f.verdict === 'right' && !Object.values(f.rubric).includes('fail');
102
- return { dir, verdict: f.verdict, passed, summary: f.summary };
103
- }
104
- //# sourceMappingURL=guard.js.map
package/dist/meta.d.ts DELETED
@@ -1,291 +0,0 @@
1
- import type { ModelUsage, TokenUsage } from '@zenera/neo';
2
- import { spawn as nodeSpawn } from 'node:child_process';
3
- import { type KeyEntry, type KeyStore, type Provider } from './keys.ts';
4
- export declare const PROMPT_DIR = ".github/prompts";
5
- /** The variable a project's `.env` names it with — a zen ref, not a wire id. */
6
- export declare const MODEL_ENV = "ZENERA_META_MODEL";
7
- export type ModelSource = 'flag' | 'shell' | 'env' | 'store' | 'project' | 'default';
8
- export interface ModelChoice {
9
- /** a zen ref: `vertex/gemini-3.8-flash`, or a bare id for the default provider */
10
- ref: string;
11
- from: ModelSource;
12
- }
13
- export interface ModelSources {
14
- flag?: string;
15
- /** `ZENERA_META_MODEL` as it stood before the project's `.env` was folded in */
16
- shell?: string;
17
- /** the same variable after it was, which is the `.env` answer when they differ */
18
- env?: string;
19
- stored?: string;
20
- /** the project's `agents.yaml` `model:` */
21
- project?: string;
22
- /** what a held key recommends, when nothing above answered */
23
- fallback?: string;
24
- }
25
- /** Where the answer is looked for, best first. */
26
- export declare const ORDER: readonly ModelSource[];
27
- export declare function chooseModel(sources: ModelSources): ModelChoice | undefined;
28
- export declare const SOURCE_LABELS: Record<ModelSource, string>;
29
- /** First of each row is the default; the rest are what `--pick` offers beside it. */
30
- export declare const RECOMMENDED: Partial<Record<Provider, readonly string[]>>;
31
- /** The recommended ref for the first provider with a key, or for `only`. */
32
- export declare function defaultRef(store: KeyStore, only?: Provider): string | undefined;
33
- export interface MetaFile {
34
- version: 1;
35
- model?: string;
36
- }
37
- export declare const readMeta: () => Promise<MetaFile>;
38
- export declare function writeMeta(file: MetaFile): void;
39
- /**
40
- * `vertex/gemini-3.8-flash` splits; `gpt-5.6-sol` does not. Only a known provider
41
- * counts as a prefix, because `google/gemini-3.8-flash` is also a perfectly good
42
- * bare id on an endpoint that speaks publisher names.
43
- *
44
- * The colon form is the library's own — `openai:gpt-5.6-sol`, and with an api
45
- * selector `openai/responses:gpt-5.6-sol` — which is what arrives when the ref
46
- * came from `agents.yaml`.
47
- */
48
- export declare function splitRef(ref: string): {
49
- provider?: Provider;
50
- id: string;
51
- };
52
- /**
53
- * The provider a misspelt prefix was reaching for, if that is what it is.
54
- *
55
- * `vertes/gemini-3.8-flash` is shaped exactly like the OpenRouter id
56
- * `meta-llama/llama-4`, so an unknown prefix cannot be an error on its own —
57
- * only one close enough to a real provider name to be a slip of the hand.
58
- */
59
- export declare function misspelledProvider(ref: string): Provider | undefined;
60
- /**
61
- * Copilot offers its tools as OpenAI *custom* tools, which the completions API
62
- * rejects outright — `400 Invalid value: 'custom'`. Only the responses API
63
- * takes them, and only the reasoning models serve it, so the wire API follows
64
- * the model rather than being a flag nobody would know to set. Older models are
65
- * listed rather than newer ones, so each new generation works without a release.
66
- */
67
- export declare function wireApi(provider: Provider, id: string): string | undefined;
68
- export interface Wiring {
69
- provider: Provider;
70
- /** what the model is called on the wire */
71
- model: string;
72
- /** every `COPILOT_*` name and value, ready to hand to the child */
73
- env: Record<string, string>;
74
- /** names whose values are secret, for `--secret-env-vars` and for masking */
75
- secret: string[];
76
- /** anything true but unwelcome: a location that will be slow, a stale default */
77
- warnings: string[];
78
- }
79
- /**
80
- * Turns a zen key entry and a model id into copilot's environment.
81
- *
82
- * `COPILOT_PROVIDER_BASE_URL` is what activates BYOK at all, so every provider
83
- * gets one — including OpenAI, whose url copilot would otherwise have reached
84
- * through a GitHub account we are deliberately not using.
85
- */
86
- export declare function wire(store: KeyStore, entry: KeyEntry, id: string): Wiring;
87
- /** Values a `--dry-run` may print. A key is four characters and an apology. */
88
- export declare function masked(env: Record<string, string>, secret: readonly string[]): string[];
89
- /** `/project-review`, `project-review`, `project-review.prompt.md` or a path. */
90
- export declare function promptPath(dir: string, name: string): string;
91
- export interface StoredPrompt {
92
- name: string;
93
- path: string;
94
- description?: string;
95
- body: string;
96
- }
97
- /**
98
- * Frontmatter is the editor's business — `mode`, `tools`, `description` — and
99
- * none of it means anything to copilot, so only the body is sent. `description`
100
- * is kept for the one line of narration that says which prompt is running.
101
- */
102
- export declare function readPrompt(path: string, name: string, text: string): StoredPrompt;
103
- export declare function loadPrompt(dir: string, name: string): Promise<StoredPrompt>;
104
- export declare function listPrompts(dir: string): Promise<string[]>;
105
- export interface Binary {
106
- command: string;
107
- args: string[];
108
- from: 'path' | 'npx';
109
- }
110
- /**
111
- * A `copilot` already installed is used as it is; otherwise `npx` fetches one.
112
- * Note that finding the name on `PATH` is not the same as finding the CLI —
113
- * an editor may have put a shim there that installs it on first run — so this
114
- * only decides how to launch, never whether it will work.
115
- */
116
- export declare function locate(has?: (cmd: string) => boolean): Binary;
117
- interface Event {
118
- type: string;
119
- data?: Record<string, unknown>;
120
- [key: string]: unknown;
121
- }
122
- export interface Outcome {
123
- /** the last thing the agent said that was not a preamble to a tool call */
124
- answer: string;
125
- exitCode: number;
126
- sessionId?: string;
127
- usage?: Record<string, unknown>;
128
- events: Event[];
129
- }
130
- export interface Sink {
131
- answer(text: string): void;
132
- narrate(line: string): void;
133
- warn(line: string): void;
134
- /** Kept by the log, never shown: the step-by-step nobody reads while it runs. */
135
- detail?(line: string): void;
136
- /** A transient last row, replaced each time and never kept. '' removes it. */
137
- status?(line: string): void;
138
- /** Called once when the run ends, so a repainting sink can clear itself. */
139
- close?(): void;
140
- }
141
- export declare const terminalSink: Sink;
142
- /** Rows of narration the window keeps on screen. */
143
- export declare const WINDOW_ROWS = 8;
144
- /** How often the border is redrawn — the wave's frame, not the clock's. */
145
- export declare const FRAME_MS = 80;
146
- /** Cells the crest travels per frame — a lap of a wide window in a few seconds. */
147
- export declare const WAVE_STEP = 3;
148
- /**
149
- * How brightly one cell of the border is lit, given where the crest has got to.
150
- *
151
- * `position` counts clockwise from the top left corner, so one index walks the
152
- * whole ring and the wave rounds the corners without a seam. Everything the
153
- * tail has passed sits at the dimmest tone, which is the border's resting
154
- * colour: the ring is always drawn, only the crest moves.
155
- */
156
- export declare function toneAt(position: number, head: number, span: number): number;
157
- /**
158
- * The bottom rule as cells, with the status label set into it near the left.
159
- *
160
- * Waiting belongs to the frame rather than to the narration: a row of its own
161
- * costs a step of what the agent is doing every time, and it settles just
162
- * under the last line, which is where the eye already is. Near the left, where
163
- * reading starts, and not so near that it displaces the corner.
164
- */
165
- export declare function footerCells(label: string, width: number): string[];
166
- /**
167
- * Narration held to a fixed number of rows that rewrite themselves in place.
168
- *
169
- * A long run is hundreds of tool calls, and printing each as its own line
170
- * scrolls the question, the model line and every warning off the top of the
171
- * screen — by the end the terminal holds a transcript nobody asked for and the
172
- * answer is somewhere in the middle of it. Only the last few steps say what it
173
- * is doing now, which is the only thing narration is for; the rest is in the
174
- * session copilot recorded.
175
- *
176
- * Warnings are not narration: they leave the window and stay on the screen.
177
- *
178
- * Without a terminal there is nothing to rewrite over, so every line is its
179
- * own — which is what a CI log wants anyway.
180
- */
181
- export declare function windowSink(rows?: number): Sink;
182
- /**
183
- * The answer in the same box `zen run` draws it in: bounded width, rounded
184
- * rule, formatted with full markdown styling (bold, italics, code, lists, quotes, tables).
185
- *
186
- * Redirected output gets the text and nothing else — `zen meta run ... > out.md`
187
- * is meant to produce a file you can read, not one with a border down its side.
188
- */
189
- export declare function answerBox(text: string, columns?: number): string[];
190
- export interface Log {
191
- readonly path: string;
192
- /** Where the answer alone goes, beside the log and openable on its own. */
193
- readonly answerPath: string;
194
- line(text: string): void;
195
- saveAnswer(text: string): void;
196
- close(): void;
197
- }
198
- /** `<project>/.tmp/logs/meta.<when>.log`, opened for append. */
199
- export declare function openLog(dir: string, now?: Date): Log;
200
- /**
201
- * Every meta session the project ran and how it ended, so `zen meta resume`
202
- * needs no id. `running` is never overwritten by a run that was killed, so it
203
- * reads the same as `failed`: not finished.
204
- */
205
- export type SessionState = 'running' | 'done' | 'failed';
206
- export declare function recordSession(dir: string, id: string, state: SessionState): void;
207
- /**
208
- * The newest session that did not finish, else the newest of all. A quick
209
- * question asked after a long run stopped must not stand in for it.
210
- */
211
- export declare function lastSession(dir: string): string | undefined;
212
- /**
213
- * Folds one event into the outcome and says what, if anything, to show for it.
214
- *
215
- * `assistant.message` carries both the running commentary and the final word;
216
- * what separates them is `toolRequests`, so the last message that asked for no
217
- * tool is the answer and the rest are narration.
218
- */
219
- export declare function absorb(event: Event, out: Outcome, sink: Sink, width: number): void;
220
- export interface Launch {
221
- binary: Binary;
222
- args: string[];
223
- env: Record<string, string>;
224
- cwd: string;
225
- sink?: Sink;
226
- log?: Log;
227
- width?: number;
228
- /** said after the elapsed time on the status row, e.g. the tokens so far */
229
- status?: () => string;
230
- /** injected by the tests, which have no copilot and want none */
231
- spawn?: typeof nodeSpawn;
232
- }
233
- export declare function launch(opts: Launch): Promise<Outcome>;
234
- /** Resumes after copilot's own retries run out; `--retries` overrides. */
235
- export declare const DEFAULT_RESUMES = 5;
236
- /** What the resumed session is told, since `-p` has to say something. */
237
- export declare const RESUME_PROMPT = "The previous model call failed. Continue the task from where you stopped.";
238
- export declare function resumeDelayMs(attempt: number): number;
239
- /**
240
- * Why the run is worth resuming, or undefined when waiting cannot help.
241
- *
242
- * Only a refusal that time can cure counts: a rate limit, the provider's own
243
- * failure, a connection that timed out. A 400 or a bad key fails the same way
244
- * however often it is sent - except the one below.
245
- */
246
- export declare function transient(out: Outcome): string | undefined;
247
- export interface MetaCall {
248
- model: string;
249
- usage: TokenUsage;
250
- cacheWriteTokens?: number;
251
- startedAt: string;
252
- durationMs: number;
253
- session?: string;
254
- traceId?: string;
255
- spanId?: string;
256
- }
257
- /**
258
- * One line of the span file, as a model call - anything else is undefined.
259
- * Only counts are read off it: whatever else a span may carry, none of it
260
- * reaches the ledger.
261
- */
262
- export declare function readSpan(line: string): MetaCall | undefined;
263
- /** Whole lines out of a stream read in chunks; the unfinished tail is carried. */
264
- export declare function splitLines(carry: string, chunk: string): {
265
- lines: string[];
266
- carry: string;
267
- };
268
- /** Reads a span file as it grows. `stop()` reads what is left, once. */
269
- export declare function tailSpans(path: string, onCall: (call: MetaCall) => void, everyMs?: number): {
270
- stop: () => void;
271
- };
272
- /** Calls and tokens so far, in the one line the status row and the summary share. */
273
- export declare class Tally {
274
- calls: number;
275
- usage: TokenUsage;
276
- add(call: MetaCall): void;
277
- toString(): string;
278
- }
279
- /** 950, 12.3k, 1.8M. */
280
- export declare function compact(n: number): string;
281
- /**
282
- * Copilot's own per-model totals from the last `session.shutdown` in a session's
283
- * events - cumulative over every resume of it. Reasoning folded into output,
284
- * as everywhere in zen.
285
- */
286
- export declare function shutdownModels(events: string): ModelUsage[] | undefined;
287
- export declare function sessionTotals(sessionId: string): ModelUsage[] | undefined;
288
- /** Non-fatal, said once, because a competing file is a silent override. */
289
- export declare function providersWarning(): string | undefined;
290
- export {};
291
- //# sourceMappingURL=meta.d.ts.map