@jmcombs/pi-tavily-search 2.1.0 → 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 +31 -45
- 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,17 +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
|
-
* 1. `
|
|
11
|
-
*
|
|
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.
|
|
12
17
|
*/
|
|
13
18
|
|
|
14
|
-
import {
|
|
15
|
-
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";
|
|
16
22
|
|
|
17
23
|
const TAVILY_SEARCH_ENDPOINT = "https://api.tavily.com/search";
|
|
18
24
|
|
|
@@ -66,20 +72,13 @@ function formatResults(data: TavilySearchResponse, query: string): string {
|
|
|
66
72
|
// ── Extension factory ──────────────────────────────────────────────────
|
|
67
73
|
|
|
68
74
|
export default function (pi: ExtensionAPI): void {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// Register /tavily_authenticate command for manual key entry.
|
|
75
|
+
// Register /tavily_setup command for onboarding the key on demand.
|
|
72
76
|
// The input is captured by the TUI and never enters the LLM's context.
|
|
73
|
-
pi.registerCommand("
|
|
74
|
-
description: "
|
|
77
|
+
pi.registerCommand("tavily_setup", {
|
|
78
|
+
description: "Set up or update your Tavily API key (never shown to the agent).",
|
|
75
79
|
handler: async (_args, ctx) => {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
authStorage.set("tavily", { type: "api_key" as const, key: apiKey });
|
|
79
|
-
ctx.ui.notify("Tavily API key saved successfully.", "info");
|
|
80
|
-
} else {
|
|
81
|
-
ctx.ui.notify("Authentication cancelled.", "warning");
|
|
82
|
-
}
|
|
80
|
+
const result = await onboardSecret(ctx, { name: "tavily", label: "Tavily" });
|
|
81
|
+
ctx.ui.notify(result.message, result.ok ? "info" : "warning");
|
|
83
82
|
},
|
|
84
83
|
});
|
|
85
84
|
|
|
@@ -90,33 +89,22 @@ export default function (pi: ExtensionAPI): void {
|
|
|
90
89
|
"Performs a web search using the Tavily API to get real-time information from the internet.",
|
|
91
90
|
parameters: tavilySearchSchema,
|
|
92
91
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
93
|
-
let apiKey = (await
|
|
92
|
+
let apiKey = (await resolveSecret("tavily")) ?? process.env.TAVILY_API_KEY;
|
|
94
93
|
|
|
95
|
-
// Auto-
|
|
94
|
+
// Auto-onboard: run the availability-branched onboarding flow if no key is
|
|
95
|
+
// configured, then re-resolve (env fallback preserved).
|
|
96
96
|
if (!apiKey) {
|
|
97
|
-
const
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
content: [{ type: "text", text: "Search cancelled: no Tavily API key provided." }],
|
|
101
|
-
details: { error: "missing_api_key" },
|
|
102
|
-
isError: true,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
authStorage.set("tavily", { type: "api_key" as const, key: newKey });
|
|
106
|
-
apiKey = await authStorage.getApiKey("tavily");
|
|
107
|
-
if (!apiKey) {
|
|
108
|
-
return {
|
|
109
|
-
content: [
|
|
110
|
-
{
|
|
111
|
-
type: "text",
|
|
112
|
-
text: "Failed to resolve Tavily API key. Check your shell configuration.",
|
|
113
|
-
},
|
|
114
|
-
],
|
|
115
|
-
details: { error: "missing_api_key" },
|
|
116
|
-
isError: true,
|
|
117
|
-
};
|
|
97
|
+
const r = await onboardSecret(ctx, { name: "tavily", label: "Tavily" });
|
|
98
|
+
if (r.ok) {
|
|
99
|
+
apiKey = (await resolveSecret("tavily")) ?? process.env.TAVILY_API_KEY;
|
|
118
100
|
}
|
|
119
101
|
}
|
|
102
|
+
if (!apiKey) {
|
|
103
|
+
return {
|
|
104
|
+
content: [{ type: "text", text: "Search cancelled: no Tavily API key provided." }],
|
|
105
|
+
details: { error: "missing_api_key" },
|
|
106
|
+
};
|
|
107
|
+
}
|
|
120
108
|
|
|
121
109
|
try {
|
|
122
110
|
const response = await fetch(TAVILY_SEARCH_ENDPOINT, {
|
|
@@ -141,7 +129,6 @@ export default function (pi: ExtensionAPI): void {
|
|
|
141
129
|
},
|
|
142
130
|
],
|
|
143
131
|
details: { status: response.status, body: errorText },
|
|
144
|
-
isError: true,
|
|
145
132
|
};
|
|
146
133
|
}
|
|
147
134
|
|
|
@@ -155,7 +142,6 @@ export default function (pi: ExtensionAPI): void {
|
|
|
155
142
|
return {
|
|
156
143
|
content: [{ type: "text", text: `Error performing Tavily search: ${message}` }],
|
|
157
144
|
details: { error: message },
|
|
158
|
-
isError: true,
|
|
159
145
|
};
|
|
160
146
|
}
|
|
161
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": "*"
|