pi-web-scout 0.1.0 → 0.1.1
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 +18 -0
- package/SECURITY.md +1 -1
- package/package.json +1 -1
- package/src/format.ts +2 -1
- package/src/providers/jina.ts +4 -1
- package/src/tool.ts +27 -4
package/README.md
CHANGED
|
@@ -10,6 +10,12 @@ No-key web search extension for [Pi](https://pi.dev). It registers a `web_search
|
|
|
10
10
|
- No credential command execution.
|
|
11
11
|
- Provider architecture ready for future keyed APIs such as Brave, Serper, Tavily, Exa, etc.
|
|
12
12
|
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pi install npm:pi-web-scout
|
|
17
|
+
```
|
|
18
|
+
|
|
13
19
|
## Try locally
|
|
14
20
|
|
|
15
21
|
```bash
|
|
@@ -89,6 +95,18 @@ The extension currently reads project config only and does not write config file
|
|
|
89
95
|
|
|
90
96
|
`mode: "combine"` queries all enabled providers in the fallback chain, deduplicates URLs, and ranks repeated results higher with a simple reciprocal-rank score.
|
|
91
97
|
|
|
98
|
+
## Compatibility aliases
|
|
99
|
+
|
|
100
|
+
`web_search` accepts these argument aliases:
|
|
101
|
+
|
|
102
|
+
- `maxResults` → `max_results`
|
|
103
|
+
- `provider: "ddg"` → `"duckduckgo"`
|
|
104
|
+
- `mode: "first-success"` → `"first_success"`
|
|
105
|
+
|
|
106
|
+
`web_read` accepts:
|
|
107
|
+
|
|
108
|
+
- `maxChars` → `max_chars`
|
|
109
|
+
|
|
92
110
|
## Security notes
|
|
93
111
|
|
|
94
112
|
This package intentionally avoids:
|
package/SECURITY.md
CHANGED
|
@@ -37,7 +37,7 @@ PI_WEB_SCOUT_BRAVE_API_KEY
|
|
|
37
37
|
|
|
38
38
|
## SSRF notes
|
|
39
39
|
|
|
40
|
-
`web_read` blocks common local/private/metadata targets and validates every redirect hop. It does not perform custom DNS resolution, so DNS rebinding protection is best-effort.
|
|
40
|
+
`web_read` blocks common local/private/metadata targets and validates every redirect hop. It does not perform custom DNS resolution, so DNS rebinding protection is best-effort and DNS rebinding is not fully blocked.
|
|
41
41
|
|
|
42
42
|
## Reporting
|
|
43
43
|
|
package/package.json
CHANGED
package/src/format.ts
CHANGED
|
@@ -13,7 +13,8 @@ export function formatSearchResults(
|
|
|
13
13
|
lines.push("", "Provider runs:");
|
|
14
14
|
for (const run of runs) {
|
|
15
15
|
const status = run.error ? `failed: ${run.error}` : `${run.results.length} result${run.results.length === 1 ? "" : "s"}`;
|
|
16
|
-
|
|
16
|
+
const suffix = run.keySource ? ` (${run.keySource})` : "";
|
|
17
|
+
lines.push(`- ${run.provider}: ${status}${suffix}`);
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
lines.push("");
|
package/src/providers/jina.ts
CHANGED
|
@@ -28,7 +28,10 @@ export const jinaProvider: SearchProvider = {
|
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
if (!response.ok) {
|
|
31
|
-
|
|
31
|
+
const hint = response.status === 401 || response.status === 403
|
|
32
|
+
? " (Jina may require an API key or may be rate-limiting no-key requests)"
|
|
33
|
+
: "";
|
|
34
|
+
throw new Error(`Jina returned HTTP ${response.status}${hint}`);
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
return parseJinaResponse(await response.json(), request.maxResults);
|
package/src/tool.ts
CHANGED
|
@@ -12,20 +12,39 @@ import type { ProviderId, ProviderRunResult, ResolvedConfig, SearchMode, SearchR
|
|
|
12
12
|
const PROVIDERS = ["auto", "duckduckgo", "marginalia", "jina", "brave"] as const;
|
|
13
13
|
const MODES = ["first_success", "combine"] as const;
|
|
14
14
|
|
|
15
|
+
function normalizeSearchArgs(args: unknown): unknown {
|
|
16
|
+
if (!args || typeof args !== "object" || Array.isArray(args)) return args;
|
|
17
|
+
const next = { ...(args as Record<string, unknown>) };
|
|
18
|
+
if (next.max_results === undefined && next.maxResults !== undefined) next.max_results = next.maxResults;
|
|
19
|
+
if (next.provider === "ddg") next.provider = "duckduckgo";
|
|
20
|
+
if (next.mode === "first-success") next.mode = "first_success";
|
|
21
|
+
return next;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeReadArgs(args: unknown): unknown {
|
|
25
|
+
if (!args || typeof args !== "object" || Array.isArray(args)) return args;
|
|
26
|
+
const next = { ...(args as Record<string, unknown>) };
|
|
27
|
+
if (next.max_chars === undefined && next.maxChars !== undefined) next.max_chars = next.maxChars;
|
|
28
|
+
return next;
|
|
29
|
+
}
|
|
30
|
+
|
|
15
31
|
export function registerWebSearchTool(pi: ExtensionAPI): void {
|
|
16
32
|
registerStatusCommand(pi);
|
|
17
33
|
|
|
18
34
|
pi.registerTool({
|
|
19
35
|
name: "web_search",
|
|
20
|
-
label: "Web Scout Search",
|
|
36
|
+
label: "Pi Web Scout Search",
|
|
21
37
|
description:
|
|
22
|
-
"
|
|
38
|
+
"pi-web-scout: search the web using no-key search providers. Defaults to DuckDuckGo HTML and is architected for future keyed providers.",
|
|
23
39
|
promptSnippet: "web_search: search the public web without requiring API keys.",
|
|
24
40
|
promptGuidelines: [
|
|
25
41
|
"Use web_search when current or source-backed information is needed.",
|
|
26
42
|
"Prefer web_search over guessing facts that may have changed recently.",
|
|
27
43
|
"Use web_search with mode=combine when broad coverage matters more than speed.",
|
|
28
44
|
],
|
|
45
|
+
prepareArguments(args) {
|
|
46
|
+
return normalizeSearchArgs(args);
|
|
47
|
+
},
|
|
29
48
|
parameters: Type.Object({
|
|
30
49
|
query: Type.String({ description: "Search query" }),
|
|
31
50
|
max_results: Type.Optional(Type.Number({ description: "Number of results, 1-20. Default comes from config or 5." })),
|
|
@@ -68,6 +87,7 @@ export function registerWebSearchTool(pi: ExtensionAPI): void {
|
|
|
68
87
|
provider: run.provider,
|
|
69
88
|
resultCount: run.results.length,
|
|
70
89
|
error: run.error,
|
|
90
|
+
keySource: run.keySource,
|
|
71
91
|
})),
|
|
72
92
|
results: runResult.results,
|
|
73
93
|
},
|
|
@@ -77,13 +97,16 @@ export function registerWebSearchTool(pi: ExtensionAPI): void {
|
|
|
77
97
|
|
|
78
98
|
pi.registerTool({
|
|
79
99
|
name: "web_read",
|
|
80
|
-
label: "Web Read",
|
|
81
|
-
description: "
|
|
100
|
+
label: "Pi Web Scout Read",
|
|
101
|
+
description: "pi-web-scout: fetch a public HTTP(S) URL and extract readable text with SSRF protection. No browser or JavaScript execution.",
|
|
82
102
|
promptSnippet: "web_read: read and extract text from a specific public URL.",
|
|
83
103
|
promptGuidelines: [
|
|
84
104
|
"Use web_read after web_search when snippets are insufficient and a source needs to be read.",
|
|
85
105
|
"Do not use web_read for localhost, private network, or metadata URLs; those are blocked.",
|
|
86
106
|
],
|
|
107
|
+
prepareArguments(args) {
|
|
108
|
+
return normalizeReadArgs(args);
|
|
109
|
+
},
|
|
87
110
|
parameters: Type.Object({
|
|
88
111
|
url: Type.String({ description: "Public HTTP(S) URL to read" }),
|
|
89
112
|
max_chars: Type.Optional(Type.Number({ description: "Maximum extracted characters, 1000-50000. Default 12000." })),
|