@sleepy_panda_srl/portulan 0.1.3 → 0.3.0

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 (89) hide show
  1. package/README.md +7 -6
  2. package/cli/README.md +145 -358
  3. package/cli/advisory.mjs +354 -0
  4. package/cli/comments.mjs +932 -0
  5. package/cli/compile.mjs +1719 -1699
  6. package/cli/context.mjs +883 -0
  7. package/cli/control-chars.mjs +12 -301
  8. package/cli/discover.mjs +17 -574
  9. package/cli/doctor.mjs +269 -1080
  10. package/cli/drills.mjs +92 -504
  11. package/cli/feedback.mjs +16 -237
  12. package/cli/finish.mjs +437 -0
  13. package/cli/form.mjs +644 -0
  14. package/cli/fuzz-shell.mjs +20 -305
  15. package/cli/gate.mjs +7 -199
  16. package/cli/goldens.mjs +26 -184
  17. package/cli/index.mjs +305 -655
  18. package/cli/init.mjs +243 -566
  19. package/cli/inside.mjs +3 -28
  20. package/cli/instructions.mjs +829 -0
  21. package/cli/ledger.mjs +882 -0
  22. package/cli/librarian.mjs +135 -599
  23. package/cli/manifest.mjs +2 -39
  24. package/cli/mutants.mjs +9 -170
  25. package/cli/new.mjs +12 -243
  26. package/cli/pack-identity.mjs +5 -74
  27. package/cli/pack-version.mjs +13 -255
  28. package/cli/plugin-lint.mjs +42 -658
  29. package/cli/portulan.mjs +6 -125
  30. package/cli/recipe-set.mjs +15 -245
  31. package/cli/release-eval.mjs +28 -348
  32. package/cli/review-meter.mjs +29 -265
  33. package/cli/rule-carriers.mjs +12 -175
  34. package/cli/sessions.mjs +82 -0
  35. package/cli/skill-goldens.mjs +10 -176
  36. package/cli/skills-set.mjs +12 -414
  37. package/cli/stop-gate.mjs +83 -514
  38. package/cli/symbols.mjs +1132 -0
  39. package/cli/telemetry.mjs +82 -505
  40. package/cli/upgrade.mjs +171 -314
  41. package/cli/vendor.mjs +146 -476
  42. package/cli/version-carriers.mjs +8 -86
  43. package/core/README.md +1 -1
  44. package/core/engine.md +3 -9
  45. package/core/operating/autonomy.md +1 -1
  46. package/core/operating/context.md +258 -0
  47. package/core/operating/evolution.md +3 -3
  48. package/core/operating/loop.md +13 -20
  49. package/core/operating/memory.md +12 -3
  50. package/core/operating/sessions.md +100 -0
  51. package/core/personas/README.md +33 -0
  52. package/core/personas/librarian.md +2 -2
  53. package/core/skills/clarify/SKILL.md +1 -1
  54. package/core/skills/consolidate/SKILL.md +5 -3
  55. package/core/templates/handoff.md +28 -15
  56. package/core/templates/memory-entry.md +1 -0
  57. package/evals/README.md +271 -845
  58. package/evals/releases/0.2.0.json +146 -0
  59. package/evals/releases/0.2.0.md +67 -0
  60. package/evals/releases/0.3.0.json +150 -0
  61. package/evals/releases/0.3.0.md +68 -0
  62. package/evals/releases/README.md +3 -3
  63. package/package.json +5 -2
  64. package/packs/.claude-plugin/plugin.json +2 -1
  65. package/packs/README.md +7 -4
  66. package/packs/rituals/checkpoints/README.md +1 -1
  67. package/packs/tools/README.md +8 -8
  68. package/packs/tools/github/verify/actions-pinned.sh +10 -55
  69. package/packs/tools/local-llm/README.md +136 -0
  70. package/packs/tools/local-llm/pack.json +32 -0
  71. package/packs/tools/local-llm/personas/local-worker.md +44 -0
  72. package/packs/tools/local-llm/runner/local-llm.mjs +1665 -0
  73. package/packs/tools/local-llm/skills/delegate-local/SKILL.md +64 -0
  74. package/packs/tools/local-llm/verify/README.md +18 -0
  75. package/spec/README.md +113 -6
  76. package/spec/migrations/0001-repository-declares-its-tree.mjs +1 -26
  77. package/spec/migrations/0002-bundle-fallback-path.mjs +13 -74
  78. package/spec/migrations/0003-handoff-index-not-kept.mjs +66 -0
  79. package/spec/migrations/0004-changelog-fragments.mjs +53 -0
  80. package/spec/migrations/0005-session-log-retired.mjs +69 -0
  81. package/spec/migrations/0006-boot-card.mjs +77 -0
  82. package/spec/migrations/0007-guidance-compiled.mjs +31 -0
  83. package/spec/migrations/0008-card-reading.mjs +55 -0
  84. package/spec/migrations/0009-instruction-sections-on-read.mjs +60 -0
  85. package/spec/migrations/0010-comment-rail.mjs +57 -0
  86. package/spec/migrations/README.md +45 -10
  87. package/spec/pack.schema.json +1 -1
  88. package/spec/slots.md +343 -27
  89. package/spec/workspace.schema.json +145 -4
