@jaybeeuu/agent-cortex 2.8.0 → 2.10.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 (52) hide show
  1. package/CHANGELOG.md +304 -0
  2. package/README.md +30 -3
  3. package/agents/README.md +7 -2
  4. package/agents/guppi/agent.md +231 -0
  5. package/agents/guppi/claude/frontmatter.json +6 -0
  6. package/agents/guppi/pi/frontmatter.json +6 -0
  7. package/agents/plan/agent.md +6 -0
  8. package/agents/ralph-plan/agent.md +6 -0
  9. package/bin/installers/claude.mjs +8 -23
  10. package/bin/installers/pi.mjs +8 -2
  11. package/extensions/interrupt-bookmark/README.md +129 -0
  12. package/extensions/interrupt-bookmark/index.test.ts +717 -0
  13. package/extensions/interrupt-bookmark/index.ts +354 -0
  14. package/extensions/interrupt-bookmark/package.json +20 -0
  15. package/extensions/skill-stats/README.md +84 -3
  16. package/extensions/skill-stats/index.ts +62 -3
  17. package/extensions/skill-stats/package.json +1 -1
  18. package/extensions/skill-stats/rollup.test.ts +643 -0
  19. package/extensions/skill-stats/rollup.ts +415 -0
  20. package/extensions/skill-stats/timeline.ts +101 -0
  21. package/package.json +1 -1
  22. package/scripts/lib/compose-agent.mjs +45 -7
  23. package/scripts/lib/manifest.mjs +29 -0
  24. package/skills/review/review-defects/EXAMPLES.md +80 -4
  25. package/skills/review/review-defects/SKILL.md +47 -16
  26. package/skills/review/review-report/EXAMPLES.md +118 -0
  27. package/skills/review/review-report/SKILL.md +138 -0
  28. package/skills/review/review-report/scripts/report.mjs +1032 -0
  29. package/skills/workflow/hitl-queue/BOUNDARY.md +122 -0
  30. package/skills/workflow/hitl-queue/DEADLINES.md +59 -0
  31. package/skills/workflow/hitl-queue/RECOMMEND.md +89 -0
  32. package/skills/workflow/hitl-queue/SCOPES.md +118 -0
  33. package/skills/workflow/hitl-queue/SKILL.md +150 -0
  34. package/skills/workflow/hitl-queue/scripts/boundary.mjs +493 -0
  35. package/skills/workflow/hitl-queue/scripts/queue.mjs +1012 -0
  36. package/skills/workflow/hitl-queue/scripts/recommend.mjs +492 -0
  37. package/skills/workflow/hitl-queue/scripts/scopes.mjs +479 -0
  38. package/skills/workflow/plan/SKILL.md +13 -0
  39. package/skills/workflow/prioritise-work/CHALLENGE.md +139 -0
  40. package/skills/workflow/prioritise-work/SKILL.md +150 -0
  41. package/skills/workflow/prioritise-work/scripts/challenge.mjs +505 -0
  42. package/skills/workflow/prioritise-work/scripts/strategy.mjs +756 -0
  43. package/skills/workflow/prioritise-work/templates/strategy.md +87 -0
  44. package/skills/workflow/ralph/REFERENCE.md +6 -1
  45. package/skills/workflow/ralph/scripts/prune-worktrees.mjs +185 -10
  46. package/skills/workflow/run-pipeline-stage/playbooks/review.md +5 -2
  47. package/skills/workflow/score-bead/FORMAT.md +73 -0
  48. package/skills/workflow/score-bead/SKILL.md +112 -0
  49. package/skills/workflow/score-bead/scripts/rice.mjs +664 -0
  50. package/skills/workflow/using-agent-skills/SKILL.md +6 -1
  51. package/token-map.README.md +14 -2
  52. package/token-map.json +17 -3
