@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.
- package/LICENSE +21 -0
- package/README.md +322 -2
- package/dist/auth-EQZRRVIV.js +213 -0
- package/dist/chunk-5JXZZSLS.js +30 -0
- package/dist/chunk-AERAIFZU.js +691 -0
- package/dist/chunk-E4N27QWM.js +53 -0
- package/dist/chunk-HQ7QBZ5H.js +8 -0
- package/dist/chunk-IRCF6F3S.js +45 -0
- package/dist/chunk-K76OUMXM.js +43 -0
- package/dist/chunk-KQFC5TMG.js +380 -0
- package/dist/chunk-PZGMUDV2.js +427 -0
- package/dist/chunk-QHWWSCVM.js +85 -0
- package/dist/chunk-SPBOEASO.js +9 -0
- package/dist/chunk-U3B7BNFI.js +40 -0
- package/dist/chunk-ULNXDG4Q.js +147 -0
- package/dist/chunk-V3OJFHHD.js +191 -0
- package/dist/cli.js +132 -0
- package/dist/client-factory-LWJMZVKT.js +338 -0
- package/dist/defaults-344GEHCS.js +204 -0
- package/dist/extract-Q5VLDRON.js +90 -0
- package/dist/init-6PKQ6UES.js +348 -0
- package/dist/repl-IHHLWI3L.js +259 -0
- package/dist/research-PU3YJMQM.js +262 -0
- package/dist/search-XTAH2SDD.js +191 -0
- package/dist/setup-JNKIKMOX.js +52 -0
- package/dist/site-ZG42IEW7.js +151 -0
- package/dist/status-UAAZ7CBV.js +36 -0
- package/dist/update-CWPZZE4P.js +129 -0
- package/package.json +55 -4
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
AGENT_NAMES
|
|
4
|
+
} from "./chunk-HQ7QBZ5H.js";
|
|
5
|
+
import {
|
|
6
|
+
VERSION
|
|
7
|
+
} from "./chunk-SPBOEASO.js";
|
|
8
|
+
import {
|
|
9
|
+
addClientNameOption,
|
|
10
|
+
addCommonOptions,
|
|
11
|
+
addOutputOption,
|
|
12
|
+
parseNumberArg,
|
|
13
|
+
resolveJson
|
|
14
|
+
} from "./chunk-IRCF6F3S.js";
|
|
15
|
+
import {
|
|
16
|
+
out
|
|
17
|
+
} from "./chunk-E4N27QWM.js";
|
|
18
|
+
import {
|
|
19
|
+
ExitError
|
|
20
|
+
} from "./chunk-5JXZZSLS.js";
|
|
21
|
+
|
|
22
|
+
// src/host.ts
|
|
23
|
+
import { spawn } from "child_process";
|
|
24
|
+
import { homedir } from "os";
|
|
25
|
+
|
|
26
|
+
// src/ui/prompts.ts
|
|
27
|
+
import * as clack from "@clack/prompts";
|
|
28
|
+
var output = process.stderr;
|
|
29
|
+
function answered(value) {
|
|
30
|
+
if (clack.isCancel(value)) {
|
|
31
|
+
clack.cancel("Setup cancelled.", { output });
|
|
32
|
+
throw new ExitError(1);
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
var options = (choices) => choices;
|
|
37
|
+
var clackPrompter = {
|
|
38
|
+
confirm: async (message, initial = true) => answered(await clack.confirm({ message, initialValue: initial, output })),
|
|
39
|
+
select: async (message, choices, initial) => answered(
|
|
40
|
+
await clack.select({
|
|
41
|
+
message,
|
|
42
|
+
options: options(choices),
|
|
43
|
+
...initial !== void 0 && { initialValue: initial },
|
|
44
|
+
output
|
|
45
|
+
})
|
|
46
|
+
),
|
|
47
|
+
multiselect: async (message, choices, initial) => answered(
|
|
48
|
+
await clack.multiselect({
|
|
49
|
+
message,
|
|
50
|
+
options: options(choices),
|
|
51
|
+
initialValues: initial,
|
|
52
|
+
required: false,
|
|
53
|
+
output
|
|
54
|
+
})
|
|
55
|
+
),
|
|
56
|
+
password: async (message) => answered(
|
|
57
|
+
await clack.password({
|
|
58
|
+
message,
|
|
59
|
+
validate: (value) => value?.trim() ? void 0 : "Enter a key",
|
|
60
|
+
output
|
|
61
|
+
})
|
|
62
|
+
).trim()
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// src/util/exec.ts
|
|
66
|
+
import {
|
|
67
|
+
execFile,
|
|
68
|
+
spawnSync
|
|
69
|
+
} from "child_process";
|
|
70
|
+
import { accessSync, constants, existsSync, statSync } from "fs";
|
|
71
|
+
import path from "path";
|
|
72
|
+
var MAX_BUFFER = 16 * 1024 * 1024;
|
|
73
|
+
var SHIM = /\.(cmd|bat)$/i;
|
|
74
|
+
function whichOnPath(name, env, win) {
|
|
75
|
+
const exts = win ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";") : [""];
|
|
76
|
+
for (const dir of (env.PATH ?? "").split(path.delimiter).filter(Boolean)) {
|
|
77
|
+
if (dir.replace(/\\/g, "/").includes("/_npx/")) continue;
|
|
78
|
+
for (const ext of exts) {
|
|
79
|
+
const candidate = path.join(dir, name + ext);
|
|
80
|
+
try {
|
|
81
|
+
if (!statSync(candidate).isFile()) continue;
|
|
82
|
+
accessSync(candidate, constants.X_OK);
|
|
83
|
+
return candidate;
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
function fromSync(r) {
|
|
91
|
+
return {
|
|
92
|
+
status: r.status,
|
|
93
|
+
signal: r.signal,
|
|
94
|
+
error: r.error,
|
|
95
|
+
timedOut: r.error?.code === "ETIMEDOUT",
|
|
96
|
+
stdout: r.stdout ?? "",
|
|
97
|
+
stderr: r.stderr ?? ""
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function fromAsync(e, stdout, stderr) {
|
|
101
|
+
const code = e?.code;
|
|
102
|
+
return {
|
|
103
|
+
status: !e ? 0 : typeof code === "number" ? code : null,
|
|
104
|
+
signal: e?.signal ?? null,
|
|
105
|
+
error: typeof code === "string" ? e : void 0,
|
|
106
|
+
timedOut: e?.killed === true && typeof code !== "string",
|
|
107
|
+
stdout: stdout ?? "",
|
|
108
|
+
stderr: stderr ?? ""
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function result(o) {
|
|
112
|
+
const failure = failureOf(o);
|
|
113
|
+
return { ok: failure === null, stdout: o.stdout, stderr: o.stderr, failure };
|
|
114
|
+
}
|
|
115
|
+
function failureOf(o) {
|
|
116
|
+
if (o.timedOut) return "timed out";
|
|
117
|
+
if (o.error) return o.error.message;
|
|
118
|
+
if (o.status === 0) return null;
|
|
119
|
+
const last = (o.stderr || o.stdout).split(/\r?\n/).filter((line) => line.trim()).pop();
|
|
120
|
+
return last ?? (o.status === null ? `signal ${o.signal}` : `exit code ${o.status}`);
|
|
121
|
+
}
|
|
122
|
+
function createExec(deps) {
|
|
123
|
+
const win = deps.platform === "win32";
|
|
124
|
+
function resolve(bin, args) {
|
|
125
|
+
if (!win || !SHIM.test(bin)) return { file: bin, argv: args, shell: false };
|
|
126
|
+
const name = path.win32.basename(bin).replace(SHIM, "").toLowerCase();
|
|
127
|
+
const cli = path.win32.join(
|
|
128
|
+
path.win32.dirname(deps.execPath),
|
|
129
|
+
"node_modules",
|
|
130
|
+
"npm",
|
|
131
|
+
"bin",
|
|
132
|
+
`${name}-cli.js`
|
|
133
|
+
);
|
|
134
|
+
if ((name === "npm" || name === "npx") && deps.exists(cli)) {
|
|
135
|
+
return { file: deps.execPath, argv: [cli, ...args], shell: false };
|
|
136
|
+
}
|
|
137
|
+
return { file: `"${bin}"`, argv: args, shell: true };
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
which: (name) => whichOnPath(name, deps.env, win),
|
|
141
|
+
run(bin, args, opts) {
|
|
142
|
+
const { file, argv, shell } = resolve(bin, args);
|
|
143
|
+
const r = deps.spawn(file, argv, {
|
|
144
|
+
stdio: opts.inherit === "stderr" ? ["ignore", 2, 2] : opts.inherit ? "inherit" : ["ignore", "pipe", "pipe"],
|
|
145
|
+
encoding: "utf8",
|
|
146
|
+
timeout: opts.timeoutMs,
|
|
147
|
+
maxBuffer: MAX_BUFFER,
|
|
148
|
+
windowsHide: true,
|
|
149
|
+
shell
|
|
150
|
+
});
|
|
151
|
+
return result(fromSync(r));
|
|
152
|
+
},
|
|
153
|
+
runAsync(bin, args, opts) {
|
|
154
|
+
const { file, argv, shell } = resolve(bin, args);
|
|
155
|
+
return new Promise((done) => {
|
|
156
|
+
const child = deps.execFile(
|
|
157
|
+
file,
|
|
158
|
+
argv,
|
|
159
|
+
{
|
|
160
|
+
encoding: "utf8",
|
|
161
|
+
timeout: opts.timeoutMs,
|
|
162
|
+
maxBuffer: MAX_BUFFER,
|
|
163
|
+
windowsHide: true,
|
|
164
|
+
shell
|
|
165
|
+
},
|
|
166
|
+
(e, stdout, stderr) => done(result(fromAsync(e, stdout, stderr)))
|
|
167
|
+
);
|
|
168
|
+
child.stdin?.end();
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
var exec = createExec({
|
|
174
|
+
spawn: spawnSync,
|
|
175
|
+
execFile,
|
|
176
|
+
exists: existsSync,
|
|
177
|
+
env: process.env,
|
|
178
|
+
platform: process.platform,
|
|
179
|
+
execPath: process.execPath
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// src/host.ts
|
|
183
|
+
function openBrowser(url, platform, spawnImpl = spawn) {
|
|
184
|
+
const [cmd, args] = platform === "darwin" ? ["open", [url]] : platform === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", url]] : ["xdg-open", [url]];
|
|
185
|
+
try {
|
|
186
|
+
const child = spawnImpl(cmd, args, { detached: true, stdio: "ignore" });
|
|
187
|
+
child.on("error", () => {
|
|
188
|
+
});
|
|
189
|
+
child.unref();
|
|
190
|
+
} catch {
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function realHost() {
|
|
194
|
+
const host = {
|
|
195
|
+
home: homedir(),
|
|
196
|
+
cwd: process.cwd(),
|
|
197
|
+
argv: process.argv,
|
|
198
|
+
env: process.env,
|
|
199
|
+
platform: process.platform,
|
|
200
|
+
stdinIsTTY: process.stdin.isTTY === true,
|
|
201
|
+
stderrIsTTY: process.stderr.isTTY === true,
|
|
202
|
+
now: Date.now,
|
|
203
|
+
fetch: globalThis.fetch,
|
|
204
|
+
exec,
|
|
205
|
+
prompt: clackPrompter,
|
|
206
|
+
openUrl: (url) => openBrowser(url, process.platform),
|
|
207
|
+
resolveClient: async (key, clientName) => (await import("./client-factory-LWJMZVKT.js")).buildClient(key, clientName, host)
|
|
208
|
+
};
|
|
209
|
+
return host;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/program.ts
|
|
213
|
+
import { Command, InvalidArgumentError, Option } from "commander";
|
|
214
|
+
var HELP_AFTER = `
|
|
215
|
+
First-time setup: tvly init
|
|
216
|
+
|
|
217
|
+
Browser authentication: tvly login
|
|
218
|
+
|
|
219
|
+
API-key authentication: tvly login --api-key tvly-YOUR_KEY
|
|
220
|
+
|
|
221
|
+
Or set TAVILY_API_KEY environment variable.
|
|
222
|
+
`;
|
|
223
|
+
var KEYLESS_HELP = "\nWorks without an API key (subject to a rate-limit cap). Run `tvly login` to authenticate and remove the cap.";
|
|
224
|
+
var int = (raw) => parseNumberArg(raw, { integer: true });
|
|
225
|
+
var seconds = (raw) => parseNumberArg(raw);
|
|
226
|
+
function addExtractOptions(cmd) {
|
|
227
|
+
return cmd.addOption(
|
|
228
|
+
new Option("--extract-depth <depth>", "Extraction depth.").choices(["basic", "advanced"])
|
|
229
|
+
).addOption(new Option("--format <format>", "Output format.").choices(["markdown", "text"]));
|
|
230
|
+
}
|
|
231
|
+
function buildProgram(host = realHost()) {
|
|
232
|
+
const program = new Command("tvly").description("Tavily CLI \u2014 search, extract, crawl, map, and research from the command line.").addHelpText("after", HELP_AFTER).option("--version", "Show version and exit").option("--status", "Show version and auth status").option("--json", "Output as JSON (for agents and scripts)").configureHelp({ showGlobalOptions: true }).exitOverride();
|
|
233
|
+
const defaults = () => import("./defaults-344GEHCS.js");
|
|
234
|
+
const research = () => import("./research-PU3YJMQM.js");
|
|
235
|
+
const agentOption = (description, choices) => new Option("--agent <name>", description).choices(choices).argParser((value, previous) => {
|
|
236
|
+
const name = value.toLowerCase();
|
|
237
|
+
if (!choices.includes(name)) {
|
|
238
|
+
throw new InvalidArgumentError(`Allowed choices are ${choices.join(", ")}.`);
|
|
239
|
+
}
|
|
240
|
+
return [...previous ?? [], name];
|
|
241
|
+
});
|
|
242
|
+
program.command("init").description("Authenticate, install Tavily skills, and verify a working CLI.").addOption(
|
|
243
|
+
agentOption("Install skills for an agent. Repeat to select more than one.", AGENT_NAMES)
|
|
244
|
+
).option(
|
|
245
|
+
"--yes",
|
|
246
|
+
"Accept every default without prompting (all detected agents; skills and MCP)."
|
|
247
|
+
).option("--skip-auth", "Keep keyless mode when no credential is configured.").option(
|
|
248
|
+
"--skip-skills",
|
|
249
|
+
"Leave coding agents alone: no skills, workflows, or MCP server (no npx needed)."
|
|
250
|
+
).option("--skip-workflows", "Do not install or update the Tavily official workflows.").option("--skip-install", "Do not install tvly globally.").option("--api-key <key>", "Authenticate with a Tavily API key instead of OAuth.").option("--browser", "Open OAuth in a browser.").option("--no-browser", "Print the OAuth URL and wait for its local callback.").action(async (opts, cmd) => {
|
|
251
|
+
await (await import("./init-6PKQ6UES.js")).run(opts, cmd, host);
|
|
252
|
+
});
|
|
253
|
+
const authModule = () => import("./auth-EQZRRVIV.js");
|
|
254
|
+
program.command("login").description("Authenticate with Tavily. Stores credentials for future use.").option("--api-key <key>", "Tavily API key (tvly-...). If omitted, opens browser for OAuth.").option("--no-browser", "Print the login URL instead of opening a browser (SSH / headless).").action(async (opts, cmd) => {
|
|
255
|
+
await (await authModule()).runLogin(opts, cmd, host);
|
|
256
|
+
});
|
|
257
|
+
program.command("logout").description("Clear stored Tavily credentials.").action(async (opts, cmd) => {
|
|
258
|
+
await (await authModule()).runLogout(opts, cmd, host);
|
|
259
|
+
});
|
|
260
|
+
program.command("auth").description("Check authentication status.").action(async (opts, cmd) => {
|
|
261
|
+
await (await authModule()).runAuth(opts, cmd, host);
|
|
262
|
+
});
|
|
263
|
+
const searchModule = () => import("./search-XTAH2SDD.js");
|
|
264
|
+
const search = program.command("search").description("Search the web using Tavily.").argument(
|
|
265
|
+
"[queries...]",
|
|
266
|
+
"One or more search queries (up to 5), run in parallel with the same options. Use - to read a single query from stdin."
|
|
267
|
+
).addHelpText("after", KEYLESS_HELP).action(async (queries, opts, cmd) => {
|
|
268
|
+
await (await searchModule()).run(queries, opts, cmd, host);
|
|
269
|
+
});
|
|
270
|
+
let searchConfigured = false;
|
|
271
|
+
const configure = async (cmd) => {
|
|
272
|
+
if (cmd !== search || searchConfigured) return;
|
|
273
|
+
searchConfigured = true;
|
|
274
|
+
(await searchModule()).configure(search);
|
|
275
|
+
};
|
|
276
|
+
program.hook("preSubcommand", (_program, sub) => configure(sub));
|
|
277
|
+
const extract = program.command("extract").description("Extract content from one or more URLs.").argument("<urls...>", "URLs to extract (max 20).").option("--query <query>", "Rerank chunks by relevance to this query.").option("--chunks-per-source <n>", "Chunks per source (1-5, requires --query).", int);
|
|
278
|
+
addExtractOptions(extract).option("--include-images", "Include image URLs.").option("--timeout <seconds>", "Max wait time in seconds (1-60).", seconds).addHelpText("after", KEYLESS_HELP).action(async (urls, opts, cmd) => {
|
|
279
|
+
await (await import("./extract-Q5VLDRON.js")).run(urls, opts, cmd, host);
|
|
280
|
+
});
|
|
281
|
+
addCommonOptions(extract);
|
|
282
|
+
const addSiteOptions = (cmd, verb, limitHelp) => {
|
|
283
|
+
cmd.option("--max-depth <n>", `Levels deep to ${verb} (1-5, default: 1).`, int).option("--max-breadth <n>", "Links per page (default: 20).", int).option("--limit <n>", limitHelp, int).option(
|
|
284
|
+
"--instructions <text>",
|
|
285
|
+
`Natural language guidance for ${verb === "crawl" ? "the crawler" : "URL discovery"}.`
|
|
286
|
+
);
|
|
287
|
+
};
|
|
288
|
+
const addSiteFilters = (cmd) => {
|
|
289
|
+
cmd.option("--select-paths <patterns>", "Comma-separated regex patterns for paths to include.").option("--exclude-paths <patterns>", "Comma-separated regex patterns for paths to exclude.").option(
|
|
290
|
+
"--select-domains <patterns>",
|
|
291
|
+
"Comma-separated regex patterns for domains to include."
|
|
292
|
+
).option(
|
|
293
|
+
"--exclude-domains <patterns>",
|
|
294
|
+
"Comma-separated regex patterns for domains to exclude."
|
|
295
|
+
).option("--allow-external", "Include external domain links.").option("--no-external", "Leave out external domain links.").option("--timeout <seconds>", "Max wait time in seconds (10-150).", seconds);
|
|
296
|
+
};
|
|
297
|
+
const needsKey = "\nRequires a Tavily API key. Sign up at https://tavily.com";
|
|
298
|
+
const crawl = program.command("crawl").description("Crawl a website starting from URL.").argument("<url>", "Where to start; returns full content for each discovered page.");
|
|
299
|
+
addSiteOptions(crawl, "crawl", "Total pages cap (default: 50).");
|
|
300
|
+
crawl.option("--chunks-per-source <n>", "Chunks per page (1-5, requires --instructions).", int);
|
|
301
|
+
addExtractOptions(crawl);
|
|
302
|
+
addSiteFilters(crawl);
|
|
303
|
+
crawl.option("--include-images", "Include images.").option("--output-dir <dir>", "Save each page as a .md file in this directory.").addHelpText("after", needsKey).action(async (url, opts, cmd) => {
|
|
304
|
+
await (await import("./site-ZG42IEW7.js")).runCrawl(url, opts, cmd, host);
|
|
305
|
+
});
|
|
306
|
+
addCommonOptions(crawl);
|
|
307
|
+
const map = program.command("map").description("Discover all URLs on a website (no content extraction).").argument("<url>", "Where to start; returns the URLs found.");
|
|
308
|
+
addSiteOptions(map, "map", "Maximum URLs to discover (default: 50).");
|
|
309
|
+
addSiteFilters(map);
|
|
310
|
+
map.addHelpText("after", needsKey).action(async (url, opts, cmd) => {
|
|
311
|
+
await (await import("./site-ZG42IEW7.js")).runMap(url, opts, cmd, host);
|
|
312
|
+
});
|
|
313
|
+
addCommonOptions(map);
|
|
314
|
+
const group = program.command("research").description("Deep research commands (run, status, poll).").addHelpText(
|
|
315
|
+
"after",
|
|
316
|
+
'\nYou can run research directly: tvly research "your query"\nOr use subcommands: tvly research status <id>'
|
|
317
|
+
);
|
|
318
|
+
const addWaitOptions = (cmd) => {
|
|
319
|
+
cmd.option("--poll-interval <n>", "Seconds between status checks.", int, 10).option("--timeout <n>", "Max seconds to wait.", int, 600);
|
|
320
|
+
};
|
|
321
|
+
const researchRun = group.command("run", { isDefault: true }).description("Start a research task.").argument("[query]", 'The research topic. Use "-" to read from stdin.').addOption(
|
|
322
|
+
new Option("--model <model>", "Research model (default: auto).").choices([
|
|
323
|
+
"mini",
|
|
324
|
+
"pro",
|
|
325
|
+
"auto"
|
|
326
|
+
])
|
|
327
|
+
).option("--no-wait", "Return request_id immediately without waiting.").option("--stream", "Stream results in real-time.").option("--output-schema <file>", "Path to JSON schema file for structured output.").addOption(
|
|
328
|
+
new Option("--citation-format <format>", "Citation format.").choices([
|
|
329
|
+
"numbered",
|
|
330
|
+
"mla",
|
|
331
|
+
"apa",
|
|
332
|
+
"chicago"
|
|
333
|
+
])
|
|
334
|
+
);
|
|
335
|
+
addOutputOption(researchRun);
|
|
336
|
+
addWaitOptions(researchRun);
|
|
337
|
+
addClientNameOption(researchRun);
|
|
338
|
+
researchRun.addHelpText("after", `
|
|
339
|
+
You can also run directly: tvly research "your query"
|
|
340
|
+
${needsKey}`).action(async (query, opts, cmd) => {
|
|
341
|
+
await (await research()).run(query, opts, cmd, host);
|
|
342
|
+
});
|
|
343
|
+
const researchStatus = group.command("status").description("Check the status of a research task.").argument("<request_id>", "The research request ID.").action(async (requestId, opts, cmd) => {
|
|
344
|
+
await (await research()).status(requestId, opts, cmd, host);
|
|
345
|
+
});
|
|
346
|
+
addClientNameOption(researchStatus);
|
|
347
|
+
const researchPoll = group.command("poll").description("Poll a research task until completion and return results.").argument("<request_id>", "The research request ID.").action(async (requestId, opts, cmd) => {
|
|
348
|
+
await (await research()).poll(requestId, opts, cmd, host);
|
|
349
|
+
});
|
|
350
|
+
addWaitOptions(researchPoll);
|
|
351
|
+
addCommonOptions(researchPoll);
|
|
352
|
+
program.command("update").description("Check for or install the latest Tavily CLI release.").option("--check", "Check for an update without installing it.").action(async (opts, cmd) => {
|
|
353
|
+
await (await import("./update-CWPZZE4P.js")).run(opts, cmd, host);
|
|
354
|
+
});
|
|
355
|
+
const addDefaultOptions = (cmd, claudeHelp, codexHelp, scope) => {
|
|
356
|
+
cmd.option("--claude", claudeHelp).option("--codex", codexHelp).option(
|
|
357
|
+
"--project",
|
|
358
|
+
`${scope} the current project (.claude/settings.local.json, .codex/config.toml) instead of globally.`
|
|
359
|
+
);
|
|
360
|
+
};
|
|
361
|
+
const setDefault = program.command("set-default").description("Make Tavily the default web tool by disabling agents' built-in web search.");
|
|
362
|
+
addDefaultOptions(
|
|
363
|
+
setDefault,
|
|
364
|
+
"Deny Claude Code's built-in WebSearch and WebFetch tools.",
|
|
365
|
+
"Disable Codex's built-in web search.",
|
|
366
|
+
"Apply to"
|
|
367
|
+
);
|
|
368
|
+
setDefault.action(async (opts, cmd) => {
|
|
369
|
+
await (await defaults()).runSetDefault(opts, cmd, host);
|
|
370
|
+
});
|
|
371
|
+
const unsetDefault = program.command("unset-default").description("Undo set-default and restore agents' built-in web search.");
|
|
372
|
+
addDefaultOptions(
|
|
373
|
+
unsetDefault,
|
|
374
|
+
"Re-allow Claude Code's built-in WebSearch and WebFetch tools.",
|
|
375
|
+
"Re-enable Codex's built-in web search.",
|
|
376
|
+
"Undo in"
|
|
377
|
+
);
|
|
378
|
+
unsetDefault.action(async (opts, cmd) => {
|
|
379
|
+
await (await defaults()).runUnsetDefault(opts, cmd, host);
|
|
380
|
+
});
|
|
381
|
+
program.command("setup").description("Configure coding agents to use Tavily (mcp).").command("mcp").description("Add the hosted Tavily MCP server to Claude Code, Codex, or Cursor.").addOption(
|
|
382
|
+
agentOption("Configure an agent, or all. Repeat to select more than one.", [
|
|
383
|
+
...AGENT_NAMES,
|
|
384
|
+
"all"
|
|
385
|
+
])
|
|
386
|
+
).option("--keyless", "Sign in with OAuth even when TAVILY_API_KEY is set.").action(async (opts, cmd) => {
|
|
387
|
+
await (await import("./setup-JNKIKMOX.js")).runMcp(opts, cmd, host);
|
|
388
|
+
});
|
|
389
|
+
program.helpCommand(false);
|
|
390
|
+
program.command("help [command]").description("display help for command").action(async (name) => {
|
|
391
|
+
if (name === void 0) return program.help();
|
|
392
|
+
const sub = program.commands.find((c) => c.name() === name || c.aliases().includes(name));
|
|
393
|
+
if (!sub) return program.help({ error: true });
|
|
394
|
+
await configure(sub);
|
|
395
|
+
sub.help();
|
|
396
|
+
});
|
|
397
|
+
program.action(async (opts, cmd) => {
|
|
398
|
+
const json = resolveJson(cmd);
|
|
399
|
+
if (opts.version) {
|
|
400
|
+
out(json ? JSON.stringify({ version: VERSION }) : `tavily-cli ${VERSION}`);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
if (opts.status) {
|
|
404
|
+
await (await import("./status-UAAZ7CBV.js")).printStatus(json, host);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
const { runRepl } = await import("./repl-IHHLWI3L.js");
|
|
408
|
+
await runRepl(host);
|
|
409
|
+
});
|
|
410
|
+
const parseAsync = program.parseAsync.bind(program);
|
|
411
|
+
program.parseAsync = async (argv = host.argv, parseOptions) => {
|
|
412
|
+
const args = [...argv];
|
|
413
|
+
const start = parseOptions?.from === "user" ? 0 : 2;
|
|
414
|
+
const at = args.findIndex((arg, i) => i >= start && !arg.startsWith("-"));
|
|
415
|
+
if (args[at] === "research") {
|
|
416
|
+
const { normalizeResearchArgs } = await research();
|
|
417
|
+
args.splice(at + 1, Infinity, ...normalizeResearchArgs(args.slice(at + 1)));
|
|
418
|
+
}
|
|
419
|
+
return parseAsync(args, parseOptions);
|
|
420
|
+
};
|
|
421
|
+
return program;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export {
|
|
425
|
+
realHost,
|
|
426
|
+
buildProgram
|
|
427
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/ui/text.ts
|
|
4
|
+
var CONTROL_CHARS = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]/g;
|
|
5
|
+
var BLANK_RUNS = /\n(?:[ \t]*\n){2,}/g;
|
|
6
|
+
function sanitizeControl(value) {
|
|
7
|
+
return String(value).replace(CONTROL_CHARS, "");
|
|
8
|
+
}
|
|
9
|
+
function plain(value) {
|
|
10
|
+
return sanitizeControl(value).replace(BLANK_RUNS, "\n\n").trim();
|
|
11
|
+
}
|
|
12
|
+
function snippet(content, maxChars) {
|
|
13
|
+
const text = sanitizeControl(content).split(/\s+/).filter(Boolean).join(" ");
|
|
14
|
+
return maxChars && text.length > maxChars ? `${text.slice(0, maxChars)}...` : text;
|
|
15
|
+
}
|
|
16
|
+
var terminalOverride = null;
|
|
17
|
+
function isTerminal(stream = process.stdout) {
|
|
18
|
+
return terminalOverride ?? Boolean(stream.isTTY);
|
|
19
|
+
}
|
|
20
|
+
function safeLink(url, label, opts = {}) {
|
|
21
|
+
const cleanUrl = sanitizeControl(url);
|
|
22
|
+
const display = label === void 0 ? cleanUrl : sanitizeControl(label);
|
|
23
|
+
let protocol = "";
|
|
24
|
+
try {
|
|
25
|
+
protocol = new URL(cleanUrl).protocol;
|
|
26
|
+
} catch {
|
|
27
|
+
}
|
|
28
|
+
const linkable = protocol === "http:" || protocol === "https:";
|
|
29
|
+
if (!linkable || !isTerminal(opts.stream ?? process.stderr)) return display;
|
|
30
|
+
return `\x1B]8;;${cleanUrl}\x1B\\${display}\x1B]8;;\x1B\\`;
|
|
31
|
+
}
|
|
32
|
+
function domainOf(url) {
|
|
33
|
+
try {
|
|
34
|
+
return new URL(url).hostname;
|
|
35
|
+
} catch {
|
|
36
|
+
return "";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function hostOf(url) {
|
|
40
|
+
try {
|
|
41
|
+
return new URL(url).host;
|
|
42
|
+
} catch {
|
|
43
|
+
return url;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function tree(root, nodes) {
|
|
47
|
+
const lines = [root];
|
|
48
|
+
nodes.forEach((node, i) => {
|
|
49
|
+
const last = i === nodes.length - 1;
|
|
50
|
+
lines.push(`${last ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 "}${node.label}`);
|
|
51
|
+
const kids = node.children ?? [];
|
|
52
|
+
kids.forEach((kid, j) => {
|
|
53
|
+
lines.push(`${last ? " " : "\u2502 "}${j === kids.length - 1 ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 "}${kid}`);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
return lines.join("\n");
|
|
57
|
+
}
|
|
58
|
+
function table(headers, rows) {
|
|
59
|
+
const widths = headers.map((h, c) => Math.max(h.length, ...rows.map((r) => (r[c] ?? "").length)));
|
|
60
|
+
const fmt = (cells) => cells.map((cell, c) => cell.padEnd(widths[c] ?? 0)).join(" ");
|
|
61
|
+
return [fmt(headers), widths.map((w) => "\u2500".repeat(w)).join(" "), ...rows.map(fmt)].join("\n");
|
|
62
|
+
}
|
|
63
|
+
async function renderMarkdown(markdown) {
|
|
64
|
+
const input = sanitizeControl(markdown);
|
|
65
|
+
const [{ Marked }, { markedTerminal }] = await Promise.all([
|
|
66
|
+
import("marked"),
|
|
67
|
+
import("marked-terminal")
|
|
68
|
+
]);
|
|
69
|
+
const width = Math.min(process.stdout.columns ?? 80, 100);
|
|
70
|
+
const marked = new Marked(markedTerminal({ width, reflowText: true }));
|
|
71
|
+
return (await marked.parse(input)).trimEnd();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export {
|
|
75
|
+
sanitizeControl,
|
|
76
|
+
plain,
|
|
77
|
+
snippet,
|
|
78
|
+
isTerminal,
|
|
79
|
+
safeLink,
|
|
80
|
+
domainOf,
|
|
81
|
+
hostOf,
|
|
82
|
+
tree,
|
|
83
|
+
table,
|
|
84
|
+
renderMarkdown
|
|
85
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
sanitizeControl
|
|
4
|
+
} from "./chunk-QHWWSCVM.js";
|
|
5
|
+
import {
|
|
6
|
+
getApiKey
|
|
7
|
+
} from "./chunk-AERAIFZU.js";
|
|
8
|
+
import {
|
|
9
|
+
colors,
|
|
10
|
+
err,
|
|
11
|
+
out
|
|
12
|
+
} from "./chunk-E4N27QWM.js";
|
|
13
|
+
import {
|
|
14
|
+
ExitError,
|
|
15
|
+
OAuthError,
|
|
16
|
+
messageOf
|
|
17
|
+
} from "./chunk-5JXZZSLS.js";
|
|
18
|
+
|
|
19
|
+
// src/commands/credential.ts
|
|
20
|
+
function handleOauthRefreshError(e, json) {
|
|
21
|
+
const text = sanitizeControl(messageOf(e));
|
|
22
|
+
if (json) {
|
|
23
|
+
out(JSON.stringify({ error: { code: "oauth_refresh_failed", message: text } }));
|
|
24
|
+
} else {
|
|
25
|
+
err(` ${colors.pink("> OAuth refresh failed:")} ${text}`);
|
|
26
|
+
}
|
|
27
|
+
throw new ExitError(3);
|
|
28
|
+
}
|
|
29
|
+
async function getApiKeyOrExit(json, host) {
|
|
30
|
+
try {
|
|
31
|
+
return await getApiKey(host);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
if (e instanceof OAuthError) handleOauthRefreshError(e, json);
|
|
34
|
+
throw e;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export {
|
|
39
|
+
getApiKeyOrExit
|
|
40
|
+
};
|