jeo-code 0.10.0 → 0.11.1

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.
@@ -1974,239 +1974,250 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
1974
1974
  };
1975
1975
 
1976
1976
  if (isOneShot) {
1977
- let messageContent = joinedArgs;
1978
- if (!process.stdin.isTTY && joinedArgs.length === 0) {
1979
- messageContent = (await Bun.stdin.text()).trim();
1980
- }
1981
- if (!messageContent) {
1982
- console.log("No input provided.");
1983
- return;
1984
- }
1985
- // One-shot piped/`-p` input that is a control slash command must be handled
1986
- // HERE — never forwarded to the model. `echo "/clear" | jeo` (and Ctrl-D after
1987
- // a piped command) previously sent the literal "/clear" to the LLM as a prompt:
1988
- // a slow, flaky, and semantically wrong round-trip. These no-arg session/system
1989
- // commands have a meaningful one-shot effect (or are simple no-ops) and exit.
1990
- {
1991
- const cmd = messageContent.trim();
1992
- if (cmd === "/exit" || cmd === "/quit") {
1993
- return;
1994
- }
1995
- if (cmd === "/clear" || cmd === "/session new" || cmd === "/session drop" || cmd === "/session delete") {
1996
- // Reset history to just the system prompt and overwrite the session file so
1997
- // the persisted transcript matches (a fresh session for /session new and /session drop).
1998
- history.length = 1;
1999
- if (sessionId && !flags.noSession) {
2000
- try {
2001
- sessionId = (await createSession(cwd, undefined, sessionModel)).id;
2002
- } catch { /* best-effort: in-memory clear already done */ }
2003
- }
2004
- console.log("(history cleared)");
2005
- return;
2006
- }
2007
- if (cmd === "/" || cmd === "/?" || cmd === "/help") {
2008
- for (const line of formatSlashCommandList("/", skillSlashDetails)) console.log(line);
2009
- console.log("Tools: read / write / edit / bash / find / search. Sessions persist to .jeo/sessions/.");
2010
- return;
2011
- }
2012
- if (cmd === "/usage") {
2013
- const cfg = await readGlobalConfig();
2014
- const ctx: SlashContext = { history, sessionModel, sessionId, cwd, config: cfg };
2015
- const result = handleUsage(ctx, sessionUsage);
2016
- if (result && "lines" in result) for (const line of result.lines) console.log(line);
2017
- return;
2018
- }
2019
- if (cmd === "/context") {
2020
- const cfg = await readGlobalConfig();
2021
- const ctx: SlashContext = { history, sessionModel, sessionId, cwd, config: cfg };
2022
- const result = await handleContext(ctx);
2023
- if (result && "lines" in result) for (const line of result.lines) console.log(line);
2024
- return;
2025
- }
2026
- if (cmd === "/tools") {
2027
- const cfg = await readGlobalConfig();
2028
- const ctx: SlashContext = { history, sessionModel, sessionId, cwd, config: cfg };
2029
- const result = await handleTools(ctx);
2030
- if (result && "lines" in result) for (const line of result.lines) console.log(line);
2031
- return;
2032
- }
2033
- if (cmd === "/hotkeys") {
2034
- const cfg = await readGlobalConfig();
2035
- const ctx: SlashContext = { history, sessionModel, sessionId, cwd, config: cfg };
2036
- const result = handleHotkeys(ctx);
2037
- if (result && "lines" in result) for (const line of result.lines) console.log(line);
2038
- return;
1977
+ // A one-shot run has ~17 exit points below. The session notify endpoint (a
1978
+ // listening socket + a discovery file the Telegram daemon polls) is created for
1979
+ // the WHOLE launch lifetime up at `startSessionNotifyEndpoint`, but its teardown
1980
+ // lives on the interactive REPL's exit path — which none of those 17 returns
1981
+ // reach. The endpoint's socket/timer are unref'd so a leak can no longer HANG the
1982
+ // process, but a one-shot run would still leave a stale discovery file behind for
1983
+ // the daemon to keep dialling. One `finally` covers every exit point.
1984
+ try {
1985
+ let messageContent = joinedArgs;
1986
+ if (!process.stdin.isTTY && joinedArgs.length === 0) {
1987
+ messageContent = (await Bun.stdin.text()).trim();
2039
1988
  }
2040
- if (cmd === "/handoff" || cmd.startsWith("/handoff ")) {
2041
- const parsed = parseHandoffCommand(cmd) ?? {};
2042
- const cfg = await readGlobalConfig();
2043
- const activeModel = sessionModel || defaultModel;
2044
- // `Config` (state.ts) predates config-schema.ts's `compaction` field and is
2045
- // out of this change's scope — read the passthrough-preserved value via a
2046
- // narrow structural cast instead of widening the shared interface.
2047
- const focus = parsed.focus ?? (cfg as { compaction?: { handoffFocus?: string } }).compaction?.handoffFocus;
2048
- const res = await buildHandoffDocument(history, { model: activeModel, focus });
2049
- for (const line of handoffLines(res, process.stdout.columns)) console.log(line);
1989
+ if (!messageContent) {
1990
+ console.log("No input provided.");
2050
1991
  return;
2051
1992
  }
2052
- if (cmd === "/theme" || cmd.startsWith("/theme ")) {
2053
- const decision = decideThemeSlash(cmd, listThemes(), resolveTheme().name);
2054
- for (const line of decision.lines) console.log(line);
2055
- if (decision.kind === "set") {
2056
- process.env.JEO_TUI_THEME = decision.themeName!;
2057
- await saveConfigPatch(raw => ({ theme: decision.themeName }));
2058
- // No refreshUiTheme() here — one-shot mode returns immediately after this
2059
- // block, so there is no live TUI paint state to re-resolve (and
2060
- // refreshUiTheme is declared later in this closure, out of scope here).
1993
+ // One-shot piped/`-p` input that is a control slash command must be handled
1994
+ // HERE — never forwarded to the model. `echo "/clear" | jeo` (and Ctrl-D after
1995
+ // a piped command) previously sent the literal "/clear" to the LLM as a prompt:
1996
+ // a slow, flaky, and semantically wrong round-trip. These no-arg session/system
1997
+ // commands have a meaningful one-shot effect (or are simple no-ops) and exit.
1998
+ {
1999
+ const cmd = messageContent.trim();
2000
+ if (cmd === "/exit" || cmd === "/quit") {
2001
+ return;
2061
2002
  }
2062
- return;
2063
- }
2064
- if (cmd === "/wiki" || cmd.startsWith("/wiki ")) {
2065
- const decision = decideWikiSlash(
2066
- cmd,
2067
- resolveWikiRoot(await readGlobalConfig()),
2068
- !!jeoEnv("WIKI_ROOT"),
2069
- );
2070
- for (const line of decision.lines) console.log(line);
2071
- if (decision.kind === "clear") {
2072
- await saveConfigPatch(() => ({ wikiRoot: undefined }));
2073
- delete process.env.JEO_WIKI_ROOT;
2074
- sessionWikiRoot = undefined;
2075
- await refreshSessionMemory("");
2076
- } else if (decision.kind === "set") {
2077
- await saveConfigPatch(() => ({ wikiRoot: decision.persistArg }));
2078
- process.env.JEO_WIKI_ROOT = decision.root;
2079
- sessionWikiRoot = decision.root;
2080
- await refreshSessionMemory("");
2003
+ if (cmd === "/clear" || cmd === "/session new" || cmd === "/session drop" || cmd === "/session delete") {
2004
+ // Reset history to just the system prompt and overwrite the session file so
2005
+ // the persisted transcript matches (a fresh session for /session new and /session drop).
2006
+ history.length = 1;
2007
+ if (sessionId && !flags.noSession) {
2008
+ try {
2009
+ sessionId = (await createSession(cwd, undefined, sessionModel)).id;
2010
+ } catch { /* best-effort: in-memory clear already done */ }
2011
+ }
2012
+ console.log("(history cleared)");
2013
+ return;
2081
2014
  }
2082
- return;
2083
- }
2084
- if (cmd === "/config" || cmd === "/settings") {
2085
- for (const line of await buildConfigPanelLines({ sessionModel, sessionThinking, sessionId })) console.log(line);
2086
- return;
2087
- }
2088
- if (cmd === "/evolve") {
2089
- await runEvolveSimulation(animateAsciiArt);
2090
- return;
2091
- }
2092
- }
2093
- // One skill run (bundle workflow → engine; regular skill → agent turn). Shared by the
2094
- // single-invocation path and the `$a $b …` chain path so every `$` skill actually runs.
2095
- const runOneSkillShot = async (inv: SkillInvocation): Promise<void> => {
2096
- const isBundleWorkflow = isWorkflowSkill(inv.skill.name);
2097
- if (isBundleWorkflow) {
2098
- const startMsg: Message = {
2099
- role: "system",
2100
- content: `[workflow:${inv.skill.name}:start]${inv.intent ? ` intent: ${inv.intent}` : ""}`
2101
- };
2102
- history.push(startMsg);
2103
- if (sessionId) {
2104
- await appendMessage(sessionId, startMsg, cwd);
2015
+ if (cmd === "/" || cmd === "/?" || cmd === "/help") {
2016
+ for (const line of formatSlashCommandList("/", skillSlashDetails)) console.log(line);
2017
+ console.log("Tools: read / write / edit / bash / find / search. Sessions persist to .jeo/sessions/.");
2018
+ return;
2105
2019
  }
2020
+ if (cmd === "/usage") {
2021
+ const cfg = await readGlobalConfig();
2022
+ const ctx: SlashContext = { history, sessionModel, sessionId, cwd, config: cfg };
2023
+ const result = handleUsage(ctx, sessionUsage);
2024
+ if (result && "lines" in result) for (const line of result.lines) console.log(line);
2025
+ return;
2026
+ }
2027
+ if (cmd === "/context") {
2028
+ const cfg = await readGlobalConfig();
2029
+ const ctx: SlashContext = { history, sessionModel, sessionId, cwd, config: cfg };
2030
+ const result = await handleContext(ctx);
2031
+ if (result && "lines" in result) for (const line of result.lines) console.log(line);
2032
+ return;
2033
+ }
2034
+ if (cmd === "/tools") {
2035
+ const cfg = await readGlobalConfig();
2036
+ const ctx: SlashContext = { history, sessionModel, sessionId, cwd, config: cfg };
2037
+ const result = await handleTools(ctx);
2038
+ if (result && "lines" in result) for (const line of result.lines) console.log(line);
2039
+ return;
2040
+ }
2041
+ if (cmd === "/hotkeys") {
2042
+ const cfg = await readGlobalConfig();
2043
+ const ctx: SlashContext = { history, sessionModel, sessionId, cwd, config: cfg };
2044
+ const result = handleHotkeys(ctx);
2045
+ if (result && "lines" in result) for (const line of result.lines) console.log(line);
2046
+ return;
2047
+ }
2048
+ if (cmd === "/handoff" || cmd.startsWith("/handoff ")) {
2049
+ const parsed = parseHandoffCommand(cmd) ?? {};
2050
+ const cfg = await readGlobalConfig();
2051
+ const activeModel = sessionModel || defaultModel;
2052
+ // `Config` (state.ts) predates config-schema.ts's `compaction` field and is
2053
+ // out of this change's scope — read the passthrough-preserved value via a
2054
+ // narrow structural cast instead of widening the shared interface.
2055
+ const focus = parsed.focus ?? (cfg as { compaction?: { handoffFocus?: string } }).compaction?.handoffFocus;
2056
+ const res = await buildHandoffDocument(history, { model: activeModel, focus });
2057
+ for (const line of handoffLines(res, process.stdout.columns)) console.log(line);
2058
+ return;
2059
+ }
2060
+ if (cmd === "/theme" || cmd.startsWith("/theme ")) {
2061
+ const decision = decideThemeSlash(cmd, listThemes(), resolveTheme().name);
2062
+ for (const line of decision.lines) console.log(line);
2063
+ if (decision.kind === "set") {
2064
+ process.env.JEO_TUI_THEME = decision.themeName!;
2065
+ await saveConfigPatch(raw => ({ theme: decision.themeName }));
2066
+ // No refreshUiTheme() here — one-shot mode returns immediately after this
2067
+ // block, so there is no live TUI paint state to re-resolve (and
2068
+ // refreshUiTheme is declared later in this closure, out of scope here).
2069
+ }
2070
+ return;
2071
+ }
2072
+ if (cmd === "/wiki" || cmd.startsWith("/wiki ")) {
2073
+ const decision = decideWikiSlash(
2074
+ cmd,
2075
+ resolveWikiRoot(await readGlobalConfig()),
2076
+ !!jeoEnv("WIKI_ROOT"),
2077
+ );
2078
+ for (const line of decision.lines) console.log(line);
2079
+ if (decision.kind === "clear") {
2080
+ await saveConfigPatch(() => ({ wikiRoot: undefined }));
2081
+ delete process.env.JEO_WIKI_ROOT;
2082
+ sessionWikiRoot = undefined;
2083
+ await refreshSessionMemory("");
2084
+ } else if (decision.kind === "set") {
2085
+ await saveConfigPatch(() => ({ wikiRoot: decision.persistArg }));
2086
+ process.env.JEO_WIKI_ROOT = decision.root;
2087
+ sessionWikiRoot = decision.root;
2088
+ await refreshSessionMemory("");
2089
+ }
2090
+ return;
2091
+ }
2092
+ if (cmd === "/config" || cmd === "/settings") {
2093
+ for (const line of await buildConfigPanelLines({ sessionModel, sessionThinking, sessionId })) console.log(line);
2094
+ return;
2095
+ }
2096
+ if (cmd === "/evolve") {
2097
+ await runEvolveSimulation(animateAsciiArt);
2098
+ return;
2099
+ }
2100
+ }
2101
+ // One skill run (bundle workflow → engine; regular skill → agent turn). Shared by the
2102
+ // single-invocation path and the `$a $b …` chain path so every `$` skill actually runs.
2103
+ const runOneSkillShot = async (inv: SkillInvocation): Promise<void> => {
2104
+ const isBundleWorkflow = isWorkflowSkill(inv.skill.name);
2105
+ if (isBundleWorkflow) {
2106
+ const startMsg: Message = {
2107
+ role: "system",
2108
+ content: `[workflow:${inv.skill.name}:start]${inv.intent ? ` intent: ${inv.intent}` : ""}`
2109
+ };
2110
+ history.push(startMsg);
2111
+ if (sessionId) {
2112
+ await appendMessage(sessionId, startMsg, cwd);
2113
+ }
2106
2114
 
2107
- const harness = createInFlightAbortHarness({
2108
- captureEsc: false,
2109
- onAbortNotice: msg => console.log(msg),
2110
- onHardExit: () => forceExitFromCtrlC(),
2111
- });
2112
- const ac = harness.controller;
2115
+ const harness = createInFlightAbortHarness({
2116
+ captureEsc: false,
2117
+ onAbortNotice: msg => console.log(msg),
2118
+ onHardExit: () => forceExitFromCtrlC(),
2119
+ });
2120
+ const ac = harness.controller;
2113
2121
 
2114
- const opts = {
2115
- cwd,
2116
- signal: ac.signal,
2117
- onProgress: (e: { skill: string; phase: string; detail?: string }) => console.log(`[workflow:${e.skill}] ${e.phase}${e.detail ? ` — ${e.detail}` : ""}`),
2118
- io: {
2119
- output: (line: string) => {
2120
- console.log(line);
2122
+ const opts = {
2123
+ cwd,
2124
+ signal: ac.signal,
2125
+ onProgress: (e: { skill: string; phase: string; detail?: string }) => console.log(`[workflow:${e.skill}] ${e.phase}${e.detail ? ` — ${e.detail}` : ""}`),
2126
+ io: {
2127
+ output: (line: string) => {
2128
+ console.log(line);
2129
+ },
2130
+ // This `$a $b …` one-shot chain runs BEFORE the interactive REPL's
2131
+ // `promptInput`/`rl` exist (this whole dispatch is reached and returns
2132
+ // long before the `const promptInput = …` further down the function
2133
+ // executes) — referencing `promptInput` here throws a TDZ
2134
+ // ReferenceError ("Cannot access 'promptInput' before initialization")
2135
+ // the instant the workflow engine asks its first question. That error
2136
+ // is caught and stashed into session history (never printed to the
2137
+ // terminal), so the visible symptom is just a frozen prompt forever
2138
+ // (the "jeo --tmux $deep-interview hangs" / "$deep-interview hangs"
2139
+ // report). Omitting `input` here is safe and correct: no other
2140
+ // readline exists yet at this point, so deep-interview.ts's own
2141
+ // fallback `createInterface({input: process.stdin, ...})` (used
2142
+ // whenever `opts.io?.input` is absent) has nothing to fight over raw
2143
+ // mode with. The INTERACTIVE `runSkillInvocation` sibling path below
2144
+ // runs AFTER `promptInput` is declared and correctly wires it — do not
2145
+ // "fix" this block by copying that wiring here again.
2121
2146
  },
2122
- // This `$a $b …` one-shot chain runs BEFORE the interactive REPL's
2123
- // `promptInput`/`rl` exist (this whole dispatch is reached and returns
2124
- // long before the `const promptInput = …` further down the function
2125
- // executes) — referencing `promptInput` here throws a TDZ
2126
- // ReferenceError ("Cannot access 'promptInput' before initialization")
2127
- // the instant the workflow engine asks its first question. That error
2128
- // is caught and stashed into session history (never printed to the
2129
- // terminal), so the visible symptom is just a frozen prompt forever
2130
- // (the "jeo --tmux $deep-interview hangs" / "$deep-interview hangs"
2131
- // report). Omitting `input` here is safe and correct: no other
2132
- // readline exists yet at this point, so deep-interview.ts's own
2133
- // fallback `createInterface({input: process.stdin, ...})` (used
2134
- // whenever `opts.io?.input` is absent) has nothing to fight over raw
2135
- // mode with. The INTERACTIVE `runSkillInvocation` sibling path below
2136
- // runs AFTER `promptInput` is declared and correctly wires it — do not
2137
- // "fix" this block by copying that wiring here again.
2138
- },
2139
2147
 
2140
- args: inv.skill.name === "deep-interview" ? (inv.intent ? inv.intent.split(/\s+/) : []) : undefined
2148
+ args: inv.skill.name === "deep-interview" ? (inv.intent ? inv.intent.split(/\s+/) : []) : undefined
2141
2149
 
2142
- };
2150
+ };
2143
2151
 
2144
- let ok = false;
2145
- let reason: string | undefined;
2146
- try {
2147
- const res: { ok: boolean; reason?: string } = await runWorkflowEngine(inv.skill.name, opts);
2148
- ok = res.ok;
2149
- reason = res.reason;
2150
- } catch (err: any) {
2151
- ok = false;
2152
- reason = err.message;
2153
- } finally {
2154
- harness.dispose();
2155
- }
2152
+ let ok = false;
2153
+ let reason: string | undefined;
2154
+ try {
2155
+ const res: { ok: boolean; reason?: string } = await runWorkflowEngine(inv.skill.name, opts);
2156
+ ok = res.ok;
2157
+ reason = res.reason;
2158
+ } catch (err: any) {
2159
+ ok = false;
2160
+ reason = err.message;
2161
+ } finally {
2162
+ harness.dispose();
2163
+ }
2156
2164
 
2157
- const endMsg: Message = {
2158
- role: "system",
2159
- content: ok
2160
- ? `[workflow:${inv.skill.name}:finish]`
2161
- : `[workflow:${inv.skill.name}:abort]${reason ? ` reason: ${reason}` : ""}`
2162
- };
2163
- if (sessionId) {
2164
- await appendMessage(sessionId, endMsg, cwd);
2165
+ const endMsg: Message = {
2166
+ role: "system",
2167
+ content: ok
2168
+ ? `[workflow:${inv.skill.name}:finish]`
2169
+ : `[workflow:${inv.skill.name}:abort]${reason ? ` reason: ${reason}` : ""}`
2170
+ };
2171
+ if (sessionId) {
2172
+ await appendMessage(sessionId, endMsg, cwd);
2173
+ }
2174
+ return;
2165
2175
  }
2166
- return;
2167
- }
2168
2176
 
2169
- const useOneShotTui = shouldUseOneShotTui(flags.noTui);
2170
- if (!useOneShotTui) {
2171
- console.log(`▶ Running skill: ${inv.skill.name}${inv.intent ? ` — ${inv.intent}` : ""}`);
2172
- }
2173
- const task = buildSkillTask(inv.skill, inv.intent, inv.invokedAs);
2174
- const { reply, rendered, usage } = await runTurn(task, useOneShotTui, undefined, { userCard: null });
2175
- if (!rendered) console.log(stripMarkdown(renderMarkdownTables(reply)) + usage);
2176
- else if (usage) console.log(usage.trim());
2177
- };
2177
+ const useOneShotTui = shouldUseOneShotTui(flags.noTui);
2178
+ if (!useOneShotTui) {
2179
+ console.log(`▶ Running skill: ${inv.skill.name}${inv.intent ? ` — ${inv.intent}` : ""}`);
2180
+ }
2181
+ const task = buildSkillTask(inv.skill, inv.intent, inv.invokedAs);
2182
+ const { reply, rendered, usage } = await runTurn(task, useOneShotTui, undefined, { userCard: null });
2183
+ if (!rendered) console.log(stripMarkdown(renderMarkdownTables(reply)) + usage);
2184
+ else if (usage) console.log(usage.trim());
2185
+ };
2178
2186
 
2179
- // `$a $b … [intent]` — run every resolved skill in order; a lone `$skill` is a chain of 1.
2180
- const skillChain = parseSkillChain(messageContent, resolvedSkills);
2181
- if (skillChain && skillChain.invocations.length) {
2182
- if (skillChain.unresolved.length) {
2183
- console.log(`(skipping unknown skill${skillChain.unresolved.length > 1 ? "s" : ""}: ${skillChain.unresolved.map(u => `$${u}`).join(", ")})`);
2187
+ // `$a $b … [intent]` — run every resolved skill in order; a lone `$skill` is a chain of 1.
2188
+ const skillChain = parseSkillChain(messageContent, resolvedSkills);
2189
+ if (skillChain && skillChain.invocations.length) {
2190
+ if (skillChain.unresolved.length) {
2191
+ console.log(`(skipping unknown skill${skillChain.unresolved.length > 1 ? "s" : ""}: ${skillChain.unresolved.map(u => `$${u}`).join(", ")})`);
2192
+ }
2193
+ if (skillChain.invocations.length > 1) {
2194
+ console.log(`▶ Chaining ${skillChain.invocations.length} skills: ${skillChain.invocations.map(i => `$${i.skill.name}`).join(" → ")}`);
2195
+ }
2196
+ for (const inv of skillChain.invocations) {
2197
+ await runOneSkillShot(inv);
2198
+ }
2199
+ await maybeDistillOneShot();
2200
+ return;
2184
2201
  }
2185
- if (skillChain.invocations.length > 1) {
2186
- console.log(`▶ Chaining ${skillChain.invocations.length} skills: ${skillChain.invocations.map(i => `$${i.skill.name}`).join(" → ")}`);
2202
+ // A `$skill` invocation resolves to a single skill (by name, declared alias, or unique prefix).
2203
+ const skillInvocation = parseSkillInvocation(messageContent, resolvedSkills);
2204
+ if (skillInvocation) {
2205
+ await runOneSkillShot(skillInvocation);
2206
+ await maybeDistillOneShot();
2207
+ return;
2187
2208
  }
2188
- for (const inv of skillChain.invocations) {
2189
- await runOneSkillShot(inv);
2209
+ try {
2210
+ const { reply, rendered, usage } = await runTurn(messageContent, shouldUseOneShotTui(flags.noTui));
2211
+ if (!rendered) console.log(stripMarkdown(renderMarkdownTables(reply)) + usage);
2212
+ else if (usage) console.log(usage.trim());
2213
+ } catch (err) {
2214
+ console.log(`! ${friendlyProviderError(err)}`);
2190
2215
  }
2191
2216
  await maybeDistillOneShot();
2192
2217
  return;
2218
+ } finally {
2219
+ await sessionNotifyEndpoint?.stop().catch(() => {});
2193
2220
  }
2194
- // A `$skill` invocation resolves to a single skill (by name, declared alias, or unique prefix).
2195
- const skillInvocation = parseSkillInvocation(messageContent, resolvedSkills);
2196
- if (skillInvocation) {
2197
- await runOneSkillShot(skillInvocation);
2198
- await maybeDistillOneShot();
2199
- return;
2200
- }
2201
- try {
2202
- const { reply, rendered, usage } = await runTurn(messageContent, shouldUseOneShotTui(flags.noTui));
2203
- if (!rendered) console.log(stripMarkdown(renderMarkdownTables(reply)) + usage);
2204
- else if (usage) console.log(usage.trim());
2205
- } catch (err) {
2206
- console.log(`! ${friendlyProviderError(err)}`);
2207
- }
2208
- await maybeDistillOneShot();
2209
- return;
2210
2221
  }
2211
2222
 
2212
2223
  // INTERACTIVE mode