@@ -0,0 +1,492 @@
1
+ // @ts-check
2
+ // One recommendation: the queue's top row, and the line of the strategy that justifies it.
3
+ //
4
+ // node recommend.mjs recommend [--why "<strategy line>"] [--bead <id>] [--file <path>]
5
+ // [--include-ideas] [--cwd <dir>] [--json]
6
+ //
7
+ // The queue ranks; this script names one row of that ranking to a session that has to decide.
8
+ // It adds no arithmetic: the total, the four components, both multipliers and the dependent
9
+ // list are the ones `buildQueue` already derived, so there is one definition of the score and
10
+ // no second copy of it to drift. The one thing it adds is the justification — a quote from the
11
+ // committed strategy, which must appear in that file. A quote the file does not contain is
12
+ // refused (exit 2), so an invented reason fails here instead of reaching the human.
13
+ //
14
+ // Nothing is recommended unless it is ranked and justified. A missing strategy, no scores at
15
+ // all, an empty queue, and a bead the human named that the queue does not hold are four stated
16
+ // conditions, each with one next step, and none of them guesses a bead.
17
+ //
18
+ // Zero runtime dependencies, async fs only, every child process with an argv array.
19
+ //
20
+ // Exit codes: 0 an answer was printed (a recommendation or a stated condition), 1 a tracker
21
+ // call failed, 2 the caller asked for something malformed.
22
+
23
+ import { readFile } from 'node:fs/promises';
24
+ import { pathToFileURL } from 'node:url';
25
+
26
+ import { DEFAULT_FILE, resolveStrategyPath, validateStrategy } from '../../prioritise-work/scripts/strategy.mjs';
27
+ import { BD_PATH, errorMessage, makeRunner } from '../../score-bead/scripts/rice.mjs';
28
+ import {
29
+ HITL_LABEL,
30
+ IDEA_LABEL,
31
+ UsageError,
32
+ buildQueue,
33
+ formatExcluded,
34
+ formatNumbers,
35
+ formatUnblocks,
36
+ } from './queue.mjs';
37
+
38
+ /** @typedef {ReturnType<typeof buildQueue> extends Promise<infer R> ? R : never} QueueReport */
39
+ /** @typedef {QueueReport['rows'][number]} QueueRow */
40
+
41
+ // ─── Contract ─────────────────────────────────────────────────────────────────
42
+
43
+ /**
44
+ * The four states are deliberately different answers, not four shades of failure: a reader can
45
+ * tell "write a strategy" from "score a bead" from "there is nothing to decide" by the first
46
+ * line alone, and each one offers the step that clears it.
47
+ */
48
+ export const RECOMMEND_STATE = /** @type {const} */ ({
49
+ recommend: 'recommend',
50
+ noStrategy: 'no-strategy',
51
+ unscored: 'unscored',
52
+ empty: 'empty',
53
+ notInQueue: 'not-in-queue',
54
+ });
55
+
56
+ // ─── Types ────────────────────────────────────────────────────────────────────
57
+
58
+ /**
59
+ * The whole answer.
60
+ * @typedef {object} Recommendation
61
+ * @property {string} state One of RECOMMEND_STATE.
62
+ * @property {string} file The strategy file the run looked for.
63
+ * @property {string[]} [strategyErrors] Why no line could be quoted from it.
64
+ * @property {number} [strategyVersion] The rubric version, when the file parsed.
65
+ * @property {QueueReport} [report] The queue the answer was read from.
66
+ * @property {QueueRow} [row] The bead presented, when one is.
67
+ * @property {QueueRow} [top] The queue's own top row, when it differs from `row`.
68
+ * @property {string} [named] The bead id the caller named.
69
+ * @property {string} [why] The strategy line, reused verbatim from the file.
70
+ * @property {string} [whySection] The section that line sits under.
71
+ */
72
+
73
+ // ─── Strategy ─────────────────────────────────────────────────────────────────
74
+
75
+ /**
76
+ * The strategy text, or the reasons no line can be quoted from it. A missing file and an
77
+ * invalid one are the same state to a reader — there is no usable rubric — but the reason
78
+ * names which one happened, so the next step is obvious.
79
+ * @param {string} file
80
+ * @returns {Promise<{ text: string } | { errors: string[] }>}
81
+ */
82
+ async function readStrategy(file) {
83
+ /** @type {string} */
84
+ let text;
85
+ try {
86
+ text = await readFile(file, 'utf8');
87
+ } catch (error) {
88
+ if (/** @type {NodeJS.ErrnoException} */ (error).code === 'ENOENT') {
89
+ return { errors: ['no file at that path — write one with prioritise-work'] };
90
+ }
91
+ throw error;
92
+ }
93
+ const report = validateStrategy(text);
94
+ return report.ok ? { text } : { errors: report.errors };
95
+ }
96
+
97
+ /** @param {string} value @returns {string} */
98
+ function collapse(value) {
99
+ return value.replace(/\s+/g, ' ').trim();
100
+ }
101
+
102
+ /**
103
+ * Every line of the file that states a rule, with the section it sits under. Comment blocks
104
+ * hold guidance for the owner writing the rubric, not the rubric itself, so they are skipped:
105
+ * a quote must be the owner's own words or it justifies nothing.
106
+ * @param {string} text
107
+ * @returns {{ text: string, section: string | undefined }[]}
108
+ */
109
+ export function quotableLines(text) {
110
+ /** @type {{ text: string, section: string | undefined }[]} */
111
+ const out = [];
112
+ /** @type {string | undefined} */
113
+ let section;
114
+ let inComment = false;
115
+
116
+ for (const raw of text.split('\n')) {
117
+ const line = raw.trim();
118
+ if (inComment) {
119
+ if (line.includes('-->')) inComment = false;
120
+ continue;
121
+ }
122
+ if (line.startsWith('<!--')) {
123
+ inComment = !line.includes('-->');
124
+ continue;
125
+ }
126
+ const heading = /^##\s+(.*?)\s*$/.exec(line);
127
+ if (heading !== null) {
128
+ section = heading[1];
129
+ continue;
130
+ }
131
+ if (line === '') continue;
132
+ out.push({ text: line.replace(/^[-*]\s+/, ''), section });
133
+ }
134
+ return out;
135
+ }
136
+
137
+ /**
138
+ * The line a quote names, and the section it sits under. The match is on collapsed
139
+ * whitespace, so a quote wraps the way the reader's terminal does; the returned line is the
140
+ * file's own text, so the recommendation repeats the rubric rather than a paraphrase of it.
141
+ * @param {string} text @param {string} quote
142
+ * @returns {{ line: string, section: string | undefined } | undefined}
143
+ */
144
+ export function findQuote(text, quote) {
145
+ const wanted = collapse(quote);
146
+ if (wanted === '') return undefined;
147
+ for (const candidate of quotableLines(text)) {
148
+ if (collapse(candidate.text).includes(wanted)) {
149
+ return { line: candidate.text, section: candidate.section };
150
+ }
151
+ }
152
+ return undefined;
153
+ }
154
+
155
+ // ─── Recommendation ───────────────────────────────────────────────────────────
156
+
157
+ /**
158
+ * Read the queue, then name the one row the session should say out loud.
159
+ *
160
+ * The row is the queue's own: `report.rows[0]` for a cold start, or the row carrying the id
161
+ * the human named. Naming a bead outside the queue is not silently replaced by the queue's
162
+ * choice — it is stated, with the top choice beside it, because the human's choice stands and
163
+ * only the disagreement needs saying.
164
+ *
165
+ * @param {object} options
166
+ * @param {(args: string[]) => Promise<string>} options.bd
167
+ * @param {string | undefined} [options.file] Strategy path; defaults to the documented default.
168
+ * @param {string} [options.cwd]
169
+ * @param {string | undefined} [options.why] The strategy line that justifies the choice.
170
+ * @param {string | undefined} [options.bead] A bead the human named.
171
+ * @param {boolean} [options.includeIdeas]
172
+ * @returns {Promise<Recommendation>}
173
+ */
174
+ export async function buildRecommendation({ bd, file, cwd = process.cwd(), why, bead, includeIdeas = false }) {
175
+ const strategyFile = await resolveStrategyPath({ ...(file === undefined ? {} : { file }), cwd });
176
+ const strategy = await readStrategy(strategyFile);
177
+ if ('errors' in strategy) {
178
+ return { state: RECOMMEND_STATE.noStrategy, file: strategyFile, strategyErrors: strategy.errors };
179
+ }
180
+
181
+ const report = await buildQueue({ bd, cwd, includeIdeas, ...(file === undefined ? {} : { file }) });
182
+ const top = report.rows[0];
183
+
184
+ /** @type {QueueRow} */
185
+ let row;
186
+ if (bead === undefined) {
187
+ if (report.rows.length === 0) {
188
+ return { state: RECOMMEND_STATE.empty, file: strategyFile, report, strategyVersion: report.strategy.version };
189
+ }
190
+ if (report.counts.ranked === 0) {
191
+ return { state: RECOMMEND_STATE.unscored, file: strategyFile, report, strategyVersion: report.strategy.version };
192
+ }
193
+ row = /** @type {QueueRow} */ (top);
194
+ } else {
195
+ const named = report.rows.find((each) => each.id === bead);
196
+ if (named === undefined) {
197
+ const ranked = top !== undefined && top.score !== undefined;
198
+ return {
199
+ state: RECOMMEND_STATE.notInQueue,
200
+ file: strategyFile,
201
+ report,
202
+ strategyVersion: report.strategy.version,
203
+ named: bead,
204
+ ...(ranked ? { top } : {}),
205
+ };
206
+ }
207
+ if (named.score === undefined) {
208
+ return {
209
+ state: RECOMMEND_STATE.unscored,
210
+ file: strategyFile,
211
+ report,
212
+ strategyVersion: report.strategy.version,
213
+ named: bead,
214
+ row: named,
215
+ };
216
+ }
217
+ row = named;
218
+ }
219
+
220
+ if (why === undefined || collapse(why) === '') {
221
+ // A caller reading the queue structurally (`--json`) may name the row before choosing the
222
+ // line; the text render refuses to print a recommendation without one.
223
+ return {
224
+ state: RECOMMEND_STATE.recommend,
225
+ file: strategyFile,
226
+ strategyVersion: report.strategy.version,
227
+ report,
228
+ row,
229
+ ...(top === undefined || top.id === row.id ? {} : { top }),
230
+ ...(bead === undefined ? {} : { named: bead }),
231
+ };
232
+ }
233
+ const quote = findQuote(strategy.text, why);
234
+ if (quote === undefined) {
235
+ throw new UsageError(
236
+ `no line of ${strategyFile} contains "${collapse(why)}" — quote the rubric itself, never a summary of it`,
237
+ );
238
+ }
239
+
240
+ return {
241
+ state: RECOMMEND_STATE.recommend,
242
+ file: strategyFile,
243
+ strategyVersion: report.strategy.version,
244
+ report,
245
+ row,
246
+ ...(top === undefined || top.id === row.id ? {} : { top }),
247
+ ...(bead === undefined ? {} : { named: bead }),
248
+ why: quote.line,
249
+ ...(quote.section === undefined ? {} : { whySection: quote.section }),
250
+ };
251
+ }
252
+
253
+ // ─── Render ───────────────────────────────────────────────────────────────────
254
+
255
+ /** @param {number} count @param {string} noun @returns {string} */
256
+ function plural(count, noun) {
257
+ return `${count} ${noun}${count === 1 ? '' : 's'}`;
258
+ }
259
+
260
+ /**
261
+ * The one line that notes the queue's own choice, for the case where the human named another
262
+ * bead. It states the disagreement without reopening it: the human's choice stands.
263
+ * @param {QueueRow} row @returns {string}
264
+ */
265
+ function topNote(row) {
266
+ return `queue top: ${row.id} · total ${/** @type {number} */ (row.total).toFixed(1)} · ${row.title}`;
267
+ }
268
+
269
+ /**
270
+ * Render one answer. A recommendation is five lines: which bead and its total, its title, the
271
+ * score components and both multipliers, what answering it unblocks, and the strategy line
272
+ * that justifies it. Every other state leads with `No recommendation` and names the condition
273
+ * and the one step that clears it.
274
+ * @param {Recommendation} recommendation @returns {string}
275
+ */
276
+ export function renderRecommendation(recommendation) {
277
+ const { state } = recommendation;
278
+
279
+ if (state === RECOMMEND_STATE.recommend) {
280
+ const row = /** @type {QueueRow} */ (recommendation.row);
281
+ if (recommendation.why === undefined) {
282
+ throw new UsageError(`--why needs the line of ${recommendation.file} that justifies ${row.id}`);
283
+ }
284
+ const version = recommendation.strategyVersion === undefined
285
+ ? 'strategy unknown'
286
+ : `strategy v${recommendation.strategyVersion}`;
287
+ const lines = [
288
+ `Recommendation · ${row.id} · total ${/** @type {number} */ (row.total).toFixed(1)} · ${version}`,
289
+ row.title,
290
+ formatNumbers(row),
291
+ formatUnblocks(row),
292
+ `why: "${recommendation.why}"${recommendation.whySection === undefined ? '' : ` · ${recommendation.whySection}`}`,
293
+ ];
294
+ if (recommendation.top !== undefined) lines.push(topNote(recommendation.top));
295
+ return `${lines.join('\n')}\n`;
296
+ }
297
+
298
+ if (state === RECOMMEND_STATE.noStrategy) {
299
+ return `${
300
+ [
301
+ 'No recommendation · no usable strategy',
302
+ ` file: ${recommendation.file}`,
303
+ ...(recommendation.strategyErrors ?? []).map((error) => ` - ${error}`),
304
+ 'next: write or fix the strategy with prioritise-work, then run this again',
305
+ ].join('\n')
306
+ }\n`;
307
+ }
308
+
309
+ const report = /** @type {QueueReport} */ (recommendation.report);
310
+
311
+ if (state === RECOMMEND_STATE.empty) {
312
+ const excluded = formatExcluded(report.counts).trimEnd();
313
+ return `${
314
+ [
315
+ 'No recommendation · the queue is empty',
316
+ ` no open ${HITL_LABEL} beads`,
317
+ ...(excluded === '' ? [] : [` ${excluded}`]),
318
+ 'next: file the decision as an execution:hitl bead, or lift a deferral with bd update <id> --status open',
319
+ ].join('\n')
320
+ }\n`;
321
+ }
322
+
323
+ if (state === RECOMMEND_STATE.unscored) {
324
+ const named = recommendation.row;
325
+ const subject = named === undefined
326
+ ? `${plural(report.rows.length, `open ${HITL_LABEL} bead`)}, none carrying a RICE score`
327
+ : `${named.id} carries no RICE score (${named.unscoredReason ?? 'no score'})`;
328
+ return `${
329
+ [
330
+ 'No recommendation · nothing is scored',
331
+ ` ${subject}`,
332
+ `next: score ${named === undefined ? 'one' : named.id} with score-bead, then run this again`,
333
+ ].join('\n')
334
+ }\n`;
335
+ }
336
+
337
+ const named = recommendation.named ?? 'that bead';
338
+ return `${
339
+ [
340
+ `No recommendation · ${named} is not in the queue`,
341
+ ` ${named} is not an open ${HITL_LABEL} bead — check it with bd show ${named}`,
342
+ ...(recommendation.top === undefined ? [] : [` ${topNote(recommendation.top)}`]),
343
+ 'next: name a bead from the queue, or file this one as execution:hitl',
344
+ ].join('\n')
345
+ }\n`;
346
+ }
347
+
348
+ // ─── CLI ──────────────────────────────────────────────────────────────────────
349
+
350
+ /** @type {readonly string[]} */
351
+ export const COMMANDS = /** @type {const} */ (['recommend']);
352
+
353
+ const USAGE = `Usage: node recommend.mjs recommend [--why "<strategy line>"] [--bead <id>] [--file <path>]
354
+ [--include-ideas] [--cwd <dir>] [--json]
355
+
356
+ recommend Print one recommendation: the queue's top row, the four score components, both
357
+ multipliers, what answering it unblocks, and the line of the strategy that
358
+ justifies it. A missing strategy, no scores, an empty queue, or a named bead the
359
+ queue does not hold each print the condition and one next step instead.
360
+
361
+ Options:
362
+ --why "<line>" A line of the strategy that justifies the choice; must appear in the file
363
+ --bead <id> Recommend the bead the human named, with the queue's own top noted
364
+ --file <path> Strategy file (default: ${DEFAULT_FILE}, relative to the repository root)
365
+ --include-ideas Rank ${IDEA_LABEL} beads too (they are excluded by default)
366
+ --cwd <dir> Directory the strategy path and bd resolve from
367
+ --json Print the answer as JSON
368
+ -h, --help Show this text
369
+
370
+ Environment:
371
+ BD_PATH bd binary (default: bd)
372
+ `;
373
+
374
+ /**
375
+ * Parse the CLI's argv.
376
+ * @param {string[]} argv
377
+ * @returns {{ command: string, file?: string, why?: string, bead?: string, includeIdeas: boolean, cwd: string, json: boolean }}
378
+ */
379
+ export function parseArgs(argv) {
380
+ /** @type {string | undefined} */
381
+ let command;
382
+ /** @type {string | undefined} */
383
+ let file;
384
+ /** @type {string | undefined} */
385
+ let why;
386
+ /** @type {string | undefined} */
387
+ let bead;
388
+ let cwd = process.cwd();
389
+ let json = false;
390
+ let includeIdeas = false;
391
+
392
+ for (let i = 0; i < argv.length; i++) {
393
+ const arg = argv[i];
394
+ if (arg === '--json') {
395
+ json = true;
396
+ continue;
397
+ }
398
+ if (arg === '--include-ideas') {
399
+ includeIdeas = true;
400
+ continue;
401
+ }
402
+ if (arg === '--file' || arg === '--cwd' || arg === '--why' || arg === '--bead') {
403
+ const value = argv[++i];
404
+ if (value === undefined) throw new UsageError(`${arg} needs a value`);
405
+ if (arg === '--cwd') cwd = value;
406
+ else if (arg === '--why') why = value;
407
+ else if (arg === '--bead') bead = value;
408
+ else file = value;
409
+ continue;
410
+ }
411
+ if (arg.startsWith('-')) throw new UsageError(`unexpected argument: ${arg}`);
412
+ if (command === undefined) {
413
+ if (!COMMANDS.includes(arg)) throw new UsageError(`unknown command "${arg}"`);
414
+ command = arg;
415
+ continue;
416
+ }
417
+ throw new UsageError(`unexpected argument: ${arg}`);
418
+ }
419
+
420
+ if (command === undefined) throw new UsageError(`a command is required: ${COMMANDS.join(', ')}`);
421
+ return {
422
+ command,
423
+ ...(file === undefined ? {} : { file }),
424
+ ...(why === undefined ? {} : { why }),
425
+ ...(bead === undefined ? {} : { bead }),
426
+ includeIdeas,
427
+ cwd,
428
+ json,
429
+ };
430
+ }
431
+
432
+ /**
433
+ * @param {object} options
434
+ * @param {string[]} options.argv CLI arguments (without node/script).
435
+ * @param {(chunk: string) => void} [options.write] stdout sink; injectable for tests.
436
+ * @param {(chunk: string) => void} [options.writeError] stderr sink; injectable for tests.
437
+ * @param {{ bd?: (args: string[]) => Promise<string> }} [options.deps] Collaborators; injectable for tests.
438
+ * @returns {Promise<number>} process exit code.
439
+ */
440
+ export async function runCli({
441
+ argv,
442
+ write = (chunk) => process.stdout.write(chunk),
443
+ writeError = (chunk) => process.stderr.write(chunk),
444
+ deps = {},
445
+ }) {
446
+ if (argv.includes('--help') || argv.includes('-h')) {
447
+ write(USAGE);
448
+ return 0;
449
+ }
450
+
451
+ /** @type {ReturnType<typeof parseArgs>} */
452
+ let options;
453
+ try {
454
+ options = parseArgs(argv);
455
+ } catch (error) {
456
+ writeError(`recommend: ${errorMessage(error)}\n\n${USAGE}`);
457
+ return 2;
458
+ }
459
+
460
+ const bd = deps.bd ?? makeRunner(BD_PATH, options.cwd);
461
+ try {
462
+ const recommendation = await buildRecommendation({
463
+ bd,
464
+ cwd: options.cwd,
465
+ includeIdeas: options.includeIdeas,
466
+ ...(options.file === undefined ? {} : { file: options.file }),
467
+ ...(options.why === undefined ? {} : { why: options.why }),
468
+ ...(options.bead === undefined ? {} : { bead: options.bead }),
469
+ });
470
+ write(options.json ? `${JSON.stringify(recommendation, null, 2)}\n` : renderRecommendation(recommendation));
471
+ return 0;
472
+ } catch (error) {
473
+ if (error instanceof UsageError) {
474
+ writeError(`recommend: ${errorMessage(error)}\n`);
475
+ return 2;
476
+ }
477
+ writeError(`recommend failed: ${errorMessage(error)}\n`);
478
+ return 1;
479
+ }
480
+ }
481
+
482
+ // Only run the CLI when invoked directly, so tests can import the module.
483
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
484
+ runCli({ argv: process.argv.slice(2) })
485
+ .then((code) => {
486
+ process.exitCode = code;
487
+ })
488
+ .catch((error) => {
489
+ process.stderr.write(`${errorMessage(error)}\n`);
490
+ process.exitCode = 1;
491
+ });
492
+ }