@demigodmode/pi-web-agent 1.6.2 → 1.7.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/CHANGELOG.md CHANGED
@@ -18,6 +18,21 @@ The format is intentionally simple and release-oriented.
18
18
  ### Breaking
19
19
  - None.
20
20
 
21
+ ## [1.7.0] - 2026-07-17
22
+ ### Added
23
+ - Exa is now available as a hosted search backend for `web_explore`, aimed at research-quality source discovery. Set `EXA_API_KEY` in the environment and choose `exa` from **Settings → Backends**. Closes #2.
24
+ - Tavily is now available as a hosted search backend for `web_explore` alongside Exa, DuckDuckGo, SearXNG, Brave, and You.com. Set `TAVILY_API_KEY` and choose `tavily` from **Settings → Backends**. Closes #2.
25
+ - Both new backends support optional fallback to DuckDuckGo, and `/web-agent doctor` reports their setup status and validates configured access when a key is present.
26
+
27
+ ### Changed
28
+ - Bumped @earendil-works/pi-coding-agent and pi-tui devDependencies to 0.80.10.
29
+
30
+ ### Fixed
31
+ - None.
32
+
33
+ ### Breaking
34
+ - None.
35
+
21
36
  ## [1.6.2] - 2026-07-10
22
37
  ### Added
23
38
  - None.
package/README.md CHANGED
@@ -119,7 +119,7 @@ Backend settings can be changed from:
119
119
  /web-agent settings
120
120
  ```
121
121
 
122
- Choose **Backends** to edit search/fetch providers, fallback behavior, and SearXNG or Firecrawl base URLs interactively. Brave Search uses `PI_WEB_AGENT_BRAVE_API_KEY` and You.com uses `YDC_API_KEY`. Firecrawl API keys should also stay in environment variables rather than being written into config files.
122
+ Choose **Backends** to edit search/fetch providers, fallback behavior, and SearXNG or Firecrawl base URLs interactively. Brave Search uses `PI_WEB_AGENT_BRAVE_API_KEY`, You.com uses `YDC_API_KEY`, Exa uses `EXA_API_KEY`, and Tavily uses `TAVILY_API_KEY`. Firecrawl API keys should also stay in environment variables rather than being written into config files.
123
123
 
124
124
  For the full backend config shape, including SearXNG, Brave, You.com, Firecrawl, and fallback behavior, see:
125
125
 
@@ -8,7 +8,7 @@ export type FirecrawlOptions = {
8
8
  onlyMainContent?: boolean;
9
9
  };
10
10
  export type SearchBackendConfig = {
11
- provider: 'duckduckgo' | 'searxng' | 'brave' | 'youcom';
11
+ provider: 'duckduckgo' | 'searxng' | 'brave' | 'youcom' | 'exa' | 'tavily';
12
12
  baseUrl?: string;
13
13
  fallback?: 'duckduckgo';
14
14
  options?: SearxngOptions;
@@ -41,7 +41,9 @@ export function extractBackendConfigOverride(file) {
41
41
  if (backends?.search?.provider === 'duckduckgo' ||
42
42
  backends?.search?.provider === 'searxng' ||
43
43
  backends?.search?.provider === 'brave' ||
44
- backends?.search?.provider === 'youcom') {
44
+ backends?.search?.provider === 'youcom' ||
45
+ backends?.search?.provider === 'exa' ||
46
+ backends?.search?.provider === 'tavily') {
45
47
  override.search = { provider: backends.search.provider };
46
48
  if (backends.search.provider === 'searxng' && typeof backends.search.baseUrl === 'string') {
47
49
  override.search.baseUrl = backends.search.baseUrl;
@@ -85,8 +87,8 @@ export function validateBackendConfig(config) {
85
87
  if (config.fetch.provider === 'firecrawl' && !config.fetch.baseUrl) {
86
88
  issues.push('fetch provider firecrawl requires backends.fetch.baseUrl');
87
89
  }
88
- if (config.search.fallback === 'duckduckgo' && config.search.provider !== 'searxng' && config.search.provider !== 'brave' && config.search.provider !== 'youcom') {
89
- issues.push('search fallback duckduckgo is only supported when search provider is searxng, brave, or youcom');
90
+ if (config.search.fallback === 'duckduckgo' && config.search.provider !== 'searxng' && config.search.provider !== 'brave' && config.search.provider !== 'youcom' && config.search.provider !== 'exa' && config.search.provider !== 'tavily') {
91
+ issues.push('search fallback duckduckgo is only supported when search provider is searxng, brave, youcom, exa, or tavily');
90
92
  }
91
93
  if (config.fetch.fallback === 'http' && config.fetch.provider !== 'firecrawl') {
92
94
  issues.push('fetch fallback http is only supported when fetch provider is firecrawl');
@@ -15,6 +15,12 @@ function braveDoctorUrl() {
15
15
  function youcomDoctorBody() {
16
16
  return JSON.stringify({ query: 'pi-web-agent-doctor', max_results: 1 });
17
17
  }
18
+ function exaDoctorBody() {
19
+ return JSON.stringify({ query: 'pi-web-agent-doctor', numResults: 1 });
20
+ }
21
+ function tavilyDoctorBody() {
22
+ return JSON.stringify({ query: 'pi-web-agent-doctor', max_results: 1 });
23
+ }
18
24
  function searxngDoctorUrl(baseUrl, options = {}) {
19
25
  const url = new URL('/search', baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`);
