@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,147 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/core/skills.ts
4
+ import { readdirSync, readFileSync, statSync } from "fs";
5
+ import path from "path";
6
+ import { isDeepStrictEqual } from "util";
7
+ var SKILLS_REPO = "tavily-ai/skills";
8
+ var WORKFLOWS_REPO = "tavily-ai/official-workflows";
9
+ var NPX_SKILLS_PACKAGE = "skills@1.7.4";
10
+ var NPX_ADD_MCP_PACKAGE = "add-mcp@1.14.0";
11
+ function agentRoots({ home: root, env }) {
12
+ return {
13
+ "claude-code": env.CLAUDE_CONFIG_DIR || path.join(root, ".claude"),
14
+ codex: env.CODEX_HOME || path.join(root, ".codex"),
15
+ cursor: path.join(root, ".cursor")
16
+ };
17
+ }
18
+ function isDir(p) {
19
+ try {
20
+ return statSync(p).isDirectory();
21
+ } catch {
22
+ return false;
23
+ }
24
+ }
25
+ function detectAgents(host) {
26
+ return Object.entries(agentRoots(host)).filter(([, dir]) => isDir(dir)).map(([name]) => name);
27
+ }
28
+ async function runNpx(args, exec) {
29
+ const npx = exec.which("npx");
30
+ return npx ? exec.runAsync(npx, args, { timeoutMs: 3e5 }) : null;
31
+ }
32
+ async function installAgentSkills(repo, agents, host) {
33
+ const roots = agentRoots(host);
34
+ const skillDirs = [
35
+ path.join(host.home, ".agents", "skills"),
36
+ ...agents.flatMap((name) => roots[name] ? [path.join(roots[name], "skills")] : [])
37
+ ];
38
+ const before = snapshotSkills(skillDirs);
39
+ const args = [
40
+ "--yes",
41
+ NPX_SKILLS_PACKAGE,
42
+ "add",
43
+ repo,
44
+ "--skill",
45
+ "*",
46
+ "--global",
47
+ "--agent",
48
+ ...agents,
49
+ "--yes"
50
+ ];
51
+ const completed = await runNpx(args, host.exec);
52
+ if (!completed) {
53
+ throw new Error(
54
+ "Installing Tavily skills requires Node.js (npx). Install Node.js, or rerun with --skip-skills."
55
+ );
56
+ }
57
+ if (completed.failure) throw new Error(`npx skills failed: ${completed.failure}`);
58
+ return { agents, restartRequired: !isDeepStrictEqual(snapshotSkills(skillDirs), before) };
59
+ }
60
+ function snapshotSkills(skillDirs) {
61
+ const snapshot = /* @__PURE__ */ new Map();
62
+ const addFiles = (dir) => {
63
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
64
+ const full = path.join(dir, entry.name);
65
+ if (entry.isDirectory()) addFiles(full);
66
+ else if (entry.isFile()) snapshot.set(full, readFileSync(full));
67
+ }
68
+ };
69
+ for (const skillDir of skillDirs.filter(isDir)) {
70
+ for (const name of readdirSync(skillDir)) {
71
+ const full = path.join(skillDir, name);
72
+ if (!isDir(full)) continue;
73
+ snapshot.set(full, null);
74
+ addFiles(full);
75
+ }
76
+ }
77
+ return snapshot;
78
+ }
79
+
80
+ // src/core/mcp-setup.ts
81
+ var MCP_SERVER_URL = "https://mcp.tavily.com/mcp/";
82
+ var SERVER_NAME = "tavily";
83
+ var KEY_VAR = "TAVILY_API_KEY";
84
+ var MCP_AUTH_NOTE = {
85
+ env: `Using ${KEY_VAR} from your environment; the key itself is not stored in any config.`,
86
+ oauth: "Your agent will open a browser to sign in to Tavily the first time it uses the server."
87
+ };
88
+ var HEADER_REF = {
89
+ "claude-code": `Bearer \${${KEY_VAR}}`,
90
+ cursor: `Bearer \${env:${KEY_VAR}}`
91
+ };
92
+ async function installMcp(agents, keyless, host) {
93
+ const auth = !keyless && host.env[KEY_VAR] ? "env" : "oauth";
94
+ const result = { configured: [], skipped: [], auth };
95
+ for (const agent of agents) {
96
+ const reason = auth === "env" && agent === "codex" ? await codexAdd(host.exec) : await addMcp(agent, auth === "env" ? HEADER_REF[agent] : void 0, host.exec);
97
+ if (reason) result.skipped.push({ agent, reason });
98
+ else result.configured.push(agent);
99
+ }
100
+ return result;
101
+ }
102
+ async function addMcp(agent, header, exec) {
103
+ const args = [
104
+ "--yes",
105
+ NPX_ADD_MCP_PACKAGE,
106
+ MCP_SERVER_URL,
107
+ "--name",
108
+ SERVER_NAME,
109
+ "--transport",
110
+ "http",
111
+ ...header ? ["--header", `Authorization: ${header}`] : [],
112
+ "--agent",
113
+ agent,
114
+ "--global",
115
+ "--yes"
116
+ ];
117
+ const completed = await runNpx(args, exec);
118
+ if (!completed) {
119
+ throw new Error(
120
+ "Adding the Tavily MCP server requires Node.js (npx). Install Node.js and rerun."
121
+ );
122
+ }
123
+ return completed.failure;
124
+ }
125
+ async function codexAdd(exec) {
126
+ const codex = exec.which("codex");
127
+ if (!codex) return "codex is not on PATH";
128
+ const args = [
129
+ "mcp",
130
+ "add",
131
+ SERVER_NAME,
132
+ "--url",
133
+ MCP_SERVER_URL,
134
+ "--bearer-token-env-var",
135
+ KEY_VAR
136
+ ];
137
+ return (await exec.runAsync(codex, args, { timeoutMs: 3e5 })).failure;
138
+ }
139
+
140
+ export {
141
+ SKILLS_REPO,
142
+ WORKFLOWS_REPO,
143
+ detectAgents,
144
+ installAgentSkills,
145
+ MCP_AUTH_NOTE,
146
+ installMcp
147
+ };
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/util/semver.ts
4
+ var VERSION_RE = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/;
5
+ var NUMERIC_RE = /^\d+$/;
6
+ function parse(version) {
7
+ const m = VERSION_RE.exec(version.trim());
8
+ if (!m) throw new Error("The installed Tavily CLI version is invalid.");
9
+ return {
10
+ core: [Number(m[1]), Number(m[2]), Number(m[3])],
11
+ pre: m[4] ? m[4].split(".") : []
12
+ };
13
+ }
14
+ function sign(n) {
15
+ return n < 0 ? -1 : n > 0 ? 1 : 0;
16
+ }
17
+ function compareIdentifier(a, b) {
18
+ const aNum = NUMERIC_RE.test(a);
19
+ const bNum = NUMERIC_RE.test(b);
20
+ if (aNum && bNum) return sign(Number(a) - Number(b));
21
+ if (aNum) return -1;
22
+ if (bNum) return 1;
23
+ return a < b ? -1 : a > b ? 1 : 0;
24
+ }
25
+ function compareVersions(a, b) {
26
+ const pa = parse(a);
27
+ const pb = parse(b);
28
+ for (let i = 0; i < 3; i++) {
29
+ const diff = sign((pa.core[i] ?? 0) - (pb.core[i] ?? 0));
30
+ if (diff !== 0) return diff;
31
+ }
32
+ if (pa.pre.length === 0 || pb.pre.length === 0) {
33
+ return sign(pb.pre.length - pa.pre.length);
34
+ }
35
+ const len = Math.min(pa.pre.length, pb.pre.length);
36
+ for (let i = 0; i < len; i++) {
37
+ const diff = compareIdentifier(pa.pre[i] ?? "", pb.pre[i] ?? "");
38
+ if (diff !== 0) return diff;
39
+ }
40
+ return sign(pa.pre.length - pb.pre.length);
41
+ }
42
+ function isNewer(candidate, current) {
43
+ return compareVersions(candidate, current) === 1;
44
+ }
45
+
46
+ // src/core/install-detect.ts
47
+ import { existsSync, realpathSync } from "fs";
48
+ import path from "path";
49
+ var NPM_LATEST_URL = "https://registry.npmjs.org/@tavily/cli/latest";
50
+ var PACKAGE_SPEC = "@tavily/cli@latest";
51
+ var MANAGER_COMMANDS = {
52
+ npm: ["npm", "install", "-g", PACKAGE_SPEC],
53
+ pnpm: ["pnpm", "add", "-g", PACKAGE_SPEC],
54
+ yarn: ["yarn", "global", "add", PACKAGE_SPEC],
55
+ bun: ["bun", "add", "-g", PACKAGE_SPEC]
56
+ };
57
+ var MANAGER_RUNNERS = {
58
+ npm: "npx @tavily/cli",
59
+ pnpm: "pnpm dlx @tavily/cli",
60
+ yarn: "yarn dlx @tavily/cli",
61
+ bun: "bunx @tavily/cli"
62
+ };
63
+ var BIN_COMMANDS = {
64
+ npm: ["prefix", "-g"],
65
+ pnpm: ["bin", "-g"],
66
+ yarn: ["global", "bin"],
67
+ bun: ["pm", "bin", "-g"]
68
+ };
69
+ var SOURCE_REASON = "This is a source or direct-URL installation. Update it from its original source.";
70
+ var NPX_REASON = "This CLI is running from an npx cache and cannot update itself in place. Exit and rerun `npx @tavily/cli@latest` instead.";
71
+ var UNKNOWN_REASON = "Could not determine how Tavily CLI was installed. Update it with the original package manager.";
72
+ var normalize = (p) => p.replace(/\\/g, "/").toLowerCase();
73
+ function normalizeDir(p) {
74
+ const dir = normalize(p);
75
+ let end = dir.length;
76
+ while (end > 0 && dir[end - 1] === "/") end--;
77
+ return dir.slice(0, end);
78
+ }
79
+ var blocked = (method, reason) => ({
80
+ method,
81
+ command: null,
82
+ blockedReason: reason
83
+ });
84
+ function npmRootGlobalDefault(host) {
85
+ return managerOutput("npm", ["root", "-g"], host.exec);
86
+ }
87
+ function globalBinDir(manager, host) {
88
+ const out = managerOutput(manager, BIN_COMMANDS[manager], host.exec);
89
+ if (!out) return null;
90
+ return manager === "npm" && host.platform !== "win32" ? `${out}/bin` : out;
91
+ }
92
+ function managerOutput(manager, args, exec) {
93
+ const bin = exec.which(manager);
94
+ if (!bin) return null;
95
+ const r = exec.run(bin, args, { timeoutMs: 15e3 });
96
+ return r.ok ? r.stdout.trim() || null : null;
97
+ }
98
+ function packageDir(file) {
99
+ if (!path.isAbsolute(file)) return null;
100
+ for (let dir = path.dirname(file); ; dir = path.dirname(dir)) {
101
+ if (existsSync(path.join(dir, "package.json"))) return dir;
102
+ if (path.dirname(dir) === dir) return null;
103
+ }
104
+ }
105
+ function safeRealpath(p) {
106
+ try {
107
+ return realpathSync(p);
108
+ } catch {
109
+ return p;
110
+ }
111
+ }
112
+ function detectPackageManager({ env, argv }, versions = process.versions) {
113
+ const entry = (argv[1] ?? "").replaceAll("\\", "/");
114
+ if (/bunx[-/]|\/\.bun\//.test(entry)) return "bun";
115
+ if (/\/pnpm\/(dlx|global)\//.test(entry)) return "pnpm";
116
+ const hint = `${env.npm_config_user_agent ?? ""} ${env.npm_execpath ?? ""}`;
117
+ if (/\bpnpm\b/i.test(hint)) return "pnpm";
118
+ if (/\byarn\b/i.test(hint)) return "yarn";
119
+ if (/\bbun\b/i.test(hint)) return "bun";
120
+ if (versions.bun) return "bun";
121
+ return "npm";
122
+ }
123
+ function detectInstall(host, npmRootGlobal = () => npmRootGlobalDefault(host)) {
124
+ const { env } = host;
125
+ const raw = host.argv[1] ?? "";
126
+ const real = safeRealpath(raw);
127
+ const normReal = normalize(real);
128
+ const normRaw = normalize(raw);
129
+ if (normReal.includes("/_npx/")) return blocked("npx", NPX_REASON);
130
+ const inNodeModules = (p) => p.split("/").includes("node_modules");
131
+ const linked = normRaw !== normReal && normRaw.includes("node_modules/@tavily/cli");
132
+ const pkgDir = packageDir(real);
133
+ if (linked || !inNodeModules(pkgDir === null ? normReal : normalize(pkgDir))) {
134
+ return blocked("source", SOURCE_REASON);
135
+ }
136
+ const pnpmHome = env.PNPM_HOME ? normalizeDir(env.PNPM_HOME) : null;
137
+ let method = "unknown";
138
+ if (normReal.includes("/pnpm/global/") || pnpmHome && normReal.startsWith(`${pnpmHome}/`)) {
139
+ method = "pnpm";
140
+ } else if (normReal.includes("/.yarn/global/") || normReal.includes("/yarn/global/")) {
141
+ method = "yarn";
142
+ } else if (normReal.includes("/.bun/install/global/")) {
143
+ method = "bun";
144
+ } else {
145
+ const root = npmRootGlobal();
146
+ if (root) {
147
+ const normRoot = normalizeDir(root);
148
+ if (normReal.startsWith(`${normRoot}/`)) method = "npm";
149
+ }
150
+ }
151
+ if (method === "unknown") return blocked("unknown", UNKNOWN_REASON);
152
+ const manager = host.exec.which(method);
153
+ if (!manager) {
154
+ return blocked(
155
+ method,
156
+ `This installation is managed by ${method}, but ${method} is not available on PATH.`
157
+ );
158
+ }
159
+ const [, ...args] = MANAGER_COMMANDS[method];
160
+ return { method, command: [manager, ...args], blockedReason: null };
161
+ }
162
+ async function fetchLatestVersion(fetchImpl, signal = AbortSignal.timeout(1e4)) {
163
+ let data;
164
+ try {
165
+ const response = await fetchImpl(NPM_LATEST_URL, { redirect: "manual", signal });
166
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
167
+ data = await response.json();
168
+ } catch {
169
+ throw new Error("Could not check npm for Tavily CLI updates.");
170
+ }
171
+ const version = data?.version;
172
+ try {
173
+ if (typeof version !== "string") throw new Error();
174
+ compareVersions(version, version);
175
+ } catch {
176
+ throw new Error("npm returned an invalid Tavily CLI version.");
177
+ }
178
+ return version;
179
+ }
180
+
181
+ export {
182
+ compareVersions,
183
+ isNewer,
184
+ MANAGER_COMMANDS,
185
+ MANAGER_RUNNERS,
186
+ globalBinDir,
187
+ safeRealpath,
188
+ detectPackageManager,
189
+ detectInstall,
190
+ fetchLatestVersion
191
+ };
package/dist/cli.js ADDED
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ buildProgram,
4
+ realHost
5
+ } from "./chunk-PZGMUDV2.js";
6
+ import "./chunk-HQ7QBZ5H.js";
7
+ import {
8
+ detectInstall,
9
+ fetchLatestVersion,
10
+ isNewer
11
+ } from "./chunk-V3OJFHHD.js";
12
+ import {
13
+ VERSION
14
+ } from "./chunk-SPBOEASO.js";
15
+ import {
16
+ sanitizeControl
17
+ } from "./chunk-QHWWSCVM.js";
18
+ import "./chunk-IRCF6F3S.js";
19
+ import {
20
+ colors,
21
+ err
22
+ } from "./chunk-E4N27QWM.js";
23
+ import {
24
+ ExitError,
25
+ messageOf
26
+ } from "./chunk-5JXZZSLS.js";
27
+
28
+ // src/cli.ts
29
+ import { realpathSync } from "fs";
30
+ import { fileURLToPath } from "url";
31
+ import { CommanderError } from "commander";
32
+
33
+ // src/core/update-notice.ts
34
+ import { mkdirSync, readFileSync, writeFileSync } from "fs";
35
+ import path from "path";
36
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
37
+ var RACE_MS = 1500;
38
+ var SKIP_FLAGS = /* @__PURE__ */ new Set(["--version", "--help", "-V", "-h"]);
39
+ var SKIP_COMMANDS = /* @__PURE__ */ new Set(["update", "init"]);
40
+ var noop = async () => {
41
+ };
42
+ function readState(file) {
43
+ try {
44
+ const data = JSON.parse(readFileSync(file, "utf8"));
45
+ if (typeof data?.checked_at !== "number") return null;
46
+ return {
47
+ checked_at: data.checked_at,
48
+ latest: typeof data.latest === "string" ? data.latest : null
49
+ };
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+ function writeState(file, state) {
55
+ try {
56
+ mkdirSync(path.dirname(file), { recursive: true });
57
+ writeFileSync(file, `${JSON.stringify(state)}
58
+ `);
59
+ } catch {
60
+ }
61
+ }
62
+ function startUpdateCheck(host) {
63
+ try {
64
+ const { env, now } = host;
65
+ const args = host.argv.slice(2);
66
+ const command = args.find((a) => !a.startsWith("-")) ?? "";
67
+ if (env.CI || env.NO_UPDATE_NOTIFIER || !host.stderrIsTTY || args.includes("--json") || args.some((a) => SKIP_FLAGS.has(a)) || SKIP_COMMANDS.has(command) || // `/_npx/` is matched before any package-manager spawn, so skipping `npm root -g` keeps this cheap.
68
+ detectInstall(host, () => null).method === "npx") {
69
+ return noop;
70
+ }
71
+ const current = VERSION;
72
+ const file = path.join(host.home, ".tavily", "update-check.json");
73
+ const notify = (latest) => {
74
+ try {
75
+ if (latest === null || !isNewer(latest, current)) return;
76
+ err();
77
+ err(
78
+ colors.dim(`Tavily CLI update available: ${current} -> ${latest}. Run \`tvly update\`.`)
79
+ );
80
+ } catch {
81
+ }
82
+ };
83
+ const state = readState(file);
84
+ if (state && now() - state.checked_at < CHECK_INTERVAL_MS) {
85
+ return async () => notify(state.latest);
86
+ }
87
+ const controller = new AbortController();
88
+ const pending = fetchLatestVersion(host.fetch, controller.signal).catch(() => null);
89
+ return async () => {
90
+ let timer;
91
+ const latest = await Promise.race([
92
+ pending,
93
+ new Promise((resolve) => {
94
+ timer = setTimeout(resolve, RACE_MS, null);
95
+ })
96
+ ]);
97
+ clearTimeout(timer);
98
+ controller.abort();
99
+ writeState(file, { checked_at: now(), latest });
100
+ notify(latest);
101
+ };
102
+ } catch {
103
+ return noop;
104
+ }
105
+ }
106
+
107
+ // src/cli.ts
108
+ async function main(host) {
109
+ const finishUpdateCheck = startUpdateCheck(host);
110
+ try {
111
+ await buildProgram(host).parseAsync(host.argv);
112
+ return 0;
113
+ } catch (err2) {
114
+ if (err2 instanceof ExitError) return err2.code;
115
+ if (err2 instanceof CommanderError) return err2.exitCode === 0 ? 0 : 2;
116
+ console.error(` > Error: ${sanitizeControl(messageOf(err2))}`);
117
+ return 1;
118
+ } finally {
119
+ await finishUpdateCheck();
120
+ }
121
+ }
122
+ var entry = process.argv[1];
123
+ if (entry && realpathSync(entry) === fileURLToPath(import.meta.url)) {
124
+ process.stdout.on("error", (err2) => {
125
+ if (err2.code !== "EPIPE") throw err2;
126
+ process.exit(0);
127
+ });
128
+ process.exitCode = await main(realHost());
129
+ }
130
+ export {
131
+ main
132
+ };