@zenera/cli 1.1.24 → 1.1.26

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 (199) hide show
  1. package/README.md +65 -0
  2. package/dist/ask.js +28 -5
  3. package/dist/banner.js +2 -2
  4. package/dist/batch.d.ts +6 -0
  5. package/dist/batch.js +76 -1
  6. package/dist/catalog.d.ts +31 -1
  7. package/dist/catalog.js +140 -7
  8. package/dist/commands/index.js +2 -0
  9. package/dist/commands/inspect.d.ts +52 -2
  10. package/dist/commands/inspect.js +220 -31
  11. package/dist/commands/memory.js +156 -32
  12. package/dist/commands/meta.js +333 -24
  13. package/dist/commands/models.js +97 -12
  14. package/dist/commands/open.js +4 -4
  15. package/dist/commands/run.js +38 -4
  16. package/dist/commands/sandbox.js +148 -16
  17. package/dist/commands/view.d.ts +8 -0
  18. package/dist/commands/view.js +114 -0
  19. package/dist/engine.d.ts +8 -1
  20. package/dist/engine.js +7 -0
  21. package/dist/eventlog.d.ts +64 -0
  22. package/dist/eventlog.js +170 -0
  23. package/dist/liveness.d.ts +7 -0
  24. package/dist/liveness.js +35 -3
  25. package/dist/main.js +0 -0
  26. package/dist/meta/editor.d.ts +9 -0
  27. package/dist/meta/editor.js +43 -0
  28. package/dist/meta/finetune/analyze-brief.d.ts +26 -0
  29. package/dist/meta/finetune/analyze-brief.js +131 -0
  30. package/dist/meta/finetune/apply-brief.d.ts +23 -0
  31. package/dist/meta/finetune/apply-brief.js +247 -0
  32. package/dist/meta/finetune/brief.d.ts +45 -0
  33. package/dist/meta/finetune/brief.js +158 -0
  34. package/dist/meta/finetune/children.d.ts +8 -0
  35. package/dist/meta/finetune/children.js +76 -0
  36. package/dist/meta/finetune/command.d.ts +30 -0
  37. package/dist/meta/finetune/command.js +516 -0
  38. package/dist/meta/finetune/cost.d.ts +46 -0
  39. package/dist/meta/finetune/cost.js +105 -0
  40. package/dist/meta/finetune/dataset/anchors.d.ts +41 -0
  41. package/dist/meta/finetune/dataset/anchors.js +239 -0
  42. package/dist/meta/finetune/dataset/changes.d.ts +36 -0
  43. package/dist/meta/finetune/dataset/changes.js +319 -0
  44. package/dist/meta/finetune/dataset/command.d.ts +17 -0
  45. package/dist/meta/finetune/dataset/command.js +1011 -0
  46. package/dist/meta/finetune/dataset/drift.d.ts +33 -0
  47. package/dist/meta/finetune/dataset/drift.js +71 -0
  48. package/dist/meta/finetune/dataset/export.d.ts +26 -0
  49. package/dist/meta/finetune/dataset/export.js +51 -0
  50. package/dist/meta/finetune/dataset/index.d.ts +2 -0
  51. package/dist/meta/finetune/dataset/index.js +2 -0
  52. package/dist/meta/finetune/dataset/sample.d.ts +50 -0
  53. package/dist/meta/finetune/dataset/sample.js +197 -0
  54. package/dist/meta/finetune/dataset/schema.d.ts +33 -0
  55. package/dist/meta/finetune/dataset/schema.js +216 -0
  56. package/dist/meta/finetune/dataset/store.d.ts +35 -0
  57. package/dist/meta/finetune/dataset/store.js +177 -0
  58. package/dist/meta/finetune/dataset/types.d.ts +127 -0
  59. package/dist/meta/finetune/dataset/types.js +39 -0
  60. package/dist/meta/finetune/failure.d.ts +15 -0
  61. package/dist/meta/finetune/failure.js +26 -0
  62. package/dist/meta/finetune/feedback.d.ts +57 -0
  63. package/dist/meta/finetune/feedback.js +104 -0
  64. package/dist/meta/finetune/guard.d.ts +29 -0
  65. package/dist/meta/finetune/guard.js +104 -0
  66. package/dist/meta/finetune/improvements.d.ts +98 -0
  67. package/dist/meta/finetune/improvements.js +501 -0
  68. package/dist/meta/finetune/index.d.ts +2 -0
  69. package/dist/meta/finetune/index.js +2 -0
  70. package/dist/meta/finetune/journal.d.ts +35 -0
  71. package/dist/meta/finetune/journal.js +135 -0
  72. package/dist/meta/finetune/live.d.ts +84 -0
  73. package/dist/meta/finetune/live.js +272 -0
  74. package/dist/meta/finetune/loop.d.ts +3 -0
  75. package/dist/meta/finetune/loop.js +163 -0
  76. package/dist/meta/finetune/memories.d.ts +47 -0
  77. package/dist/meta/finetune/memories.js +154 -0
  78. package/dist/meta/finetune/recheck.d.ts +58 -0
  79. package/dist/meta/finetune/recheck.js +311 -0
  80. package/dist/meta/finetune/report.d.ts +40 -0
  81. package/dist/meta/finetune/report.js +1397 -0
  82. package/dist/meta/finetune/retry.d.ts +24 -0
  83. package/dist/meta/finetune/retry.js +96 -0
  84. package/dist/meta/finetune/sampler.d.ts +18 -0
  85. package/dist/meta/finetune/sampler.js +47 -0
  86. package/dist/meta/finetune/system.d.ts +27 -0
  87. package/dist/meta/finetune/system.js +132 -0
  88. package/dist/meta/finetune/train.d.ts +43 -0
  89. package/dist/meta/finetune/train.js +425 -0
  90. package/dist/meta/finetune/tui.d.ts +16 -0
  91. package/dist/meta/finetune/tui.js +316 -0
  92. package/dist/meta/finetune/tuning.d.ts +193 -0
  93. package/dist/meta/finetune/tuning.js +308 -0
  94. package/dist/meta/finetune/usage.d.ts +66 -0
  95. package/dist/meta/finetune/usage.js +260 -0
  96. package/dist/meta/finetune/view.d.ts +9 -0
  97. package/dist/meta/finetune/view.js +27 -0
  98. package/dist/meta/host.d.ts +15 -0
  99. package/dist/meta/host.js +21 -0
  100. package/dist/meta/index.d.ts +11 -0
  101. package/dist/meta/index.js +11 -0
  102. package/dist/meta/launch.d.ts +81 -0
  103. package/dist/meta/launch.js +286 -0
  104. package/dist/meta/model.d.ts +44 -0
  105. package/dist/meta/model.js +119 -0
  106. package/dist/meta/prompts.d.ts +18 -0
  107. package/dist/meta/prompts.js +61 -0
  108. package/dist/meta/relay.d.ts +20 -0
  109. package/dist/meta/relay.js +123 -0
  110. package/dist/meta/tokens.d.ts +52 -0
  111. package/dist/meta/tokens.js +226 -0
  112. package/dist/meta/window.d.ts +61 -0
  113. package/dist/meta/window.js +198 -0
  114. package/dist/meta/wire.d.ts +42 -0
  115. package/dist/meta/wire.js +129 -0
  116. package/dist/meta.d.ts +71 -1
  117. package/dist/meta.js +303 -6
  118. package/dist/modelref.d.ts +16 -0
  119. package/dist/modelref.js +41 -0
  120. package/dist/narrate.js +5 -2
  121. package/dist/podman.d.ts +26 -0
  122. package/dist/podman.js +83 -3
  123. package/dist/projects.d.ts +0 -5
  124. package/dist/projects.js +2 -15
  125. package/dist/resources.d.ts +215 -0
  126. package/dist/resources.js +856 -0
  127. package/dist/sandbox.d.ts +2 -0
  128. package/dist/sandbox.js +10 -3
  129. package/dist/scaffold.d.ts +15 -7
  130. package/dist/scaffold.js +24 -67
  131. package/dist/session.js +4 -29
  132. package/dist/term.d.ts +2 -0
  133. package/dist/term.js +11 -2
  134. package/dist/trace.d.ts +5 -0
  135. package/dist/trace.js +107 -7
  136. package/dist/tui/app.js +19 -5
  137. package/dist/tui/links.d.ts +27 -0
  138. package/dist/tui/links.js +142 -0
  139. package/dist/tui/models.d.ts +35 -0
  140. package/dist/tui/models.js +447 -0
  141. package/dist/tui/sandbox.d.ts +10 -0
  142. package/dist/tui/sandbox.js +131 -0
  143. package/dist/tui/wrap.d.ts +5 -0
  144. package/dist/tui/wrap.js +12 -0
  145. package/dist/usage.d.ts +78 -0
  146. package/dist/usage.js +51 -0
  147. package/package.json +4 -2
  148. package/templates/editor/.github/copilot-instructions.md +60 -145
  149. package/templates/editor/.github/prompts/analyze.prompt.md +33 -0
  150. package/templates/editor/.github/prompts/dataset.prompt.md +108 -0
  151. package/templates/editor/.github/prompts/finetune-apply.prompt.md +270 -0
  152. package/templates/editor/.github/prompts/finetune.prompt.md +691 -198
  153. package/templates/editor/.github/prompts/new-agent.prompt.md +3 -1
  154. package/templates/editor/.github/prompts/new-skill.prompt.md +2 -0
  155. package/templates/editor/.github/prompts/project-review.prompt.md +5 -4
  156. package/templates/editor/.github/skills/zen-analyze-run/SKILL.md +588 -0
  157. package/templates/editor/.github/skills/zen-cli/SKILL.md +9 -1
  158. package/templates/editor/.github/skills/zen-cli/references/dataset.md +34 -0
  159. package/templates/editor/.github/skills/zen-cli/references/finetune.md +137 -0
  160. package/templates/editor/.github/skills/zen-cli/references/inspect.md +63 -11
  161. package/templates/editor/.github/skills/zen-cli/references/memory.md +33 -10
  162. package/templates/editor/.github/skills/zen-cli/references/meta.md +61 -0
  163. package/templates/editor/.github/skills/zen-cli/references/models.md +5 -1
  164. package/templates/editor/.github/skills/zen-cli/references/projects.md +4 -6
  165. package/templates/editor/.github/skills/zen-cli/references/rag.md +24 -6
  166. package/templates/editor/.github/skills/zen-cli/references/sandbox.md +35 -14
  167. package/templates/editor/.github/skills/zen-code-python/SKILL.md +123 -0
  168. package/templates/editor/.github/skills/zen-code-python/references/code_python.md +48 -0
  169. package/templates/editor/.github/skills/zen-dataset/SKILL.md +253 -0
  170. package/templates/editor/.github/skills/zen-finetune/SKILL.md +1492 -660
  171. package/templates/editor/.github/skills/zen-finetune/references/readme-template.md +268 -0
  172. package/templates/editor/.github/skills/zen-finetune/scripts/batch.mjs +390 -0
  173. package/templates/editor/.github/skills/zen-finetune/scripts/difficult.mjs +243 -0
  174. package/templates/editor/.github/skills/zen-finetune/scripts/memory.mjs +280 -0
  175. package/templates/editor/.github/skills/zen-finetune/scripts/next.mjs +398 -0
  176. package/templates/editor/.github/skills/zen-finetune/scripts/report.mjs +797 -0
  177. package/templates/editor/.github/skills/zen-finetune/scripts/select.mjs +354 -0
  178. package/templates/editor/.github/skills/zen-finetune/scripts/usage.mjs +722 -0
  179. package/templates/editor/.github/skills/zen-inspect/SKILL.md +74 -26
  180. package/templates/editor/.github/skills/zen-inspect-ask/SKILL.md +271 -0
  181. package/templates/editor/.github/skills/zen-instructions/SKILL.md +4 -12
  182. package/templates/editor/.github/skills/zen-memory/SKILL.md +82 -53
  183. package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +20 -8
  184. package/templates/editor/.github/skills/zen-review/SKILL.md +8 -31
  185. package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +5 -5
  186. package/templates/editor/.github/skills/zen-review/scripts/review.sh +3 -12
  187. package/templates/editor/.github/skills/zen-sandbox/SKILL.md +9 -4
  188. package/templates/editor/.github/skills/zen-sandbox-capacity/SKILL.md +2 -2
  189. package/templates/editor/.github/skills/zen-spec-sync/SKILL.md +2 -4
  190. package/templates/editor/.github/skills/zen-topology/SKILL.md +283 -0
  191. package/templates/editor/.vscode/settings.json +1 -0
  192. package/templates/project/agents/files-instructions.md +29 -20
  193. package/templates/project/agents/fork-instructions.md +78 -28
  194. package/templates/project/agents/memory-instructions.md +222 -494
  195. package/templates/project/agents/tools-instructions.md +14 -4
  196. package/templates/project/gitignore +2 -0
  197. package/templates/editor/.github/skills/zen-finetune/scripts/collect.sh +0 -432
  198. package/templates/editor/.github/skills/zen-finetune/scripts/sample.sh +0 -199
  199. package/templates/editor/.github/skills/zen-memory/scripts/check-instructions.sh +0 -116