20
26
  url.searchParams.set('q', 'pi-web-agent-doctor');
@@ -108,6 +114,78 @@ export async function checkBackendHealth(config, { fetchImpl = fetch, timeoutMs
108
114
  }
109
115
  }
110
116
  }
117
+ else if (config.search.provider === 'exa') {
118
+ const apiKey = process.env.EXA_API_KEY;
119
+ if (!apiKey?.trim()) {
120
+ lines.push('search backend: exa warning (missing EXA_API_KEY)');
121
+ }
122
+ else {
123
+ const timeout = withTimeout(timeoutMs);
124
+ try {
125
+ const response = await fetchImpl('https://api.exa.ai/search', {
126
+ method: 'POST',
127
+ headers: {
128
+ Accept: 'application/json',
129
+ 'Content-Type': 'application/json',
130
+ 'x-api-key': apiKey
131
+ },
132
+ body: exaDoctorBody(),
133
+ signal: timeout.signal
134
+ });
135
+ if (!response.ok) {
136
+ lines.push(`search backend: exa warning (HTTP ${response.status})`);
137
+ }
138
+ else {
139
+ const json = (await response.json());
140
+ lines.push(Array.isArray(json.results)
141
+ ? 'search backend: exa ok'
142
+ : 'search backend: exa warning (unexpected response)');
143
+ }
144
+ }
145
+ catch (error) {
146
+ lines.push(`search backend: exa warning (${message(error)})`);
147
+ }
148
+ finally {
149
+ timeout.done();
150
+ }
151
+ }
152
+ }
153
+ else if (config.search.provider === 'tavily') {
154
+ const apiKey = process.env.TAVILY_API_KEY;
155
+ if (!apiKey?.trim()) {
156
+ lines.push('search backend: tavily warning (missing TAVILY_API_KEY)');
157
+ }
158
+ else {
159
+ const timeout = withTimeout(timeoutMs);
160
+ try {
161
+ const response = await fetchImpl('https://api.tavily.com/search', {
162
+ method: 'POST',
163
+ headers: {
164
+ Accept: 'application/json',
165
+ 'Content-Type': 'application/json',
166
+ Authorization: `Bearer ${apiKey}`
167
+ },
168
+ body: tavilyDoctorBody(),
169
+ signal: timeout.signal
170
+ });
171
+ if (!response.ok) {
172
+ lines.push(`search backend: tavily warning (HTTP ${response.status})`);
173
+ }
174
+ else {
175
+ const json = (await response.json());
176
+ lines.push(Array.isArray(json.results)
177
+ ? 'search backend: tavily ok'
178
+ : 'search backend: tavily warning (unexpected response)');
179
+ }
180
+ }
181
+ catch (error) {
182
+ lines.push(`search backend: tavily warning (${message(error)})`);
183
+ }
184
+ finally {
185
+ timeout.done();
186
+ }
187
+ }
188
+ }
111
189
  else if (!config.search.baseUrl) {
112
190
  lines.push('search backend: searxng warning (missing baseUrl)');
113
191
  }
@@ -1,6 +1,8 @@
1
1
  import { createFirecrawlFetcher } from '../fetch/firecrawl-fetch.js';
2
2
  import { createBraveSearchTool } from '../search/brave.js';
3
3
  import { createYouComSearchTool } from '../search/youcom.js';
