@tavily/cli 0.0.0-stage → 1.0.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.
@@ -0,0 +1,348 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ MCP_AUTH_NOTE,
4
+ SKILLS_REPO,
5
+ WORKFLOWS_REPO,
6
+ detectAgents,
7
+ installAgentSkills,
8
+ installMcp
9
+ } from "./chunk-ULNXDG4Q.js";
10
+ import {
11
+ MANAGER_COMMANDS,
12
+ MANAGER_RUNNERS,
13
+ detectPackageManager,
14
+ globalBinDir
15
+ } from "./chunk-V3OJFHHD.js";
16
+ import {
17
+ VERSION
18
+ } from "./chunk-SPBOEASO.js";
19
+ import {
20
+ withSpinner
21
+ } from "./chunk-K76OUMXM.js";
22
+ import {
23
+ sanitizeControl
24
+ } from "./chunk-QHWWSCVM.js";
25
+ import {
26
+ authMethod,
27
+ getApiKey,
28
+ looksHeadless,
29
+ runBrowserLogin,
30
+ saveApiKey,
31
+ saveOauthSession
32
+ } from "./chunk-AERAIFZU.js";
33
+ import {
34
+ resolveJson
35
+ } from "./chunk-IRCF6F3S.js";
36
+ import {
37
+ colors,
38
+ err,
39
+ out
40
+ } from "./chunk-E4N27QWM.js";
41
+ import {
42
+ ExitError,
43
+ messageOf
44
+ } from "./chunk-5JXZZSLS.js";
45
+
46
+ // src/commands/init.ts
47
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "fs";
48
+ import path from "path";
49
+ import { intro, log, outro } from "@clack/prompts";
50
+ async function run(opts, cmd, host, hooks = {}) {
51
+ const json = resolveJson(cmd);
52
+ const requested = [...new Set((opts.agent ?? []).map((name) => name.toLowerCase()))];
53
+ if (opts.skipAuth && opts.apiKey) {
54
+ cmd.error("error: Use either --skip-auth or --api-key, not both.");
55
+ }
56
+ const interactive = host.stdinIsTTY && host.stderrIsTTY && !json && !opts.yes;
57
+ if (interactive) intro("tvly init", { output });
58
+ const cli = await installStep(opts, json, host);
59
+ const result = emptyResult();
60
+ result.install = cli.install;
61
+ let mcpSetup = null;
62
+ try {
63
+ const auth = await ensureAuth(opts, interactive, host, hooks);
64
+ result.auth = auth;
65
+ result.mode = auth.authenticated ? "authenticated" : "keyless";
66
+ result.verification.auth = auth.authenticated;
67
+ } catch (e) {
68
+ if (e instanceof ExitError) throw e;
69
+ return fail(result, "auth", messageOf(e), 3, json);
70
+ }
71
+ const picked = await plan(requested, opts, interactive, host);
72
+ const agents = picked.agents.join(", ");
73
+ for (const stage of ["skills", "workflows"]) {
74
+ if (!picked[stage]) continue;
75
+ const part = result[stage];
76
+ part.agents = picked.agents;
77
+ part.skipped = false;
78
+ const { label } = BUNDLES[stage];
79
+ try {
80
+ const installed = await step(
81
+ interactive,
82
+ `Installing ${label}`,
83
+ `${label} \u2192 ${agents}`,
84
+ () => installAgentSkills(part.source, picked.agents, host)
85
+ );
86
+ part.restart_required = installed.restartRequired;
87
+ } catch (e) {
88
+ return fail(result, stage, messageOf(e), 1, json);
89
+ }
90
+ }
91
+ if (picked.mcp) {
92
+ const { label } = BUNDLES.mcp;
93
+ try {
94
+ mcpSetup = await step(
95
+ interactive,
96
+ `Adding the ${label}`,
97
+ `${label} \u2192 ${agents}`,
98
+ () => installMcp(picked.agents, false, host)
99
+ );
100
+ result.mcp = { agents: mcpSetup.configured, skipped: mcpSetup.configured.length === 0 };
101
+ } catch (e) {
102
+ return fail(result, "mcp", messageOf(e), 1, json);
103
+ }
104
+ }
105
+ result.verification.cli = result.install.on_path || result.install.global;
106
+ if (!result.verification.cli && !opts.skipInstall && !cli.failed) {
107
+ return fail(result, "cli", "tvly is not available on PATH.", 1, json);
108
+ }
109
+ try {
110
+ await step(interactive, "Verifying live search", "Live search verified", async () => {
111
+ if (!await verifyLiveSearch(host)) {
112
+ throw new Error("Tavily search returned no result payload.");
113
+ }
114
+ });
115
+ result.verification.live_search = true;
116
+ } catch (e) {
117
+ return fail(result, "live_search", messageOf(e), 4, json);
118
+ }
119
+ if (cli.failed) {
120
+ return fail(result, "install", INSTALL_INCOMPLETE, 1, json, cli.pathNote);
121
+ }
122
+ result.ok = true;
123
+ if (interactive) outro("Setup complete", { output });
124
+ printResult(result, json, cli.pathNote, mcpSetup);
125
+ }
126
+ var output = process.stderr;
127
+ var BUNDLES = {
128
+ skills: { value: "skills", label: "Tavily skills", hint: "search, extract, crawl, research" },
129
+ workflows: { value: "workflows", label: "Official workflows" },
130
+ mcp: { value: "mcp", label: "MCP server", hint: "hosted, signs in with OAuth" }
131
+ };
132
+ async function step(interactive, label, done, fn) {
133
+ const value = await withSpinner(label, interactive, fn);
134
+ if (interactive) log.success(done, { output });
135
+ return value;
136
+ }
137
+ async function plan(requested, opts, interactive, host) {
138
+ const none = { agents: [], skills: false, workflows: false, mcp: false };
139
+ if (opts.skipSkills) return none;
140
+ const candidates = requested.length ? requested : detectAgents(host);
141
+ if (!candidates.length) {
142
+ if (interactive) {
143
+ log.warn("No coding agents found. Skills and MCP need Claude Code, Cursor, or Codex.", {
144
+ output
145
+ });
146
+ }
147
+ return none;
148
+ }
149
+ const agents = interactive && !requested.length ? await host.prompt.multiselect(
150
+ "Which agents do you want to install to?",
151
+ candidates.map((a) => ({ value: a, label: a })),
152
+ candidates
153
+ ) : candidates;
154
+ if (!agents.length) return none;
155
+ const choices = [BUNDLES.skills, ...opts.skipWorkflows ? [] : [BUNDLES.workflows], BUNDLES.mcp];
156
+ const defaults = choices.map((c) => c.value).filter((v) => v !== "workflows");
157
+ const bundles = interactive ? await host.prompt.multiselect("What should I install?", choices, defaults) : defaults;
158
+ return {
159
+ agents,
160
+ skills: bundles.includes("skills"),
161
+ workflows: bundles.includes("workflows"),
162
+ mcp: bundles.includes("mcp")
163
+ };
164
+ }
165
+ var INSTALL_INCOMPLETE = "Setup incomplete: global CLI installation failed. Other completed steps are preserved.";
166
+ async function installStep(opts, json, host) {
167
+ const onPath = () => host.exec.which("tvly") !== null;
168
+ const step2 = {
169
+ install: { global: false, manager: null, on_path: onPath(), path_updated: false },
170
+ failed: false,
171
+ pathNote: null
172
+ };
173
+ if (opts.skipInstall || step2.install.on_path) return step2;
174
+ const interactive = !json && !opts.yes && host.stdinIsTTY && host.stderrIsTTY;
175
+ if (interactive && !await host.prompt.confirm("Install tvly globally?")) return step2;
176
+ const manager = detectPackageManager(host);
177
+ step2.install.manager = manager;
178
+ const [name, ...args] = MANAGER_COMMANDS[manager];
179
+ const bin = host.exec.which(name);
180
+ if (!bin || !host.exec.run(bin, args, { inherit: json ? "stderr" : true }).ok) {
181
+ err(`Failed to install @tavily/cli globally using ${manager}.`);
182
+ err(`Retry: ${MANAGER_COMMANDS[manager].join(" ")}`);
183
+ err(`You can run without a global install: ${MANAGER_RUNNERS[manager]} <command>`);
184
+ step2.failed = true;
185
+ return step2;
186
+ }
187
+ step2.install.global = true;
188
+ step2.install.on_path = onPath();
189
+ if (!step2.install.on_path) {
190
+ step2.pathNote = await fixPath(manager, interactive, host);
191
+ step2.install.path_updated = step2.pathNote !== null;
192
+ }
193
+ return step2;
194
+ }
195
+ function rcFileForShell(shell, platform) {
196
+ if (shell.includes("zsh")) return "~/.zshrc";
197
+ if (shell.includes("fish")) return "~/.config/fish/config.fish";
198
+ if (shell.includes("bash")) return platform === "darwin" ? "~/.bash_profile" : "~/.bashrc";
199
+ return "~/.profile";
200
+ }
201
+ async function fixPath(manager, interactive, host) {
202
+ const bin = globalBinDir(manager, host);
203
+ err("");
204
+ err(`tvly was installed but isn't on your PATH yet.`);
205
+ if (bin && host.platform === "win32") {
206
+ err("Add this directory to your PATH, then open a new terminal:");
207
+ err(` ${bin}`);
208
+ } else if (bin) {
209
+ const rc = rcFileForShell(host.env.SHELL ?? "", host.platform);
210
+ const line = rc.includes("fish") ? `fish_add_path ${bin}` : `export PATH="${bin}:$PATH"`;
211
+ const question = `Add ${bin} to PATH in ${rc}?`;
212
+ if (interactive && await host.prompt.confirm(question)) {
213
+ const file = path.join(host.home, rc.slice(2));
214
+ if (!existsSync(file) || !readFileSync(file, "utf8").includes(line)) {
215
+ mkdirSync(path.dirname(file), { recursive: true });
216
+ appendFileSync(file, `
217
+ # Added by tvly init
218
+ ${line}
219
+ `);
220
+ }
221
+ err("Restart your terminal, then run tvly.");
222
+ return `Added ${bin} to PATH in ${rc}`;
223
+ }
224
+ err("Add it to your shell, then restart your terminal:");
225
+ err(` echo '${line}' >> ${rc}`);
226
+ } else {
227
+ err(`Add your ${manager} global bin directory to PATH.`);
228
+ }
229
+ err(`Until then you can still run: ${MANAGER_RUNNERS[manager]} <command>`);
230
+ return null;
231
+ }
232
+ async function ensureAuth(opts, interactive, host, hooks) {
233
+ if (opts.apiKey) await saveApiKey(opts.apiKey, host);
234
+ const key = await getApiKey(host);
235
+ if (key) return { authenticated: true, method: authMethod(key, host) };
236
+ if (opts.skipAuth) return { authenticated: false, method: "none" };
237
+ if (interactive && opts.browser === void 0) {
238
+ const method = await host.prompt.select(
239
+ "How do you want to sign in?",
240
+ [
241
+ { value: "browser", label: "Browser login", hint: "recommended" },
242
+ { value: "key", label: "Paste an API key" },
243
+ { value: "keyless", label: "Continue without a key", hint: "rate-limited" }
244
+ ],
245
+ "browser"
246
+ );
247
+ if (method === "key") {
248
+ await saveApiKey((await host.prompt.password("API key")).trim(), host);
249
+ return { authenticated: true, method: "api_key" };
250
+ }
251
+ if (method === "keyless") return { authenticated: false, method: "none" };
252
+ }
253
+ if (opts.browser === void 0 && !host.stdinIsTTY) {
254
+ throw new Error(
255
+ "No Tavily credential found. Use --api-key, --browser, --no-browser, or --skip-auth in non-interactive runs."
256
+ );
257
+ }
258
+ const openBrowser = opts.browser ?? !looksHeadless(host.env, host.platform);
259
+ const session = await (hooks.login ?? runBrowserLogin)({
260
+ openBrowser,
261
+ onStatus: (url) => err(`Open this URL to authenticate: ${sanitizeControl(url)}`),
262
+ fetchImpl: host.fetch,
263
+ openImpl: host.openUrl
264
+ });
265
+ await saveOauthSession(session, host);
266
+ return { authenticated: true, method: "oauth" };
267
+ }
268
+ async function verifyLiveSearch(host) {
269
+ const client = await host.resolveClient(await getApiKey(host), "tavily-cli-init");
270
+ const response = await client.search({ query: "Tavily Search API", max_results: 1 });
271
+ return Array.isArray(response?.results);
272
+ }
273
+ function emptyResult() {
274
+ return {
275
+ ok: false,
276
+ version: VERSION,
277
+ mode: "unknown",
278
+ auth: { authenticated: false, method: "none" },
279
+ skills: { source: SKILLS_REPO, agents: [], restart_required: false, skipped: true },
280
+ workflows: { source: WORKFLOWS_REPO, agents: [], restart_required: false, skipped: true },
281
+ mcp: { agents: [], skipped: true },
282
+ verification: { cli: false, auth: false, live_search: false },
283
+ install: { global: false, manager: null, on_path: false, path_updated: false }
284
+ };
285
+ }
286
+ function fail(result, stage, message, code, json, pathNote = null) {
287
+ result.error = { stage, message };
288
+ printResult(result, json, pathNote);
289
+ throw new ExitError(code);
290
+ }
291
+ function printResult(result, json, pathNote = null, mcp = null) {
292
+ if (json) {
293
+ out(JSON.stringify(result));
294
+ return;
295
+ }
296
+ out();
297
+ if (!result.ok) {
298
+ const message = sanitizeControl(result.error?.message ?? "Initialization failed.");
299
+ out(` ${colors.pink("> Setup failed:")} ${message}`);
300
+ out();
301
+ return;
302
+ }
303
+ out(` ${colors.green("> Tavily is ready")}`);
304
+ const { auth, skills, workflows } = result;
305
+ if (auth.authenticated) out(` ${colors.green("\u2713")} Authenticated via ${auth.method}`);
306
+ else out(` ${colors.yellow("\u2022")} Keyless mode`);
307
+ if (skills.skipped) out(` ${colors.dim("\u2022 Skills skipped")}`);
308
+ else out(` ${colors.green("\u2713")} Tavily skills ready for ${skills.agents.join(", ")}`);
309
+ if (workflows.skipped) out(` ${colors.dim("\u2022 Workflows skipped")}`);
310
+ else out(` ${colors.green("\u2713")} Tavily workflows ready for ${workflows.agents.join(", ")}`);
311
+ if (mcp) {
312
+ if (mcp.configured.length) {
313
+ out(` ${colors.green("\u2713")} Tavily MCP server ready for ${mcp.configured.join(", ")}`);
314
+ }
315
+ for (const { agent, reason } of mcp.skipped) {
316
+ out(` ${colors.dim(`\u2022 MCP skipped for ${agent} (${sanitizeControl(reason)})`)}`);
317
+ }
318
+ }
319
+ out(` ${colors.green("\u2713")} Live search verified`);
320
+ if (result.install.global) {
321
+ out(` ${colors.green("\u2713")} tvly installed globally (${result.install.manager})`);
322
+ }
323
+ if (pathNote) out(` ${colors.green("\u2713")} ${pathNote}`);
324
+ if (skills.restart_required || workflows.restart_required) {
325
+ const restart = skills.restart_required ? skills.agents : workflows.agents;
326
+ out();
327
+ out(` ${colors.dim(`Restart ${restart.join(", ")} to load newly installed skills.`)}`);
328
+ }
329
+ if (mcp?.configured.length) {
330
+ out();
331
+ out(` ${colors.dim(MCP_AUTH_NOTE[mcp.auth])}`);
332
+ }
333
+ out();
334
+ out(" Try:");
335
+ out(` ${colors.green('tvly search "latest AI news"')}`);
336
+ out(` ${colors.green("tvly extract https://example.com")}`);
337
+ out(` ${colors.green('tvly research "your topic"')}`);
338
+ out(` Or ask your agent: "search for the latest AI news"`);
339
+ if (result.mcp.skipped) {
340
+ out();
341
+ out(" Add the hosted Tavily MCP server to your agents:");
342
+ out(` ${colors.green("tvly setup mcp")}`);
343
+ }
344
+ out();
345
+ }
346
+ export {
347
+ run
348
+ };
@@ -0,0 +1,259 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ buildProgram
4
+ } from "./chunk-PZGMUDV2.js";
5
+ import "./chunk-HQ7QBZ5H.js";
6
+ import {
7
+ VERSION
8
+ } from "./chunk-SPBOEASO.js";
9
+ import {
10
+ getApiKeyOrExit
11
+ } from "./chunk-U3B7BNFI.js";
12
+ import {
13
+ sanitizeControl
14
+ } from "./chunk-QHWWSCVM.js";
15
+ import {
16
+ authSource
17
+ } from "./chunk-AERAIFZU.js";
18
+ import "./chunk-IRCF6F3S.js";
19
+ import {
20
+ LOGO,
21
+ colorEnabled,
22
+ colors
23
+ } from "./chunk-E4N27QWM.js";
24
+ import {
25
+ ExitError,
26
+ messageOf
27
+ } from "./chunk-5JXZZSLS.js";
28
+
29
+ // src/repl.ts
30
+ import { createInterface } from "readline/promises";
31
+ import { CommanderError } from "commander";
32
+
33
+ // src/util/shell-words.ts
34
+ var DOUBLE_QUOTE_ESCAPABLE = /* @__PURE__ */ new Set(['"', "\\"]);
35
+ function splitShellWords(line) {
36
+ const words = [];
37
+ let current = "";
38
+ let inWord = false;
39
+ let i = 0;
40
+ while (i < line.length) {
41
+ const ch = line.charAt(i);
42
+ if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
43
+ if (inWord) {
44
+ words.push(current);
45
+ current = "";
46
+ inWord = false;
47
+ }
48
+ i++;
49
+ } else if (ch === "'") {
50
+ const end = line.indexOf("'", i + 1);
51
+ if (end === -1) throw new Error("No closing quotation");
52
+ current += line.slice(i + 1, end);
53
+ inWord = true;
54
+ i = end + 1;
55
+ } else if (ch === '"') {
56
+ inWord = true;
57
+ i++;
58
+ for (; ; ) {
59
+ if (i >= line.length) throw new Error("No closing quotation");
60
+ const c = line.charAt(i);
61
+ if (c === '"') {
62
+ i++;
63
+ break;
64
+ }
65
+ const next = line.charAt(i + 1);
66
+ if (c === "\\" && DOUBLE_QUOTE_ESCAPABLE.has(next)) {
67
+ current += next;
68
+ i += 2;
69
+ } else {
70
+ current += c;
71
+ i++;
72
+ }
73
+ }
74
+ } else if (ch === "\\") {
75
+ if (i + 1 >= line.length) throw new Error("No escaped character");
76
+ current += line.charAt(i + 1);
77
+ inWord = true;
78
+ i += 2;
79
+ } else {
80
+ current += ch;
81
+ inWord = true;
82
+ i++;
83
+ }
84
+ }
85
+ if (inWord) words.push(current);
86
+ return words;
87
+ }
88
+
89
+ // src/repl.ts
90
+ var PROMPT = "\x1B[38;2;92;217;230m\u276F\x1B[0m ";
91
+ async function printBanner(say, host) {
92
+ const key = await getApiKeyOrExit(false, host);
93
+ say(LOGO);
94
+ say(` ${colors.dim(`v${VERSION}`)}`);
95
+ say();
96
+ if (key) {
97
+ say(` ${colors.green(">")} Authenticated via ${authSource(key, host.env)}`);
98
+ } else {
99
+ say(` ${colors.pink(">")} Not authenticated`);
100
+ say(` ${colors.dim("search and extract work without a key (with a rate-limit cap).")}`);
101
+ say(` Type ${colors.green("init")} for guided setup and skill installation.`);
102
+ say(` Type ${colors.green("login")} for browser authentication only.`);
103
+ }
104
+ say();
105
+ const sep = colors.dim(" | ");
106
+ say(
107
+ [
108
+ ` ${colors.bold("Tips: ")}${colors.green("init")}`,
109
+ `${colors.green("search ")}${colors.dim('"query"')}`,
110
+ `${colors.green("extract ")}${colors.dim("<url>")}`,
111
+ `${colors.green("research ")}${colors.dim('"topic"')}`,
112
+ colors.green("update"),
113
+ colors.green("help"),
114
+ colors.green("exit")
115
+ ].join(sep)
116
+ );
117
+ say();
118
+ }
119
+ function printHelp(say) {
120
+ const row = (cmd, pad, text) => ` ${colors.green(cmd)}${" ".repeat(pad)}${text}`;
121
+ say();
122
+ say(` ${colors.bold("Commands")}`);
123
+ say();
124
+ say(row('search "your query"', 12, "Web search"));
125
+ say(row("extract <url>", 18, "Extract content"));
126
+ say(row("crawl <url>", 20, "Crawl a website"));
127
+ say(row("map <url>", 22, "Discover URLs"));
128
+ say(row('research "topic"', 14, "Deep research"));
129
+ say(row("init", 26, "Guided setup and skill installation"));
130
+ say(row("login", 25, "Browser or API-key authentication"));
131
+ say(row("logout", 24, "Clear credentials"));
132
+ say(row("auth", 26, "Auth status"));
133
+ say(row("update", 24, "Check for or install CLI updates"));
134
+ say(row("exit / quit / Ctrl+C", 10, "Leave"));
135
+ }
136
+ function silenceErrors(cmd) {
137
+ cmd.configureOutput({ writeErr: () => {
138
+ } });
139
+ for (const sub of cmd.commands) silenceErrors(sub);
140
+ }
141
+ async function runRepl(host, opts = {}) {
142
+ const input = opts.input ?? process.stdin;
143
+ const output = opts.output ?? process.stderr;
144
+ const makeProgram = opts.program ?? (() => buildProgram(host));
145
+ const say = (line = "") => {
146
+ output.write(`${line}
147
+ `);
148
+ };
149
+ const goodbye = () => {
150
+ say();
151
+ say(` ${colors.dim("Goodbye!")}`);
152
+ say();
153
+ };
154
+ say();
155
+ await printBanner(say, host);
156
+ const queue = [];
157
+ let closed = false;
158
+ let detaching = false;
159
+ let interrupted = false;
160
+ let history = [];
161
+ let wake;
162
+ const notify = () => wake?.();
163
+ const open = () => {
164
+ const rl2 = createInterface({
165
+ input,
166
+ output,
167
+ terminal: input.isTTY === true,
168
+ history,
169
+ historySize: 100
170
+ });
171
+ rl2.setPrompt(colorEnabled() ? PROMPT : "\u276F ");
172
+ rl2.on("history", (h) => {
173
+ history = h;
174
+ });
175
+ rl2.on("line", (l) => {
176
+ queue.push(l);
177
+ notify();
178
+ });
179
+ rl2.on("close", () => {
180
+ if (detaching) return;
181
+ closed = true;
182
+ notify();
183
+ });
184
+ rl2.on("SIGINT", () => {
185
+ interrupted = true;
186
+ notify();
187
+ });
188
+ return rl2;
189
+ };
190
+ let rl = open();
191
+ const nextLine = async () => {
192
+ const width = output.columns ?? 80;
193
+ say(colors.dim("\u2500".repeat(width)));
194
+ if (!closed) rl.prompt();
195
+ while (queue.length === 0 && !closed && !interrupted) {
196
+ await new Promise((resolve) => {
197
+ wake = resolve;
198
+ });
199
+ }
200
+ wake = void 0;
201
+ if (interrupted) return null;
202
+ return queue.shift() ?? "exit";
203
+ };
204
+ try {
205
+ for (; ; ) {
206
+ const raw = await nextLine();
207
+ if (raw === null) {
208
+ goodbye();
209
+ break;
210
+ }
211
+ const line = raw.trim();
212
+ if (!line) continue;
213
+ if (line === "exit" || line === "quit" || line === "q") {
214
+ goodbye();
215
+ break;
216
+ }
217
+ if (line === "help" || line === "?") {
218
+ printHelp(say);
219
+ continue;
220
+ }
221
+ let args;
222
+ try {
223
+ args = splitShellWords(line);
224
+ } catch (e) {
225
+ say(` ${colors.pink("Parse error:")} ${sanitizeControl(messageOf(e))}`);
226
+ continue;
227
+ }
228
+ if (args[0] === "tvly") args = args.slice(1);
229
+ if (args.length === 0) continue;
230
+ say();
231
+ if (!closed) {
232
+ detaching = true;
233
+ rl.close();
234
+ detaching = false;
235
+ }
236
+ try {
237
+ const program = makeProgram();
238
+ silenceErrors(program);
239
+ await program.parseAsync(["node", "tvly", ...args]);
240
+ } catch (e) {
241
+ if (e instanceof ExitError) {
242
+ } else if (e instanceof CommanderError) {
243
+ if (e.exitCode !== 0) say(` ${colors.pink(">")} ${sanitizeControl(e.message)}`);
244
+ } else {
245
+ say(` ${colors.pink("> Error:")} ${sanitizeControl(messageOf(e))}`);
246
+ }
247
+ } finally {
248
+ if (input.readableEnded) closed = true;
249
+ if (!closed) rl = open();
250
+ }
251
+ say();
252
+ }
253
+ } finally {
254
+ rl.close();
255
+ }
256
+ }
257
+ export {
258
+ runRepl
259
+ };