webfox 4.2.2 → 4.4.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 +21 -1
- package/dist/adapter-RL7JNB22.js +183 -0
- package/dist/{adapter-A7LV6L55.js → adapter-VW4FMMJC.js} +1 -1
- package/dist/application-types.d.ts +1 -0
- package/dist/{chunk-ZBD4DRCY.js → chunk-7KCSWOBG.js} +11 -4
- package/dist/{chunk-QRHDXM2L.js → chunk-BTWECET5.js} +1 -1
- package/dist/{chunk-WVHMWLKU.js → chunk-GPACLYNT.js} +1 -0
- package/dist/chunk-VGVUGOAV.js +244 -0
- package/dist/{chunk-FMYKJ7JC.js → chunk-VYMIR35R.js} +140 -236
- package/dist/cli.js +7 -5
- package/dist/config.schema.json +196 -0
- package/dist/domain.d.ts +1 -1
- package/dist/index.js +6 -4
- package/dist/pi.js +11 -5
- package/dist/providers/contract.d.ts +1 -0
- package/dist/providers/definition.d.ts +4 -0
- package/dist/providers/registry.d.ts +1 -0
- package/dist/providers/serpbase/adapter.d.ts +5 -0
- package/dist/providers/serpbase/definition.d.ts +1 -0
- package/dist/providers/serpbase/types.d.ts +37 -0
- package/docs/provider.md +102 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,11 +25,25 @@ configuration in your terminal, TypeScript applications, and pi.
|
|
|
25
25
|
|
|
26
26
|
## 🚀 Installation
|
|
27
27
|
|
|
28
|
-
- **Terminal:** Install the `web` command:
|
|
28
|
+
- **Terminal:** Install the `web` command with [Homebrew](https://github.com/mavam/homebrew-tap):
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
brew install mavam/tap/webfox
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Or use npm:
|
|
29
35
|
|
|
30
36
|
```sh
|
|
31
37
|
npm install -g webfox
|
|
32
38
|
```
|
|
39
|
+
- **Nix:** Run the CLI directly:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
nix run github:mavam/webfox -- --help
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The flake exports `packages.<system>.default` and `overlays.default` for Linux
|
|
46
|
+
(`aarch64`, `x86_64`) and macOS (Apple Silicon).
|
|
33
47
|
- **TypeScript:** Add `webfox` as an application dependency.
|
|
34
48
|
- **Pi:** Install the extension:
|
|
35
49
|
|
|
@@ -37,6 +51,7 @@ configuration in your terminal, TypeScript applications, and pi.
|
|
|
37
51
|
pi install npm:webfox
|
|
38
52
|
```
|
|
39
53
|
|
|
54
|
+
This installs the extension, not the `web` command on your shell's `PATH`.
|
|
40
55
|
Then follow [Use with Pi](#-use-with-pi) to select a web provider and supply its
|
|
41
56
|
API key.
|
|
42
57
|
|
|
@@ -134,6 +149,7 @@ Use different providers for different tasks:
|
|
|
134
149
|
| [OpenAI] | ✔︎ | | ✔︎ | ✔︎ |
|
|
135
150
|
| [Parallel] | ✔︎ | ✔︎ | | |
|
|
136
151
|
| [Perplexity] | ✔︎ | | ✔︎ | ✔︎ |
|
|
152
|
+
| [SerpBase] | ✔︎ | | | |
|
|
137
153
|
| [Serper] | ✔︎ | | | |
|
|
138
154
|
| [Tavily] | ✔︎ | ✔︎ | | |
|
|
139
155
|
| [Valyu] | ✔︎ | ✔︎ | ✔︎ | ✔︎ |
|
|
@@ -150,6 +166,7 @@ Use different providers for different tasks:
|
|
|
150
166
|
[OpenAI]: ./docs/provider.md#openai
|
|
151
167
|
[Parallel]: ./docs/provider.md#parallel
|
|
152
168
|
[Perplexity]: ./docs/provider.md#perplexity
|
|
169
|
+
[SerpBase]: ./docs/provider.md#serpbase
|
|
153
170
|
[Serper]: ./docs/provider.md#serper
|
|
154
171
|
[Tavily]: ./docs/provider.md#tavily
|
|
155
172
|
[Valyu]: ./docs/provider.md#valyu
|
|
@@ -204,6 +221,9 @@ Install the [Pi](https://pi.dev) extension:
|
|
|
204
221
|
pi install npm:webfox
|
|
205
222
|
```
|
|
206
223
|
|
|
224
|
+
To also use the `web` command in your terminal, follow [Installation](#-installation).
|
|
225
|
+
Both installations share the same Webfox configuration.
|
|
226
|
+
|
|
207
227
|
Add to `~/.config/webfox/config.yaml`:
|
|
208
228
|
|
|
209
229
|
```yaml
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SERPBASE_MODES,
|
|
3
|
+
serpbaseProvider,
|
|
4
|
+
validateOptions
|
|
5
|
+
} from "./chunk-VYMIR35R.js";
|
|
6
|
+
import "./chunk-GPACLYNT.js";
|
|
7
|
+
import {
|
|
8
|
+
trimSnippet
|
|
9
|
+
} from "./chunk-X6ZBIHZC.js";
|
|
10
|
+
import {
|
|
11
|
+
WebfoxError,
|
|
12
|
+
httpError
|
|
13
|
+
} from "./chunk-U4BLULLV.js";
|
|
14
|
+
|
|
15
|
+
// src/providers/serpbase/adapter.ts
|
|
16
|
+
var DEFAULT_BASE_URL = "https://api.serpbase.dev";
|
|
17
|
+
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([1029, 1500, 1502, 1503, 1504]);
|
|
18
|
+
var FEATURE_ID = /^0x[0-9a-f]+:0x[0-9a-f]+$/i;
|
|
19
|
+
var adapter = {
|
|
20
|
+
async search(request, config, context) {
|
|
21
|
+
const options = {
|
|
22
|
+
...request.options,
|
|
23
|
+
mode: request.options?.mode ?? "search"
|
|
24
|
+
};
|
|
25
|
+
validateOptions(serpbaseProvider, "search", options);
|
|
26
|
+
const mode = options.mode;
|
|
27
|
+
const endpoint = SERPBASE_MODES[mode];
|
|
28
|
+
if (mode === "maps-detail" && !FEATURE_ID.test(request.query)) {
|
|
29
|
+
throw new WebfoxError(
|
|
30
|
+
"INVALID_INPUT",
|
|
31
|
+
"SerpBase maps-detail requires a feature_id (0x...:0x...) in each queries entry. First use mode maps and copy the returned feature_id; names, place_id, CID, and Maps URLs are not accepted."
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const apiKey = config.credentials?.api;
|
|
35
|
+
if (!apiKey) throw new Error("SerpBase search is missing an API key");
|
|
36
|
+
const response = await fetch(
|
|
37
|
+
`${(config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "")}/google/${endpoint.path}`,
|
|
38
|
+
{
|
|
39
|
+
method: "POST",
|
|
40
|
+
headers: { "content-type": "application/json", "X-API-Key": apiKey },
|
|
41
|
+
body: JSON.stringify({
|
|
42
|
+
...mode === "maps-detail" ? { feature_id: request.query } : { q: request.query },
|
|
43
|
+
...Object.fromEntries(
|
|
44
|
+
endpoint.options.flatMap(
|
|
45
|
+
(key) => options[key] === void 0 ? [] : [[key, options[key]]]
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
}),
|
|
49
|
+
signal: context.signal
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
const body = await response.text();
|
|
53
|
+
let payload;
|
|
54
|
+
try {
|
|
55
|
+
payload = asRecord(JSON.parse(body));
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
const status = payload?.status;
|
|
59
|
+
const apiFailure = typeof status === "number" && status !== 0;
|
|
60
|
+
if (!response.ok || apiFailure) {
|
|
61
|
+
const detail = (text(payload?.error) ?? body.trim()).slice(0, 500);
|
|
62
|
+
const message = `SerpBase ${mode} request failed (HTTP ${response.status}${apiFailure ? `, status ${status}` : ""})${detail ? `: ${detail}` : "."}`;
|
|
63
|
+
if (apiFailure) {
|
|
64
|
+
throw new WebfoxError("PROVIDER_FAILURE", message, {
|
|
65
|
+
retryable: RETRYABLE_STATUSES.has(status)
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
throw httpError(response, message);
|
|
69
|
+
}
|
|
70
|
+
const collection = payload?.[endpoint.result];
|
|
71
|
+
if (!payload || status !== 0 || collection !== void 0 && (mode === "maps-detail" ? !asRecord(collection) : !Array.isArray(collection))) {
|
|
72
|
+
throw new WebfoxError(
|
|
73
|
+
"PROVIDER_FAILURE",
|
|
74
|
+
`SerpBase returned an invalid ${mode} response.`,
|
|
75
|
+
{ retryable: false }
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
const searchContext = { ...payload };
|
|
79
|
+
for (const { result } of Object.values(SERPBASE_MODES))
|
|
80
|
+
delete searchContext[result];
|
|
81
|
+
const entries = mode === "maps-detail" ? collection === void 0 ? [] : [collection] : collection ?? [];
|
|
82
|
+
const results = entries.map((entry) => toSearchResult(entry, searchContext, mode)).filter((result) => result !== void 0);
|
|
83
|
+
return {
|
|
84
|
+
provider: "serpbase",
|
|
85
|
+
// No upstream count parameter or automatic pagination: one page or place.
|
|
86
|
+
results: results.slice(
|
|
87
|
+
0,
|
|
88
|
+
Math.max(1, Math.trunc(request.maxResults || 0))
|
|
89
|
+
)
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
function toSearchResult(entry, searchContext, mode) {
|
|
94
|
+
const record = asRecord(entry);
|
|
95
|
+
if (!record || mode === "images" && isNavigationImage(record))
|
|
96
|
+
return void 0;
|
|
97
|
+
const isPlace = mode === "maps" || mode === "maps-detail";
|
|
98
|
+
const featureId = text(record.feature_id);
|
|
99
|
+
const url = isPlace ? text(record.google_maps_url) ?? text(record.url) ?? text(record.website) ?? (featureId && FEATURE_ID.test(featureId) ? `https://www.google.com/maps?ftid=${encodeURIComponent(featureId)}` : void 0) : text(record.link) ?? text(record.url) ?? (mode === "images" ? text(record.image_url) : void 0);
|
|
100
|
+
if (!url) return void 0;
|
|
101
|
+
const { title, link: _link, url: _url, snippet, ...metadata } = record;
|
|
102
|
+
return {
|
|
103
|
+
title: text(title) ?? text(record.name) ?? url,
|
|
104
|
+
url,
|
|
105
|
+
snippet: resultSnippet(record, mode),
|
|
106
|
+
metadata: { ...metadata, mode, searchContext }
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function resultSnippet(record, mode) {
|
|
110
|
+
const lines = [];
|
|
111
|
+
const excerpt = text(record.snippet) ?? text(record.short_description) ?? text(record.description);
|
|
112
|
+
if (excerpt && !(mode === "videos" && /^\s*Duration:\s*Posted:\s*$/i.test(excerpt)))
|
|
113
|
+
lines.push(trimSnippet(excerpt));
|
|
114
|
+
const add = (label, value, limit = 600) => {
|
|
115
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
116
|
+
value = String(value);
|
|
117
|
+
if (text(value))
|
|
118
|
+
lines.push(`${label}: ${trimSnippet(value, limit)}`);
|
|
119
|
+
};
|
|
120
|
+
const json = (label, value) => {
|
|
121
|
+
if (value && typeof value === "object")
|
|
122
|
+
add(label, JSON.stringify(value), 1200);
|
|
123
|
+
};
|
|
124
|
+
if (mode === "images") {
|
|
125
|
+
add("Image URL", record.image_url, Infinity);
|
|
126
|
+
add("Source page", text(record.link) ?? text(record.url), Infinity);
|
|
127
|
+
add(
|
|
128
|
+
"Thumbnail",
|
|
129
|
+
text(record.thumbnail_url) ?? text(record.thumbnail),
|
|
130
|
+
Infinity
|
|
131
|
+
);
|
|
132
|
+
add("Source", text(record.source) ?? text(record.domain));
|
|
133
|
+
} else if (mode === "news" || mode === "videos") {
|
|
134
|
+
add("Source", record.source);
|
|
135
|
+
const published = text(record.published_at) ?? text(record.time);
|
|
136
|
+
add(
|
|
137
|
+
published && /^\d{1,3}:\d{2}(?::\d{2})?$/.test(published.trim()) ? "Time (upstream)" : "Published",
|
|
138
|
+
published
|
|
139
|
+
);
|
|
140
|
+
if (mode === "videos") add("Duration", record.duration);
|
|
141
|
+
add(
|
|
142
|
+
"Thumbnail",
|
|
143
|
+
text(record.thumbnail_url) ?? text(record.thumbnail),
|
|
144
|
+
Infinity
|
|
145
|
+
);
|
|
146
|
+
} else if (mode === "maps" || mode === "maps-detail") {
|
|
147
|
+
add("feature_id", record.feature_id, Infinity);
|
|
148
|
+
add("Address", record.address);
|
|
149
|
+
add("Rating", record.rating);
|
|
150
|
+
add("Reviews", record.review_count);
|
|
151
|
+
add("Phone", text(record.phone_international) ?? text(record.phone));
|
|
152
|
+
add("Website", record.website, Infinity);
|
|
153
|
+
if (typeof record.latitude === "number" && typeof record.longitude === "number")
|
|
154
|
+
add("Coordinates", `${record.latitude}, ${record.longitude}`);
|
|
155
|
+
if (Array.isArray(record.types))
|
|
156
|
+
add(
|
|
157
|
+
"Categories",
|
|
158
|
+
record.types.filter((item) => typeof item === "string").join(", ")
|
|
159
|
+
);
|
|
160
|
+
json("Hours", record.hours);
|
|
161
|
+
json("Open status", record.open_status);
|
|
162
|
+
if (mode === "maps-detail") json("Attributes", record.attributes);
|
|
163
|
+
}
|
|
164
|
+
return lines.join("\n");
|
|
165
|
+
}
|
|
166
|
+
function isNavigationImage(record) {
|
|
167
|
+
try {
|
|
168
|
+
const image = new URL(String(record.image_url));
|
|
169
|
+
const source = new URL(String(record.link ?? record.url));
|
|
170
|
+
return image.hostname === "www.gstatic.com" && image.pathname === "/m/images/icons/googleg.gif" && (source.hostname === "www.google.com" || source.hostname === "google.com") && (source.pathname === "/" || source.pathname === "/search");
|
|
171
|
+
} catch {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function asRecord(value) {
|
|
176
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
177
|
+
}
|
|
178
|
+
function text(value) {
|
|
179
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
180
|
+
}
|
|
181
|
+
export {
|
|
182
|
+
adapter
|
|
183
|
+
};
|
|
@@ -5,20 +5,22 @@ import {
|
|
|
5
5
|
withSignal
|
|
6
6
|
} from "./chunk-WCTOYZVB.js";
|
|
7
7
|
import {
|
|
8
|
-
configurationSchema
|
|
8
|
+
configurationSchema
|
|
9
|
+
} from "./chunk-VGVUGOAV.js";
|
|
10
|
+
import {
|
|
9
11
|
effectiveOptions,
|
|
10
12
|
optionSchema,
|
|
11
13
|
providers,
|
|
12
14
|
selectProvider,
|
|
13
15
|
validateConfiguredOptions
|
|
14
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-VYMIR35R.js";
|
|
15
17
|
import {
|
|
16
18
|
cancelProcessGroup,
|
|
17
19
|
killProcessGroup
|
|
18
20
|
} from "./chunk-ZVTVERW5.js";
|
|
19
21
|
import {
|
|
20
22
|
CAPABILITIES
|
|
21
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-GPACLYNT.js";
|
|
22
24
|
import {
|
|
23
25
|
WebfoxError,
|
|
24
26
|
asWebfoxError
|
|
@@ -361,7 +363,7 @@ var ExecutionRuntime = class {
|
|
|
361
363
|
credentials;
|
|
362
364
|
async execute(plan, request) {
|
|
363
365
|
const { capability, definition, inputs } = plan;
|
|
364
|
-
const timeoutMs = request.timeoutMs ?? (capability === "research" ? plan.policy.researchTimeoutMs
|
|
366
|
+
const timeoutMs = request.timeoutMs ?? (capability === "research" ? plan.policy.researchTimeoutMs : plan.policy.timeoutMs) ?? definition.capabilities[capability]?.defaultTimeoutMs ?? (capability === "research" ? 18e5 : 3e4);
|
|
365
367
|
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2147483647)
|
|
366
368
|
throw new WebfoxError(
|
|
367
369
|
"INVALID_INPUT",
|
|
@@ -689,6 +691,11 @@ function createWebfox(options = {}) {
|
|
|
689
691
|
capability,
|
|
690
692
|
provider: selected,
|
|
691
693
|
configured: configured(definition, capability),
|
|
694
|
+
...definition.capabilities[capability]?.promptGuidelines ? {
|
|
695
|
+
promptGuidelines: [
|
|
696
|
+
...definition.capabilities[capability].promptGuidelines
|
|
697
|
+
]
|
|
698
|
+
} : {},
|
|
692
699
|
// Static provider schemas contain no credentials. Redacting property
|
|
693
700
|
// names such as maximum_number_of_tokens would invalidate the schema.
|
|
694
701
|
optionSchema: optionSchema(
|
|
@@ -9,7 +9,7 @@ function renderTextDocument(document, { includeErrors = true } = {}) {
|
|
|
9
9
|
body = entry.value.results.length ? entry.value.results.map(
|
|
10
10
|
(result, i) => `${i + 1}. ${result.title}
|
|
11
11
|
${result.url}
|
|
12
|
-
${result.snippet}`
|
|
12
|
+
${result.snippet.replaceAll("\n", "\n ")}`
|
|
13
13
|
).join("\n\n") : "No results found.";
|
|
14
14
|
else if ("text" in entry.value) body = entry.value.text;
|
|
15
15
|
else body = renderContents(entry.value);
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import {
|
|
2
|
+
optionSchema,
|
|
3
|
+
providers
|
|
4
|
+
} from "./chunk-VYMIR35R.js";
|
|
5
|
+
import {
|
|
6
|
+
CAPABILITIES
|
|
7
|
+
} from "./chunk-GPACLYNT.js";
|
|
8
|
+
|
|
9
|
+
// package.json
|
|
10
|
+
var package_default = {
|
|
11
|
+
name: "webfox",
|
|
12
|
+
version: "4.4.0",
|
|
13
|
+
description: "Search the web, extract pages, get grounded answers, and run deep research with the web CLI, a TypeScript library, and a pi extension. Bring your own providers and API keys.",
|
|
14
|
+
type: "module",
|
|
15
|
+
files: [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"assets/webfox-lockup.png",
|
|
19
|
+
"assets/webfox-lockup-dark.png",
|
|
20
|
+
"docs/cli-experience.md",
|
|
21
|
+
"docs/provider.md",
|
|
22
|
+
"docs/reference.md",
|
|
23
|
+
"LICENSE",
|
|
24
|
+
"example-config.yaml",
|
|
25
|
+
"examples"
|
|
26
|
+
],
|
|
27
|
+
keywords: [
|
|
28
|
+
"webfox",
|
|
29
|
+
"web-cli",
|
|
30
|
+
"typescript-library",
|
|
31
|
+
"pi-package",
|
|
32
|
+
"pi-extension",
|
|
33
|
+
"coding-agent",
|
|
34
|
+
"web-search",
|
|
35
|
+
"web-contents",
|
|
36
|
+
"web-answer",
|
|
37
|
+
"web-research",
|
|
38
|
+
"provider-routing",
|
|
39
|
+
"brave",
|
|
40
|
+
"cloudflare",
|
|
41
|
+
"custom",
|
|
42
|
+
"exa",
|
|
43
|
+
"firecrawl",
|
|
44
|
+
"gemini",
|
|
45
|
+
"linkup",
|
|
46
|
+
"ollama",
|
|
47
|
+
"openai",
|
|
48
|
+
"parallel",
|
|
49
|
+
"perplexity",
|
|
50
|
+
"serper",
|
|
51
|
+
"tavily",
|
|
52
|
+
"valyu"
|
|
53
|
+
],
|
|
54
|
+
author: "mavam",
|
|
55
|
+
license: "MIT",
|
|
56
|
+
repository: {
|
|
57
|
+
type: "git",
|
|
58
|
+
url: "git+https://github.com/mavam/webfox.git"
|
|
59
|
+
},
|
|
60
|
+
engines: {
|
|
61
|
+
node: ">=22.0.0"
|
|
62
|
+
},
|
|
63
|
+
publishConfig: {
|
|
64
|
+
access: "public"
|
|
65
|
+
},
|
|
66
|
+
pi: {
|
|
67
|
+
extensions: [
|
|
68
|
+
"./dist/pi.js"
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
bin: {
|
|
72
|
+
web: "./dist/cli.js"
|
|
73
|
+
},
|
|
74
|
+
main: "./dist/index.js",
|
|
75
|
+
types: "./dist/index.d.ts",
|
|
76
|
+
exports: {
|
|
77
|
+
".": {
|
|
78
|
+
types: "./dist/index.d.ts",
|
|
79
|
+
import: "./dist/index.js"
|
|
80
|
+
},
|
|
81
|
+
"./pi": {
|
|
82
|
+
types: "./dist/pi.d.ts",
|
|
83
|
+
import: "./dist/pi.js"
|
|
84
|
+
},
|
|
85
|
+
"./config.schema.json": "./dist/config.schema.json"
|
|
86
|
+
},
|
|
87
|
+
scripts: {
|
|
88
|
+
build: "rm -rf dist && esbuild index=src/index.ts cli=src/cli.ts pi=src/pi.ts generate-schema=scripts/generate-schema.ts --bundle --splitting --format=esm --platform=node --target=node22 --packages=external --outdir=dist && node dist/generate-schema.js && biome format --write src/config.schema.json >&2 && rm dist/generate-schema.js && tsc --project tsconfig.build.json && cp src/config.schema.json dist/config.schema.json && chmod +x dist/cli.js",
|
|
89
|
+
prepare: "npm run build",
|
|
90
|
+
prepack: "npm run build",
|
|
91
|
+
check: "tsc --noEmit",
|
|
92
|
+
format: "biome format --write .",
|
|
93
|
+
"format:check": "biome format .",
|
|
94
|
+
"smoke:live": "npm run build && node scripts/live-smoke.mjs",
|
|
95
|
+
"smoke:package": "node scripts/package-smoke.mjs",
|
|
96
|
+
test: "vitest run",
|
|
97
|
+
"test:watch": "vitest"
|
|
98
|
+
},
|
|
99
|
+
dependencies: {
|
|
100
|
+
"@mendable/firecrawl-js": "^4.38.0",
|
|
101
|
+
"@perplexity-ai/perplexity_ai": "^0.38.5",
|
|
102
|
+
"@tavily/core": "^0.7.9",
|
|
103
|
+
cloudflare: "^7.1.0",
|
|
104
|
+
commander: "^14.0.3",
|
|
105
|
+
"exa-js": "^2.19.0",
|
|
106
|
+
"linkup-sdk": "^3.6.0",
|
|
107
|
+
openai: "^7.10.0",
|
|
108
|
+
"parallel-web": "^1.3.3",
|
|
109
|
+
picocolors: "^1.1.1",
|
|
110
|
+
typebox: "^1.3.6",
|
|
111
|
+
"valyu-js": "^2.10.1",
|
|
112
|
+
yaml: "^2.9.0"
|
|
113
|
+
},
|
|
114
|
+
devDependencies: {
|
|
115
|
+
"@biomejs/biome": "^2.5.4",
|
|
116
|
+
"@earendil-works/pi-ai": "^0.85.1",
|
|
117
|
+
"@earendil-works/pi-coding-agent": "^0.85.1",
|
|
118
|
+
"@earendil-works/pi-tui": "^0.85.1",
|
|
119
|
+
"@types/node": "^26.1.1",
|
|
120
|
+
esbuild: "^0.28.1",
|
|
121
|
+
typescript: "^7.0.2",
|
|
122
|
+
vitest: "^4.1.10"
|
|
123
|
+
},
|
|
124
|
+
peerDependencies: {
|
|
125
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
126
|
+
"@earendil-works/pi-tui": "*"
|
|
127
|
+
},
|
|
128
|
+
peerDependenciesMeta: {
|
|
129
|
+
"@earendil-works/pi-coding-agent": {
|
|
130
|
+
optional: true
|
|
131
|
+
},
|
|
132
|
+
"@earendil-works/pi-tui": {
|
|
133
|
+
optional: true
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// src/package-metadata.ts
|
|
139
|
+
var PACKAGE_NAME = package_default.name;
|
|
140
|
+
var PACKAGE_VERSION = package_default.version;
|
|
141
|
+
var CONFIG_SCHEMA_URL = `https://unpkg.com/${PACKAGE_NAME}@latest/dist/config.schema.json`;
|
|
142
|
+
|
|
143
|
+
// src/configuration/schema.ts
|
|
144
|
+
var text = { type: "string", minLength: 1 };
|
|
145
|
+
var object = (properties) => ({
|
|
146
|
+
type: "object",
|
|
147
|
+
additionalProperties: false,
|
|
148
|
+
properties
|
|
149
|
+
});
|
|
150
|
+
var source = {
|
|
151
|
+
oneOf: ["env", "value", "command"].map((key) => ({
|
|
152
|
+
...object({
|
|
153
|
+
[key]: key === "command" ? { type: "array", minItems: 1, items: text } : text
|
|
154
|
+
}),
|
|
155
|
+
required: [key]
|
|
156
|
+
}))
|
|
157
|
+
};
|
|
158
|
+
var sourceMap = { type: "object", additionalProperties: source };
|
|
159
|
+
var command = {
|
|
160
|
+
...object({
|
|
161
|
+
argv: { type: "array", minItems: 1, items: text },
|
|
162
|
+
cwd: text,
|
|
163
|
+
env: sourceMap
|
|
164
|
+
}),
|
|
165
|
+
required: ["argv"]
|
|
166
|
+
};
|
|
167
|
+
var positive = { type: "integer", minimum: 1 };
|
|
168
|
+
var timeout = { ...positive, maximum: 2147483647 };
|
|
169
|
+
var fields = {
|
|
170
|
+
credentials: sourceMap,
|
|
171
|
+
baseUrl: text,
|
|
172
|
+
accountId: source,
|
|
173
|
+
commands: object(
|
|
174
|
+
Object.fromEntries(CAPABILITIES.map((capability) => [capability, command]))
|
|
175
|
+
)
|
|
176
|
+
};
|
|
177
|
+
var configurationSchema = {
|
|
178
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
179
|
+
$id: CONFIG_SCHEMA_URL,
|
|
180
|
+
title: "webfox configuration",
|
|
181
|
+
...object({
|
|
182
|
+
$schema: text,
|
|
183
|
+
defaults: object(
|
|
184
|
+
Object.fromEntries(
|
|
185
|
+
CAPABILITIES.map((capability) => [
|
|
186
|
+
capability,
|
|
187
|
+
object({
|
|
188
|
+
provider: {
|
|
189
|
+
enum: Object.values(providers).filter((provider) => provider.capabilities[capability]).map((provider) => provider.id)
|
|
190
|
+
},
|
|
191
|
+
...capability === "search" ? { maxResults: positive } : {}
|
|
192
|
+
})
|
|
193
|
+
])
|
|
194
|
+
)
|
|
195
|
+
),
|
|
196
|
+
execution: object({
|
|
197
|
+
timeoutMs: timeout,
|
|
198
|
+
researchTimeoutMs: timeout,
|
|
199
|
+
retries: { type: "integer", minimum: 0 },
|
|
200
|
+
retryDelayMs: { type: "integer", minimum: 0 },
|
|
201
|
+
concurrency: positive
|
|
202
|
+
}),
|
|
203
|
+
providers: object(
|
|
204
|
+
Object.fromEntries(
|
|
205
|
+
Object.values(providers).map((provider) => [
|
|
206
|
+
provider.id,
|
|
207
|
+
object(
|
|
208
|
+
Object.fromEntries(
|
|
209
|
+
provider.fields.map((field) => [
|
|
210
|
+
field,
|
|
211
|
+
field === "options" ? object(
|
|
212
|
+
Object.fromEntries(
|
|
213
|
+
CAPABILITIES.filter(
|
|
214
|
+
(capability) => provider.capabilities[capability]
|
|
215
|
+
).map((capability) => [
|
|
216
|
+
capability,
|
|
217
|
+
optionSchema(provider, capability, "defaults") ?? {
|
|
218
|
+
type: "object",
|
|
219
|
+
additionalProperties: provider.id === "custom"
|
|
220
|
+
}
|
|
221
|
+
])
|
|
222
|
+
)
|
|
223
|
+
) : field === "credentials" ? object(
|
|
224
|
+
Object.fromEntries(
|
|
225
|
+
provider.credentials.map((credential) => [
|
|
226
|
+
credential.name,
|
|
227
|
+
source
|
|
228
|
+
])
|
|
229
|
+
)
|
|
230
|
+
) : fields[field]
|
|
231
|
+
])
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
])
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
})
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
export {
|
|
241
|
+
PACKAGE_VERSION,
|
|
242
|
+
CONFIG_SCHEMA_URL,
|
|
243
|
+
configurationSchema
|
|
244
|
+
};
|