@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,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/ui/theme.ts
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
var AQUA = "#5CD9E6";
|
|
6
|
+
var PINK = "#FAA2FB";
|
|
7
|
+
var YELLOW = "#FFC769";
|
|
8
|
+
var PURPLE = "#8385F9";
|
|
9
|
+
var GREEN = "#9BC0AE";
|
|
10
|
+
function colorEnabled(env = process.env) {
|
|
11
|
+
if (env.NO_COLOR || env.FORCE_COLOR === "0") return false;
|
|
12
|
+
return Boolean(env.FORCE_COLOR) || process.stdout.isTTY === true;
|
|
13
|
+
}
|
|
14
|
+
function paint(color, s) {
|
|
15
|
+
if (!colorEnabled()) return s;
|
|
16
|
+
const [r, g, b] = [1, 3, 5].map((i) => Number.parseInt(color.slice(i, i + 2), 16));
|
|
17
|
+
const cube = (c) => Math.round(c / 255 * 5);
|
|
18
|
+
const code = ["truecolor", "24bit"].includes(process.env.COLORTERM ?? "") ? `38;2;${r};${g};${b}` : `38;5;${16 + 36 * cube(r) + 6 * cube(g) + cube(b)}`;
|
|
19
|
+
return `\x1B[${code}m${s}\x1B[39m`;
|
|
20
|
+
}
|
|
21
|
+
var colors = {
|
|
22
|
+
aqua: (s) => paint(AQUA, s),
|
|
23
|
+
pink: (s) => paint(PINK, s),
|
|
24
|
+
yellow: (s) => paint(YELLOW, s),
|
|
25
|
+
purple: (s) => paint(PURPLE, s),
|
|
26
|
+
green: (s) => paint(GREEN, s),
|
|
27
|
+
dim: (s) => pc.createColors(colorEnabled()).dim(s),
|
|
28
|
+
bold: (s) => pc.createColors(colorEnabled()).bold(s)
|
|
29
|
+
};
|
|
30
|
+
var LOGO = [
|
|
31
|
+
colors.aqua(" _ _ _ "),
|
|
32
|
+
colors.pink(" | |_ __ ___ _(_) |_ _ "),
|
|
33
|
+
colors.yellow(" | __/ _` \\ \\ / / | | | | |"),
|
|
34
|
+
colors.purple(" | || (_| |\\ V /| | | |_| |"),
|
|
35
|
+
colors.green(" \\__\\__,_| \\_/ |_|_|\\__, |"),
|
|
36
|
+
colors.dim(" |___/ ")
|
|
37
|
+
].join("\n");
|
|
38
|
+
function out(line = "") {
|
|
39
|
+
process.stdout.write(`${line}
|
|
40
|
+
`);
|
|
41
|
+
}
|
|
42
|
+
function err(line = "") {
|
|
43
|
+
process.stderr.write(`${line}
|
|
44
|
+
`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
colorEnabled,
|
|
49
|
+
colors,
|
|
50
|
+
LOGO,
|
|
51
|
+
out,
|
|
52
|
+
err
|
|
53
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/commands/options.ts
|
|
4
|
+
import { InvalidArgumentError, Option } from "commander";
|
|
5
|
+
function resolveJson(cmd) {
|
|
6
|
+
return cmd.optsWithGlobals().json === true;
|
|
7
|
+
}
|
|
8
|
+
function addClientNameOption(cmd) {
|
|
9
|
+
return cmd.addOption(
|
|
10
|
+
new Option("--client-name <name>", "Set optional client_name for request attribution.").env(
|
|
11
|
+
"TAVILY_CLIENT_NAME"
|
|
12
|
+
)
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
function addOutputOption(cmd) {
|
|
16
|
+
return cmd.addOption(
|
|
17
|
+
new Option(
|
|
18
|
+
"-o, --output [file]",
|
|
19
|
+
"Save the full JSON response to this file. With no value, save it under .tavily/ with a generated name."
|
|
20
|
+
).preset("")
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
function addCommonOptions(cmd) {
|
|
24
|
+
return addClientNameOption(addOutputOption(cmd));
|
|
25
|
+
}
|
|
26
|
+
function parseNumberArg(raw, opts = {}) {
|
|
27
|
+
const n = raw.trim() === "" ? Number.NaN : Number(raw);
|
|
28
|
+
if (Number.isNaN(n)) throw new InvalidArgumentError("Not a number.");
|
|
29
|
+
if (opts.integer && !Number.isInteger(n)) throw new InvalidArgumentError("Not an integer.");
|
|
30
|
+
if (opts.minimum !== void 0 && n < opts.minimum) {
|
|
31
|
+
throw new InvalidArgumentError(`${n} is below the minimum ${opts.minimum}.`);
|
|
32
|
+
}
|
|
33
|
+
if (opts.maximum !== void 0 && n > opts.maximum) {
|
|
34
|
+
throw new InvalidArgumentError(`${n} is above the maximum ${opts.maximum}.`);
|
|
35
|
+
}
|
|
36
|
+
return n;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
resolveJson,
|
|
41
|
+
addClientNameOption,
|
|
42
|
+
addOutputOption,
|
|
43
|
+
addCommonOptions,
|
|
44
|
+
parseNumberArg
|
|
45
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
isTerminal
|
|
4
|
+
} from "./chunk-QHWWSCVM.js";
|
|
5
|
+
|
|
6
|
+
// src/ui/spinner.ts
|
|
7
|
+
var FRAMES = "\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F";
|
|
8
|
+
var INTERVAL_MS = 80;
|
|
9
|
+
function spinner(message, enabled) {
|
|
10
|
+
const stream = process.stderr;
|
|
11
|
+
if (!enabled || !isTerminal(stream)) return { update() {
|
|
12
|
+
}, stop() {
|
|
13
|
+
} };
|
|
14
|
+
let frame = 0;
|
|
15
|
+
const draw = () => stream.write(`\r${FRAMES[frame++ % FRAMES.length]} ${message}`);
|
|
16
|
+
draw();
|
|
17
|
+
const timer = setInterval(draw, INTERVAL_MS);
|
|
18
|
+
let stopped = false;
|
|
19
|
+
return {
|
|
20
|
+
update(next) {
|
|
21
|
+
message = next;
|
|
22
|
+
},
|
|
23
|
+
stop() {
|
|
24
|
+
if (stopped) return;
|
|
25
|
+
stopped = true;
|
|
26
|
+
clearInterval(timer);
|
|
27
|
+
stream.write("\r\x1B[2K");
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
async function withSpinner(message, enabled, fn) {
|
|
32
|
+
const s = spinner(message, enabled);
|
|
33
|
+
try {
|
|
34
|
+
return await fn();
|
|
35
|
+
} finally {
|
|
36
|
+
s.stop();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export {
|
|
41
|
+
spinner,
|
|
42
|
+
withSpinner
|
|
43
|
+
};
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getApiKeyOrExit
|
|
4
|
+
} from "./chunk-U3B7BNFI.js";
|
|
5
|
+
import {
|
|
6
|
+
withSpinner
|
|
7
|
+
} from "./chunk-K76OUMXM.js";
|
|
8
|
+
import {
|
|
9
|
+
plain,
|
|
10
|
+
safeLink,
|
|
11
|
+
sanitizeControl
|
|
12
|
+
} from "./chunk-QHWWSCVM.js";
|
|
13
|
+
import {
|
|
14
|
+
parseNumberArg,
|
|
15
|
+
resolveJson
|
|
16
|
+
} from "./chunk-IRCF6F3S.js";
|
|
17
|
+
import {
|
|
18
|
+
colors,
|
|
19
|
+
err,
|
|
20
|
+
out
|
|
21
|
+
} from "./chunk-E4N27QWM.js";
|
|
22
|
+
import {
|
|
23
|
+
ExitError,
|
|
24
|
+
isObject,
|
|
25
|
+
messageOf
|
|
26
|
+
} from "./chunk-5JXZZSLS.js";
|
|
27
|
+
|
|
28
|
+
// src/ui/render/emit.ts
|
|
29
|
+
import { randomBytes } from "crypto";
|
|
30
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
31
|
+
import path from "path";
|
|
32
|
+
var DEFAULT_OUTPUT_DIR = ".tavily";
|
|
33
|
+
var list = (value) => Array.isArray(value) ? value : [];
|
|
34
|
+
function emit(data, opts) {
|
|
35
|
+
const text = JSON.stringify(data, null, opts.pretty ? 2 : void 0);
|
|
36
|
+
if (!opts.outputFile) {
|
|
37
|
+
out(text);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const folder = path.dirname(opts.outputFile);
|
|
41
|
+
if (!existsSync(folder)) {
|
|
42
|
+
mkdirSync(folder, { recursive: true });
|
|
43
|
+
if (path.basename(path.normalize(folder)) === DEFAULT_OUTPUT_DIR) {
|
|
44
|
+
writeFileSync(path.join(folder, ".gitignore"), "*\n");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
writeFileSync(opts.outputFile, `${text}
|
|
48
|
+
`);
|
|
49
|
+
err(`Output saved to ${opts.outputFile}`);
|
|
50
|
+
}
|
|
51
|
+
function emitIfRequested(data, opts) {
|
|
52
|
+
if (!opts.json && !opts.outputFile) return false;
|
|
53
|
+
emit(data, { outputFile: opts.outputFile, pretty: true });
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
function footer(count, unit, responseTime) {
|
|
57
|
+
const parts = [`${count} ${unit}`];
|
|
58
|
+
if (typeof responseTime === "number" && responseTime) parts.push(`${responseTime.toFixed(2)}s`);
|
|
59
|
+
const label = ` ${parts.join(" | ")} `;
|
|
60
|
+
const fill = Math.max(0, (process.stdout.columns ?? 80) - label.length);
|
|
61
|
+
const left = Math.floor(fill / 2);
|
|
62
|
+
out();
|
|
63
|
+
out(colors.dim(`${"\u2500".repeat(left)}${label}${"\u2500".repeat(fill - left)}`));
|
|
64
|
+
}
|
|
65
|
+
function echoPages(pages) {
|
|
66
|
+
for (const page of pages) {
|
|
67
|
+
out(`# ${plain(page.url ?? "")}
|
|
68
|
+
`);
|
|
69
|
+
out(`${plain(page.raw_content || "(no content)")}
|
|
70
|
+
`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
var pad = (n) => String(n).padStart(2, "0");
|
|
74
|
+
function resolveOutput(outputFile, command, label, now = /* @__PURE__ */ new Date(), random = () => randomBytes(3).toString("hex")) {
|
|
75
|
+
if (outputFile !== "") return outputFile;
|
|
76
|
+
const slug = (String(label).toLowerCase().match(/[a-z0-9]+/g) ?? []).join("-").slice(0, 40).replace(/^-+|-+$/g, "") || "result";
|
|
77
|
+
const time = `${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
78
|
+
return path.join(DEFAULT_OUTPUT_DIR, `${command}-${slug}-${time}-${random()}.json`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/ui/keyless.ts
|
|
82
|
+
function formatSeconds(seconds) {
|
|
83
|
+
if (seconds < 60) return `${seconds}s`;
|
|
84
|
+
if (seconds < 3600) {
|
|
85
|
+
const m = Math.floor(seconds / 60);
|
|
86
|
+
const s = seconds % 60;
|
|
87
|
+
return s ? `${m}m ${s}s` : `${m}m`;
|
|
88
|
+
}
|
|
89
|
+
if (seconds < 86400) {
|
|
90
|
+
const h2 = Math.floor(seconds / 3600);
|
|
91
|
+
const m = Math.floor(seconds % 3600 / 60);
|
|
92
|
+
return m ? `${h2}h ${m}m` : `${h2}h`;
|
|
93
|
+
}
|
|
94
|
+
const d = Math.floor(seconds / 86400);
|
|
95
|
+
const h = Math.floor(seconds % 86400 / 3600);
|
|
96
|
+
return h ? `${d}d ${h}h` : `${d}d`;
|
|
97
|
+
}
|
|
98
|
+
var orDefault = (value, fallback) => value ? String(value) : fallback;
|
|
99
|
+
function keylessCapError(e) {
|
|
100
|
+
return {
|
|
101
|
+
code: e.capType,
|
|
102
|
+
message: e.message,
|
|
103
|
+
window: e.window,
|
|
104
|
+
retry_after_seconds: e.retryAfter,
|
|
105
|
+
next_actions: e.continuationPaths
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function printKeylessCapHit(e) {
|
|
109
|
+
err();
|
|
110
|
+
sanitizeControl(formatKeylessEnvelopeForTerminal(e)).split("\n").forEach((line, i) => {
|
|
111
|
+
if (i === 0) err(` ${colors.yellow(">")} ${colors.bold(line)}`);
|
|
112
|
+
else err(line ? ` ${line}` : "");
|
|
113
|
+
});
|
|
114
|
+
err();
|
|
115
|
+
err(
|
|
116
|
+
` ${colors.dim("Run ")}${colors.green("tvly login")}${colors.dim(" to authenticate and remove this cap.")}`
|
|
117
|
+
);
|
|
118
|
+
err();
|
|
119
|
+
}
|
|
120
|
+
function formatKeylessEnvelopeForTerminal(e) {
|
|
121
|
+
const lines = ["Tavily rate limit reached."];
|
|
122
|
+
if (e.message.trim()) lines.push(e.message.trim());
|
|
123
|
+
const retry = e.retryAfter;
|
|
124
|
+
if (typeof retry === "number" && Number.isInteger(retry) && retry > 0) {
|
|
125
|
+
lines.push("", `Retry after: ${formatSeconds(retry)} (${retry}s)`);
|
|
126
|
+
}
|
|
127
|
+
const actions = e.continuationPaths.filter(isObject);
|
|
128
|
+
const last = (type) => [...actions].reverse().find((a) => a.type === type);
|
|
129
|
+
const signup = last("signup");
|
|
130
|
+
const payment = last("agentic_payment");
|
|
131
|
+
const bonus = last("bonus_credits");
|
|
132
|
+
if (signup || payment || bonus) {
|
|
133
|
+
lines.push("", "Continuation options:");
|
|
134
|
+
if (signup) {
|
|
135
|
+
lines.push(
|
|
136
|
+
` - Sign up for a Tavily API key: ${orDefault(signup.resource, "https://tavily.com")}`
|
|
137
|
+
);
|
|
138
|
+
if (signup.instruction) lines.push(` ${signup.instruction}`);
|
|
139
|
+
}
|
|
140
|
+
if (payment) {
|
|
141
|
+
const label = ` - Pay via ${orDefault(payment.scheme, "x402")} agentic payment`;
|
|
142
|
+
const resource = payment.resource;
|
|
143
|
+
lines.push(resource ? `${label}: ${resource}` : label);
|
|
144
|
+
if (payment.instruction) lines.push(` ${payment.instruction}`);
|
|
145
|
+
}
|
|
146
|
+
if (bonus) {
|
|
147
|
+
const params = isObject(bonus.params) ? bonus.params : {};
|
|
148
|
+
const questions = Array.isArray(params.questions) ? params.questions : [];
|
|
149
|
+
if (questions.length > 0) {
|
|
150
|
+
const credits = params.credits_on_completion;
|
|
151
|
+
const noun = `${questions.length} question${questions.length !== 1 ? "s" : ""}`;
|
|
152
|
+
lines.push(
|
|
153
|
+
typeof credits === "number" && Number.isInteger(credits) ? ` - Earn ${credits} bonus credits by answering ${noun}:` : ` - Earn bonus credits by answering ${noun}:`
|
|
154
|
+
);
|
|
155
|
+
questions.forEach((q, i) => {
|
|
156
|
+
if (typeof q === "string") lines.push(` ${i + 1}. ${q}`);
|
|
157
|
+
});
|
|
158
|
+
lines.push(` POST your answers to: ${orDefault(bonus.resource, "/v1/keyless/bonus")}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return lines.join("\n");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/commands/invoke.ts
|
|
166
|
+
import { TavilyKeylessLimitError, TavilyRequestError } from "@tavily/core";
|
|
167
|
+
var LIMIT_STATUSES = /* @__PURE__ */ new Set([429, 432]);
|
|
168
|
+
var AUTH_STATUSES = /* @__PURE__ */ new Set([401, 403]);
|
|
169
|
+
async function invoke(cmd, host, opts, fn) {
|
|
170
|
+
const json = resolveJson(cmd);
|
|
171
|
+
const key = await getApiKeyOrExit(json, host);
|
|
172
|
+
if (opts.keyed && !key) requireKey(opts.keyed);
|
|
173
|
+
const client = await host.resolveClient(key, opts.clientName);
|
|
174
|
+
try {
|
|
175
|
+
const { label } = opts;
|
|
176
|
+
return label === void 0 ? await fn(client) : await withSpinner(label, !json, () => fn(client));
|
|
177
|
+
} catch (e) {
|
|
178
|
+
if (e instanceof ExitError) throw e;
|
|
179
|
+
if (e instanceof TavilyKeylessLimitError) {
|
|
180
|
+
if (json) out(JSON.stringify({ error: keylessCapError(e) }));
|
|
181
|
+
else printKeylessCapHit(e);
|
|
182
|
+
throw new ExitError(3);
|
|
183
|
+
}
|
|
184
|
+
return handleApiError(e, json);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function requireKey(command) {
|
|
188
|
+
err();
|
|
189
|
+
err(` ${colors.pink(">")} The ${colors.bold(command)} command requires a Tavily API key.`);
|
|
190
|
+
err();
|
|
191
|
+
err(` Sign up for a free key at ${safeLink("https://tavily.com")}`);
|
|
192
|
+
err(
|
|
193
|
+
` Then run ${colors.green("tvly login")} or ${colors.green("tvly login --api-key tvly-YOUR_KEY")}`
|
|
194
|
+
);
|
|
195
|
+
err();
|
|
196
|
+
err(
|
|
197
|
+
colors.dim(
|
|
198
|
+
` Tip: ${colors.green("tvly search")} and ${colors.green("tvly extract")} work without an API key (subject to a rate-limit cap).`
|
|
199
|
+
)
|
|
200
|
+
);
|
|
201
|
+
err();
|
|
202
|
+
throw new ExitError(3);
|
|
203
|
+
}
|
|
204
|
+
function handleApiError(e, json) {
|
|
205
|
+
const limit = e instanceof TavilyRequestError && LIMIT_STATUSES.has(e.status);
|
|
206
|
+
const code = limit || e instanceof TavilyRequestError && AUTH_STATUSES.has(e.status) ? 3 : 4;
|
|
207
|
+
if (json) {
|
|
208
|
+
out(JSON.stringify({ error: messageOf(e) }));
|
|
209
|
+
throw new ExitError(code);
|
|
210
|
+
}
|
|
211
|
+
const text = sanitizeControl(messageOf(e));
|
|
212
|
+
if (limit) {
|
|
213
|
+
err();
|
|
214
|
+
err(` ${colors.yellow(">")} ${text}`);
|
|
215
|
+
err();
|
|
216
|
+
err(
|
|
217
|
+
` ${colors.dim("Upgrade your plan at")} ${colors.green(safeLink("https://tavily.com", "tavily.com"))}`
|
|
218
|
+
);
|
|
219
|
+
err();
|
|
220
|
+
throw new ExitError(3);
|
|
221
|
+
}
|
|
222
|
+
err(` ${colors.pink("> Error:")} ${text}`);
|
|
223
|
+
throw new ExitError(code);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// src/core/search-flags.ts
|
|
227
|
+
import { searchParameters } from "@tavily/core/parameters";
|
|
228
|
+
import { InvalidArgumentError, Option } from "commander";
|
|
229
|
+
var SEARCH_FLAG_OVERRIDES = {
|
|
230
|
+
query: { positional: true },
|
|
231
|
+
searchDepth: { flag: "depth" },
|
|
232
|
+
includeAnswer: { bareValue: "basic" },
|
|
233
|
+
includeRawContent: { bareValue: "markdown" }
|
|
234
|
+
};
|
|
235
|
+
var PYTHON_ORDER = [
|
|
236
|
+
"searchDepth",
|
|
237
|
+
"maxResults",
|
|
238
|
+
"topic",
|
|
239
|
+
"timeRange",
|
|
240
|
+
"startDate",
|
|
241
|
+
"endDate",
|
|
242
|
+
"includeDomains",
|
|
243
|
+
"excludeDomains",
|
|
244
|
+
"country",
|
|
245
|
+
"includeAnswer",
|
|
246
|
+
"includeRawContent",
|
|
247
|
+
"includeImages",
|
|
248
|
+
"includeImageDescriptions",
|
|
249
|
+
"chunksPerSource"
|
|
250
|
+
];
|
|
251
|
+
var kebab = (key) => key.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
252
|
+
var snake = (key) => key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
253
|
+
function checkChoice(raw, choices) {
|
|
254
|
+
if (!choices.includes(raw)) {
|
|
255
|
+
throw new InvalidArgumentError(`Allowed choices are ${choices.join(", ")}.`);
|
|
256
|
+
}
|
|
257
|
+
return raw;
|
|
258
|
+
}
|
|
259
|
+
function toSpec(key, def) {
|
|
260
|
+
const override = SEARCH_FLAG_OVERRIDES[key] ?? {};
|
|
261
|
+
const flag = `--${override.flag ?? kebab(key)}`;
|
|
262
|
+
const base = { key, description: def.description };
|
|
263
|
+
if (Array.isArray(def.type)) {
|
|
264
|
+
const values = "enum" in def ? def.enum : [];
|
|
265
|
+
const choices = [
|
|
266
|
+
...values.filter((v) => typeof v === "string"),
|
|
267
|
+
...values.filter((v) => typeof v === "boolean")
|
|
268
|
+
].map(String);
|
|
269
|
+
return {
|
|
270
|
+
...base,
|
|
271
|
+
flags: `${flag} [value]`,
|
|
272
|
+
choices,
|
|
273
|
+
parse: (raw) => {
|
|
274
|
+
const value = checkChoice(raw === true ? override.bareValue ?? "true" : raw, choices);
|
|
275
|
+
return value === "true" ? true : value === "false" ? false : value;
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
switch (def.type) {
|
|
280
|
+
case "boolean":
|
|
281
|
+
return { ...base, flags: flag, parse: () => true };
|
|
282
|
+
case "array":
|
|
283
|
+
return {
|
|
284
|
+
...base,
|
|
285
|
+
flags: `${flag} <list>`,
|
|
286
|
+
parse: (raw) => String(raw).split(",").map((s) => s.trim()).filter(Boolean)
|
|
287
|
+
};
|
|
288
|
+
case "integer":
|
|
289
|
+
case "number":
|
|
290
|
+
return {
|
|
291
|
+
...base,
|
|
292
|
+
flags: `${flag} <n>`,
|
|
293
|
+
parse: (raw) => parseNumberArg(String(raw), {
|
|
294
|
+
integer: def.type === "integer",
|
|
295
|
+
...def.minimum !== void 0 ? { minimum: def.minimum } : {},
|
|
296
|
+
...def.maximum !== void 0 ? { maximum: def.maximum } : {}
|
|
297
|
+
})
|
|
298
|
+
};
|
|
299
|
+
default: {
|
|
300
|
+
const choices = "enum" in def ? def.enum.map(String) : void 0;
|
|
301
|
+
return {
|
|
302
|
+
...base,
|
|
303
|
+
flags: `${flag} <value>`,
|
|
304
|
+
...choices ? { choices } : {},
|
|
305
|
+
parse: (raw) => choices ? checkChoice(String(raw), choices) : String(raw)
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function searchFlagSpecs() {
|
|
311
|
+
const keys = Object.keys(searchParameters).filter(
|
|
312
|
+
(key) => !SEARCH_FLAG_OVERRIDES[key]?.positional
|
|
313
|
+
);
|
|
314
|
+
const specs = keys.map((key) => toSpec(key, searchParameters[key]));
|
|
315
|
+
const rank = (s) => PYTHON_ORDER.indexOf(s.key);
|
|
316
|
+
const python = specs.filter((s) => rank(s) >= 0).sort((a, b) => rank(a) - rank(b));
|
|
317
|
+
const rest = specs.filter((s) => rank(s) < 0).sort((a, b) => a.flags < b.flags ? -1 : 1);
|
|
318
|
+
return [...python, ...rest];
|
|
319
|
+
}
|
|
320
|
+
function applySearchFlags(cmd) {
|
|
321
|
+
for (const spec of searchFlagSpecs()) {
|
|
322
|
+
const option = new Option(spec.flags, spec.description);
|
|
323
|
+
if (spec.choices) option.choices(spec.choices);
|
|
324
|
+
if (!option.isBoolean()) option.argParser((raw) => spec.parse(raw));
|
|
325
|
+
const bareValue = SEARCH_FLAG_OVERRIDES[spec.key]?.bareValue;
|
|
326
|
+
if (bareValue !== void 0) option.preset(bareValue);
|
|
327
|
+
option.attributeName = () => spec.key;
|
|
328
|
+
cmd.addOption(option);
|
|
329
|
+
}
|
|
330
|
+
return cmd;
|
|
331
|
+
}
|
|
332
|
+
function searchBodyFromOptions(opts) {
|
|
333
|
+
const body = {};
|
|
334
|
+
for (const key of Object.keys(searchParameters)) {
|
|
335
|
+
const value = opts[key];
|
|
336
|
+
if (value === void 0 || Array.isArray(value) && value.length === 0) continue;
|
|
337
|
+
body[snake(key)] = value;
|
|
338
|
+
}
|
|
339
|
+
return body;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// src/commands/shared.ts
|
|
343
|
+
import { readFileSync } from "fs";
|
|
344
|
+
function splitCsv(value) {
|
|
345
|
+
return value ? value.split(",").map((part) => part.trim()) : void 0;
|
|
346
|
+
}
|
|
347
|
+
function definedSnake(opts, keys) {
|
|
348
|
+
const body = {};
|
|
349
|
+
for (const key of keys) {
|
|
350
|
+
const value = opts[key];
|
|
351
|
+
if (value !== void 0) body[snake(key)] = value;
|
|
352
|
+
}
|
|
353
|
+
return body;
|
|
354
|
+
}
|
|
355
|
+
var MAX_STDIN_CHARS = 1e5;
|
|
356
|
+
var QUERY_REQUIRED = "error: QUERY is required. Pass a query string or use '-' to read from stdin.";
|
|
357
|
+
function readQueryArg(query, cmd) {
|
|
358
|
+
const value = (query === "-" ? readFileSync(0, "utf8").slice(0, MAX_STDIN_CHARS) : query ?? "").trim();
|
|
359
|
+
if (!value) cmd.error(QUERY_REQUIRED);
|
|
360
|
+
return value;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export {
|
|
364
|
+
list,
|
|
365
|
+
emit,
|
|
366
|
+
emitIfRequested,
|
|
367
|
+
footer,
|
|
368
|
+
echoPages,
|
|
369
|
+
resolveOutput,
|
|
370
|
+
keylessCapError,
|
|
371
|
+
printKeylessCapHit,
|
|
372
|
+
LIMIT_STATUSES,
|
|
373
|
+
invoke,
|
|
374
|
+
applySearchFlags,
|
|
375
|
+
searchBodyFromOptions,
|
|
376
|
+
splitCsv,
|
|
377
|
+
definedSnake,
|
|
378
|
+
QUERY_REQUIRED,
|
|
379
|
+
readQueryArg
|
|
380
|
+
};
|