@@ -0,0 +1,21 @@
1
+ // ---------------------------------------------------------------------------
2
+ // The meta layer's one door into the rest of the CLI
3
+ //
4
+ // Everything under `src/meta/` imports the runtime through this file and no
5
+ // other, so the layer can leave for a package of its own by turning this into
6
+ // a dependency. `test/meta-boundary.test.ts` holds the line.
7
+ // ---------------------------------------------------------------------------
8
+ export { invokedAs, parse } from "../args.js";
9
+ export { paths, readJson, writeJson } from "../home.js";
10
+ export { envOf } from "../keys.js";
11
+ export { PROVIDER_NAMES, splitRef } from "../modelref.js";
12
+ export { duration } from "../narrate.js";
13
+ export { copyTree, SHARED_RULES, TEMPLATES } from "../scaffold.js";
14
+ export { bold, CliError, confirm, credentialError, cut, cyan, dim, EXIT, green, invalidError, json, note, pad, plain, red, styled, table, usageError, write, writeAll, yellow, } from "../term.js";
15
+ export { safe } from "../trace.js";
16
+ export { formatInline, formatMarkdown } from "../tui/markdown.js";
17
+ export { resolveTheme } from "../tui/theme.js";
18
+ export { boxWidth } from "../tui/wrap.js";
19
+ export { META_PROMPT_ENV, META_SESSION_ENV } from "../usage.js";
20
+ export { validateProject } from "../validate.js";
21
+ //# sourceMappingURL=host.js.map
@@ -0,0 +1,11 @@
1
+ export * from './editor.ts';
2
+ export * from './finetune/dataset/index.ts';
3
+ export * from './finetune/index.ts';
4
+ export * from './launch.ts';
5
+ export * from './model.ts';
6
+ export * from './prompts.ts';
7
+ export * from './relay.ts';
8
+ export * from './tokens.ts';
9
+ export * from './window.ts';
10
+ export * from './wire.ts';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,11 @@
1
+ export * from "./editor.js";
2
+ export * from "./finetune/dataset/index.js";
3
+ export * from "./finetune/index.js";
4
+ export * from "./launch.js";
5
+ export * from "./model.js";
6
+ export * from "./prompts.js";
7
+ export * from "./relay.js";
8
+ export * from "./tokens.js";
9
+ export * from "./window.js";
10
+ export * from "./wire.js";
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,81 @@
1
+ import { spawn as nodeSpawn } from 'node:child_process';
2
+ import { type Sink } from './window.ts';
3
+ export interface Binary {
4
+ command: string;
5
+ args: string[];
6
+ from: 'path' | 'npx';
7
+ }
8
+ /**
9
+ * A `copilot` already installed is used as it is; otherwise `npx` fetches one.
10
+ * Note that finding the name on `PATH` is not the same as finding the CLI —
11
+ * an editor may have put a shim there that installs it on first run — so this
12
+ * only decides how to launch, never whether it will work.
13
+ */
14
+ export declare function locate(has?: (cmd: string) => boolean): Binary;
15
+ interface Event {
16
+ type: string;
17
+ data?: Record<string, unknown>;
18
+ [key: string]: unknown;
19
+ }
20
+ export interface Outcome {
21
+ /** the last thing the agent said that was not a preamble to a tool call */
22
+ answer: string;
23
+ exitCode: number;
24
+ sessionId?: string;
25
+ usage?: Record<string, unknown>;
26
+ events: Event[];
27
+ }
28
+ export interface Log {
29
+ readonly path: string;
30
+ /** Where the answer alone goes, beside the log and openable on its own. */
31
+ readonly answerPath: string;
32
+ line(text: string): void;
33
+ saveAnswer(text: string): void;
34
+ close(): void;
35
+ }
36
+ /**
37
+ * `<project>/.tmp/logs/meta.<when>[-<session>].log`, opened for append. The
38
+ * session keeps runs started in the same second - a tuning loop's - apart.
39
+ */
40
+ export declare function openLog(dir: string, now?: Date, session?: string): Log;
41
+ export declare function recordSession(dir: string, id: string): void;
42
+ export declare function lastSession(dir: string): string | undefined;
43
+ /**
44
+ * Folds one event into the outcome and says what, if anything, to show for it.
45
+ *
46
+ * `assistant.message` carries both the running commentary and the final word;
47
+ * what separates them is `toolRequests`, so the last message that asked for no
48
+ * tool is the answer and the rest are narration.
49
+ */
50
+ export declare function absorb(event: Event, out: Outcome, sink: Sink, width: number): void;
51
+ export interface Launch {
52
+ binary: Binary;
53
+ args: string[];
54
+ env: Record<string, string>;
55
+ cwd: string;
56
+ sink?: Sink;
57
+ log?: Log;
58
+ width?: number;
59
+ /** said after the elapsed time on the status row, e.g. the tokens so far */
60
+ status?: () => string;
61
+ /** every event copilot prints, before it is folded */
62
+ onEvent?: (event: Event) => void;
63
+ /** injected by the tests, which have no copilot and want none */
64
+ spawn?: typeof nodeSpawn;
65
+ }
66
+ export declare function launch(opts: Launch): Promise<Outcome>;
67
+ /** Resumes after copilot's own retries run out; `--retries` overrides. */
68
+ export declare const DEFAULT_RESUMES = 5;
69
+ /** What the resumed session is told, since `-p` has to say something. */
70
+ export declare const RESUME_PROMPT = "The previous model call failed. Continue the task from where you stopped.";
71
+ export declare function resumeDelayMs(attempt: number): number;
72
+ /**
73
+ * Why the run is worth resuming, or undefined when waiting cannot help.
74
+ *
75
+ * Only a refusal that time can cure counts: a rate limit, the provider's own
76
+ * failure, a connection that timed out. A 400 or a bad key fails the same way
77
+ * however often it is sent - except the one below.
78
+ */
79
+ export declare function transient(out: Outcome): string | undefined;
80
+ export {};
81
+ //# sourceMappingURL=launch.d.ts.map
@@ -0,0 +1,286 @@
1
+ import { spawn as nodeSpawn } from 'node:child_process';
2
+ import { createWriteStream, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { createInterface } from 'node:readline';
4
+ import { cut, dim, duration, formatInline, plain } from "./host.js";
5
+ import { FRAME_MS, windowSink } from "./window.js";
6
+ /**
7
+ * A `copilot` already installed is used as it is; otherwise `npx` fetches one.
8
+ * Note that finding the name on `PATH` is not the same as finding the CLI —
9
+ * an editor may have put a shim there that installs it on first run — so this
10
+ * only decides how to launch, never whether it will work.
11
+ */
12
+ export function locate(has = (cmd) => onPath(cmd)) {
13
+ return has('copilot')
14
+ ? { command: 'copilot', args: [], from: 'path' }
15
+ : { command: 'npx', args: ['--yes', '@github/copilot'], from: 'npx' };
16
+ }
17
+ function onPath(cmd) {
18
+ const dirs = (process.env.PATH ?? '').split(':').filter(Boolean);
19
+ return dirs.some((d) => existsSync(`${d}/${cmd}`));
20
+ }
21
+ /**
22
+ * `<project>/.tmp/logs/meta.<when>[-<session>].log`, opened for append. The
23
+ * session keeps runs started in the same second - a tuning loop's - apart.
24
+ */
25
+ export function openLog(dir, now = new Date(), session) {
26
+ const two = (n) => String(n).padStart(2, '0');
27
+ const stamp = `${now.getFullYear()}${two(now.getMonth() + 1)}${two(now.getDate())}` +
28
+ `${two(now.getHours())}${two(now.getMinutes())}${two(now.getSeconds())}` +
29
+ (session ? `-${session.slice(0, 8)}` : '');
30
+ const folder = `${dir}/.tmp/logs`;
31
+ mkdirSync(folder, { recursive: true });
32
+ const path = `${folder}/meta.${stamp}.log`;
33
+ const answerPath = `${folder}/meta.${stamp}.md`;
34
+ const file = createWriteStream(path, { flags: 'a' });
35
+ return {
36
+ path,
37
+ answerPath,
38
+ line: (text) => {
39
+ file.write(`${plain(text)}\n`);
40
+ },
41
+ saveAnswer: (text) => {
42
+ writeFileSync(answerPath, text.endsWith('\n') ? text : `${text}\n`);
43
+ },
44
+ close: () => {
45
+ file.end();
46
+ },
47
+ };
48
+ }
49
+ /** The project's last meta session, so `zen meta resume` needs no id. */
50
+ function sessionFile(dir) {
51
+ return `${dir}/.tmp/logs/meta.session`;
52
+ }
53
+ export function recordSession(dir, id) {
54
+ mkdirSync(`${dir}/.tmp/logs`, { recursive: true });
55
+ writeFileSync(sessionFile(dir), `${id}\n`);
56
+ }
57
+ export function lastSession(dir) {
58
+ try {
59
+ return readFileSync(sessionFile(dir), 'utf8').trim() || undefined;
60
+ }
61
+ catch {
62
+ return undefined;
63
+ }
64
+ }
65
+ /** Everything the sink is told, kept by the log too — and the detail only there. */
66
+ function tee(sink, log) {
67
+ return {
68
+ answer: (text) => sink.answer(text),
69
+ narrate: (line) => {
70
+ log.line(line);
71
+ sink.narrate(line);
72
+ },
73
+ detail: (line) => log.line(line),
74
+ warn: (line) => {
75
+ log.line(line);
76
+ sink.warn(line);
77
+ },
78
+ status: (line) => sink.status?.(line),
79
+ close: () => sink.close?.(),
80
+ };
81
+ }
82
+ /**
83
+ * Folds one event into the outcome and says what, if anything, to show for it.
84
+ *
85
+ * `assistant.message` carries both the running commentary and the final word;
86
+ * what separates them is `toolRequests`, so the last message that asked for no
87
+ * tool is the answer and the rest are narration.
88
+ */
89
+ export function absorb(event, out, sink, width) {
90
+ const data = (event.data ?? {});
91
+ switch (event.type) {
92
+ case 'assistant.message': {
93
+ const content = String(data.content ?? '').trim();
94
+ const requests = Array.isArray(data.toolRequests) ? data.toolRequests : [];
95
+ if (requests.length === 0) {
96
+ out.answer = content;
97
+ }
98
+ else if (content) {
99
+ sink.narrate(cut(formatInline(content.replace(/\s+/g, ' ')), width));
100
+ }
101
+ break;
102
+ }
103
+ case 'tool.execution_start': {
104
+ const args = (data.arguments ?? {});
105
+ const what = String(args.command ?? args.description ?? data.toolName ?? '').trim();
106
+ // Step-by-step belongs in the log: on screen it is a wall of `$`
107
+ // saying less than the one line of commentary above it.
108
+ sink.detail?.(`$ ${what.replace(/\s+/g, ' ')}`);
109
+ break;
110
+ }
111
+ case 'tool.execution_complete': {
112
+ if (data.success === false) {
113
+ sink.detail?.(` ${String(data.toolName ?? 'the tool')} failed`);
114
+ }
115
+ break;
116
+ }
117
+ case 'session.tools_updated': {
118
+ if (data.model) {
119
+ sink.detail?.(`model ${String(data.model)}`);
120
+ }
121
+ break;
122
+ }
123
+ case 'session.error': {
124
+ sink.warn(String(data.message ?? 'the session failed'));
125
+ break;
126
+ }
127
+ case 'model.call_failure': {
128
+ const status = data.statusCode ? ` (${String(data.statusCode)})` : '';
129
+ sink.warn(`${cut(String(data.errorMessage ?? 'the model call failed'), width)}${status}`);
130
+ break;
131
+ }
132
+ case 'result': {
133
+ out.exitCode = Number(event.exitCode ?? 0);
134
+ out.sessionId = event.sessionId;
135
+ out.usage = event.usage;
136
+ break;
137
+ }
138
+ default:
139
+ break;
140
+ }
141
+ out.events.push(event);
142
+ }
143
+ export async function launch(opts) {
144
+ const shown = opts.sink ?? windowSink();
145
+ const sink = opts.log ? tee(shown, opts.log) : shown;
146
+ const width = opts.width ?? Math.max(40, (process.stderr.columns ?? 100) - 4);
147
+ const start = opts.spawn ?? nodeSpawn;
148
+ const out = { answer: '', exitCode: 0, events: [] };
149
+ const child = start(opts.binary.command, [...opts.binary.args, ...opts.args], {
150
+ cwd: opts.cwd,
151
+ env: { ...process.env, ...opts.env },
152
+ stdio: ['ignore', 'pipe', 'pipe'],
153
+ shell: false,
154
+ });
155
+ const stop = (signal) => () => {
156
+ child.kill(signal);
157
+ };
158
+ const onInt = stop('SIGINT');
159
+ const onTerm = stop('SIGTERM');
160
+ process.on('SIGINT', onInt);
161
+ process.on('SIGTERM', onTerm);
162
+ // The first model call says nothing for as long as it takes, and silence
163
+ // after the model line is indistinguishable from a hang.
164
+ const began = Date.now();
165
+ const beat = setInterval(() => {
166
+ const extra = opts.status?.();
167
+ sink.status?.(`working: ${duration(Date.now() - began)}${extra ? ` · ${extra}` : ''}`);
168
+ }, FRAME_MS);
169
+ beat.unref();
170
+ child.stderr?.on('data', (chunk) => {
171
+ const text = chunk.toString().trimEnd();
172
+ if (text) {
173
+ sink.narrate(dim(text));
174
+ }
175
+ });
176
+ try {
177
+ const lines = createInterface({ input: child.stdout, crlfDelay: Infinity });
178
+ for await (const line of lines) {
179
+ if (!line.trim()) {
180
+ continue;
181
+ }
182
+ let event;
183
+ try {
184
+ event = JSON.parse(line);
185
+ }
186
+ catch {
187
+ // Not ours to interpret, but losing it is worse than showing it.
188
+ sink.narrate(line);
189
+ continue;
190
+ }
191
+ opts.onEvent?.(event);
192
+ absorb(event, out, sink, width);
193
+ }
194
+ const code = await new Promise((resolve) => {
195
+ child.on('close', (value, signal) => resolve(value ?? (signal ? 1 : 0)));
196
+ });
197
+ if (out.exitCode === 0 && code !== 0) {
198
+ out.exitCode = code;
199
+ }
200
+ }
201
+ finally {
202
+ clearInterval(beat);
203
+ process.off('SIGINT', onInt);
204
+ process.off('SIGTERM', onTerm);
205
+ // The answer is written by the caller, so the window has to be gone first.
206
+ sink.close?.();
207
+ }
208
+ return out;
209
+ }
210
+ // ---------------------------------------------------------------------------
211
+ // Resuming
212
+ //
213
+ // Copilot retries a failed model call itself, five times over about a minute,
214
+ // and has no setting to change that. A run that outlasts it is still a session
215
+ // on disk, so the retry happens one level up: wait, then resume that session.
216
+ // ---------------------------------------------------------------------------
217
+ /** Resumes after copilot's own retries run out; `--retries` overrides. */
218
+ export const DEFAULT_RESUMES = 5;
219
+ /** The first wait; it doubles per resume, up to `RESUME_MAX_MS`. */
220
+ const RESUME_INITIAL_MS = 30_000;
221
+ const RESUME_MAX_MS = 300_000;
222
+ /** What the resumed session is told, since `-p` has to say something. */
223
+ export const RESUME_PROMPT = 'The previous model call failed. Continue the task from where you stopped.';
224
+ export function resumeDelayMs(attempt) {
225
+ return Math.min(RESUME_INITIAL_MS * 2 ** attempt, RESUME_MAX_MS);
226
+ }
227
+ /**
228
+ * Why the run is worth resuming, or undefined when waiting cannot help.
229
+ *
230
+ * Only a refusal that time can cure counts: a rate limit, the provider's own
231
+ * failure, a connection that timed out. A 400 or a bad key fails the same way
232
+ * however often it is sent - except the one below.
233
+ */
234
+ export function transient(out) {
235
+ if (out.exitCode === 0 || !out.sessionId) {
236
+ return undefined;
237
+ }
238
+ const at = out.events.findLastIndex((e) => e.type === 'session.error');
239
+ const data = out.events[at]?.data;
240
+ if (!data) {
241
+ return undefined;
242
+ }
243
+ const status = Number(data.statusCode ?? 0);
244
+ if (data.errorType === 'rate_limit' || status === 429) {
245
+ return 'rate-limited (429)';
246
+ }
247
+ if (status === 408 || status >= 500) {
248
+ return `the provider failed (${status})`;
249
+ }
250
+ if (/timed out|ECONNRESET|socket hang up/i.test(String(data.message ?? ''))) {
251
+ return 'the connection failed';
252
+ }
253
+ if (status === 400 && injected(out.events.slice(0, at))) {
254
+ return 'refused a tool call copilot made itself (400)';
255
+ }
256
+ return undefined;
257
+ }
258
+ /**
259
+ * Whether the turn that failed began with a tool call copilot wrote, not the
260
+ * model. When a background command finishes, copilot adds a `read_bash` of its
261
+ * own; Gemini 3 rejects any tool call in the current turn that lacks its thought
262
+ * signature, so that request is a 400 every time. A new user message - the
263
+ * resume - starts a turn that no longer holds it.
264
+ */
265
+ function injected(before) {
266
+ for (let i = before.length - 1; i >= 0; i--) {
267
+ const event = before[i];
268
+ if (event.type === 'system.notification') {
269
+ return true;
270
+ }
271
+ if (event.type === 'user.message') {
272
+ return false;
273
+ }
274
+ if (event.type === 'assistant.message') {
275
+ if (event.data?.model) {
276
+ return false;
277
+ }
278
+ const requests = event.data?.toolRequests;
279
+ if (Array.isArray(requests) && requests.length > 0) {
280
+ return true;
281
+ }
282
+ }
283
+ }
284
+ return false;
285
+ }
286
+ //# sourceMappingURL=launch.js.map
@@ -0,0 +1,44 @@
1
+ import { type KeyStore, type Provider } from './host.ts';
2
+ /** The variable a project's `.env` names it with — a zen ref, not a wire id. */
3
+ export declare const MODEL_ENV = "ZENERA_META_MODEL";
4
+ export type ModelSource = 'flag' | 'shell' | 'env' | 'store' | 'project' | 'default';
5
+ export interface ModelChoice {
6
+ /** a zen ref: `vertex/gemini-3.8-flash`, or a bare id for the default provider */
7
+ ref: string;
8
+ from: ModelSource;
9
+ }
10
+ export interface ModelSources {
11
+ flag?: string;
12
+ /** `ZENERA_META_MODEL` as it stood before the project's `.env` was folded in */
13
+ shell?: string;
14
+ /** the same variable after it was, which is the `.env` answer when they differ */
15
+ env?: string;
16
+ stored?: string;
17
+ /** the project's `agents.yaml` `model:` */
18
+ project?: string;
19
+ /** what a held key recommends, when nothing above answered */
20
+ fallback?: string;
21
+ }
22
+ /** Where the answer is looked for, best first. */
23
+ export declare const ORDER: readonly ModelSource[];
24
+ export declare function chooseModel(sources: ModelSources): ModelChoice | undefined;
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. */
27
+ export declare const RECOMMENDED: Partial<Record<Provider, readonly string[]>>;
28
+ /** The recommended ref for the first provider with a key, or for `only`. */
29
+ export declare function defaultRef(store: KeyStore, only?: Provider): string | undefined;
30
+ export interface MetaFile {
31
+ version: 1;
32
+ model?: string;
33
+ }
34
+ export declare const readMeta: () => Promise<MetaFile>;
35
+ export declare function writeMeta(file: MetaFile): void;
36
+ /**
37
+ * The provider a misspelt prefix was reaching for, if that is what it is.
38
+ *
39
+ * `vertes/gemini-3.8-flash` is shaped exactly like the OpenRouter id
40
+ * `meta-llama/llama-4`, so an unknown prefix cannot be an error on its own —
41
+ * only one close enough to a real provider name to be a slip of the hand.
42
+ */
43
+ export declare function misspelledProvider(ref: string): Provider | undefined;
44
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1,119 @@
1
+ import { paths, PROVIDER_NAMES, readJson, writeJson, } from "./host.js";
2
+ // ---------------------------------------------------------------------------
3
+ // The model
4
+ // ---------------------------------------------------------------------------
5
+ /** The variable a project's `.env` names it with — a zen ref, not a wire id. */
6
+ export const MODEL_ENV = 'ZENERA_META_MODEL';
7
+ /** Where the answer is looked for, best first. */
8
+ export const ORDER = [
9
+ 'flag',
10
+ 'shell',
11
+ 'env',
12
+ 'store',
13
+ 'project',
14
+ 'default',
15
+ ];
16
+ export function chooseModel(sources) {
17
+ const at = {
18
+ flag: sources.flag,
19
+ // `.env` only fills gaps, so an answer that survived it came from the shell
20
+ shell: sources.shell,
21
+ env: sources.shell ? undefined : sources.env,
22
+ store: sources.stored,
23
+ project: sources.project,
24
+ default: sources.fallback,
25
+ };
26
+ for (const from of ORDER) {
27
+ const ref = at[from]?.trim();
28
+ if (ref) {
29
+ return { ref, from };
30
+ }
31
+ }
32
+ return undefined;
33
+ }
34
+ export const SOURCE_LABELS = {
35
+ flag: '--model',
36
+ shell: `${MODEL_ENV} (shell)`,
37
+ env: `${MODEL_ENV} (.env)`,
38
+ store: 'zen meta model',
39
+ project: 'agents.yaml model:',
40
+ default: 'recommended',
41
+ };
42
+ // ---------------------------------------------------------------------------
43
+ // What to run when nobody said
44
+ //
45
+ // A first `zen meta` should work on a machine that has a key and nothing else,
46
+ // so the last resort is the best model the keys on hand can buy rather than an
47
+ // error. Deep tier deliberately: this agent is pointed at the project itself,
48
+ // and a cheap model reading a specification is a false economy.
49
+ // ---------------------------------------------------------------------------
50
+ /** First of each row is the default; the rest are what `--pick` offers beside it. */
51
+ export const RECOMMENDED = {
52
+ openai: ['gpt-5.6-sol', 'gpt-5.6-terra'],
53
+ anthropic: ['claude-opus-5', 'claude-sonnet-5'],
54
+ vertex: ['gemini-3.8-flash', 'gemini-3.5-flash-lite'],
55
+ google: ['gemini-3.8-flash', 'gemini-3.5-flash-lite'],
56
+ openrouter: ['anthropic/claude-opus-5', 'openai/gpt-5.6-sol'],
57
+ };
58
+ /** Which provider is tried first when several are held. */
59
+ const PREFERENCE = ['anthropic', 'openai', 'vertex', 'google', 'openrouter'];
60
+ /** The recommended ref for the first provider with a key, or for `only`. */
61
+ export function defaultRef(store, only) {
62
+ for (const provider of only ? [only] : PREFERENCE) {
63
+ const id = RECOMMENDED[provider]?.[0];
64
+ if (id && store.active(provider)) {
65
+ return `${provider}/${id}`;
66
+ }
67
+ }
68
+ return undefined;
69
+ }
70
+ const EMPTY = { version: 1 };
71
+ export const readMeta = () => readJson(paths.meta(), EMPTY);
72
+ export function writeMeta(file) {
73
+ writeJson(paths.meta(), file);
74
+ }
75
+ // ---------------------------------------------------------------------------
76
+ // Refs
77
+ // ---------------------------------------------------------------------------
78
+ /**
79
+ * The provider a misspelt prefix was reaching for, if that is what it is.
80
+ *
81
+ * `vertes/gemini-3.8-flash` is shaped exactly like the OpenRouter id
82
+ * `meta-llama/llama-4`, so an unknown prefix cannot be an error on its own —
83
+ * only one close enough to a real provider name to be a slip of the hand.
84
+ */
85
+ export function misspelledProvider(ref) {
86
+ const slash = ref.indexOf('/');
87
+ if (slash <= 0) {
88
+ return undefined;
89
+ }
90
+ const head = ref.slice(0, slash).toLowerCase();
91
+ if (PROVIDER_NAMES.has(head)) {
92
+ return undefined;
93
+ }
94
+ for (const name of PROVIDER_NAMES) {
95
+ // Two, so that a transposition — which Levenshtein counts twice — reads
96
+ // as the typo it is. No provider name is short enough for that to be loose.
97
+ if (distance(head, name) <= 2) {
98
+ return name;
99
+ }
100
+ }
101
+ return undefined;
102
+ }
103
+ /** Levenshtein, one row at a time. Both operands are a word long. */
104
+ function distance(a, b) {
105
+ if (Math.abs(a.length - b.length) > 2) {
106
+ return 3;
107
+ }
108
+ let row = Array.from({ length: b.length + 1 }, (_, i) => i);
109
+ for (let i = 1; i <= a.length; i++) {
110
+ const next = [i];
111
+ for (let j = 1; j <= b.length; j++) {
112
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
113
+ next[j] = Math.min(next[j - 1] + 1, row[j] + 1, row[j - 1] + cost);
114
+ }
115
+ row = next;
116
+ }
117
+ return row[b.length];
118
+ }
119
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1,18 @@
1
+ export declare const PROMPT_DIR = ".github/prompts";
2
+ /** `/project-review`, `project-review`, `project-review.prompt.md` or a path. */
3
+ export declare function promptPath(dir: string, name: string): string;
4
+ export interface StoredPrompt {
5
+ name: string;
6
+ path: string;
7
+ description?: string;
8
+ body: string;
9
+ }
10
+ /**
11
+ * Frontmatter is the editor's business — `mode`, `tools`, `description` — and
12
+ * none of it means anything to copilot, so only the body is sent. `description`
13
+ * is kept for the one line of narration that says which prompt is running.
14
+ */
15
+ export declare function readPrompt(path: string, name: string, text: string): StoredPrompt;
16
+ export declare function loadPrompt(dir: string, name: string): Promise<StoredPrompt>;
17
+ export declare function listPrompts(dir: string): Promise<string[]>;
18
+ //# sourceMappingURL=prompts.d.ts.map
@@ -0,0 +1,61 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { usageError } from "./host.js";
4
+ // ---------------------------------------------------------------------------
5
+ // Stored prompts
6
+ //
7
+ // Copilot reads `AGENTS.md`, `.github/skills/` and `.github/agents/`, but it
8
+ // has no notion of `.github/prompts/*.prompt.md` — the files an editor offers
9
+ // as slash commands. Reading one and handing over its body is the whole of
10
+ // `zen meta run`, and it is why that subcommand exists.
11
+ // ---------------------------------------------------------------------------
12
+ export const PROMPT_DIR = '.github/prompts';
13
+ const PROMPT_SUFFIX = '.prompt.md';
14
+ /** `/project-review`, `project-review`, `project-review.prompt.md` or a path. */
15
+ export function promptPath(dir, name) {
16
+ const bare = name.replace(/^\//, '');
17
+ if (bare.includes('/') || bare.endsWith('.md')) {
18
+ return `${dir}/${bare}`;
19
+ }
20
+ return `${dir}/${PROMPT_DIR}/${bare}${PROMPT_SUFFIX}`;
21
+ }
22
+ /**
23
+ * Frontmatter is the editor's business — `mode`, `tools`, `description` — and
24
+ * none of it means anything to copilot, so only the body is sent. `description`
25
+ * is kept for the one line of narration that says which prompt is running.
26
+ */
27
+ export function readPrompt(path, name, text) {
28
+ let body = text;
29
+ let description;
30
+ const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text);
31
+ if (match) {
32
+ body = text.slice(match[0].length);
33
+ const found = /^description:\s*(.+)$/m.exec(match[1]);
34
+ description = found?.[1].trim().replace(/^['"]|['"]$/g, '');
35
+ }
36
+ return { name, path, description, body: body.trim() };
37
+ }
38
+ export async function loadPrompt(dir, name) {
39
+ const path = promptPath(dir, name);
40
+ if (!existsSync(path)) {
41
+ const known = await listPrompts(dir);
42
+ throw usageError(`no prompt named ${name.replace(/^\//, '')}`, known.length > 0
43
+ ? `try: ${known.map((p) => `/${p}`).join(', ')}`
44
+ : `put one in ${PROMPT_DIR}/`);
45
+ }
46
+ return readPrompt(path, name.replace(/^\//, ''), await readFile(path, 'utf8'));
47
+ }
48
+ export async function listPrompts(dir) {
49
+ const { readdir } = await import('node:fs/promises');
50
+ try {
51
+ const names = await readdir(`${dir}/${PROMPT_DIR}`);
52
+ return names
53
+ .filter((n) => n.endsWith(PROMPT_SUFFIX))
54
+ .map((n) => n.slice(0, -PROMPT_SUFFIX.length))
55
+ .sort();
56
+ }
57
+ catch {
58
+ return [];
59
+ }
60
+ }
61
+ //# sourceMappingURL=prompts.js.map
@@ -0,0 +1,20 @@
1
+ export interface Relay {
2
+ url: string;
3
+ close(): Promise<void>;
4
+ }
5
+ export interface RelaySpec {
6
+ /** the per-session secret copilot must present; anything else on the machine is refused */
7
+ key: string;
8
+ /** `https://…/v1/projects/<project>/locations/<location>` */
9
+ base: string;
10
+ token: () => Promise<string>;
11
+ }
12
+ export interface VertexRelaySpec {
13
+ key: string;
14
+ project: string;
15
+ location: string;
16
+ keyFile: string;
17
+ }
18
+ export declare function startVertexRelay(spec: VertexRelaySpec): Promise<Relay>;
19
+ export declare function startRelay(spec: RelaySpec): Promise<Relay>;
20
+ //# sourceMappingURL=relay.d.ts.map