@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.
Files changed (3) hide show
  1. package/README.md +84 -19
  2. package/index.ts +35 -24
  3. 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
- You must configure a Tavily API key. Pi resolves the key in this order:
80
+ The `tavily_search` tool resolves the key in this order:
29
81
 
30
- 1. `AuthStorage` under the `tavily` key (`~/.pi/agent/auth.json`) — **recommended**.
31
- 2. The `TAVILY_API_KEY` environment variable.
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
- ### Option 1 — `~/.pi/agent/auth.json` (recommended)
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
- #### Plain key
89
+ ### Option 1 — `/tavily_setup` (recommended)
36
90
 
37
- ```json
38
- {
39
- "tavily": {
40
- "type": "api_key",
41
- "key": "tvly-..."
42
- }
43
- }
91
+ Run the command and follow the flow:
92
+
93
+ ```
94
+ /tavily_setup
44
95
  ```
45
96
 
46
- #### Shell-resolved key (macOS Keychain)
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": "!security find-generic-password -ws tavily"
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": "!pass show tavily"
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 2 — environment variable
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.72.0` (uses `AuthStorage` and `ExtensionAPI`)
102
- - Node `>= 20.6.0`
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. The Tavily API key is resolved from (in order):
6
- * 1. `AuthStorage` under the "tavily" key (`~/.pi/agent/auth.json`)
7
- * 2. The `TAVILY_API_KEY` environment variable
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
- * See README.md for configuration details and recommended secret-storage
10
- * patterns (env var, plain auth.json, or shell-resolved 1Password / Keychain).
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 { AuthStorage, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
14
- import { Type, type Static } from "typebox";
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
- const authStorage = AuthStorage.create();
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, _ctx) {
87
- const apiKey = (await authStorage.getApiKey("tavily")) ?? process.env.TAVILY_API_KEY;
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: MISSING_KEY_MESSAGE }],
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": "2.0.1",
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": "*"