4
+ import { createExaSearchTool } from '../search/exa.js';
5
+ import { createTavilySearchTool } from '../search/tavily.js';
4
6
  import { createSearxngSearchTool } from '../search/searxng.js';
5
7
  import { createWebFetchHeadlessTool } from '../tools/web-fetch-headless.js';
6
8
  import { createWebFetchTool } from '../tools/web-fetch.js';
@@ -23,6 +25,8 @@ export type BackendFactoryDeps = {
23
25
  createSearxngSearch?: typeof createSearxngSearchTool;
24
26
  createBraveSearch?: typeof createBraveSearchTool;
25
27
  createYouComSearch?: typeof createYouComSearchTool;
28
+ createExaSearch?: typeof createExaSearchTool;
29
+ createTavilySearch?: typeof createTavilySearchTool;
26
30
  createHttpFetch?: typeof createWebFetchTool;
27
31
  createFirecrawlFetch?: typeof createFirecrawlFetcher;
28
32
  createHeadlessFetch?: typeof createWebFetchHeadlessTool;
@@ -1,6 +1,8 @@
1
1
  import { createFirecrawlFetcher } from '../fetch/firecrawl-fetch.js';
2
2
  import { createBraveSearchTool } from '../search/brave.js';
3
3
  import { createYouComSearchTool } from '../search/youcom.js';
4
+ import { createExaSearchTool } from '../search/exa.js';
5
+ import { createTavilySearchTool } from '../search/tavily.js';
4
6
  import { createSearxngSearchTool } from '../search/searxng.js';
5
7
  import { buildFetchPresentation } from '../presentation/fetch-presentation.js';
6
8
  import { buildSearchPresentation } from '../presentation/search-presentation.js';
@@ -75,6 +77,8 @@ export function createBackendSet(config = DEFAULT_BACKEND_CONFIG, deps = {}) {
75
77
  const createSearxngSearch = deps.createSearxngSearch ?? createSearxngSearchTool;
76
78
  const createBraveSearch = deps.createBraveSearch ?? createBraveSearchTool;
77
79
  const createYouComSearch = deps.createYouComSearch ?? createYouComSearchTool;
80
+ const createExaSearch = deps.createExaSearch ?? createExaSearchTool;
81
+ const createTavilySearch = deps.createTavilySearch ?? createTavilySearchTool;
78
82
  const createHttpFetch = deps.createHttpFetch ?? createWebFetchTool;
79
83
  const createFirecrawlFetch = deps.createFirecrawlFetch ?? createFirecrawlFetcher;
80
84
  const createHeadlessFetch = deps.createHeadlessFetch ?? createWebFetchHeadlessTool;
@@ -86,7 +90,11 @@ export function createBackendSet(config = DEFAULT_BACKEND_CONFIG, deps = {}) {
86
90
  ? createBraveSearch({ apiKey: process.env.PI_WEB_AGENT_BRAVE_API_KEY })
87
91
  : config.search.provider === 'youcom'
88
92
  ? createYouComSearch({ apiKey: process.env.YDC_API_KEY })
89
- : createDuckDuckGoSearch();
93
+ : config.search.provider === 'exa'
94
+ ? createExaSearch({ apiKey: process.env.EXA_API_KEY })
95
+ : config.search.provider === 'tavily'
96
+ ? createTavilySearch({ apiKey: process.env.TAVILY_API_KEY })
97
+ : createDuckDuckGoSearch();
90
98
  if (config.search.provider === 'searxng' && config.search.fallback === 'duckduckgo') {
91
99
  search = withSearchFallback(search, createDuckDuckGoSearch(), 'searxng');
92
100
  }
@@ -96,6 +104,12 @@ export function createBackendSet(config = DEFAULT_BACKEND_CONFIG, deps = {}) {
96
104
  if (config.search.provider === 'youcom' && config.search.fallback === 'duckduckgo') {
97
105
  search = withSearchFallback(search, createDuckDuckGoSearch(), 'youcom');
98
106
  }
107
+ if (config.search.provider === 'exa' && config.search.fallback === 'duckduckgo') {
108
+ search = withSearchFallback(search, createDuckDuckGoSearch(), 'exa');
109
+ }
110
+ if (config.search.provider === 'tavily' && config.search.fallback === 'duckduckgo') {
111
+ search = withSearchFallback(search, createDuckDuckGoSearch(), 'tavily');
112
+ }
99
113
  const httpFetch = createHttpFetch();
100
114
  let fetchPage = config.fetch.provider === 'firecrawl'
101
115
  ? config.fetch.baseUrl
@@ -166,7 +166,7 @@ function buildBackendSettingsItems(scope, backends, theme, onUrlEditorOpenChange
166
166
  id: 'backend:search:provider',
167
167
  label: 'Search backend',
168
168
  currentValue: backends.search.provider,
169
- values: ['duckduckgo', 'searxng', 'brave', 'youcom']
169
+ values: ['duckduckgo', 'searxng', 'brave', 'youcom', 'exa', 'tavily']
170
170
  },
171
171
  {
172
172
  id: 'backend:search:baseUrl',
@@ -177,8 +177,8 @@ function buildBackendSettingsItems(scope, backends, theme, onUrlEditorOpenChange
177
177
  {
178
178
  id: 'backend:search:fallback',
179
179
  label: 'Search fallback',
180
- currentValue: backends.search.provider === 'searxng' || backends.search.provider === 'brave' || backends.search.provider === 'youcom' ? backends.search.fallback ?? 'off' : 'off',
181
- values: backends.search.provider === 'searxng' || backends.search.provider === 'brave' || backends.search.provider === 'youcom' ? ['off', 'duckduckgo'] : ['off']
180
+ currentValue: backends.search.provider === 'searxng' || backends.search.provider === 'brave' || backends.search.provider === 'youcom' || backends.search.provider === 'exa' || backends.search.provider === 'tavily' ? backends.search.fallback ?? 'off' : 'off',
181
+ values: backends.search.provider === 'searxng' || backends.search.provider === 'brave' || backends.search.provider === 'youcom' || backends.search.provider === 'exa' || backends.search.provider === 'tavily' ? ['off', 'duckduckgo'] : ['off']
182
182
  },
183
183
  {
184
184
  id: 'backend:secret:brave',
@@ -192,6 +192,18 @@ function buildBackendSettingsItems(scope, backends, theme, onUrlEditorOpenChange
192
192
  currentValue: 'env var',
193
193
  values: ['env var']
194
194
  },
195
+ {
196
+ id: 'backend:secret:exa',
197
+ label: 'Exa API key',
198
+ currentValue: 'env var',
199
+ values: ['env var']
200
+ },
201
+ {
202
+ id: 'backend:secret:tavily',
203
+ label: 'Tavily API key',
204
+ currentValue: 'env var',
205
+ values: ['env var']
206
+ },
195
207
  {
196
208
  id: 'backend:fetch:provider',
197
209
  label: 'Fetch backend',
@@ -303,7 +315,7 @@ export function applySettingsValue(state, id, newValue) {
303
315
  }
304
316
  currentDraft.tools = nextTools;
305
317
  }
306
- if (id === 'backend:search:provider' && (newValue === 'duckduckgo' || newValue === 'searxng' || newValue === 'brave' || newValue === 'youcom')) {
318
+ if (id === 'backend:search:provider' && (newValue === 'duckduckgo' || newValue === 'searxng' || newValue === 'brave' || newValue === 'youcom' || newValue === 'exa' || newValue === 'tavily')) {
307
319
  currentBackends.search.provider = newValue;
308
320
  if (newValue !== 'searxng') {
309
321
  delete currentBackends.search.baseUrl;
@@ -314,7 +326,7 @@ export function applySettingsValue(state, id, newValue) {
314
326
  }
315
327
  }
316
328
  if (id === 'backend:search:fallback') {
317
- if (newValue === 'duckduckgo' && (currentBackends.search.provider === 'searxng' || currentBackends.search.provider === 'brave' || currentBackends.search.provider === 'youcom')) {
329
+ if (newValue === 'duckduckgo' && (currentBackends.search.provider === 'searxng' || currentBackends.search.provider === 'brave' || currentBackends.search.provider === 'youcom' || currentBackends.search.provider === 'exa' || currentBackends.search.provider === 'tavily')) {
318
330
  currentBackends.search.fallback = 'duckduckgo';
319
331
  }
320
332
  else {
@@ -0,0 +1,7 @@
1
+ import type { WebSearchResponse } from '../types.js';
2
+ export declare function createExaSearchTool({ apiKey, fetchImpl }: {
3
+ apiKey?: string;
4
+ fetchImpl?: typeof fetch;
5
+ }): ({ query }: {
6
+ query: string;
7
+ }) => Promise<WebSearchResponse>;
@@ -0,0 +1,81 @@
1
+ import { buildSearchPresentation } from '../presentation/search-presentation.js';
2
+ const EXA_SEARCH_URL = 'https://api.exa.ai/search';
3
+ function resultWithPresentation(result) {
4
+ return { ...result, presentation: buildSearchPresentation(result) };
5
+ }
6
+ function normalizeResults(response) {
7
+ return (response.results ?? []).flatMap((item) => {
8
+ if (typeof item.title !== 'string' || typeof item.url !== 'string') {
9
+ return [];
10
+ }
11
+ return [
12
+ {
13
+ title: item.title,
14
+ url: item.url,
15
+ snippet: typeof item.text === 'string' ? item.text : ''
16
+ }
17
+ ];
18
+ });
19
+ }
20
+ export function createExaSearchTool({ apiKey, fetchImpl = fetch }) {
21
+ return async function exaSearch({ query }) {
22
+ const normalizedQuery = query.trim();
23
+ if (!normalizedQuery) {
24
+ return resultWithPresentation({
25
+ status: 'error',
26
+ results: [],
27
+ metadata: { backend: 'exa', cacheHit: false },
28
+ error: { code: 'INVALID_QUERY', message: 'Query must not be empty.' }
29
+ });
30
+ }
31
+ if (!apiKey?.trim()) {
32
+ return resultWithPresentation({
33
+ status: 'error',
34
+ results: [],
35
+ metadata: { backend: 'exa', cacheHit: false },
36
+ error: {
37
+ code: 'BACKEND_CONFIG_INVALID',
38
+ message: 'Exa search requires EXA_API_KEY.'
39
+ }
40
+ });
41
+ }
42
+ try {
43
+ const response = await fetchImpl(EXA_SEARCH_URL, {
44
+ method: 'POST',
45
+ headers: {
46
+ Accept: 'application/json',
47
+ 'Content-Type': 'application/json',
48
+ 'x-api-key': apiKey
49
+ },
50
+ body: JSON.stringify({ query: normalizedQuery, numResults: 10 })
51
+ });
52
+ if (!response.ok) {
53
+ throw new Error(`HTTP ${response.status}`);
54
+ }
55
+ const parsed = (await response.json());
56
+ const results = normalizeResults(parsed);
57
+ if (results.length === 0) {
58
+ return resultWithPresentation({
59
+ status: 'error',
60
+ results: [],
61
+ metadata: { backend: 'exa', cacheHit: false },
62
+ error: { code: 'NO_RESULTS', message: 'Exa returned no usable results for this query.' }
63
+ });
64
+ }
65
+ return resultWithPresentation({
66
+ status: 'ok',
67
+ results,
68
+ metadata: { backend: 'exa', cacheHit: false }
69
+ });
70
+ }
71
+ catch (error) {
72
+ const rawMessage = error instanceof Error ? error.message : String(error);
73
+ return resultWithPresentation({
74
+ status: 'error',
75
+ results: [],
76
+ metadata: { backend: 'exa', cacheHit: false },
77
+ error: { code: 'FETCH_FAILED', message: `Exa search request failed: ${rawMessage}` }
78
+ });
79
+ }
80
+ };
81
+ }
@@ -0,0 +1,7 @@
1
+ import type { WebSearchResponse } from '../types.js';
2
+ export declare function createTavilySearchTool({ apiKey, fetchImpl }: {
3
+ apiKey?: string;
4
+ fetchImpl?: typeof fetch;
5
+ }): ({ query }: {
6
+ query: string;
7
+ }) => Promise<WebSearchResponse>;
@@ -0,0 +1,81 @@
1
+ import { buildSearchPresentation } from '../presentation/search-presentation.js';
2
+ const TAVILY_SEARCH_URL = 'https://api.tavily.com/search';
3
+ function resultWithPresentation(result) {
4
+ return { ...result, presentation: buildSearchPresentation(result) };
5
+ }
6
+ function normalizeResults(response) {
7
+ return (response.results ?? []).flatMap((item) => {
8
+ if (typeof item.title !== 'string' || typeof item.url !== 'string') {
9
+ return [];
10
+ }
11
+ return [
12
+ {
13
+ title: item.title,
14
+ url: item.url,
15
+ snippet: typeof item.content === 'string' ? item.content : ''
16
+ }
17
+ ];
18
+ });
19
+ }
20
+ export function createTavilySearchTool({ apiKey, fetchImpl = fetch }) {
21
+ return async function tavilySearch({ query }) {
22
+ const normalizedQuery = query.trim();
23
+ if (!normalizedQuery) {
24
+ return resultWithPresentation({
25
+ status: 'error',
26
+ results: [],
27
+ metadata: { backend: 'tavily', cacheHit: false },
28
+ error: { code: 'INVALID_QUERY', message: 'Query must not be empty.' }
29
+ });
30
+ }
31
+ if (!apiKey?.trim()) {
32
+ return resultWithPresentation({
33
+ status: 'error',
34
+ results: [],
35
+ metadata: { backend: 'tavily', cacheHit: false },
36
+ error: {
37
+ code: 'BACKEND_CONFIG_INVALID',
38
+ message: 'Tavily search requires TAVILY_API_KEY.'
39
+ }
40
+ });
41
+ }
42
+ try {
43
+ const response = await fetchImpl(TAVILY_SEARCH_URL, {
44
+ method: 'POST',
45
+ headers: {
46
+ Accept: 'application/json',
47
+ 'Content-Type': 'application/json',
48
+ Authorization: `Bearer ${apiKey}`
49
+ },
50
+ body: JSON.stringify({ query: normalizedQuery, max_results: 10 })
51
+ });
52
+ if (!response.ok) {
53
+ throw new Error(`HTTP ${response.status}`);
54
+ }
55
+ const parsed = (await response.json());
56
+ const results = normalizeResults(parsed);
57
+ if (results.length === 0) {
58
+ return resultWithPresentation({
59
+ status: 'error',
60
+ results: [],
61
+ metadata: { backend: 'tavily', cacheHit: false },
62
+ error: { code: 'NO_RESULTS', message: 'Tavily returned no usable results for this query.' }
63
+ });
64
+ }
65
+ return resultWithPresentation({
66
+ status: 'ok',
67
+ results,
68
+ metadata: { backend: 'tavily', cacheHit: false }
69
+ });
70
+ }
71
+ catch (error) {
72
+ const rawMessage = error instanceof Error ? error.message : String(error);
73
+ return resultWithPresentation({
74
+ status: 'error',
75
+ results: [],
76
+ metadata: { backend: 'tavily', cacheHit: false },
77
+ error: { code: 'FETCH_FAILED', message: `Tavily search request failed: ${rawMessage}` }
78
+ });
79
+ }
80
+ };
81
+ }
package/dist/types.d.ts CHANGED
@@ -11,9 +11,9 @@ export type ToolError = {
11
11
  message: string;
12
12
  };
13
13
  export type SearchMetadata = {
14
- backend: 'duckduckgo' | 'searxng' | 'brave' | 'youcom';
14
+ backend: 'duckduckgo' | 'searxng' | 'brave' | 'youcom' | 'exa' | 'tavily';
15
15
  cacheHit: boolean;
16
- fallbackFrom?: 'searxng' | 'brave' | 'youcom';
16
+ fallbackFrom?: 'searxng' | 'brave' | 'youcom' | 'exa' | 'tavily';
17
17
  fallbackReason?: string;
18
18
  };
19
19
  export type FetchMetadata = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@demigodmode/pi-web-agent",
3
- "version": "1.6.2",
3
+ "version": "1.7.0",
4
4
  "description": "Pi package for reliable web access with explicit search, fetch, and headless boundaries.",
5
5
  "type": "module",
6
6
  "main": "./dist/extension.js",
@@ -27,6 +27,8 @@
27
27
  "searxng",
28
28
  "brave-search",
29
29
  "you-com",
30
+ "exa",
31
+ "tavily",
30
32
  "firecrawl"
31
33
  ],
32
34
  "repository": {
@@ -68,8 +70,8 @@
68
70
  "typebox": "^1.1.37"
69
71
  },
70
72
  "devDependencies": {
71
- "@earendil-works/pi-coding-agent": "^0.79.10",
72
- "@earendil-works/pi-tui": "^0.79.10",
73
+ "@earendil-works/pi-coding-agent": "^0.80.10",
74
+ "@earendil-works/pi-tui": "^0.80.10",
73
75
  "@types/jsdom": "^21.1.7",
74
76
  "@types/node": "^24.0.0",
75
77
  "@vitest/coverage-v8": "^3.2.6",