@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,262 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
emit,
|
|
4
|
+
emitIfRequested,
|
|
5
|
+
footer,
|
|
6
|
+
invoke,
|
|
7
|
+
list,
|
|
8
|
+
readQueryArg,
|
|
9
|
+
resolveOutput
|
|
10
|
+
} from "./chunk-KQFC5TMG.js";
|
|
11
|
+
import "./chunk-U3B7BNFI.js";
|
|
12
|
+
import {
|
|
13
|
+
spinner,
|
|
14
|
+
withSpinner
|
|
15
|
+
} from "./chunk-K76OUMXM.js";
|
|
16
|
+
import {
|
|
17
|
+
isTerminal,
|
|
18
|
+
plain,
|
|
19
|
+
renderMarkdown,
|
|
20
|
+
sanitizeControl,
|
|
21
|
+
table
|
|
22
|
+
} from "./chunk-QHWWSCVM.js";
|
|
23
|
+
import "./chunk-AERAIFZU.js";
|
|
24
|
+
import {
|
|
25
|
+
resolveJson
|
|
26
|
+
} from "./chunk-IRCF6F3S.js";
|
|
27
|
+
import {
|
|
28
|
+
colors,
|
|
29
|
+
err,
|
|
30
|
+
out
|
|
31
|
+
} from "./chunk-E4N27QWM.js";
|
|
32
|
+
import {
|
|
33
|
+
messageOf
|
|
34
|
+
} from "./chunk-5JXZZSLS.js";
|
|
35
|
+
|
|
36
|
+
// src/commands/research.ts
|
|
37
|
+
import { readFileSync } from "fs";
|
|
38
|
+
|
|
39
|
+
// src/ui/render/research.ts
|
|
40
|
+
async function printResearchResult(data, opts) {
|
|
41
|
+
if (emitIfRequested(data, opts)) return;
|
|
42
|
+
const terminal = isTerminal(process.stdout);
|
|
43
|
+
const style = (paint, s) => terminal ? paint(s) : s;
|
|
44
|
+
const status2 = data.status ?? "unknown";
|
|
45
|
+
const content = data.content;
|
|
46
|
+
const sources = list(data.sources);
|
|
47
|
+
if (status2 !== "completed") {
|
|
48
|
+
out(`${style(colors.bold, "Status: ")}${sanitizeControl(status2)}`);
|
|
49
|
+
if (data.error) out(`${style(colors.pink, "Error: ")}${sanitizeControl(data.error)}`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (!terminal) {
|
|
53
|
+
if (content) out(`${plain(content)}
|
|
54
|
+
`);
|
|
55
|
+
if (sources.length > 0) {
|
|
56
|
+
out("Sources:");
|
|
57
|
+
sources.forEach((s, i) => {
|
|
58
|
+
out(`${i + 1}. ${plain(s.title ?? "")} ${plain(s.url ?? "")}`);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (content) {
|
|
64
|
+
out();
|
|
65
|
+
out(` ${colors.bold(colors.aqua("Research Report"))}`);
|
|
66
|
+
out();
|
|
67
|
+
out(await renderMarkdown(String(content)));
|
|
68
|
+
}
|
|
69
|
+
if (sources.length > 0) {
|
|
70
|
+
out();
|
|
71
|
+
out(colors.bold(`Sources (${sources.length})`));
|
|
72
|
+
const rows = sources.map((s, i) => [
|
|
73
|
+
String(i + 1),
|
|
74
|
+
sanitizeControl(s.title ?? ""),
|
|
75
|
+
sanitizeControl(s.url ?? "")
|
|
76
|
+
]);
|
|
77
|
+
out(table(["#", "Title", "URL"], rows));
|
|
78
|
+
}
|
|
79
|
+
footer(sources.length, "sources", data.response_time);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/commands/research.ts
|
|
83
|
+
var SUBCOMMANDS = /* @__PURE__ */ new Set(["run", "status", "poll"]);
|
|
84
|
+
var NEED_ID = /* @__PURE__ */ new Set(["status", "poll"]);
|
|
85
|
+
function normalizeResearchArgs(args) {
|
|
86
|
+
const [first, next] = args;
|
|
87
|
+
if (first === void 0) return ["--help"];
|
|
88
|
+
if (first.startsWith("-")) return args;
|
|
89
|
+
if (!SUBCOMMANDS.has(first)) return ["run", ...args];
|
|
90
|
+
if (NEED_ID.has(first)) {
|
|
91
|
+
const hasPositional = next !== void 0 && !next.startsWith("-");
|
|
92
|
+
const hasHelp = args.includes("--help") || args.includes("-h");
|
|
93
|
+
if (!hasPositional && !hasHelp) return ["run", ...args];
|
|
94
|
+
}
|
|
95
|
+
return args;
|
|
96
|
+
}
|
|
97
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
98
|
+
var isDone = (r) => r.status === "completed" || r.status === "failed";
|
|
99
|
+
async function waitForResult(client, requestId, opts) {
|
|
100
|
+
const start = Date.now();
|
|
101
|
+
const deadline = start + opts.timeout * 1e3;
|
|
102
|
+
const message = () => colors.aqua(`${opts.label}... ${Math.floor((Date.now() - start) / 1e3)}s elapsed`);
|
|
103
|
+
const live = spinner(message(), !opts.json);
|
|
104
|
+
try {
|
|
105
|
+
let nextPoll = start;
|
|
106
|
+
while (Date.now() < deadline) {
|
|
107
|
+
live.update(message());
|
|
108
|
+
if (Date.now() >= nextPoll) {
|
|
109
|
+
const response = await client.getResearch(requestId);
|
|
110
|
+
if (isDone(response)) return response;
|
|
111
|
+
nextPoll = Date.now() + Math.max(1, opts.pollInterval) * 1e3;
|
|
112
|
+
}
|
|
113
|
+
await sleep(1e3);
|
|
114
|
+
}
|
|
115
|
+
return void 0;
|
|
116
|
+
} finally {
|
|
117
|
+
live.stop();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async function finishWait(response, requestId, opts, hint = "") {
|
|
121
|
+
const { json, outputFile } = opts;
|
|
122
|
+
if (response) return printResearchResult(response, { json, outputFile });
|
|
123
|
+
if (json) emit({ request_id: requestId, status: "timeout" }, { outputFile, pretty: false });
|
|
124
|
+
else err(colors.yellow(`Timed out after ${opts.timeout}s.${hint}`));
|
|
125
|
+
}
|
|
126
|
+
async function* streamEvents(payloads) {
|
|
127
|
+
for await (const payload of payloads) {
|
|
128
|
+
if (!payload.trim()) continue;
|
|
129
|
+
try {
|
|
130
|
+
yield JSON.parse(payload);
|
|
131
|
+
} catch {
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function renderStream(events, outputFile) {
|
|
136
|
+
const parts = [];
|
|
137
|
+
let sources = [];
|
|
138
|
+
let currentStep;
|
|
139
|
+
for await (const event of events) {
|
|
140
|
+
const delta = event?.choices?.[0]?.delta ?? {};
|
|
141
|
+
const toolCalls = delta.tool_calls ?? {};
|
|
142
|
+
if (toolCalls.type === "tool_call") {
|
|
143
|
+
for (const tc of toolCalls.tool_call ?? []) {
|
|
144
|
+
const name = tc.name ?? "";
|
|
145
|
+
const step = tc.arguments ? `${name}: ${tc.arguments}` : name;
|
|
146
|
+
if (step !== currentStep) {
|
|
147
|
+
currentStep = step;
|
|
148
|
+
err(` ${colors.dim(sanitizeControl(step))}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (toolCalls.type === "tool_response") {
|
|
153
|
+
for (const tr of toolCalls.tool_response ?? []) {
|
|
154
|
+
const count = tr.sources?.length ?? 0;
|
|
155
|
+
if (tr.name === "WebSearch" && count) err(` ${colors.dim(`Found ${count} sources`)}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (delta.content) parts.push(delta.content);
|
|
159
|
+
if (delta.sources?.length) sources = delta.sources;
|
|
160
|
+
}
|
|
161
|
+
await printResearchResult(
|
|
162
|
+
{ status: "completed", content: parts.join(""), sources },
|
|
163
|
+
{ json: false, outputFile }
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
async function run(query, opts, cmd, host) {
|
|
167
|
+
const json = resolveJson(cmd);
|
|
168
|
+
const input = readQueryArg(query, cmd);
|
|
169
|
+
const outputFile = resolveOutput(opts.output, "research", input);
|
|
170
|
+
const body = { input };
|
|
171
|
+
if (opts.model !== void 0) body.model = opts.model;
|
|
172
|
+
if (opts.outputSchema !== void 0) {
|
|
173
|
+
try {
|
|
174
|
+
body.output_schema = JSON.parse(readFileSync(opts.outputSchema, "utf8"));
|
|
175
|
+
} catch (e) {
|
|
176
|
+
cmd.error(`error: --output-schema must be a path to a JSON file: ${messageOf(e)}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (opts.citationFormat !== void 0) body.citation_format = opts.citationFormat;
|
|
180
|
+
const call = { keyed: "research", clientName: opts.clientName };
|
|
181
|
+
if (opts.stream) {
|
|
182
|
+
await invoke(cmd, host, call, async (client) => {
|
|
183
|
+
const events = streamEvents(await client.researchStream(body));
|
|
184
|
+
if (!json) return renderStream(events, outputFile);
|
|
185
|
+
for await (const event of events) out(JSON.stringify(event));
|
|
186
|
+
});
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const render = await invoke(cmd, host, call, async (client) => {
|
|
190
|
+
const result = await withSpinner("Starting research...", !json, () => client.research(body));
|
|
191
|
+
if (isDone(result) || result.content) {
|
|
192
|
+
return () => printResearchResult(result, { json, outputFile });
|
|
193
|
+
}
|
|
194
|
+
const requestId = result.request_id;
|
|
195
|
+
if (typeof requestId !== "string" || !requestId) {
|
|
196
|
+
throw new Error(`Unexpected API response: ${JSON.stringify(result)}`);
|
|
197
|
+
}
|
|
198
|
+
if (opts.wait === false) {
|
|
199
|
+
const pending = { request_id: requestId, status: result.status ?? "pending" };
|
|
200
|
+
return () => emit(pending, { outputFile, pretty: false });
|
|
201
|
+
}
|
|
202
|
+
const response = await waitForResult(client, requestId, {
|
|
203
|
+
json,
|
|
204
|
+
pollInterval: opts.pollInterval,
|
|
205
|
+
timeout: opts.timeout,
|
|
206
|
+
label: "Researching"
|
|
207
|
+
});
|
|
208
|
+
const hint = ` Resume with: tvly research poll ${sanitizeControl(requestId)}`;
|
|
209
|
+
return () => finishWait(response, requestId, { json, outputFile, timeout: opts.timeout }, hint);
|
|
210
|
+
});
|
|
211
|
+
await render();
|
|
212
|
+
}
|
|
213
|
+
var STATUS_COLORS = {
|
|
214
|
+
completed: colors.green,
|
|
215
|
+
failed: colors.pink
|
|
216
|
+
};
|
|
217
|
+
async function status(requestId, opts, cmd, host) {
|
|
218
|
+
const json = resolveJson(cmd);
|
|
219
|
+
const response = await invoke(
|
|
220
|
+
cmd,
|
|
221
|
+
host,
|
|
222
|
+
{ keyed: "research status", clientName: opts.clientName },
|
|
223
|
+
(client) => client.getResearch(requestId)
|
|
224
|
+
);
|
|
225
|
+
if (json) {
|
|
226
|
+
emit(response, { pretty: false });
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const s = String(response.status ?? "unknown");
|
|
230
|
+
const paint = STATUS_COLORS[s] ?? colors.yellow;
|
|
231
|
+
const safeId = sanitizeControl(requestId);
|
|
232
|
+
out(` ${colors.bold("Request:")} ${safeId}`);
|
|
233
|
+
out(` ${colors.bold("Status:")} ${paint(sanitizeControl(s))}`);
|
|
234
|
+
if (s === "completed")
|
|
235
|
+
out(` ${colors.dim(`Run 'tvly research poll ${safeId}' to view results.`)}`);
|
|
236
|
+
else if (s === "failed") {
|
|
237
|
+
out(` ${colors.pink("Error:")} ${sanitizeControl(response.error ?? "Unknown error")}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
async function poll(requestId, opts, cmd, host) {
|
|
241
|
+
const json = resolveJson(cmd);
|
|
242
|
+
const outputFile = resolveOutput(opts.output, "research", requestId);
|
|
243
|
+
const label = `Polling research ${sanitizeControl(requestId.slice(0, 8))}`;
|
|
244
|
+
const response = await invoke(
|
|
245
|
+
cmd,
|
|
246
|
+
host,
|
|
247
|
+
{ keyed: "research poll", clientName: opts.clientName },
|
|
248
|
+
(client) => waitForResult(client, requestId, {
|
|
249
|
+
json,
|
|
250
|
+
pollInterval: opts.pollInterval,
|
|
251
|
+
timeout: opts.timeout,
|
|
252
|
+
label
|
|
253
|
+
})
|
|
254
|
+
);
|
|
255
|
+
await finishWait(response, requestId, { json, outputFile, timeout: opts.timeout });
|
|
256
|
+
}
|
|
257
|
+
export {
|
|
258
|
+
normalizeResearchArgs,
|
|
259
|
+
poll,
|
|
260
|
+
run,
|
|
261
|
+
status
|
|
262
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
LIMIT_STATUSES,
|
|
4
|
+
QUERY_REQUIRED,
|
|
5
|
+
applySearchFlags,
|
|
6
|
+
emit,
|
|
7
|
+
footer,
|
|
8
|
+
invoke,
|
|
9
|
+
keylessCapError,
|
|
10
|
+
list,
|
|
11
|
+
printKeylessCapHit,
|
|
12
|
+
readQueryArg,
|
|
13
|
+
resolveOutput,
|
|
14
|
+
searchBodyFromOptions
|
|
15
|
+
} from "./chunk-KQFC5TMG.js";
|
|
16
|
+
import "./chunk-U3B7BNFI.js";
|
|
17
|
+
import "./chunk-K76OUMXM.js";
|
|
18
|
+
import {
|
|
19
|
+
isTerminal,
|
|
20
|
+
plain,
|
|
21
|
+
renderMarkdown,
|
|
22
|
+
safeLink,
|
|
23
|
+
sanitizeControl,
|
|
24
|
+
snippet
|
|
25
|
+
} from "./chunk-QHWWSCVM.js";
|
|
26
|
+
import "./chunk-AERAIFZU.js";
|
|
27
|
+
import {
|
|
28
|
+
addCommonOptions,
|
|
29
|
+
parseNumberArg,
|
|
30
|
+
resolveJson
|
|
31
|
+
} from "./chunk-IRCF6F3S.js";
|
|
32
|
+
import {
|
|
33
|
+
colors,
|
|
34
|
+
out
|
|
35
|
+
} from "./chunk-E4N27QWM.js";
|
|
36
|
+
import {
|
|
37
|
+
ExitError,
|
|
38
|
+
messageOf
|
|
39
|
+
} from "./chunk-5JXZZSLS.js";
|
|
40
|
+
|
|
41
|
+
// src/commands/search.ts
|
|
42
|
+
import { TavilyKeylessLimitError, TavilyRequestError } from "@tavily/core";
|
|
43
|
+
import { Option } from "commander";
|
|
44
|
+
|
|
45
|
+
// src/ui/render/search.ts
|
|
46
|
+
var imageUrl = (img) => typeof img === "object" && img !== null ? img.url ?? JSON.stringify(img) : img;
|
|
47
|
+
async function printSearchResults(data, opts) {
|
|
48
|
+
const maxChars = opts.maxChars ?? 600;
|
|
49
|
+
if (opts.json || opts.outputFile) {
|
|
50
|
+
emit(data, { outputFile: opts.outputFile, pretty: true });
|
|
51
|
+
if (opts.json || isTerminal(process.stdout)) return;
|
|
52
|
+
}
|
|
53
|
+
const results = list(data.results);
|
|
54
|
+
const images = list(data.images);
|
|
55
|
+
const answer = data.answer;
|
|
56
|
+
if (!isTerminal(process.stdout)) {
|
|
57
|
+
if (answer) out(`Answer: ${plain(answer)}
|
|
58
|
+
`);
|
|
59
|
+
results.forEach((r, i) => {
|
|
60
|
+
out(`${i + 1}. ${plain(r.title || "Untitled")}`);
|
|
61
|
+
out(plain(r.url ?? ""));
|
|
62
|
+
if (r.content) out(snippet(r.content, maxChars));
|
|
63
|
+
out();
|
|
64
|
+
});
|
|
65
|
+
if (results.length === 0) out("No results found.");
|
|
66
|
+
for (const img of images) out(`Image: ${plain(imageUrl(img))}`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (answer) {
|
|
70
|
+
out();
|
|
71
|
+
out(` ${colors.bold(colors.aqua("Answer"))}`);
|
|
72
|
+
out();
|
|
73
|
+
out(await renderMarkdown(String(answer)));
|
|
74
|
+
out();
|
|
75
|
+
}
|
|
76
|
+
if (results.length === 0) {
|
|
77
|
+
out(colors.dim("No results found."));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
results.forEach((r, i) => {
|
|
81
|
+
const score = typeof r.score === "number" ? colors.dim(` score: ${r.score.toFixed(2)}`) : "";
|
|
82
|
+
out(
|
|
83
|
+
`${colors.bold(colors.purple(`${i + 1}. `))}${colors.bold(sanitizeControl(r.title ?? "Untitled"))} ${score}`
|
|
84
|
+
);
|
|
85
|
+
out(` ${colors.pink(safeLink(r.url ?? "", void 0, { stream: process.stdout }))}`);
|
|
86
|
+
if (r.content) out(` ${colors.dim(snippet(r.content, maxChars))}`);
|
|
87
|
+
out();
|
|
88
|
+
});
|
|
89
|
+
footer(results.length, "results", data.response_time);
|
|
90
|
+
if (images.length > 0) {
|
|
91
|
+
out();
|
|
92
|
+
out(colors.bold(`Images (${images.length}):`));
|
|
93
|
+
for (const img of images) out(` ${sanitizeControl(imageUrl(img))}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function printMultiSearchResults(responses, opts) {
|
|
97
|
+
if (opts.json || opts.outputFile) {
|
|
98
|
+
emit(responses, { outputFile: opts.outputFile, pretty: true });
|
|
99
|
+
if (opts.json || isTerminal(process.stdout)) return;
|
|
100
|
+
}
|
|
101
|
+
const terminal = isTerminal(process.stdout);
|
|
102
|
+
for (const [i, r] of responses.entries()) {
|
|
103
|
+
const header = `=== [${i + 1}/${responses.length}] ${plain(r.query ?? "")}`;
|
|
104
|
+
out(terminal ? colors.bold(header) : header);
|
|
105
|
+
if ("error" in r) {
|
|
106
|
+
const error = r.error;
|
|
107
|
+
const text = typeof error === "object" && error !== null ? error.message : error;
|
|
108
|
+
out(`error: ${plain(text)}
|
|
109
|
+
`);
|
|
110
|
+
} else {
|
|
111
|
+
await printSearchResults(r, {
|
|
112
|
+
json: false,
|
|
113
|
+
...opts.maxChars !== void 0 ? { maxChars: opts.maxChars } : {}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/commands/search.ts
|
|
120
|
+
var MAX_QUERIES = 5;
|
|
121
|
+
var DEFAULT_CHARS = 600;
|
|
122
|
+
function configure(cmd) {
|
|
123
|
+
applySearchFlags(cmd);
|
|
124
|
+
cmd.addOption(
|
|
125
|
+
new Option(
|
|
126
|
+
"--max-chars <n>",
|
|
127
|
+
"Characters of each result's content to print (default 600, shared across queries: 300 each for 2, 200 for 3); 0 prints it whole. --json and -o files keep it whole."
|
|
128
|
+
).argParser((raw) => parseNumberArg(raw, { integer: true, minimum: 0 }))
|
|
129
|
+
);
|
|
130
|
+
addCommonOptions(cmd);
|
|
131
|
+
}
|
|
132
|
+
function readQueries(args, cmd) {
|
|
133
|
+
const queries = args.length === 1 && args[0] === "-" ? [readQueryArg("-", cmd)] : args.filter((q) => q.trim());
|
|
134
|
+
if (queries.length === 0) cmd.error(QUERY_REQUIRED);
|
|
135
|
+
if (queries.length > MAX_QUERIES) {
|
|
136
|
+
cmd.error(`error: At most ${MAX_QUERIES} queries per command, got ${queries.length}.`);
|
|
137
|
+
}
|
|
138
|
+
return queries;
|
|
139
|
+
}
|
|
140
|
+
async function run(args, opts, cmd, host) {
|
|
141
|
+
const json = resolveJson(cmd);
|
|
142
|
+
const queries = readQueries(args, cmd);
|
|
143
|
+
const outputFile = resolveOutput(opts.output, "search", queries[0]);
|
|
144
|
+
const maxChars = opts.maxChars ?? Math.floor(DEFAULT_CHARS / queries.length);
|
|
145
|
+
const body = searchBodyFromOptions(opts);
|
|
146
|
+
const call = { label: "Searching...", keyed: false, clientName: opts.clientName };
|
|
147
|
+
if (queries.length === 1) {
|
|
148
|
+
const response = await invoke(
|
|
149
|
+
cmd,
|
|
150
|
+
host,
|
|
151
|
+
call,
|
|
152
|
+
(client) => client.search({ query: queries[0], ...body })
|
|
153
|
+
);
|
|
154
|
+
await printSearchResults(response, { json, outputFile, maxChars });
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const capHits = [];
|
|
158
|
+
let limitHit = false;
|
|
159
|
+
const responses = await invoke(
|
|
160
|
+
cmd,
|
|
161
|
+
host,
|
|
162
|
+
call,
|
|
163
|
+
(client) => Promise.all(
|
|
164
|
+
queries.map(async (query) => {
|
|
165
|
+
try {
|
|
166
|
+
return await client.search({ query, ...body });
|
|
167
|
+
} catch (e) {
|
|
168
|
+
if (e instanceof TavilyKeylessLimitError) {
|
|
169
|
+
capHits.push(e);
|
|
170
|
+
return { query, error: keylessCapError(e) };
|
|
171
|
+
}
|
|
172
|
+
if (e instanceof TavilyRequestError && LIMIT_STATUSES.has(e.status)) limitHit = true;
|
|
173
|
+
return { query, error: messageOf(e) };
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
)
|
|
177
|
+
);
|
|
178
|
+
const [firstCap] = capHits;
|
|
179
|
+
if (firstCap && !json) {
|
|
180
|
+
printKeylessCapHit(firstCap);
|
|
181
|
+
throw new ExitError(3);
|
|
182
|
+
}
|
|
183
|
+
await printMultiSearchResults(responses, { json, outputFile, maxChars });
|
|
184
|
+
if (responses.every((r) => "error" in r)) {
|
|
185
|
+
throw new ExitError(capHits.length > 0 || limitHit ? 3 : 4);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
export {
|
|
189
|
+
configure,
|
|
190
|
+
run
|
|
191
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
AGENT_NAMES
|
|
4
|
+
} from "./chunk-HQ7QBZ5H.js";
|
|
5
|
+
import {
|
|
6
|
+
MCP_AUTH_NOTE,
|
|
7
|
+
detectAgents,
|
|
8
|
+
installMcp
|
|
9
|
+
} from "./chunk-ULNXDG4Q.js";
|
|
10
|
+
import {
|
|
11
|
+
sanitizeControl
|
|
12
|
+
} from "./chunk-QHWWSCVM.js";
|
|
13
|
+
import {
|
|
14
|
+
resolveJson
|
|
15
|
+
} from "./chunk-IRCF6F3S.js";
|
|
16
|
+
import {
|
|
17
|
+
colors,
|
|
18
|
+
out
|
|
19
|
+
} from "./chunk-E4N27QWM.js";
|
|
20
|
+
import {
|
|
21
|
+
ExitError
|
|
22
|
+
} from "./chunk-5JXZZSLS.js";
|
|
23
|
+
|
|
24
|
+
// src/commands/setup.ts
|
|
25
|
+
async function runMcp(opts, cmd, host) {
|
|
26
|
+
const json = resolveJson(cmd);
|
|
27
|
+
const requested = opts.agent ?? [];
|
|
28
|
+
const agents = requested.includes("all") ? [...AGENT_NAMES] : requested.length ? [...new Set(requested)] : detectAgents(host);
|
|
29
|
+
const result = await installMcp(agents, opts.keyless === true, host);
|
|
30
|
+
const ok = agents.length === 0 || result.configured.length > 0;
|
|
31
|
+
printMcp(result, ok, json);
|
|
32
|
+
if (!ok) throw new ExitError(1);
|
|
33
|
+
}
|
|
34
|
+
function printMcp(result, ok, json) {
|
|
35
|
+
if (json) {
|
|
36
|
+
out(JSON.stringify({ ok, ...result }));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
for (const agent of result.configured) {
|
|
40
|
+
out(` ${colors.green("\u2713")} ${agent}: Tavily MCP server added`);
|
|
41
|
+
}
|
|
42
|
+
for (const { agent, reason } of result.skipped) {
|
|
43
|
+
out(` ${colors.dim("\u2022")} ${agent}: skipped (${sanitizeControl(reason)})`);
|
|
44
|
+
}
|
|
45
|
+
if (result.configured.length) out(` ${colors.dim(MCP_AUTH_NOTE[result.auth])}`);
|
|
46
|
+
else if (!result.skipped.length) {
|
|
47
|
+
out(` ${colors.dim("No supported agents detected. Use --agent <name> or --agent all.")}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export {
|
|
51
|
+
runMcp
|
|
52
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
definedSnake,
|
|
4
|
+
echoPages,
|
|
5
|
+
emitIfRequested,
|
|
6
|
+
footer,
|
|
7
|
+
invoke,
|
|
8
|
+
list,
|
|
9
|
+
resolveOutput,
|
|
10
|
+
splitCsv
|
|
11
|
+
} from "./chunk-KQFC5TMG.js";
|
|
12
|
+
import "./chunk-U3B7BNFI.js";
|
|
13
|
+
import "./chunk-K76OUMXM.js";
|
|
14
|
+
import {
|
|
15
|
+
hostOf,
|
|
16
|
+
isTerminal,
|
|
17
|
+
plain,
|
|
18
|
+
safeLink,
|
|
19
|
+
sanitizeControl,
|
|
20
|
+
tree
|
|
21
|
+
} from "./chunk-QHWWSCVM.js";
|
|
22
|
+
import "./chunk-AERAIFZU.js";
|
|
23
|
+
import {
|
|
24
|
+
resolveJson
|
|
25
|
+
} from "./chunk-IRCF6F3S.js";
|
|
26
|
+
import {
|
|
27
|
+
colors,
|
|
28
|
+
err,
|
|
29
|
+
out
|
|
30
|
+
} from "./chunk-E4N27QWM.js";
|
|
31
|
+
import "./chunk-5JXZZSLS.js";
|
|
32
|
+
|
|
33
|
+
// src/ui/render/crawl.ts
|
|
34
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
35
|
+
import path from "path";
|
|
36
|
+
function slugOf(url) {
|
|
37
|
+
let location = url;
|
|
38
|
+
try {
|
|
39
|
+
const parsed = new URL(url);
|
|
40
|
+
location = parsed.host + parsed.pathname.replace(/\/+$/, "") + parsed.search;
|
|
41
|
+
} catch {
|
|
42
|
+
}
|
|
43
|
+
return location.replace(/[^\p{L}\p{N}_\-.]/gu, "_").replace(/^_+|_+$/g, "") || "index";
|
|
44
|
+
}
|
|
45
|
+
function saveCrawlToDir(data, dir) {
|
|
46
|
+
mkdirSync(dir, { recursive: true });
|
|
47
|
+
const used = /* @__PURE__ */ new Set();
|
|
48
|
+
for (const page of list(data.results)) {
|
|
49
|
+
if (!page.raw_content) continue;
|
|
50
|
+
const url = page.url ?? "";
|
|
51
|
+
const slug = slugOf(url);
|
|
52
|
+
let name = slug;
|
|
53
|
+
for (let n = 2; used.has(name.toLowerCase()); n++) name = `${slug}-${n}`;
|
|
54
|
+
used.add(name.toLowerCase());
|
|
55
|
+
writeFileSync(path.join(dir, `${name}.md`), `# ${url}
|
|
56
|
+
|
|
57
|
+
${page.raw_content}
|
|
58
|
+
`);
|
|
59
|
+
}
|
|
60
|
+
err(`Saved ${used.size} pages to ${dir}/`);
|
|
61
|
+
}
|
|
62
|
+
function printCrawlResults(data, opts) {
|
|
63
|
+
if (opts.outputDir) {
|
|
64
|
+
saveCrawlToDir(data, opts.outputDir);
|
|
65
|
+
emitIfRequested(data, opts);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (emitIfRequested(data, opts)) return;
|
|
69
|
+
const pages = list(data.results);
|
|
70
|
+
if (!isTerminal(process.stdout)) {
|
|
71
|
+
echoPages(pages);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const nodes = pages.map((page) => {
|
|
75
|
+
const url = page.url ?? "";
|
|
76
|
+
const raw = page.raw_content ?? "";
|
|
77
|
+
let pathname = url;
|
|
78
|
+
try {
|
|
79
|
+
pathname = new URL(url).pathname || "/";
|
|
80
|
+
} catch {
|
|
81
|
+
}
|
|
82
|
+
const label = `${colors.aqua(sanitizeControl(pathname))}${colors.dim(` (${raw.length.toLocaleString("en-US")} chars)`)}`;
|
|
83
|
+
const preview = raw.trim().split("\n")[0]?.slice(0, 120);
|
|
84
|
+
return { label, children: preview ? [colors.dim(sanitizeControl(preview))] : [] };
|
|
85
|
+
});
|
|
86
|
+
out(tree(colors.bold(sanitizeControl(data.base_url ?? "")), nodes));
|
|
87
|
+
footer(pages.length, "pages", data.response_time);
|
|
88
|
+
}
|
|
89
|
+
function printMapResults(data, opts) {
|
|
90
|
+
if (emitIfRequested(data, opts)) return;
|
|
91
|
+
const urls = list(data.results);
|
|
92
|
+
if (!isTerminal(process.stdout)) {
|
|
93
|
+
for (const url of urls) out(plain(url));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
out(
|
|
97
|
+
tree(
|
|
98
|
+
colors.bold(sanitizeControl(data.base_url ?? "")),
|
|
99
|
+
urls.map((url) => ({ label: safeLink(url, void 0, { stream: process.stdout }) }))
|
|
100
|
+
)
|
|
101
|
+
);
|
|
102
|
+
footer(urls.length, "URLs", data.response_time);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/commands/site.ts
|
|
106
|
+
function siteBody(url, opts) {
|
|
107
|
+
const body = {
|
|
108
|
+
url,
|
|
109
|
+
...definedSnake(opts, ["maxDepth", "maxBreadth", "limit", "instructions"])
|
|
110
|
+
};
|
|
111
|
+
const lists = {
|
|
112
|
+
select_paths: splitCsv(opts.selectPaths),
|
|
113
|
+
exclude_paths: splitCsv(opts.excludePaths),
|
|
114
|
+
select_domains: splitCsv(opts.selectDomains),
|
|
115
|
+
exclude_domains: splitCsv(opts.excludeDomains)
|
|
116
|
+
};
|
|
117
|
+
for (const [key, value] of Object.entries(lists)) if (value) body[key] = value;
|
|
118
|
+
const allowExternal = opts.external === false ? false : opts.allowExternal;
|
|
119
|
+
if (allowExternal !== void 0) body.allow_external = allowExternal;
|
|
120
|
+
return { ...body, ...definedSnake(opts, ["timeout"]) };
|
|
121
|
+
}
|
|
122
|
+
async function runCrawl(url, opts, cmd, host) {
|
|
123
|
+
const json = resolveJson(cmd);
|
|
124
|
+
const outputFile = resolveOutput(opts.output, "crawl", hostOf(url));
|
|
125
|
+
const body = {
|
|
126
|
+
...siteBody(url, opts),
|
|
127
|
+
...definedSnake(opts, ["chunksPerSource", "extractDepth", "format", "includeImages"])
|
|
128
|
+
};
|
|
129
|
+
const response = await invoke(
|
|
130
|
+
cmd,
|
|
131
|
+
host,
|
|
132
|
+
{ label: `Crawling ${url}...`, keyed: "crawl", clientName: opts.clientName },
|
|
133
|
+
(client) => client.crawl(body)
|
|
134
|
+
);
|
|
135
|
+
await printCrawlResults(response, { json, outputFile, outputDir: opts.outputDir });
|
|
136
|
+
}
|
|
137
|
+
async function runMap(url, opts, cmd, host) {
|
|
138
|
+
const json = resolveJson(cmd);
|
|
139
|
+
const outputFile = resolveOutput(opts.output, "map", hostOf(url));
|
|
140
|
+
const response = await invoke(
|
|
141
|
+
cmd,
|
|
142
|
+
host,
|
|
143
|
+
{ label: `Mapping ${url}...`, keyed: "map", clientName: opts.clientName },
|
|
144
|
+
(client) => client.map(siteBody(url, opts))
|
|
145
|
+
);
|
|
146
|
+
await printMapResults(response, { json, outputFile });
|
|
147
|
+
}
|
|
148
|
+
export {
|
|
149
|
+
runCrawl,
|
|
150
|
+
runMap
|
|
151
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
VERSION
|
|
4
|
+
} from "./chunk-SPBOEASO.js";
|
|
5
|
+
import {
|
|
6
|
+
getApiKeyOrExit
|
|
7
|
+
} from "./chunk-U3B7BNFI.js";
|
|
8
|
+
import "./chunk-QHWWSCVM.js";
|
|
9
|
+
import {
|
|
10
|
+
authSource
|
|
11
|
+
} from "./chunk-AERAIFZU.js";
|
|
12
|
+
import {
|
|
13
|
+
colors,
|
|
14
|
+
out
|
|
15
|
+
} from "./chunk-E4N27QWM.js";
|
|
16
|
+
import "./chunk-5JXZZSLS.js";
|
|
17
|
+
|
|
18
|
+
// src/commands/status.ts
|
|
19
|
+
async function printStatus(json, host) {
|
|
20
|
+
const key = await getApiKeyOrExit(json, host);
|
|
21
|
+
if (json) {
|
|
22
|
+
out(JSON.stringify({ version: VERSION, authenticated: key !== null }));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
out(` ${colors.bold(colors.green("tavily"))} v${VERSION}`);
|
|
26
|
+
out();
|
|
27
|
+
if (key !== null) {
|
|
28
|
+
out(` ${colors.green(">")} Authenticated via ${authSource(key, host.env)}`);
|
|
29
|
+
} else {
|
|
30
|
+
out(` ${colors.pink(">")} Not authenticated`);
|
|
31
|
+
out(" Run: tvly login");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
printStatus
|
|
36
|
+
};
|