@jmcombs/pi-tavily-search 2.0.1 → 3.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/README.md +84 -19
- package/index.ts +35 -24
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -3,6 +3,56 @@
|
|
|
3
3
|
A [Pi coding agent](https://pi.dev) extension that adds real-time web search via the
|
|
4
4
|
[Tavily API](https://tavily.com).
|
|
5
5
|
|
|
6
|
+
## Breaking changes in v3.0.0
|
|
7
|
+
|
|
8
|
+
- **`AuthStorage` is gone.** Pi 0.80.8 removed the `AuthStorage` API this extension used
|
|
9
|
+
to store and read its API key. Credentials now resolve through the
|
|
10
|
+
[`@jmcombs/pi-1password`](https://www.npmjs.com/package/@jmcombs/pi-1password)
|
|
11
|
+
**credential API**, which this package now depends on directly and **installs
|
|
12
|
+
automatically** — no separate install step.
|
|
13
|
+
- **Availability-branched onboarding.** When the `op` CLI is installed and an account is
|
|
14
|
+
configured, setup opens a 1Password **vault → item → field picker**; when `op` is
|
|
15
|
+
unavailable it falls back to **masked manual key entry**.
|
|
16
|
+
- **Existing keys keep working.** Any Tavily key already in `~/.pi/agent/auth.json` — a
|
|
17
|
+
literal value or an `!op read` reference — resolves unchanged. No migration action is
|
|
18
|
+
required.
|
|
19
|
+
|
|
20
|
+
## What's New — 1Password credential integration
|
|
21
|
+
|
|
22
|
+
Tavily search now handles your API key through the
|
|
23
|
+
[`@jmcombs/pi-1password`](https://www.npmjs.com/package/@jmcombs/pi-1password) credential
|
|
24
|
+
API, which installs automatically as a dependency. What this means for you:
|
|
25
|
+
|
|
26
|
+
- **Onboarding branches on 1Password availability.** If the `op` CLI is installed and an
|
|
27
|
+
account is configured, `/tavily_setup` opens a live **vault → item → field
|
|
28
|
+
picker** (or lets you type an `op://…` reference) and stores it as a `!op read '…'`
|
|
29
|
+
entry that resolves fresh on every use. If `op` is not available, it falls back to
|
|
30
|
+
**manual API-key entry** and nudges you to enable the 1Password extension for vault
|
|
31
|
+
integration.
|
|
32
|
+
- **The `TAVILY_API_KEY` environment variable still works.** It remains a supported
|
|
33
|
+
fallback, resolved after the stored `tavily` key.
|
|
34
|
+
- **Existing keys keep working.** Any Tavily key already in `~/.pi/agent/auth.json` — a
|
|
35
|
+
literal key or an `!op read` reference — continues to resolve unchanged. No migration
|
|
36
|
+
action is required.
|
|
37
|
+
- **The key is never exposed to the model.** Entry happens entirely in the TUI, and only
|
|
38
|
+
the resolved value is used to call the Tavily API.
|
|
39
|
+
- **Enable 1Password for vault integration and startup unlock.** Install and enable the
|
|
40
|
+
[`@jmcombs/pi-1password`](https://www.npmjs.com/package/@jmcombs/pi-1password) extension:
|
|
41
|
+
it makes the vault picker available during onboarding and runs a one-time `op read` at
|
|
42
|
+
session startup, so the biometric unlock prompt lands once.
|
|
43
|
+
|
|
44
|
+
```mermaid
|
|
45
|
+
flowchart TD
|
|
46
|
+
A["/tavily_setup or first tool use"] --> B{"is1PasswordAvailable()<br/>(op installed AND configured)"}
|
|
47
|
+
B -- "Yes" --> C["Live vault → item → field picker<br/>or manual op:// reference"]
|
|
48
|
+
C --> D["Store !op read 'op://…' entry"]
|
|
49
|
+
B -- "No" --> E["Manual API-key entry<br/>+ nudge to enable 1Password"]
|
|
50
|
+
E --> F["Store literal api_key entry"]
|
|
51
|
+
D --> G["resolveSecret('tavily') ?? TAVILY_API_KEY<br/>resolves fresh on each tool call"]
|
|
52
|
+
F --> G
|
|
53
|
+
G --> H["api_key → Tavily API<br/>(never shown to the LLM)"]
|
|
54
|
+
```
|
|
55
|
+
|
|
6
56
|
## Install
|
|
7
57
|
|
|
8
58
|
```bash
|
|
@@ -22,34 +72,48 @@ available) to get one, then configure it using one of the methods below.
|
|
|
22
72
|
five formatted results (title, URL, content) plus the raw API response under
|
|
23
73
|
`details.raw`. The tool is callable by the LLM whenever it needs current
|
|
24
74
|
information from the public web.
|
|
75
|
+
- **Command**: `/tavily_setup` — runs the `@jmcombs/pi-1password` onboarding flow
|
|
76
|
+
to save (or update) your Tavily key. The input is never visible to the LLM.
|
|
25
77
|
|
|
26
78
|
## Configuration
|
|
27
79
|
|
|
28
|
-
|
|
80
|
+
The `tavily_search` tool resolves the key in this order:
|
|
29
81
|
|
|
30
|
-
1. `
|
|
31
|
-
|
|
82
|
+
1. `resolveSecret("tavily")` from `@jmcombs/pi-1password` — reads `~/.pi/agent/auth.json`
|
|
83
|
+
fresh on each call (a literal key or an `!op read` reference). **Recommended.**
|
|
84
|
+
2. The `TAVILY_API_KEY` environment variable — fallback.
|
|
32
85
|
|
|
33
|
-
|
|
86
|
+
If neither is set, the tool automatically runs onboarding (the availability branch above)
|
|
87
|
+
on first use, then re-resolves — preserving the "prompt on first use" experience.
|
|
34
88
|
|
|
35
|
-
|
|
89
|
+
### Option 1 — `/tavily_setup` (recommended)
|
|
36
90
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"key": "tvly-..."
|
|
42
|
-
}
|
|
43
|
-
}
|
|
91
|
+
Run the command and follow the flow:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
/tavily_setup
|
|
44
95
|
```
|
|
45
96
|
|
|
46
|
-
|
|
97
|
+
- When the `op` CLI is available, pick your key from the live vault picker (or paste an
|
|
98
|
+
`op://vault/item/field` reference); it is stored as a `!op read '…'` entry that resolves
|
|
99
|
+
fresh on every use.
|
|
100
|
+
- When `op` is not available, enter the key on a masked prompt; it is stored as a literal
|
|
101
|
+
`api_key` entry.
|
|
102
|
+
|
|
103
|
+
Either way the value is written to `~/.pi/agent/auth.json` (`0600`) and never shown to the
|
|
104
|
+
model.
|
|
105
|
+
|
|
106
|
+
### Option 2 — edit `~/.pi/agent/auth.json` directly
|
|
107
|
+
|
|
108
|
+
The stored entry is provider-shaped under the `tavily` key. Any of these resolve:
|
|
109
|
+
|
|
110
|
+
#### Plain key
|
|
47
111
|
|
|
48
112
|
```json
|
|
49
113
|
{
|
|
50
114
|
"tavily": {
|
|
51
115
|
"type": "api_key",
|
|
52
|
-
"key": "
|
|
116
|
+
"key": "tvly-..."
|
|
53
117
|
}
|
|
54
118
|
}
|
|
55
119
|
```
|
|
@@ -65,13 +129,13 @@ You must configure a Tavily API key. Pi resolves the key in this order:
|
|
|
65
129
|
}
|
|
66
130
|
```
|
|
67
131
|
|
|
68
|
-
#### Shell-resolved key (`pass`)
|
|
132
|
+
#### Shell-resolved key (macOS Keychain / `pass`)
|
|
69
133
|
|
|
70
134
|
```json
|
|
71
135
|
{
|
|
72
136
|
"tavily": {
|
|
73
137
|
"type": "api_key",
|
|
74
|
-
"key": "!
|
|
138
|
+
"key": "!security find-generic-password -ws tavily"
|
|
75
139
|
}
|
|
76
140
|
}
|
|
77
141
|
```
|
|
@@ -79,7 +143,7 @@ You must configure a Tavily API key. Pi resolves the key in this order:
|
|
|
79
143
|
The `!`-prefixed value is executed by your shell at lookup time, so no secret is
|
|
80
144
|
ever stored on disk in plaintext.
|
|
81
145
|
|
|
82
|
-
### Option
|
|
146
|
+
### Option 3 — environment variable
|
|
83
147
|
|
|
84
148
|
```bash
|
|
85
149
|
export TAVILY_API_KEY="tvly-..."
|
|
@@ -98,9 +162,10 @@ export TAVILY_API_KEY="tvly-..."
|
|
|
98
162
|
|
|
99
163
|
## Requirements
|
|
100
164
|
|
|
101
|
-
- Pi `>= 0.
|
|
102
|
-
- Node `>=
|
|
165
|
+
- Pi `>= 0.80.8` (credentials via the `@jmcombs/pi-1password` API and `ExtensionAPI`)
|
|
166
|
+
- Node `>= 22.0.0`
|
|
103
167
|
- A Tavily API key
|
|
168
|
+
- Optional: the `op` (1Password) CLI for vault-backed onboarding and startup unlock
|
|
104
169
|
|
|
105
170
|
## Development
|
|
106
171
|
|
package/index.ts
CHANGED
|
@@ -2,16 +2,23 @@
|
|
|
2
2
|
* @jmcombs/pi-tavily-search — Real-time web search for the Pi coding agent.
|
|
3
3
|
*
|
|
4
4
|
* Registers a `tavily_search` tool that the LLM can call to perform a Tavily
|
|
5
|
-
* web search.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* web search. Credentials are handled entirely through the imported
|
|
6
|
+
* `@jmcombs/pi-1password` credential API (`resolveSecret` / `onboardSecret`),
|
|
7
|
+
* so the key is never leaked into the agent's context.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* Credential handling:
|
|
10
|
+
* 1. `resolveSecret("tavily")` reads `~/.pi/agent/auth.json` and resolves the
|
|
11
|
+
* stored entry (literal key or `!op read 'op://…'` reference) fresh on each
|
|
12
|
+
* use; the `TAVILY_API_KEY` environment variable is the fallback.
|
|
13
|
+
* 2. If nothing is stored, the tool auto-invokes `onboardSecret`, which branches
|
|
14
|
+
* on 1Password availability — the live vault picker when `op` is configured,
|
|
15
|
+
* manual API-key entry otherwise — then re-resolves.
|
|
16
|
+
* 3. `/tavily_setup` runs the same onboarding flow on demand.
|
|
11
17
|
*/
|
|
12
18
|
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
19
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
20
|
+
import { onboardSecret, resolveSecret } from "@jmcombs/pi-1password";
|
|
21
|
+
import { type Static, Type } from "typebox";
|
|
15
22
|
|
|
16
23
|
const TAVILY_SEARCH_ENDPOINT = "https://api.tavily.com/search";
|
|
17
24
|
|
|
@@ -48,16 +55,6 @@ interface TavilySearchResponse {
|
|
|
48
55
|
|
|
49
56
|
// ── Helpers ────────────────────────────────────────────────────────────
|
|
50
57
|
|
|
51
|
-
const MISSING_KEY_MESSAGE = [
|
|
52
|
-
"Error: No Tavily API key configured.",
|
|
53
|
-
"",
|
|
54
|
-
"Configure one of the following:",
|
|
55
|
-
" • Environment variable: export TAVILY_API_KEY=<your-key>",
|
|
56
|
-
' • ~/.pi/agent/auth.json: { "tavily": { "type": "api_key", "key": "<your-key>" } }',
|
|
57
|
-
' • Shell-resolved: { "tavily": { "type": "api_key", "key": "!security find-generic-password -ws tavily" } }',
|
|
58
|
-
' • 1Password: { "tavily": { "type": "api_key", "key": "!op read \'op://Personal/tavily/credential\'" } }',
|
|
59
|
-
].join("\n");
|
|
60
|
-
|
|
61
58
|
function formatResults(data: TavilySearchResponse, query: string): string {
|
|
62
59
|
const results = data.results ?? [];
|
|
63
60
|
if (results.length === 0) {
|
|
@@ -75,7 +72,15 @@ function formatResults(data: TavilySearchResponse, query: string): string {
|
|
|
75
72
|
// ── Extension factory ──────────────────────────────────────────────────
|
|
76
73
|
|
|
77
74
|
export default function (pi: ExtensionAPI): void {
|
|
78
|
-
|
|
75
|
+
// Register /tavily_setup command for onboarding the key on demand.
|
|
76
|
+
// The input is captured by the TUI and never enters the LLM's context.
|
|
77
|
+
pi.registerCommand("tavily_setup", {
|
|
78
|
+
description: "Set up or update your Tavily API key (never shown to the agent).",
|
|
79
|
+
handler: async (_args, ctx) => {
|
|
80
|
+
const result = await onboardSecret(ctx, { name: "tavily", label: "Tavily" });
|
|
81
|
+
ctx.ui.notify(result.message, result.ok ? "info" : "warning");
|
|
82
|
+
},
|
|
83
|
+
});
|
|
79
84
|
|
|
80
85
|
pi.registerTool({
|
|
81
86
|
name: "tavily_search",
|
|
@@ -83,13 +88,21 @@ export default function (pi: ExtensionAPI): void {
|
|
|
83
88
|
description:
|
|
84
89
|
"Performs a web search using the Tavily API to get real-time information from the internet.",
|
|
85
90
|
parameters: tavilySearchSchema,
|
|
86
|
-
async execute(_toolCallId, params, signal, _onUpdate,
|
|
87
|
-
|
|
91
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
92
|
+
let apiKey = (await resolveSecret("tavily")) ?? process.env.TAVILY_API_KEY;
|
|
93
|
+
|
|
94
|
+
// Auto-onboard: run the availability-branched onboarding flow if no key is
|
|
95
|
+
// configured, then re-resolve (env fallback preserved).
|
|
96
|
+
if (!apiKey) {
|
|
97
|
+
const r = await onboardSecret(ctx, { name: "tavily", label: "Tavily" });
|
|
98
|
+
if (r.ok) {
|
|
99
|
+
apiKey = (await resolveSecret("tavily")) ?? process.env.TAVILY_API_KEY;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
88
102
|
if (!apiKey) {
|
|
89
103
|
return {
|
|
90
|
-
content: [{ type: "text", text:
|
|
104
|
+
content: [{ type: "text", text: "Search cancelled: no Tavily API key provided." }],
|
|
91
105
|
details: { error: "missing_api_key" },
|
|
92
|
-
isError: true,
|
|
93
106
|
};
|
|
94
107
|
}
|
|
95
108
|
|
|
@@ -116,7 +129,6 @@ export default function (pi: ExtensionAPI): void {
|
|
|
116
129
|
},
|
|
117
130
|
],
|
|
118
131
|
details: { status: response.status, body: errorText },
|
|
119
|
-
isError: true,
|
|
120
132
|
};
|
|
121
133
|
}
|
|
122
134
|
|
|
@@ -130,7 +142,6 @@ export default function (pi: ExtensionAPI): void {
|
|
|
130
142
|
return {
|
|
131
143
|
content: [{ type: "text", text: `Error performing Tavily search: ${message}` }],
|
|
132
144
|
details: { error: message },
|
|
133
|
-
isError: true,
|
|
134
145
|
};
|
|
135
146
|
}
|
|
136
147
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jmcombs/pi-tavily-search",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Pi extension that performs real-time web search via the Tavily API.",
|
|
5
5
|
"homepage": "https://github.com/jmcombs/pi-extensions/tree/main/packages/tavily-search",
|
|
6
6
|
"repository": {
|
|
@@ -38,6 +38,9 @@
|
|
|
38
38
|
"engines": {
|
|
39
39
|
"node": ">=22.0.0"
|
|
40
40
|
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@jmcombs/pi-1password": "^1.0.2 || ^2.0.0"
|
|
43
|
+
},
|
|
41
44
|
"peerDependencies": {
|
|
42
45
|
"@earendil-works/pi-coding-agent": "*",
|
|
43
46
|
"typebox": "*"
|