@@ -0,0 +1,354 @@
1
+ #!/usr/bin/env node
2
+ // The restart advisory — one line, once, where the agent is, and the same figure for the human.
3
+ //
4
+ // node cli/advisory.mjs tool [<figures>] a PostToolUse hook: the line, as additionalContext, once, mid-stretch
5
+ // node cli/advisory.mjs prompt [<figures>] a UserPromptSubmit hook: the same line, at the next prompt, if not said yet
6
+ // node cli/advisory.mjs status [<figures>] a status-line command: the figure and the request count, on every refresh
7
+ // node cli/advisory.mjs stop [<figures>] a Stop hook, where a workspace declares it: the same line as a block, once
8
+ //
9
+ // <figures> --read <m> --write-5m <m> --write-1h <m>, all three or none, and --horizon <n>: a workspace's
10
+ // `spend`, as `./compile.mjs` writes it; undeclared, the general multipliers and horizon.
11
+ //
12
+ // Exit 0 on every path: a 2 would erase the person's prompt at `UserPromptSubmit` and hold every turn's end at
13
+ // `Stop`. What it cannot read or use, it passes over and names on stderr.
14
+ //
15
+ // The line rides a tool result or a prompt: a non-blocking `Stop` hook's output never reaches the model, and a
16
+ // headless run's one prompt comes before any request. The transcript is written asynchronously, so the line may
17
+ // come one request late, never early.
18
+
19
+ import fs from "node:fs";
20
+ import os from "node:os";
21
+ import path from "node:path";
22
+ import { pathToFileURL } from "node:url";
23
+
24
+ import { HORIZON, LedgerError, SPEND_FIGURES, figureOf, foldFigures, overflowingWrite, readLine, sessionFigures } from "./ledger.mjs";
25
+
26
+ const grouped = (n) => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
27
+
28
+ const STATE_VERSION = 2;
29
+
30
+ const CHUNK = 1 << 16;
31
+
32
+ /** The bytes before the offset that recognise the transcript on the next call, kept only as a digest: they can be what the session said. */
33
+ const TAIL = 64;
34
+
35
+ export function compact(n) {
36
+ if (n < 1000) return String(n);
37
+ if (n < 1_000_000) return `${Math.round(n / 1000)}k`;
38
+ return `${(n / 1_000_000).toFixed(2)}M`;
39
+ }
40
+
41
+ function digest(value) {
42
+ let h = 0;
43
+ for (const ch of String(value)) h = (h * 31 + ch.charCodeAt(0)) >>> 0;
44
+ return h.toString(36);
45
+ }
46
+
47
+ function stem(sessionId) {
48
+ return `portulan-advisory-${String(sessionId).replace(/[^a-zA-Z0-9-]/g, "").slice(0, 40)}-${digest(sessionId)}`;
49
+ }
50
+
51
+ export function toldFile(sessionId, compactions, dir = os.tmpdir()) {
52
+ return path.join(dir, `${stem(sessionId)}-${compactions}`);
53
+ }
54
+
55
+ export function heldFile(sessionId, compactions, dir = os.tmpdir()) {
56
+ return path.join(dir, `${stem(sessionId)}-${compactions}-held`);
57
+ }
58
+
59
+ export function stateFile(sessionId, dir = os.tmpdir()) {
60
+ return path.join(dir, `${stem(sessionId)}.json`);
61
+ }
62
+
63
+ const isCount = (v) => Number.isSafeInteger(v) && v >= 0;
64
+ const isLifetime = (v) => v === null || v === "1h" || v === "5m";
65
+ const FIGURE_KEYS = Object.keys(sessionFigures()).sort().join();
66
+
67
+ function wellFormed(f) {
68
+ return (
69
+ f !== null &&
70
+ typeof f === "object" &&
71
+ Object.keys(f).sort().join() === FIGURE_KEYS &&
72
+ isCount(f.compactions) &&
73
+ typeof f.pending === "boolean" &&
74
+ (f.fresh === null || isCount(f.fresh)) &&
75
+ (f.last === null || isCount(f.last)) &&
76
+ isCount(f.requests) &&
77
+ isLifetime(f.freshLifetime) &&
78
+ isLifetime(f.lifetime) &&
79
+ Array.isArray(f.recent) &&
80
+ f.recent.every((id) => typeof id === "string")
81
+ );
82
+ }
83
+
84
+ function tailBefore(fd, offset) {
85
+ const length = Math.min(TAIL, offset);
86
+ const bytes = Buffer.alloc(length);
87
+ const got = length === 0 ? 0 : fs.readSync(fd, bytes, 0, length, offset - length);
88
+ return digest(bytes.subarray(0, got).toString("latin1"));
89
+ }
90
+
91
+ function restore(file, transcriptPath, stat) {
92
+ let kept;
93
+ try {
94
+ kept = JSON.parse(fs.readFileSync(file, "utf8"));
95
+ } catch {
96
+ return null;
97
+ }
98
+ if (kept === null || typeof kept !== "object" || kept.v !== STATE_VERSION || kept.transcript !== transcriptPath) return null;
99
+ if (kept.ino !== stat.ino || !isCount(kept.offset) || kept.offset > stat.size || typeof kept.tail !== "string" || !wellFormed(kept.figures)) return null;
100
+ return kept;
101
+ }
102
+
103
+ function advance(kept, transcriptPath, size) {
104
+ if (size === kept.offset) return false;
105
+ const fd = fs.openSync(transcriptPath, "r");
106
+ try {
107
+ if (kept.offset > 0 && tailBefore(fd, kept.offset) !== kept.tail) {
108
+ Object.assign(kept, { offset: 0, tail: "", figures: sessionFigures() });
109
+ }
110
+ const start = kept.offset;
111
+ let position = kept.offset;
112
+ // Joined once the newline lands: joining at every chunk would copy a long line once per chunk it spans.
113
+ let waiting = [];
114
+ while (position < size) {
115
+ const chunk = Buffer.allocUnsafe(Math.min(CHUNK, size - position));
116
+ const got = fs.readSync(fd, chunk, 0, chunk.length, position);
117
+ if (got === 0) break;
118
+ position += got;
119
+ const bytes = chunk.subarray(0, got);
120
+ const end = bytes.lastIndexOf(0x0a);
121
+ if (end === -1) {
122
+ waiting.push(bytes);
123
+ continue;
124
+ }
125
+ const complete = waiting.length === 0 ? bytes.subarray(0, end) : Buffer.concat([...waiting, bytes.subarray(0, end)]);
126
+ for (const line of complete.toString("utf8").split("\n")) {
127
+ const read = readLine(line);
128
+ if (read !== null && !read.malformed) foldFigures(kept.figures, read);
129
+ }
130
+ waiting = end + 1 < got ? [bytes.subarray(end + 1)] : [];
131
+ kept.offset = position - (got - end - 1);
132
+ }
133
+ if (kept.offset !== start) kept.tail = tailBefore(fd, kept.offset);
134
+ return kept.offset !== start;
135
+ } finally {
136
+ fs.closeSync(fd);
137
+ }
138
+ }
139
+
140
+ /** Written whole, then renamed over the old: the prompt's and the status line's calls can race, and either snapshot counts nothing twice. */
141
+ function keep(file, kept, warn) {
142
+ const temporary = `${file}.${process.pid}-${Date.now().toString(36)}`;
143
+ let created = false;
144
+ try {
145
+ fs.writeFileSync(temporary, `${JSON.stringify(kept)}\n`, { flag: "wx", mode: 0o600 });
146
+ created = true;
147
+ fs.renameSync(temporary, file);
148
+ } catch (error) {
149
+ if (created) fs.rmSync(temporary, { force: true });
150
+ warn(`the running figures could not be kept — ${error.code ?? error.message}; the next call reads from where the last kept ones end`);
151
+ }
152
+ }
153
+
154
+ const sessionOf = (payload) => (typeof payload?.session_id === "string" && payload.session_id !== "" ? payload.session_id : null);
155
+
156
+ function locate(payload, dir) {
157
+ const transcriptPath = typeof payload?.transcript_path === "string" ? payload.transcript_path : null;
158
+ if (transcriptPath === null) return { why: "the host sent no transcript_path" };
159
+ let stat;
160
+ try {
161
+ stat = fs.statSync(transcriptPath);
162
+ } catch (error) {
163
+ return { why: `the transcript could not be read — ${error.code ?? error.message}` };
164
+ }
165
+ if (!stat.isFile()) return { why: "the transcript could not be read — it is not a file" };
166
+ const sessionId = sessionOf(payload);
167
+ const file = sessionId === null ? null : stateFile(sessionId, dir);
168
+ const kept = (file === null ? null : restore(file, transcriptPath, stat)) ?? { v: STATE_VERSION, transcript: transcriptPath, ino: stat.ino, offset: 0, tail: "", figures: sessionFigures() };
169
+ return { transcriptPath, sessionId, stat, file, kept };
170
+ }
171
+
172
+ function refresh(found, warn, spend) {
173
+ try {
174
+ if (advance(found.kept, found.transcriptPath, found.stat.size) && found.file !== null) keep(found.file, found.kept, warn);
175
+ } catch (error) {
176
+ return { why: `the transcript could not be read — ${error.code ?? error.message}` };
177
+ }
178
+ let figure;
179
+ try {
180
+ figure = figureOf(found.kept.figures, spend);
181
+ } catch (error) {
182
+ if (!(error instanceof LedgerError)) throw error;
183
+ return { why: error.message };
184
+ }
185
+ if (figure !== null) return figure;
186
+ return found.kept.figures.pending
187
+ ? { why: "no request is recorded since the compaction", after: "the first request since the compaction" }
188
+ : { why: "no request is recorded yet", after: "the first recorded request" };
189
+ }
190
+
191
+ const FINISH = { PostToolUse: "finish the current step, then ", UserPromptSubmit: "finish what this prompt asks, then ", Stop: "" };
192
+
193
+ export function adviceLine(figure, event = "UserPromptSubmit") {
194
+ const m = figure.multipliers;
195
+ const assumed =
196
+ m.source === "declared"
197
+ ? "multipliers declared"
198
+ : `multipliers undeclared, so the general read multiplier and the ${m.recorded ? `${m.lifetime === "1h" ? "one-hour" : "five-minute"} writes the host recorded` : "five-minute write the records did not state"}`;
199
+ return (
200
+ `Portulan restart advisory: after ${grouped(figure.requests)} requests, each re-reading it, this session's context, ${grouped(figure.context)} tokens, has reached its restart threshold of ` +
201
+ `${grouped(figure.threshold)} = fresh context ${grouped(figure.fresh)} × (1 + write ${m.write}× / (${figure.horizon} more requests × read ${m.read}×)); ${assumed}. ` +
202
+ `Continuing costs more in re-reads than restarting: ${FINISH[event]}write the handoff and end the session. Said once.`
203
+ );
204
+ }
205
+
206
+ export function statusLine(figure) {
207
+ const m = figure.multipliers;
208
+ const multiplied = `${m.source === "declared" ? "multipliers declared" : "multipliers undeclared"}: read ${m.read}×, write ${m.write}×`;
209
+ const requests = `${grouped(figure.requests)} request${figure.requests === 1 ? "" : "s"}`;
210
+ return figure.context >= figure.threshold
211
+ ? `${requests} · context ${compact(figure.context)} has reached its ${compact(figure.threshold)} restart threshold: write the handoff and restart · ${multiplied}`
212
+ : `${requests} · context ${compact(figure.context)} of a ${compact(figure.threshold)} restart threshold · ${multiplied}`;
213
+ }
214
+
215
+ function once(event, payload, { dir = os.tmpdir(), warn = () => {}, declared = null, horizon = HORIZON } = {}) {
216
+ // Claude Code 2.1.281: a subagent's tool call carries its own `agent_id` and the main session's transcript,
217
+ // and a subagent told to end its session would end nothing.
218
+ if (typeof payload?.agent_id === "string" && payload.agent_id !== "") return null;
219
+ // A host can give a block's retry a new session id, where the held-once record would hold it again.
220
+ if (event === "Stop" && payload?.stop_hook_active === true) return null;
221
+ const sessionId = sessionOf(payload);
222
+ if (sessionId === null) {
223
+ warn("the host sent no session_id, so saying the line once could not be kept");
224
+ return null;
225
+ }
226
+ const found = locate(payload, dir);
227
+ if (found.why !== undefined) {
228
+ warn(found.why);
229
+ return null;
230
+ }
231
+ const record = event === "Stop" ? heldFile : toldFile;
232
+ // Nothing written since the figures were kept, so nothing compacted: the record at the kept count answers without a read.
233
+ if (found.stat.size === found.kept.offset && fs.existsSync(record(sessionId, found.kept.figures.compactions, dir))) return null;
234
+ const figure = refresh(found, warn, { declared, horizon });
235
+ if (figure.why !== undefined) {
236
+ warn(figure.why);
237
+ return null;
238
+ }
239
+ if (figure.context < figure.threshold) return null;
240
+ const mark = (file) => fs.writeFileSync(file, `${figure.context} ${figure.threshold}\n`, { flag: "wx", mode: 0o600 });
241
+ try {
242
+ mark(record(sessionId, figure.compactions, dir));
243
+ } catch (error) {
244
+ if (error.code !== "EEXIST") {
245
+ warn(
246
+ event === "Stop"
247
+ ? `the held-once record could not be written — ${error.code ?? error.message}; letting the turn end rather than holding it at every stop`
248
+ : `the told-once record could not be written — ${error.code ?? error.message}; staying silent rather than saying the line at every prompt`,
249
+ );
250
+ }
251
+ return null;
252
+ }
253
+ if (event !== "Stop") return JSON.stringify({ hookSpecificOutput: { hookEventName: event, additionalContext: adviceLine(figure, event) } });
254
+ try {
255
+ mark(toldFile(sessionId, figure.compactions, dir));
256
+ } catch (error) {
257
+ if (error.code !== "EEXIST") warn(`the told-once record could not be written — ${error.code ?? error.message}; the line may come once more after the block`);
258
+ }
259
+ return JSON.stringify({ decision: "block", reason: adviceLine(figure, event) });
260
+ }
261
+
262
+ export const onTool = (payload, options) => once("PostToolUse", payload, options);
263
+
264
+ export const onPrompt = (payload, options) => once("UserPromptSubmit", payload, options);
265
+
266
+ export const onStop = (payload, options) => once("Stop", payload, options);
267
+
268
+ /** The status line, its context the host's own last-call counts where sent: current where the transcript may lag a request. */
269
+ export function onStatus(payload, { dir = os.tmpdir(), warn = () => {}, declared = null, horizon = HORIZON } = {}) {
270
+ const found = locate(payload, dir);
271
+ const figure = found.why === undefined ? refresh(found, warn, { declared, horizon }) : found;
272
+ if (figure.why !== undefined) return figure.after !== undefined ? `restart threshold: after ${figure.after}` : `restart threshold: not known, because ${figure.why}`;
273
+ const usage = payload?.context_window?.current_usage;
274
+ const counts = usage !== null && typeof usage === "object" ? ["input_tokens", "cache_creation_input_tokens", "cache_read_input_tokens"].map((k) => usage[k]) : [];
275
+ if (counts.length === 3 && counts.every((n) => Number.isSafeInteger(n) && n >= 0)) figure.context = counts.reduce((a, b) => a + b, 0);
276
+ return statusLine(figure);
277
+ }
278
+
279
+ function readPayload() {
280
+ try {
281
+ return JSON.parse(fs.readFileSync(0, "utf8"));
282
+ } catch {
283
+ return null;
284
+ }
285
+ }
286
+
287
+ const SPEND_FLAGS = { "--read": "read", "--write-5m": "write", "--write-1h": "write", "--horizon": "requests" };
288
+
289
+ const MULTIPLIER_FLAGS = ["--read", "--write-5m", "--write-1h"];
290
+
291
+ /** A figure as `String` spells a positive number, and nothing looser: no sign, no hex, no blank. */
292
+ const DECIMAL = /^[0-9]+(\.[0-9]+)?(e[+-]?[0-9]+)?$/;
293
+
294
+ /** The figures after the mode, `{ declared, horizon, fault }`, each null where absent; a partial multiplier set is undeclared whole. */
295
+ export function spendFlags(args) {
296
+ const values = new Map();
297
+ const faults = { multipliers: [], horizon: [], other: [] };
298
+ const shown = (s) => (typeof s === "string" && /^[-+.a-zA-Z0-9]+$/.test(s) ? s : JSON.stringify(s));
299
+ for (let i = 0; i < args.length; i += 2) {
300
+ const [flag, raw] = [args[i], args[i + 1]];
301
+ if (!Object.hasOwn(SPEND_FLAGS, flag)) {
302
+ faults.other.push(`${shown(flag)} is no flag it takes`);
303
+ continue;
304
+ }
305
+ const part = flag === "--horizon" ? faults.horizon : faults.multipliers;
306
+ const range = SPEND_FIGURES[SPEND_FLAGS[flag]];
307
+ const value = typeof raw === "string" && DECIMAL.test(raw) ? Number(raw) : Number.NaN;
308
+ if (values.has(flag)) part.push(`${flag} is given twice`);
309
+ else if (raw === undefined) part.push(`${flag} has no value`);
310
+ else if (!range.holds(value)) part.push(`${flag} ${shown(raw)} is not ${range.is}`);
311
+ values.set(flag, value);
312
+ }
313
+ const missing = MULTIPLIER_FLAGS.filter((flag) => !values.has(flag));
314
+ if (missing.length > 0 && missing.length < MULTIPLIER_FLAGS.length) faults.multipliers.push(`${missing.join(" and ")} ${missing.length === 1 ? "is" : "are"} missing from the set of three`);
315
+ if (missing.length === 0 && !faults.multipliers.length) {
316
+ const over = overflowingWrite({ read: values.get("--read"), write: { "5m": values.get("--write-5m"), "1h": values.get("--write-1h") } });
317
+ if (over !== null) faults.multipliers.push(`--write-${over} divided by --read overflows, which gives no finite threshold`);
318
+ }
319
+ const said = [];
320
+ if (faults.multipliers.length) said.push(`${faults.multipliers.join(", ")}, so the multipliers are undeclared`);
321
+ if (faults.horizon.length) said.push(`${faults.horizon.join(", ")}, so the horizon is undeclared, and ${HORIZON} requests`);
322
+ if (faults.other.length) said.push(`${faults.other.join(", ")}, so ${faults.other.length === 1 ? "it is" : "each is"} passed over with the value after it`);
323
+ return {
324
+ declared: missing.length === 0 && !faults.multipliers.length ? { read: values.get("--read"), write: { "5m": values.get("--write-5m"), "1h": values.get("--write-1h") } } : null,
325
+ horizon: values.has("--horizon") && !faults.horizon.length ? values.get("--horizon") : null,
326
+ fault: said.length ? said.join("; ") : null,
327
+ };
328
+ }
329
+
330
+ export function main(argv = process.argv.slice(2), { stdout = process.stdout, stderr = process.stderr, payload = undefined, dir = os.tmpdir() } = {}) {
331
+ const mode = argv[0];
332
+ const input = payload === undefined ? readPayload() : payload;
333
+ const warn = (why) => stderr.write(`portulan advisory: ${why}\n`);
334
+ try {
335
+ const figures = spendFlags(argv.slice(1));
336
+ if (figures.fault !== null) warn(`the figures after ${mode} are not all usable — ${figures.fault}`);
337
+ const priced = { dir, warn, declared: figures.declared, horizon: figures.horizon ?? HORIZON };
338
+ if (mode === "prompt" || mode === "tool" || mode === "stop") {
339
+ const out = { tool: onTool, prompt: onPrompt, stop: onStop }[mode](input, priced);
340
+ if (out !== null) stdout.write(`${out}\n`);
341
+ } else if (mode === "status") {
342
+ stdout.write(`${onStatus(input, priced)}\n`);
343
+ } else {
344
+ warn(`unknown mode ${JSON.stringify(mode)}: the compiled commands pass tool, prompt, status or stop`);
345
+ }
346
+ } catch (cause) {
347
+ warn(`could not run — ${cause?.message ?? cause}`);
348
+ }
349
+ return 0;
350
+ }
351
+
352
+ if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
353
+ process.exitCode = main();
354
+ }