@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alpha AI Technologies Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,323 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Tavily CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`tvly` runs [Tavily](https://docs.tavily.com) search, extract, crawl, map, and research from the terminal, and sets up Tavily skills and the MCP server for Claude Code, Codex, and Cursor. It is built on the official JavaScript SDK, [`@tavily/core`](https://www.npmjs.com/package/@tavily/core), and the `tvly search` flags follow the SDK's parameter catalog.
|
|
4
|
+
|
|
5
|
+
- Rich output in a terminal, plain text when piped or run by an agent, and `--json` everywhere.
|
|
6
|
+
- Search and extract work without an account, within a fair-use rate limit.
|
|
7
|
+
- An interactive shell when run with no arguments.
|
|
8
|
+
- Self-update through the package manager that installed it.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npx @tavily/cli init
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
This installs `tvly` globally, signs you in, sets up your coding agents, and verifies a live search. Requires Node.js 20.1 or later.
|
|
17
|
+
|
|
18
|
+
To install without running setup, use your package manager and run `tvly init` afterwards:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g @tavily/cli
|
|
22
|
+
pnpm add -g @tavily/cli
|
|
23
|
+
yarn global add @tavily/cli
|
|
24
|
+
bun add -g @tavily/cli
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The shell installer does the same on macOS and Linux, and starts `tvly init` when it runs in an interactive terminal:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
curl -fsSL https://raw.githubusercontent.com/tavily-ai/cli/main/install.sh | sh
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Setup
|
|
34
|
+
|
|
35
|
+
`tvly init` walks through four steps:
|
|
36
|
+
|
|
37
|
+
1. **Global install.** If `tvly` is not on your PATH, it is installed with the package manager that ran `init`. If the install directory is not on your PATH either, `init` offers to add it to your shell profile.
|
|
38
|
+
2. **How do you want to sign in?** Browser login, paste an API key, or continue without a key. An existing credential is reused.
|
|
39
|
+
3. **Which agents do you want to install to?** Every detected agent is pre-checked. `--agent` picks the agents and skips the question.
|
|
40
|
+
4. **What should I install?** Tavily skills and the hosted MCP server are checked by default; the official workflows from `tavily-ai/official-workflows` are not.
|
|
41
|
+
|
|
42
|
+
It then verifies the CLI with a live search. Rerunning `tvly init` updates installed skills to the latest published version.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
tvly init # prompts for everything
|
|
46
|
+
tvly init --yes # accept every default
|
|
47
|
+
tvly init --agent codex # one agent, no agent question
|
|
48
|
+
tvly init --api-key tvly-YOUR_KEY # API key instead of browser login
|
|
49
|
+
tvly init --skip-auth # keyless setup when no credential exists
|
|
50
|
+
tvly init --skip-skills # sign in and verify, leave agents alone
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`--yes`, `--json`, and runs without a terminal ask nothing and take the defaults: every detected agent, with skills and the MCP server.
|
|
54
|
+
|
|
55
|
+
Skills are installed with the [skills.sh CLI](https://skills.sh) into `~/.agents/skills`, which Codex and Cursor read directly; Claude Code gets links in `~/.claude/skills`. The MCP server is added with `npx add-mcp`. Both need `npx` on your PATH. Restart your agent after setup.
|
|
56
|
+
|
|
57
|
+
### Signing in
|
|
58
|
+
|
|
59
|
+
`tvly login` manages credentials without the rest of setup:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
tvly login # browser login
|
|
63
|
+
tvly login --no-browser # print the login URL instead of opening it
|
|
64
|
+
tvly login --api-key tvly-YOUR_KEY # store an API key
|
|
65
|
+
tvly auth # show authentication status
|
|
66
|
+
tvly logout # revoke tokens and clear stored credentials
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`TAVILY_API_KEY` in the environment takes priority over stored credentials, so use it for CI and SSH sessions. `--no-browser` still waits for a callback on `127.0.0.1`, so over SSH either forward the port or use an API key.
|
|
70
|
+
|
|
71
|
+
Browser login goes through Tavily's MCP endpoint, which rejects some search, crawl, and research options ([details](https://github.com/tavily-ai/tavily-cli/issues/24)). Use an API key for the full option set.
|
|
72
|
+
|
|
73
|
+
## Usage
|
|
74
|
+
|
|
75
|
+
Search and extract work right after installation, with no sign-in, until a fair-use cap is reached. Crawl, map, and research require a credential.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
tvly search "latest AI trends"
|
|
79
|
+
tvly search "quantum computing" --depth advanced --max-results 10 --time-range week
|
|
80
|
+
tvly search "SEC filings for Apple" --include-domains sec.gov,reuters.com
|
|
81
|
+
tvly search "Rust 2024 edition" "Rust 1.85 release notes" # up to 5 queries, run in parallel
|
|
82
|
+
|
|
83
|
+
tvly extract https://example.com/article
|
|
84
|
+
tvly extract https://example.com https://other.com --query "pricing information"
|
|
85
|
+
|
|
86
|
+
tvly crawl https://docs.example.com --max-depth 2 --limit 100 --select-paths "/api/.*,/guides/.*"
|
|
87
|
+
tvly crawl https://docs.example.com --output-dir ./docs # one Markdown file per page
|
|
88
|
+
|
|
89
|
+
tvly map https://example.com --select-paths "/blog/.*" --limit 500
|
|
90
|
+
|
|
91
|
+
tvly research "Competitive landscape of AI code assistants"
|
|
92
|
+
tvly research "EV market analysis" --model pro --stream
|
|
93
|
+
tvly research "topic" --no-wait --json # returns a request_id
|
|
94
|
+
tvly research poll REQUEST_ID --json
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Running `tvly` with no arguments opens a shell where commands are typed without the `tvly` prefix.
|
|
98
|
+
|
|
99
|
+
### Output
|
|
100
|
+
|
|
101
|
+
In a terminal, results are formatted and wrapped. When stdout is a pipe, a file, or an agent's shell, output is plain text: `search` prints each result's title, URL, and the first 600 characters of content on one line (`--max-chars` changes the length, `0` prints everything); `extract` and `crawl` print each page in full under a `# <url>` heading; `map` prints one URL per line; `research` prints the report and its sources. `--json` prints the API response unchanged.
|
|
102
|
+
|
|
103
|
+
`-o` / `--output` saves the full JSON response. With no value it writes under `.tavily/` with a generated name and prints the path; a new `.tavily/` folder gets its own `.gitignore`. `crawl --output-dir` saves each page as a Markdown file instead.
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
tvly search "AI news" -o
|
|
107
|
+
tvly search "AI news" --output results/ai-news.json
|
|
108
|
+
tvly extract https://example.com --format markdown --output extracted.json
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Scripts and agents
|
|
112
|
+
|
|
113
|
+
Every command accepts `--json`. A `-` argument reads the query from stdin.
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
echo "What is the latest funding for Anthropic?" | tvly search - --json
|
|
117
|
+
tvly init --yes --json # TAVILY_API_KEY supplied by the environment
|
|
118
|
+
tvly init --agent codex --skip-auth --yes --json # keyless setup
|
|
119
|
+
tvly --status --json # version and auth status
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Setup JSON includes `ok`, `mode`, `install`, `auth`, `skills`, `workflows`, `mcp`, and `verification`; on failure, `error.stage` names the failed step. Setup always runs a live search, so it needs network access.
|
|
123
|
+
|
|
124
|
+
| Exit code | Meaning |
|
|
125
|
+
|-----------|---------|
|
|
126
|
+
| 0 | Success |
|
|
127
|
+
| 1 | Local setup or update error |
|
|
128
|
+
| 2 | Invalid input or usage error |
|
|
129
|
+
| 3 | Authentication failure, keyless rate-limit cap, or API usage/plan limit |
|
|
130
|
+
| 4 | API error, setup live-search failure, or update-check error |
|
|
131
|
+
|
|
132
|
+
HTTP 401, 403, 429, and 432 exit 3 on every credential type.
|
|
133
|
+
|
|
134
|
+
## Commands
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
tvly
|
|
138
|
+
├── (no command) Interactive shell
|
|
139
|
+
├── init Install, sign in, set up agents, verify
|
|
140
|
+
├── login Sign in with the browser or an API key
|
|
141
|
+
├── logout Clear stored credentials
|
|
142
|
+
├── auth Show authentication status
|
|
143
|
+
├── search <query...> Web search
|
|
144
|
+
├── extract <urls...> Extract content from URLs
|
|
145
|
+
├── crawl <url> Crawl a website
|
|
146
|
+
├── map <url> Discover URLs without content
|
|
147
|
+
├── research <query> Deep research
|
|
148
|
+
│ ├── status <id> Check a research task
|
|
149
|
+
│ └── poll <id> Wait for a research task
|
|
150
|
+
├── setup mcp Add the hosted Tavily MCP server to an agent
|
|
151
|
+
├── set-default Disable built-in web search in Claude Code and Codex
|
|
152
|
+
├── unset-default Restore it
|
|
153
|
+
└── update Check for or install a newer release
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### `tvly init`
|
|
157
|
+
|
|
158
|
+
| Option | Description |
|
|
159
|
+
|--------|-------------|
|
|
160
|
+
| `--agent` | Set up `claude-code`, `codex`, or `cursor`; repeat for more than one |
|
|
161
|
+
| `--yes` | Accept every default without prompting |
|
|
162
|
+
| `--skip-auth` | Skip sign-in when no credential is configured |
|
|
163
|
+
| `--skip-skills` | Leave coding agents alone: no skills, workflows, or MCP server |
|
|
164
|
+
| `--skip-workflows` | Skip the official workflows |
|
|
165
|
+
| `--skip-install` | Skip the global `tvly` install |
|
|
166
|
+
| `--api-key` | Sign in with an API key |
|
|
167
|
+
| `--browser` / `--no-browser` | Open the login page or print its URL |
|
|
168
|
+
|
|
169
|
+
`--skip-auth` and `--api-key` cannot be combined.
|
|
170
|
+
|
|
171
|
+
### `tvly setup mcp`
|
|
172
|
+
|
|
173
|
+
Adds the hosted Tavily MCP server (`https://mcp.tavily.com/mcp/`, named `tavily`) to Claude Code, Codex, and Cursor. Nothing runs locally.
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
tvly setup mcp # every detected agent
|
|
177
|
+
tvly setup mcp --agent claude-code --agent cursor
|
|
178
|
+
tvly setup mcp --agent all
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
| Option | Description |
|
|
182
|
+
|--------|-------------|
|
|
183
|
+
| `--agent` | `claude-code`, `codex`, `cursor`, or `all`; repeat for more than one |
|
|
184
|
+
| `--keyless` | Write the URL-only config so the agent signs in with OAuth even when `TAVILY_API_KEY` is set |
|
|
185
|
+
|
|
186
|
+
With `TAVILY_API_KEY` exported, the config references the variable (`${TAVILY_API_KEY}` for Claude Code, `${env:TAVILY_API_KEY}` for Cursor, `--bearer-token-env-var` for Codex) and the key itself is never written to disk. Without it, the agent opens a browser to sign in the first time it uses the server. A key stored by `tvly login` is not used for MCP. Agents that cannot be configured, such as Codex when `codex` is not on PATH, are skipped and listed; the command exits 1 only when nothing was configured.
|
|
187
|
+
|
|
188
|
+
### `tvly search <query...>`
|
|
189
|
+
|
|
190
|
+
| Option | Description |
|
|
191
|
+
|--------|-------------|
|
|
192
|
+
| `--depth` | `basic`, `advanced`, `fast`, or `ultra-fast` |
|
|
193
|
+
| `--max-results` | Result count limit |
|
|
194
|
+
| `--topic` | `general`, `news`, or `finance` |
|
|
195
|
+
| `--time-range` | `year`, `month`, `week`, or `day` (or `y`, `m`, `w`, `d`) |
|
|
196
|
+
| `--start-date` / `--end-date` | Date window bounds (YYYY-MM-DD) |
|
|
197
|
+
| `--include-domains` / `--exclude-domains` | Domains to prefer or omit |
|
|
198
|
+
| `--include-domains-mode` | How `--include-domains` applies: `restrict` or `prefer` |
|
|
199
|
+
| `--country` | Country preference |
|
|
200
|
+
| `--language` | Preferred content language |
|
|
201
|
+
| `--filter-by-language` | Restrict results to the requested language |
|
|
202
|
+
| `--include-answer` | Generated answer: `basic`, `advanced`, `true`, or `false` |
|
|
203
|
+
| `--include-raw-content` | Page content as `markdown` or `text`, or `false` |
|
|
204
|
+
| `--include-images` / `--include-image-descriptions` | Images alongside results |
|
|
205
|
+
| `--include-favicon` | Site icons |
|
|
206
|
+
| `--include-published-date` | Detected publication dates |
|
|
207
|
+
| `--filter-by-published-date` | Drop undated results and results outside the date window |
|
|
208
|
+
| `--include-usage` | Credit usage details |
|
|
209
|
+
| `--chunks-per-source` | Content chunks per source for advanced search |
|
|
210
|
+
| `--auto-parameters` | Let Tavily choose search settings from the query |
|
|
211
|
+
| `--cache-fallback` | Allow cached content when live fetching fails |
|
|
212
|
+
| `--max-age-hours` | Maximum acceptable cache age |
|
|
213
|
+
| `--fetch-timeout` | Live-fetch time budget in seconds |
|
|
214
|
+
| `--days` | Lookback in days for news searches |
|
|
215
|
+
| `--exact-match` | Require exact matches for quoted phrases |
|
|
216
|
+
| `--safe-search` | Filter explicit content |
|
|
217
|
+
| `--max-chars` | Characters of content printed per result (default 600, `0` for all) |
|
|
218
|
+
| `-o` / `--output` | Save the full JSON response |
|
|
219
|
+
| `--client-name` | `client_name` for request attribution |
|
|
220
|
+
|
|
221
|
+
Up to 5 queries run in parallel with the same options. Each query's results print under a `=== [i/n] <query>` header; with `--json`, the output is a list in query order. A failing query reports its error without stopping the others.
|
|
222
|
+
|
|
223
|
+
### `tvly extract <urls...>`
|
|
224
|
+
|
|
225
|
+
| Option | Description |
|
|
226
|
+
|--------|-------------|
|
|
227
|
+
| `--query` | Rerank chunks by relevance |
|
|
228
|
+
| `--chunks-per-source` | Chunks per source (1-5, requires `--query`) |
|
|
229
|
+
| `--extract-depth` | `basic` or `advanced` |
|
|
230
|
+
| `--format` | `markdown` or `text` |
|
|
231
|
+
| `--include-images` | Include image URLs |
|
|
232
|
+
| `--timeout` | Max wait (1-60 seconds) |
|
|
233
|
+
| `-o` / `--output` | Save the full JSON response |
|
|
234
|
+
| `--client-name` | `client_name` for request attribution |
|
|
235
|
+
|
|
236
|
+
### `tvly crawl <url>` and `tvly map <url>`
|
|
237
|
+
|
|
238
|
+
| Option | Description |
|
|
239
|
+
|--------|-------------|
|
|
240
|
+
| `--max-depth` | Levels deep (1-5, default 1) |
|
|
241
|
+
| `--max-breadth` | Links per page (default 20) |
|
|
242
|
+
| `--limit` | Total pages or URLs (default 50) |
|
|
243
|
+
| `--instructions` | Natural-language guidance |
|
|
244
|
+
| `--select-paths` / `--exclude-paths` | Regex patterns for paths |
|
|
245
|
+
| `--select-domains` / `--exclude-domains` | Regex patterns for domains |
|
|
246
|
+
| `--allow-external` / `--no-external` | Follow or ignore links to other domains |
|
|
247
|
+
| `--timeout` | Max wait (10-150 seconds) |
|
|
248
|
+
| `-o` / `--output` | Save the full JSON response |
|
|
249
|
+
| `--client-name` | `client_name` for request attribution |
|
|
250
|
+
|
|
251
|
+
`crawl` only:
|
|
252
|
+
|
|
253
|
+
| Option | Description |
|
|
254
|
+
|--------|-------------|
|
|
255
|
+
| `--chunks-per-source` | Chunks per page (1-5) |
|
|
256
|
+
| `--extract-depth` | `basic` or `advanced` |
|
|
257
|
+
| `--format` | `markdown` or `text` |
|
|
258
|
+
| `--include-images` | Include images |
|
|
259
|
+
| `--output-dir` | Save each page as a Markdown file in a directory |
|
|
260
|
+
|
|
261
|
+
### `tvly research <query>`
|
|
262
|
+
|
|
263
|
+
Streaming, structured output, and async research need an API key.
|
|
264
|
+
|
|
265
|
+
| Option | Description |
|
|
266
|
+
|--------|-------------|
|
|
267
|
+
| `--model` | `mini`, `pro`, or `auto` (default) |
|
|
268
|
+
| `--no-wait` | Return the request ID immediately |
|
|
269
|
+
| `--stream` | Stream results as they arrive |
|
|
270
|
+
| `--output-schema` | Path to a JSON schema for structured output |
|
|
271
|
+
| `--citation-format` | `numbered`, `mla`, `apa`, or `chicago` |
|
|
272
|
+
| `--poll-interval` | Seconds between status checks (default 10) |
|
|
273
|
+
| `--timeout` | Max wait in seconds (default 600) |
|
|
274
|
+
| `-o` / `--output` | Save the full JSON response |
|
|
275
|
+
| `--client-name` | `client_name` for request attribution |
|
|
276
|
+
|
|
277
|
+
`tvly research status <id>` checks a task; `tvly research poll <id>` waits for it with the same `--poll-interval`, `--timeout`, and `-o` options.
|
|
278
|
+
|
|
279
|
+
### `tvly set-default` / `tvly unset-default`
|
|
280
|
+
|
|
281
|
+
Makes Tavily the web tool your coding agents use by disabling their built-in web search. Without flags, you are asked per agent. `unset-default` reverses the change but does not restore values you had set yourself before.
|
|
282
|
+
|
|
283
|
+
| Option | Description |
|
|
284
|
+
|--------|-------------|
|
|
285
|
+
| `--claude` | Add `WebSearch` and `WebFetch` to `permissions.deny` in `~/.claude/settings.json` (or `$CLAUDE_CONFIG_DIR`) |
|
|
286
|
+
| `--codex` | Set `web_search = "disabled"` in `~/.codex/config.toml` (or `$CODEX_HOME`) |
|
|
287
|
+
| `--project` | Apply to the current directory instead (`.claude/settings.local.json` and `.codex/config.toml`) |
|
|
288
|
+
|
|
289
|
+
### `tvly update`
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
tvly update --check # report without changing anything
|
|
293
|
+
tvly update # update through npm, pnpm, yarn, or bun, whichever installed it
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Source and direct-URL installations are reported but not modified. JSON output includes `can_update` and `blocked_reason`.
|
|
297
|
+
|
|
298
|
+
The CLI also checks for a newer release at most once a day and prints a one-line notice on stderr. It stays silent under `--json`, when stderr is not a terminal, when run through `npx`, or when `CI` or `NO_UPDATE_NOTIFIER` is set.
|
|
299
|
+
|
|
300
|
+
## Environment variables
|
|
301
|
+
|
|
302
|
+
| Variable | Description |
|
|
303
|
+
|----------|-------------|
|
|
304
|
+
| `TAVILY_API_KEY` | API key; takes priority over stored credentials |
|
|
305
|
+
| `TAVILY_API_BASE_URL` | Override the API base URL (also `api_base_url` in `~/.tavily/config.json`) |
|
|
306
|
+
| `TAVILY_CLIENT_NAME` | Default for `--client-name` |
|
|
307
|
+
| `TAVILY_HUMAN_ID` | Identifier attached to every request for usage attribution (also `human_id` in the config file) |
|
|
308
|
+
| `TAVILY_PROJECT` | Project ID attached to requests |
|
|
309
|
+
| `TAVILY_ORG_ID` | Organization ID attached to requests |
|
|
310
|
+
| `TAVILY_HTTP_PROXY` / `TAVILY_HTTPS_PROXY` | Proxy URLs for API calls made with an API key |
|
|
311
|
+
| `NO_UPDATE_NOTIFIER` | Disable the daily update notice (`CI` disables it too) |
|
|
312
|
+
|
|
313
|
+
Browser login, OAuth-credentialed commands, and `tvly update` use Node's `fetch`, which ignores proxy variables unless Node 24 or later runs with `NODE_USE_ENV_PROXY=1`.
|
|
314
|
+
|
|
315
|
+
## Related
|
|
316
|
+
|
|
317
|
+
- [Tavily CLI docs](https://docs.tavily.com/documentation/tavily-cli)
|
|
318
|
+
- [`@tavily/core`](https://www.npmjs.com/package/@tavily/core), the JavaScript SDK
|
|
319
|
+
- [Tavily API reference](https://docs.tavily.com)
|
|
320
|
+
|
|
321
|
+
## License
|
|
322
|
+
|
|
323
|
+
MIT
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getApiKeyOrExit
|
|
4
|
+
} from "./chunk-U3B7BNFI.js";
|
|
5
|
+
import {
|
|
6
|
+
spinner
|
|
7
|
+
} from "./chunk-K76OUMXM.js";
|
|
8
|
+
import {
|
|
9
|
+
sanitizeControl
|
|
10
|
+
} from "./chunk-QHWWSCVM.js";
|
|
11
|
+
import {
|
|
12
|
+
authMethod,
|
|
13
|
+
clearCredentials,
|
|
14
|
+
configPaths,
|
|
15
|
+
getApiKey,
|
|
16
|
+
looksHeadless,
|
|
17
|
+
runBrowserLogin,
|
|
18
|
+
saveApiKey,
|
|
19
|
+
saveOauthSession
|
|
20
|
+
} from "./chunk-AERAIFZU.js";
|
|
21
|
+
import {
|
|
22
|
+
resolveJson
|
|
23
|
+
} from "./chunk-IRCF6F3S.js";
|
|
24
|
+
import {
|
|
25
|
+
LOGO,
|
|
26
|
+
colors,
|
|
27
|
+
err,
|
|
28
|
+
out
|
|
29
|
+
} from "./chunk-E4N27QWM.js";
|
|
30
|
+
import {
|
|
31
|
+
ExitError,
|
|
32
|
+
OAuthError
|
|
33
|
+
} from "./chunk-5JXZZSLS.js";
|
|
34
|
+
|
|
35
|
+
// src/commands/auth.ts
|
|
36
|
+
var ENV_CREDENTIAL_WARNING = "TAVILY_API_KEY is still set and cannot be removed from the parent environment by this process. Run: unset TAVILY_API_KEY";
|
|
37
|
+
async function runLogin(opts, cmd, host, hooks = {}) {
|
|
38
|
+
const json = resolveJson(cmd);
|
|
39
|
+
const configFile = configPaths(host.home).file;
|
|
40
|
+
if (opts.apiKey) {
|
|
41
|
+
await saveApiKey(opts.apiKey, host);
|
|
42
|
+
printLoginSuccess("API key", `Saved to ${configFile}`, json);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const openBrowser = !(opts.browser === false || looksHeadless(host.env, host.platform));
|
|
46
|
+
const showUrl = (rawUrl) => {
|
|
47
|
+
const url = sanitizeControl(rawUrl);
|
|
48
|
+
if (json) {
|
|
49
|
+
err(JSON.stringify({ event: "authorization_required", authorization_url: url }));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
err();
|
|
53
|
+
if (openBrowser) {
|
|
54
|
+
err(` ${colors.aqua(">")} Opening the browser to sign in with Tavily.`);
|
|
55
|
+
err(` ${colors.dim("If nothing opens, visit:")}`);
|
|
56
|
+
} else {
|
|
57
|
+
err(` ${colors.aqua(">")} Open this URL in a browser on this machine:`);
|
|
58
|
+
}
|
|
59
|
+
err(` ${url}`);
|
|
60
|
+
err();
|
|
61
|
+
if (!openBrowser) {
|
|
62
|
+
err(
|
|
63
|
+
` ${colors.dim("Headless / SSH sessions cannot complete browser login unless this machine can receive http://127.0.0.1 callbacks.")}`
|
|
64
|
+
);
|
|
65
|
+
err(
|
|
66
|
+
` ${colors.dim("Use")} ${colors.green("tvly login --api-key tvly-YOUR_KEY")} ${colors.dim("instead.")}`
|
|
67
|
+
);
|
|
68
|
+
err();
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const waiting = () => spinner(colors.aqua("Waiting for browser authorization..."), !json);
|
|
72
|
+
let spin = waiting();
|
|
73
|
+
const onStatus = (url) => {
|
|
74
|
+
spin.stop();
|
|
75
|
+
showUrl(url);
|
|
76
|
+
spin = waiting();
|
|
77
|
+
};
|
|
78
|
+
try {
|
|
79
|
+
const session = await (hooks.login ?? runBrowserLogin)({
|
|
80
|
+
openBrowser,
|
|
81
|
+
// Always report the URL: a browser launch can fail silently, even in --json mode.
|
|
82
|
+
onStatus,
|
|
83
|
+
fetchImpl: host.fetch,
|
|
84
|
+
openImpl: host.openUrl
|
|
85
|
+
});
|
|
86
|
+
spin.stop();
|
|
87
|
+
await saveOauthSession(session, host);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
spin.stop();
|
|
90
|
+
if (e instanceof OAuthError) failLogin(e.message, json);
|
|
91
|
+
throw e;
|
|
92
|
+
}
|
|
93
|
+
printLoginSuccess("OAuth", `Token stored in ${configFile}`, json);
|
|
94
|
+
}
|
|
95
|
+
function printLoginSuccess(method, detail, json) {
|
|
96
|
+
if (json) {
|
|
97
|
+
out(
|
|
98
|
+
JSON.stringify({
|
|
99
|
+
authenticated: true,
|
|
100
|
+
method: method === "OAuth" ? "oauth" : "api_key",
|
|
101
|
+
detail
|
|
102
|
+
})
|
|
103
|
+
);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
out();
|
|
107
|
+
out(LOGO);
|
|
108
|
+
out();
|
|
109
|
+
out(` ${colors.green(`> Authenticated via ${method}`)}`);
|
|
110
|
+
out(` ${colors.dim(detail)}`);
|
|
111
|
+
out();
|
|
112
|
+
out(` ${colors.bold("Get started")}`);
|
|
113
|
+
out();
|
|
114
|
+
const hint = (cmd, arg) => out(` ${colors.green(cmd)} ${colors.dim(arg)}`);
|
|
115
|
+
hint("tvly search", '"your first query"');
|
|
116
|
+
hint("tvly extract", "<url>");
|
|
117
|
+
hint("tvly crawl", "<url>");
|
|
118
|
+
hint("tvly map", "<url>");
|
|
119
|
+
hint("tvly research", '"deep dive topic"');
|
|
120
|
+
out();
|
|
121
|
+
}
|
|
122
|
+
function failLogin(message, json) {
|
|
123
|
+
if (json) {
|
|
124
|
+
out(JSON.stringify({ authenticated: false, error: message }));
|
|
125
|
+
} else {
|
|
126
|
+
err();
|
|
127
|
+
err(` ${colors.pink(`> ${sanitizeControl(message)}`)}`);
|
|
128
|
+
err();
|
|
129
|
+
err(" Use an API key instead:");
|
|
130
|
+
err(` ${colors.green("tvly login --api-key tvly-YOUR_KEY")}`);
|
|
131
|
+
err();
|
|
132
|
+
}
|
|
133
|
+
throw new ExitError(3);
|
|
134
|
+
}
|
|
135
|
+
function effectiveAuthState(key, host) {
|
|
136
|
+
if (!key) return { key: null, method: null, source: null };
|
|
137
|
+
const method = authMethod(key, host);
|
|
138
|
+
const { file } = configPaths(host.home);
|
|
139
|
+
const source = {
|
|
140
|
+
env: "TAVILY_API_KEY environment variable",
|
|
141
|
+
oauth: `OAuth (${file})`,
|
|
142
|
+
oauth_legacy: "OAuth (~/.mcp-auth/)",
|
|
143
|
+
api_key: `config file (${file})`
|
|
144
|
+
}[method];
|
|
145
|
+
return { key, method, source };
|
|
146
|
+
}
|
|
147
|
+
async function runLogout(_opts, cmd, host) {
|
|
148
|
+
const json = resolveJson(cmd);
|
|
149
|
+
const result = await clearCredentials(host);
|
|
150
|
+
const { key, method, source } = effectiveAuthState(await getApiKey(host), host);
|
|
151
|
+
const envPresent = method === "env";
|
|
152
|
+
const payload = {
|
|
153
|
+
authenticated: key !== null,
|
|
154
|
+
method,
|
|
155
|
+
source,
|
|
156
|
+
local_credentials_cleared: true,
|
|
157
|
+
environment_credential_present: envPresent
|
|
158
|
+
};
|
|
159
|
+
if (envPresent) payload.warning = ENV_CREDENTIAL_WARNING;
|
|
160
|
+
const envStillSet = () => {
|
|
161
|
+
err(` ${colors.yellow(">")} TAVILY_API_KEY is still set, so the CLI remains authenticated.`);
|
|
162
|
+
err(` ${colors.dim("Run in your shell: unset TAVILY_API_KEY")}`);
|
|
163
|
+
};
|
|
164
|
+
if (result.revocationError) {
|
|
165
|
+
if (json) {
|
|
166
|
+
payload.server_revoked = false;
|
|
167
|
+
payload.error = result.revocationError;
|
|
168
|
+
out(JSON.stringify(payload));
|
|
169
|
+
} else {
|
|
170
|
+
err(` ${colors.yellow(">")} Local credentials cleared, but server revocation failed.`);
|
|
171
|
+
err(` ${colors.dim(sanitizeControl(result.revocationError))}`);
|
|
172
|
+
err(" The previous OAuth token may remain usable.");
|
|
173
|
+
if (envPresent) envStillSet();
|
|
174
|
+
}
|
|
175
|
+
throw new ExitError(3);
|
|
176
|
+
}
|
|
177
|
+
if (json) {
|
|
178
|
+
if (result.serverRevoked !== null) payload.server_revoked = result.serverRevoked;
|
|
179
|
+
out(JSON.stringify(payload));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
err(
|
|
183
|
+
colors.dim(
|
|
184
|
+
result.serverRevoked ? " Server session revoked and credentials cleared." : " Credentials cleared."
|
|
185
|
+
)
|
|
186
|
+
);
|
|
187
|
+
if (envPresent) envStillSet();
|
|
188
|
+
else err(` Run ${colors.green("tvly login")} to authenticate again.`);
|
|
189
|
+
}
|
|
190
|
+
async function runAuth(_opts, cmd, host) {
|
|
191
|
+
const json = resolveJson(cmd);
|
|
192
|
+
const { key, method, source } = effectiveAuthState(await getApiKeyOrExit(json, host), host);
|
|
193
|
+
if (json) {
|
|
194
|
+
out(JSON.stringify({ authenticated: key !== null, method, source }));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
out();
|
|
198
|
+
if (key) {
|
|
199
|
+
const masked = key.length > 12 ? `${key.slice(0, 8)}...${key.slice(-4)}` : "***";
|
|
200
|
+
out(` ${colors.green(">")} Authenticated via ${source}`);
|
|
201
|
+
out(` ${colors.dim(`Key: ${masked}`)}`);
|
|
202
|
+
} else {
|
|
203
|
+
out(` ${colors.pink(">")} Not authenticated`);
|
|
204
|
+
out();
|
|
205
|
+
out(` Run ${colors.green("tvly login")} to authenticate.`);
|
|
206
|
+
}
|
|
207
|
+
out();
|
|
208
|
+
}
|
|
209
|
+
export {
|
|
210
|
+
runAuth,
|
|
211
|
+
runLogin,
|
|
212
|
+
runLogout
|
|
213
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/errors.ts
|
|
4
|
+
var ExitError = class extends Error {
|
|
5
|
+
constructor(code) {
|
|
6
|
+
super(`exit ${code}`);
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.name = "ExitError";
|
|
9
|
+
}
|
|
10
|
+
code;
|
|
11
|
+
};
|
|
12
|
+
var OAuthError = class extends Error {
|
|
13
|
+
constructor(message) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "OAuthError";
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
function isObject(value) {
|
|
19
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20
|
+
}
|
|
21
|
+
function messageOf(e) {
|
|
22
|
+
return e instanceof Error ? e.message : String(e);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
ExitError,
|
|
27
|
+
OAuthError,
|
|
28
|
+
isObject,
|
|
29
|
+
messageOf
|
|
30
|
+
};
|