@letta-ai/letta-code 0.25.9 → 0.25.11

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.
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Structured diff of openai/codex `codex-rs/models-manager/models.json`.
3
+ *
4
+ * We don't care about every field — only the ones that affect the tool/schema
5
+ * surface exposed to the model. When any of these change, the letta-code
6
+ * harness (src/agent/prompts/source_codex.md and src/tools/*) may need updating.
7
+ */
8
+
9
+ /** Tool-relevant fields lifted off each model entry in models.json. */
10
+ export interface ModelToolConfig {
11
+ slug: string;
12
+ apply_patch_tool_type?: string;
13
+ web_search_tool_type?: string;
14
+ shell_type?: string;
15
+ supports_search_tool?: boolean;
16
+ supports_parallel_tool_calls?: boolean;
17
+ supports_image_detail_original?: boolean;
18
+ experimental_supported_tools?: string[];
19
+ input_modalities?: string[];
20
+ truncation_policy?: unknown;
21
+ /** Tool names mentioned anywhere in base_instructions / instructions_template. */
22
+ prompt_tool_mentions: string[];
23
+ }
24
+
25
+ /** Substrings we treat as "this prompt mentions tool X". */
26
+ const TOOL_MENTIONS = [
27
+ "apply_patch",
28
+ "exec_command",
29
+ "view_image",
30
+ "multi_tool_use.parallel",
31
+ "web_search",
32
+ "update_plan",
33
+ "container.exec",
34
+ ];
35
+
36
+ export interface ModelsJson {
37
+ models: Array<Record<string, unknown>>;
38
+ }
39
+
40
+ export interface ToolFieldDelta {
41
+ slug: string;
42
+ field: string;
43
+ previous: unknown;
44
+ current: unknown;
45
+ }
46
+
47
+ export interface ModelsDiff {
48
+ added_models: string[];
49
+ removed_models: string[];
50
+ field_deltas: ToolFieldDelta[];
51
+ /** True if any field_delta is in TOOL_SCHEMA_FIELDS. */
52
+ has_tool_schema_change: boolean;
53
+ /** True if any prompt_tool_mentions added or removed. */
54
+ has_prompt_tool_change: boolean;
55
+ }
56
+
57
+ /** Fields whose change implies a tool-schema update may be needed in letta-code. */
58
+ export const TOOL_SCHEMA_FIELDS = new Set([
59
+ "apply_patch_tool_type",
60
+ "web_search_tool_type",
61
+ "shell_type",
62
+ "supports_search_tool",
63
+ "supports_parallel_tool_calls",
64
+ "experimental_supported_tools",
65
+ "input_modalities",
66
+ "truncation_policy",
67
+ ]);
68
+
69
+ function collectMentions(text: string): string[] {
70
+ const found = new Set<string>();
71
+ for (const m of TOOL_MENTIONS) {
72
+ if (text.includes(m)) found.add(m);
73
+ }
74
+ return Array.from(found).sort();
75
+ }
76
+
77
+ export function extractToolConfig(
78
+ model: Record<string, unknown>,
79
+ ): ModelToolConfig {
80
+ const slug = typeof model.slug === "string" ? model.slug : "<unknown>";
81
+ const promptText = [
82
+ typeof model.base_instructions === "string" ? model.base_instructions : "",
83
+ typeof model.model_messages === "object" && model.model_messages !== null
84
+ ? JSON.stringify(model.model_messages)
85
+ : "",
86
+ ].join("\n");
87
+ return {
88
+ slug,
89
+ apply_patch_tool_type: model.apply_patch_tool_type as string | undefined,
90
+ web_search_tool_type: model.web_search_tool_type as string | undefined,
91
+ shell_type: model.shell_type as string | undefined,
92
+ supports_search_tool: model.supports_search_tool as boolean | undefined,
93
+ supports_parallel_tool_calls: model.supports_parallel_tool_calls as
94
+ | boolean
95
+ | undefined,
96
+ supports_image_detail_original: model.supports_image_detail_original as
97
+ | boolean
98
+ | undefined,
99
+ experimental_supported_tools: model.experimental_supported_tools as
100
+ | string[]
101
+ | undefined,
102
+ input_modalities: model.input_modalities as string[] | undefined,
103
+ truncation_policy: model.truncation_policy,
104
+ prompt_tool_mentions: collectMentions(promptText),
105
+ };
106
+ }
107
+
108
+ function equal(a: unknown, b: unknown): boolean {
109
+ return JSON.stringify(a) === JSON.stringify(b);
110
+ }
111
+
112
+ /** Compute the diff between two models.json payloads. */
113
+ export function diffModelsJson(prev: ModelsJson, curr: ModelsJson): ModelsDiff {
114
+ const prevBySlug = new Map<string, ModelToolConfig>();
115
+ const currBySlug = new Map<string, ModelToolConfig>();
116
+ for (const m of prev.models) {
117
+ const cfg = extractToolConfig(m);
118
+ prevBySlug.set(cfg.slug, cfg);
119
+ }
120
+ for (const m of curr.models) {
121
+ const cfg = extractToolConfig(m);
122
+ currBySlug.set(cfg.slug, cfg);
123
+ }
124
+
125
+ const added_models: string[] = [];
126
+ const removed_models: string[] = [];
127
+ for (const slug of currBySlug.keys()) {
128
+ if (!prevBySlug.has(slug)) added_models.push(slug);
129
+ }
130
+ for (const slug of prevBySlug.keys()) {
131
+ if (!currBySlug.has(slug)) removed_models.push(slug);
132
+ }
133
+
134
+ const field_deltas: ToolFieldDelta[] = [];
135
+ let has_tool_schema_change = false;
136
+ let has_prompt_tool_change = false;
137
+
138
+ const fieldsToCompare = [
139
+ ...TOOL_SCHEMA_FIELDS,
140
+ "supports_image_detail_original",
141
+ "prompt_tool_mentions",
142
+ ];
143
+
144
+ for (const [slug, prevCfg] of prevBySlug) {
145
+ const currCfg = currBySlug.get(slug);
146
+ if (!currCfg) continue;
147
+ for (const field of fieldsToCompare) {
148
+ const p = (prevCfg as unknown as Record<string, unknown>)[field];
149
+ const c = (currCfg as unknown as Record<string, unknown>)[field];
150
+ if (!equal(p, c)) {
151
+ field_deltas.push({ slug, field, previous: p, current: c });
152
+ if (TOOL_SCHEMA_FIELDS.has(field)) has_tool_schema_change = true;
153
+ if (field === "prompt_tool_mentions") has_prompt_tool_change = true;
154
+ }
155
+ }
156
+ }
157
+
158
+ return {
159
+ added_models,
160
+ removed_models,
161
+ field_deltas,
162
+ has_tool_schema_change,
163
+ has_prompt_tool_change,
164
+ };
165
+ }
166
+
167
+ export type Verdict =
168
+ | "no-op"
169
+ | "prompt-only update"
170
+ | "tool-schema update needed"
171
+ | "tool-surface review needed"
172
+ | "manual review required";
173
+
174
+ export interface VerdictInput {
175
+ models_diff: ModelsDiff | null;
176
+ prompt_md_changed: boolean;
177
+ tools_dir_changed: boolean;
178
+ apply_patch_dir_changed: boolean;
179
+ parse_error: boolean;
180
+ }
181
+
182
+ /** Decide which verdict best describes the upstream change set. */
183
+ export function decideVerdict(input: VerdictInput): Verdict {
184
+ if (input.parse_error) return "manual review required";
185
+ if (!input.models_diff) return "manual review required";
186
+
187
+ const removedModels = input.models_diff.removed_models.length > 0;
188
+ if (removedModels) return "manual review required";
189
+
190
+ if (input.models_diff.has_tool_schema_change) {
191
+ return "tool-schema update needed";
192
+ }
193
+
194
+ if (input.tools_dir_changed || input.apply_patch_dir_changed) {
195
+ return "tool-surface review needed";
196
+ }
197
+
198
+ if (input.models_diff.has_prompt_tool_change || input.prompt_md_changed) {
199
+ return "prompt-only update";
200
+ }
201
+
202
+ if (input.models_diff.field_deltas.length > 0) {
203
+ return "prompt-only update";
204
+ }
205
+
206
+ return "no-op";
207
+ }
@@ -0,0 +1,273 @@
1
+ /**
2
+ * Renders a GitHub issue body summarizing a Codex release's impact on the
3
+ * letta-code harness.
4
+ */
5
+
6
+ import type { ModelsDiff, Verdict } from "./diff-models-json.ts";
7
+
8
+ const ACTION_REVIEWERS = ["@kl2806", "@devanshrj"];
9
+
10
+ export interface PathChangeSummary {
11
+ path: string;
12
+ /** One-line commit subjects under this path between the two refs. */
13
+ commits: string[];
14
+ }
15
+
16
+ export interface RenderInput {
17
+ previous_tag: string;
18
+ current_tag: string;
19
+ release_url: string;
20
+ release_notes_md: string;
21
+ verdict: Verdict;
22
+ models_diff: ModelsDiff | null;
23
+ prompt_md_changed: boolean;
24
+ prompt_md_diff_preview: string | null;
25
+ path_changes: PathChangeSummary[];
26
+ workflow_run_url: string;
27
+ }
28
+
29
+ /** Mapping from upstream concept to local letta-code file(s) to inspect. */
30
+ const LOCAL_MIRRORS: Record<string, string[]> = {
31
+ apply_patch_tool_type: [
32
+ "src/agent/prompts/source_codex.md",
33
+ "src/tools/impl/",
34
+ "src/tools/schemas/ApplyPatch.json",
35
+ "src/tools/descriptions/ApplyPatch.md",
36
+ ],
37
+ web_search_tool_type: ["src/tools/toolDefinitions.ts", "src/tools/impl/"],
38
+ shell_type: [
39
+ "src/tools/toolDefinitions.ts",
40
+ "src/tools/schemas/ShellCommand.json",
41
+ "src/tools/descriptions/ShellCommand.md",
42
+ "src/tools/impl/",
43
+ "src/agent/prompts/source_codex.md",
44
+ ],
45
+ supports_search_tool: ["src/tools/filter.ts", "src/tools/toolDefinitions.ts"],
46
+ supports_parallel_tool_calls: ["src/agent/prompts/source_codex.md"],
47
+ experimental_supported_tools: [
48
+ "src/tools/toolDefinitions.ts",
49
+ "src/tools/schemas/",
50
+ "src/tools/descriptions/",
51
+ "src/tools/impl/",
52
+ ],
53
+ input_modalities: ["src/tools/toolDefinitions.ts", "src/tools/impl/"],
54
+ truncation_policy: ["src/agent/prompts/source_codex.md"],
55
+ prompt_tool_mentions: ["src/agent/prompts/source_codex.md"],
56
+ };
57
+
58
+ function localMirror(field: string): string {
59
+ const mirrors = LOCAL_MIRRORS[field];
60
+ if (!mirrors || mirrors.length === 0) return "—";
61
+ return mirrors.join(", ");
62
+ }
63
+
64
+ function fmtVal(v: unknown): string {
65
+ if (v === undefined) return "_(unset)_";
66
+ if (v === null) return "`null`";
67
+ const s = JSON.stringify(v);
68
+ return s.length > 80 ? `\`${s.slice(0, 77)}...\`` : `\`${s}\``;
69
+ }
70
+
71
+ export function renderTitle(input: RenderInput): string {
72
+ return `[codex-watch] openai/codex ${input.current_tag} — ${input.verdict}`;
73
+ }
74
+
75
+ export function renderBody(input: RenderInput): string {
76
+ const parts: string[] = [];
77
+
78
+ // Verdict
79
+ parts.push(`## Verdict`);
80
+ parts.push("");
81
+ parts.push(`**${input.verdict}**`);
82
+ parts.push("");
83
+ parts.push(verdictRationale(input));
84
+ parts.push("");
85
+ if (needsReviewerAttention(input)) {
86
+ parts.push(`Reviewers: ${ACTION_REVIEWERS.join(" ")}`);
87
+ parts.push("");
88
+ }
89
+
90
+ // Tool / schema deltas
91
+ parts.push(`## Tool / schema deltas`);
92
+ parts.push("");
93
+ if (
94
+ input.models_diff &&
95
+ (input.models_diff.field_deltas.length > 0 ||
96
+ input.models_diff.added_models.length > 0 ||
97
+ input.models_diff.removed_models.length > 0)
98
+ ) {
99
+ if (input.models_diff.added_models.length > 0) {
100
+ parts.push(
101
+ `**Added models:** ${input.models_diff.added_models.map((s) => `\`${s}\``).join(", ")}`,
102
+ );
103
+ parts.push("");
104
+ }
105
+ if (input.models_diff.removed_models.length > 0) {
106
+ parts.push(
107
+ `**Removed models:** ${input.models_diff.removed_models.map((s) => `\`${s}\``).join(", ")}`,
108
+ );
109
+ parts.push("");
110
+ }
111
+ if (input.models_diff.field_deltas.length > 0) {
112
+ parts.push(
113
+ "| Model | Field | Previous | New | Affected letta-code path |",
114
+ );
115
+ parts.push(
116
+ "|-------|-------|----------|-----|--------------------------|",
117
+ );
118
+ for (const d of input.models_diff.field_deltas) {
119
+ parts.push(
120
+ `| \`${d.slug}\` | \`${d.field}\` | ${fmtVal(d.previous)} | ${fmtVal(d.current)} | ${localMirror(d.field)} |`,
121
+ );
122
+ }
123
+ parts.push("");
124
+ }
125
+ } else {
126
+ parts.push("_No tool-field deltas detected in `models.json`._");
127
+ parts.push("");
128
+ }
129
+
130
+ // Upstream changes by path
131
+ parts.push(`## Upstream changes by path`);
132
+ parts.push("");
133
+ if (input.path_changes.length > 0) {
134
+ for (const p of input.path_changes) {
135
+ parts.push(`### \`${p.path}\``);
136
+ if (p.commits.length === 0) {
137
+ parts.push("_No commits touching this path between releases._");
138
+ } else {
139
+ for (const c of p.commits) parts.push(`- ${c}`);
140
+ }
141
+ parts.push("");
142
+ }
143
+ } else {
144
+ parts.push("_No changes under watched paths._");
145
+ parts.push("");
146
+ }
147
+
148
+ const impacts = potentialImpacts(input);
149
+ if (impacts.length > 0) {
150
+ parts.push(`## Potential letta-code impact`);
151
+ parts.push("");
152
+ for (const impact of impacts) parts.push(`- ${impact}`);
153
+ parts.push("");
154
+ }
155
+
156
+ // Prompt diff preview
157
+ if (input.prompt_md_changed && input.prompt_md_diff_preview) {
158
+ parts.push(`## \`codex-rs/models-manager/prompt.md\` diff (preview)`);
159
+ parts.push("");
160
+ parts.push("```diff");
161
+ parts.push(input.prompt_md_diff_preview);
162
+ parts.push("```");
163
+ parts.push("");
164
+ }
165
+
166
+ // Suggested actions
167
+ parts.push(`## Suggested actions`);
168
+ parts.push("");
169
+ for (const a of suggestedActions(input)) parts.push(`- [ ] ${a}`);
170
+ parts.push("");
171
+
172
+ // Provenance
173
+ parts.push(`## How this was generated`);
174
+ parts.push("");
175
+ parts.push(`- Release/notes: ${input.release_url}`);
176
+ parts.push(
177
+ `- Compare: https://github.com/openai/codex/compare/${input.previous_tag}...${input.current_tag}`,
178
+ );
179
+ parts.push(`- Workflow run: ${input.workflow_run_url}`);
180
+
181
+ return parts.join("\n");
182
+ }
183
+
184
+ function needsReviewerAttention(input: RenderInput): boolean {
185
+ return input.verdict !== "no-op";
186
+ }
187
+
188
+ function verdictRationale(input: RenderInput): string {
189
+ switch (input.verdict) {
190
+ case "no-op":
191
+ return "No watched tool-surface changes detected.";
192
+ case "prompt-only update":
193
+ return "Prompt text or tool mentions changed; `src/agent/prompts/source_codex.md` may need re-extraction.";
194
+ case "tool-schema update needed":
195
+ return "Upstream changed tool-config fields in `models.json`. Review local mirrors only where the model contract changed.";
196
+ case "tool-surface review needed":
197
+ return "No `models.json` tool-field deltas, but upstream tool implementation paths changed. Check whether letta-code has a mirror.";
198
+ case "manual review required":
199
+ return "Could not classify automatically; review the upstream diff manually.";
200
+ }
201
+ }
202
+
203
+ function hasPath(input: RenderInput, prefix: string): boolean {
204
+ return input.path_changes.some((p) => p.path.startsWith(prefix));
205
+ }
206
+
207
+ function potentialImpacts(input: RenderInput): string[] {
208
+ const out: string[] = [];
209
+
210
+ if (input.models_diff?.field_deltas.length) {
211
+ out.push(
212
+ "`models.json` field deltas: inspect listed local mirrors for schema, availability, or prompt drift.",
213
+ );
214
+ }
215
+
216
+ if (hasPath(input, "codex-rs/core/src/tools")) {
217
+ out.push(
218
+ "Core tools changed: compare relevant commits against `src/tools/toolDefinitions.ts`, `schemas/`, `descriptions/`, `impl/`, and `manager.ts`.",
219
+ );
220
+ out.push(
221
+ "Likely mirrors: `view_image` → `ViewImage`, shell/exec → `ShellCommand`/`Bash`, `apply_patch` → `ApplyPatch`; MCP/search/approval may be upstream-only.",
222
+ );
223
+ }
224
+
225
+ if (hasPath(input, "codex-rs/apply-patch")) {
226
+ out.push(
227
+ "`apply-patch` changed: compare parser/failure semantics with `ApplyPatch.ts`, `MemoryApplyPatch.ts`, descriptions, and tests.",
228
+ );
229
+ }
230
+
231
+ if (input.prompt_md_changed) {
232
+ out.push(
233
+ "Prompt changed: reconcile `src/agent/prompts/source_codex.md` and README provenance.",
234
+ );
235
+ }
236
+
237
+ if (input.verdict === "no-op") {
238
+ out.push(
239
+ "No watched tool surfaces changed. Skim release notes, then close if nothing applies.",
240
+ );
241
+ }
242
+
243
+ return out;
244
+ }
245
+
246
+ function suggestedActions(input: RenderInput): string[] {
247
+ const out: string[] = [];
248
+ if (input.verdict === "no-op") {
249
+ out.push("Skim release notes; close if nothing stands out.");
250
+ return out;
251
+ }
252
+ if (input.verdict === "prompt-only update" || input.prompt_md_changed) {
253
+ out.push(
254
+ "Re-extract/reconcile `src/agent/prompts/source_codex.md` if prompt semantics changed.",
255
+ );
256
+ }
257
+ if (input.verdict === "tool-schema update needed") {
258
+ out.push(
259
+ "Inspect each `models.json` delta; update local schema/description/impl/tests only if the exposed contract changed.",
260
+ );
261
+ }
262
+ if (input.verdict === "tool-surface review needed") {
263
+ out.push(
264
+ "Review watched-path commits; if a change has a letta-code mirror, update schema/description/impl/prompt/tests together.",
265
+ );
266
+ }
267
+ if (input.verdict === "manual review required") {
268
+ out.push(
269
+ "Diff `openai/codex` between the two tags manually and decide whether letta-code needs to react.",
270
+ );
271
+ }
272
+ return out;
273
+ }
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Renames all camelCase/PascalCase .ts files in src/ to kebab-case.
4
+ * .tsx files are skipped — PascalCase is correct for React components.
5
+ * .d.ts files are skipped.
6
+ * Runs git mv to preserve history.
7
+ */
8
+
9
+ import { execSync } from "node:child_process";
10
+ import { existsSync, readdirSync, statSync } from "node:fs";
11
+ import { basename, dirname, join } from "node:path";
12
+
13
+ function toKebab(s) {
14
+ return s
15
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
16
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2")
17
+ .toLowerCase();
18
+ }
19
+
20
+ function* walkTs(dir) {
21
+ for (const entry of readdirSync(dir)) {
22
+ const full = join(dir, entry);
23
+ if (statSync(full).isDirectory()) {
24
+ yield* walkTs(full);
25
+ } else if (entry.endsWith(".ts") && !entry.endsWith(".d.ts")) {
26
+ yield full;
27
+ }
28
+ }
29
+ }
30
+
31
+ const renames = [];
32
+
33
+ for (const file of walkTs("src")) {
34
+ const dir = dirname(file);
35
+ const filename = basename(file);
36
+ // Split on first dot to handle e.g. "FooBar.test.ts"
37
+ const dotIdx = filename.indexOf(".");
38
+ const base = filename.slice(0, dotIdx);
39
+ const ext = filename.slice(dotIdx); // e.g. ".ts" or ".test.ts"
40
+ const kebab = toKebab(base);
41
+ if (kebab !== base) {
42
+ const newFilename = kebab + ext;
43
+ const newFile = join(dir, newFilename);
44
+ renames.push({ from: file, to: newFile });
45
+ }
46
+ }
47
+
48
+ console.log(`Found ${renames.length} files to rename.\n`);
49
+
50
+ for (const { from, to } of renames) {
51
+ if (existsSync(to)) {
52
+ console.warn(`SKIP (target exists): ${from} -> ${to}`);
53
+ continue;
54
+ }
55
+ execSync(`git mv "${from}" "${to}"`, { stdio: "inherit" });
56
+ }
57
+
58
+ console.log(`\nDone. ${renames.length} files renamed.`);
59
+ console.log("\nNext: run the import update script to fix all references.");
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+ const { execSync } = require("node:child_process");
3
+ const { readdirSync, statSync } = require("node:fs");
4
+ const path = require("node:path");
5
+
6
+ // Unit test directories — bun discovers *.test.ts / *.test.tsx within each.
7
+ // Listed explicitly so we skip src/integration-tests (API-gated) and avoid
8
+ // shell expansion that can exceed the Windows command-line length limit.
9
+ const dirs = [
10
+ "src/agent",
11
+ "src/auth",
12
+ "src/backend",
13
+ "src/cli",
14
+ "src/cron",
15
+ "src/experiments",
16
+ "src/hooks",
17
+ "src/lsp",
18
+ "src/permissions",
19
+ "src/providers",
20
+ "src/queue",
21
+ "src/ralph",
22
+ "src/reminders",
23
+ "src/skills",
24
+ "src/telemetry",
25
+ "src/test-utils",
26
+ "src/tools",
27
+ "src/types",
28
+ "src/updater",
29
+ "src/utils",
30
+ "src/websocket",
31
+ // Root-level test files (not inside a subdirectory)
32
+ "src/*.test.ts",
33
+ ];
34
+
35
+ // slack-media.test.ts imports the real ./slack/media module. slack-adapter.test.ts
36
+ // calls mock.module("./slack/media") which in Bun 1.3.x poisons the shared module
37
+ // registry across parallel workers. We run slack-media in an isolated process first,
38
+ // then run src/channels with all OTHER test files (excluding slack-media).
39
+ function findTestFiles(dir, exclude) {
40
+ const results = [];
41
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
42
+ const full = path.join(dir, entry.name);
43
+ if (entry.isDirectory()) {
44
+ results.push(...findTestFiles(full, exclude));
45
+ } else if (
46
+ (entry.name.endsWith(".test.ts") || entry.name.endsWith(".test.tsx")) &&
47
+ !exclude.includes(full.replace(/\\/g, "/"))
48
+ ) {
49
+ results.push(full.replace(/\\/g, "/"));
50
+ }
51
+ }
52
+ return results;
53
+ }
54
+
55
+ const channelTestFiles = findTestFiles("src/channels", [
56
+ "src/channels/slack-media.test.ts",
57
+ ]);
58
+
59
+ const opts = { stdio: "inherit", shell: process.platform === "win32" };
60
+ let exitCode = 0;
61
+
62
+ // Run slack-media in isolation first (clean module registry)
63
+ try {
64
+ execSync("bun test src/channels/slack-media.test.ts --timeout 15000", opts);
65
+ } catch (e) {
66
+ exitCode = e.status ?? 1;
67
+ }
68
+
69
+ // Run everything else
70
+ try {
71
+ execSync(
72
+ `bun test ${[...dirs, ...channelTestFiles].join(" ")} --timeout 15000`,
73
+ opts,
74
+ );
75
+ } catch (e) {
76
+ exitCode = e.status ?? 1;
77
+ }
78
+
79
+ process.exit(exitCode);