@jmcombs/pi-tavily-search 2.0.1 → 2.1.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 (2) hide show
  1. package/index.ts +47 -22
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -2,12 +2,13 @@
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. If no Tavily API key is configured, the tool prompts the user
6
+ * interactively via the TUI (never leaking the key into the agent's context).
7
+ * The key can also be set manually by running `/tavily_authenticate`.
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
+ * Supported configuration (if not using interactive prompt):
10
+ * 1. `AuthStorage` under the "tavily" key (`~/.pi/agent/auth.json`)
11
+ * 2. The `TAVILY_API_KEY` environment variable
11
12
  */
12
13
 
13
14
  import { AuthStorage, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
@@ -48,16 +49,6 @@ interface TavilySearchResponse {
48
49
 
49
50
  // ── Helpers ────────────────────────────────────────────────────────────
50
51
 
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
52
  function formatResults(data: TavilySearchResponse, query: string): string {
62
53
  const results = data.results ?? [];
63
54
  if (results.length === 0) {
@@ -77,20 +68,54 @@ function formatResults(data: TavilySearchResponse, query: string): string {
77
68
  export default function (pi: ExtensionAPI): void {
78
69
  const authStorage = AuthStorage.create();
79
70
 
71
+ // Register /tavily_authenticate command for manual key entry.
72
+ // The input is captured by the TUI and never enters the LLM's context.
73
+ pi.registerCommand("tavily_authenticate", {
74
+ description: "Securely save your Tavily API key (input never visible to LLM).",
75
+ handler: async (_args, ctx) => {
76
+ const apiKey = await ctx.ui.input("Enter your Tavily API key:");
77
+ if (apiKey) {
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
+ }
83
+ },
84
+ });
85
+
80
86
  pi.registerTool({
81
87
  name: "tavily_search",
82
88
  label: "Tavily Web Search",
83
89
  description:
84
90
  "Performs a web search using the Tavily API to get real-time information from the internet.",
85
91
  parameters: tavilySearchSchema,
86
- async execute(_toolCallId, params, signal, _onUpdate, _ctx) {
87
- const apiKey = (await authStorage.getApiKey("tavily")) ?? process.env.TAVILY_API_KEY;
92
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
93
+ let apiKey = (await authStorage.getApiKey("tavily")) ?? process.env.TAVILY_API_KEY;
94
+
95
+ // Auto-authenticate: prompt for key if none is configured
88
96
  if (!apiKey) {
89
- return {
90
- content: [{ type: "text", text: MISSING_KEY_MESSAGE }],
91
- details: { error: "missing_api_key" },
92
- isError: true,
93
- };
97
+ const newKey = await ctx.ui.input("Enter your Tavily API key:");
98
+ if (!newKey) {
99
+ return {
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
+ };
118
+ }
94
119
  }
95
120
 
96
121
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jmcombs/pi-tavily-search",
3
- "version": "2.0.1",
3
+ "version": "2.1.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": {