pi-chrome-search 0.1.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.
- package/README.md +52 -76
- package/assets/anchor.html +2 -2
- package/package.json +7 -3
- package/src/cdp/bytes.ts +14 -14
- package/src/cdp/client.ts +687 -0
- package/src/cdp/page.ts +716 -0
- package/src/cdp/readiness.ts +119 -0
- package/src/cdp/shared.ts +70 -0
- package/src/cdp/types.ts +30 -1
- package/src/cli/doctor.ts +14 -17
- package/src/commands/focus.ts +2 -2
- package/src/config/defaults.ts +177 -0
- package/src/config/doc.ts +183 -0
- package/src/config/env.ts +58 -0
- package/src/config/index.ts +10 -0
- package/src/config/load.ts +40 -0
- package/src/config/schema.ts +266 -0
- package/src/engines/avito.ts +552 -0
- package/src/engines/duckduckgo.ts +7 -10
- package/src/engines/google.ts +98 -23
- package/src/engines/images.ts +1 -1
- package/src/engines/index.ts +17 -16
- package/src/engines/types.ts +90 -8
- package/src/engines/yandex.ts +40 -21
- package/src/extract/claims.ts +1 -1
- package/src/extract/compact.ts +88 -6
- package/src/extract/facts.ts +39 -0
- package/src/extract/goto.ts +124 -2
- package/src/extract/text.ts +43 -0
- package/src/index.ts +52 -80
- package/src/runtime/artifacts.ts +6 -11
- package/src/runtime/attention.ts +2 -2
- package/src/runtime/budget.ts +142 -0
- package/src/runtime/cache.ts +1 -3
- package/src/runtime/doctor.ts +43 -46
- package/src/runtime/error-codes.ts +19 -8
- package/src/runtime/errors-doc.ts +45 -0
- package/src/runtime/limiter.ts +38 -226
- package/src/runtime/log.ts +2 -2
- package/src/runtime/panel.ts +32 -6
- package/src/runtime/types.ts +2 -17
- package/src/{cdp/discovery.ts → session/discover.ts} +3 -3
- package/src/session/index.ts +34 -0
- package/src/session/lock.ts +346 -0
- package/src/{cdp → session}/page-lock.ts +12 -13
- package/src/session/session.ts +684 -0
- package/src/session/tabs.ts +277 -0
- package/src/tools/image_search.ts +21 -7
- package/src/tools/index.ts +18 -7
- package/src/tools/surface.ts +97 -0
- package/src/tools/types.ts +2 -5
- package/src/tools/web_doctor.ts +2 -2
- package/src/tools/web_read.ts +159 -114
- package/src/tools/web_research.ts +33 -207
- package/src/tools/web_search.ts +246 -425
- package/src/version.ts +12 -3
- package/src/cdp/browser.ts +0 -1286
- package/src/cdp/session.ts +0 -1446
- package/src/config.ts +0 -651
- package/src/reflex/classifier.ts +0 -1578
- package/src/reflex/index.ts +0 -172
- package/src/reflex/policy.ts +0 -792
- package/src/reflex/rules.ts +0 -557
- package/src/reflex/types.ts +0 -117
package/README.md
CHANGED
|
@@ -1,90 +1,66 @@
|
|
|
1
1
|
# pi-chrome-search
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/pi-chrome-search)
|
|
4
|
+
[](https://www.npmjs.com/package/pi-chrome-search)
|
|
5
|
+
[](https://pi.dev/packages)
|
|
6
|
+
[](https://github.com/sh2nk/pi-chrome-search/actions/workflows/ci.yml)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](package.json)
|
|
9
|
+
|
|
10
|
+
Web search and page reading for [pi](https://pi.dev), through your own visible Chrome. Same profile,
|
|
11
|
+
cookies and IP. Google, DuckDuckGo and Yandex return compact Markdown and their native AI summary when
|
|
12
|
+
there is one; Avito listings (price, location, age) are available opt-in. No keys, no paid APIs, no
|
|
13
|
+
headless mode, no fingerprint spoofing. Node 26+, Chrome 136+.
|
|
9
14
|
|
|
10
15
|
## Install
|
|
11
|
-
Node ≥ 26 (native TypeScript, no build step), Chrome 136+ (tested on 157, Wayland), pi, a headed desktop
|
|
12
|
-
session. Then `/web-doctor` inside pi must report a live CDP endpoint and `navigator.webdriver === false`.
|
|
13
16
|
|
|
14
17
|
```bash
|
|
15
|
-
|
|
16
|
-
pi install . # register this checkout as a pi package
|
|
17
|
-
pi -e ./src/index.ts # …or load it for one throwaway session
|
|
18
|
+
pi install npm:pi-chrome-search
|
|
18
19
|
```
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
Chrome 136+ ignores `--remote-debugging-port` for the default profile; debugging is enabled from inside
|
|
22
|
-
the browser and needs an explicit **Allow** click.
|
|
21
|
+
Chrome 136+ enables debugging only after an explicit click:
|
|
23
22
|
|
|
24
|
-
1. Open `chrome://inspect/#remote-debugging
|
|
25
|
-
2. Tick **Discover network targets**, then click **Allow
|
|
26
|
-
3. Check the endpoint file (line 1 = port, line 2 = `/devtools/browser/<uuid>`):
|
|
27
|
-
`cat ~/.config/google-chrome/DevToolsActivePort`
|
|
23
|
+
1. Open `chrome://inspect/#remote-debugging`.
|
|
24
|
+
2. Tick **Discover network targets**, then click **Allow**.
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
Discovery order: `PI_CHROME_CDP_WS` → `PI_CHROME_CDP_PORTS` → `DevToolsActivePort` → `cdp.ports`. The
|
|
31
|
-
extension never launches, restarts or kills Chrome, never edits your profile, never closes foreign tabs.
|
|
26
|
+
The window stays visible. The extension attaches to it and never launches, restarts or closes Chrome.
|
|
32
27
|
|
|
33
28
|
## Tools
|
|
34
|
-
`web_search` is `direct`; everything else is `deferred` (tool search + `codemode`, not paid per prompt).
|
|
35
|
-
All descriptions together stay under 900 characters.
|
|
36
29
|
|
|
30
|
+
`web_search` and `web_read` are direct. The other three sit behind `tool_search`.
|
|
31
|
+
|
|
32
|
+
<!-- tools:start -->
|
|
37
33
|
| Tool | Exposure | Arguments |
|
|
38
34
|
|---|---|---|
|
|
39
|
-
| `web_search` | direct | `q
|
|
40
|
-
| `web_read` |
|
|
41
|
-
| `image_search` | deferred | `q
|
|
42
|
-
| `web_research` | deferred | `q
|
|
43
|
-
| `web_doctor` | deferred | `probe
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
`engines
|
|
52
|
-
|
|
53
|
-
`
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
artifacts under `~/.pi/agent/web-search/`.
|
|
72
|
-
|
|
73
|
-
## Security and ToS
|
|
74
|
-
Your session, your cookies: searches and reads happen in your browser as you. `web_read` can open pages
|
|
75
|
-
you are logged into but never exfiltrates content, and it refuses loopback/private/`file:`/`data:` URLs
|
|
76
|
-
(re-checked on redirect). No telemetry; artifacts are sanitized and git-ignored. CDP is powerful — any
|
|
77
|
-
local process that reaches the debugging port can drive the browser; it binds loopback only and you
|
|
78
|
-
enable it explicitly. The optional page gate sends only URL/title/snippet of ≤10 sources (never page
|
|
79
|
-
text) to your configured classifier; `reflex.localOnly: true` keeps it local.
|
|
80
|
-
|
|
81
|
-
Google and Yandex prohibit automated access in their terms: this project does not claim compliance and
|
|
82
|
-
must not be run as a service. If an engine shows a captcha, solve it in the window or stop.
|
|
83
|
-
|
|
84
|
-
## Development
|
|
85
|
-
`npm run lint` (typecheck + audit) · `npm test` (offline contract suite) · `npm run test:live` (opt-in,
|
|
86
|
-
one query per engine, no retries, `PI_CHROME_LIVE=1`). Decisions: [`docs/architecture.md`](docs/architecture.md)
|
|
87
|
-
· selectors: [`docs/engines.md`](docs/engines.md). Never launch a browser from tests; never loop probes.
|
|
88
|
-
|
|
89
|
-
## License
|
|
90
|
-
MIT — see [`LICENSE`](LICENSE).
|
|
35
|
+
| `web_search` | direct | `q` string; `n?` integer 1..10; `engines?` "auto" or ("google" or "duckduckgo" or "yandex" or "avito") or list of ("google" or "duckduckgo" or "yandex" or "avito") 1..4; `summary?` boolean; `page?` integer >=1; `format?` "md" or "json" |
|
|
36
|
+
| `web_read` | direct | `url` string; `offset?` integer >=0; `maxChars?` integer >=1; `format?` "md" or "text" |
|
|
37
|
+
| `image_search` | deferred | `q` string; `n?` integer 1..12; `engine?` "google" or "yandex"; `attach?` boolean or "bytes" |
|
|
38
|
+
| `web_research` | deferred | `q` string; `budget?` "quick" or "normal" or "deep" |
|
|
39
|
+
| `web_doctor` | deferred | `probe?` boolean; `fix?` boolean |
|
|
40
|
+
<!-- tools:end -->
|
|
41
|
+
|
|
42
|
+
A call has one 60 second deadline. DuckDuckGo has no pagination: `page > 1` is reported as
|
|
43
|
+
`ignored.page`; Avito has no AI block: `summary` is reported as `ignored.summary`. Every unresolved
|
|
44
|
+
link stays marked `unresolved`, and card facts (price/availability/rating/location) render on their
|
|
45
|
+
own line after the snippet.
|
|
46
|
+
|
|
47
|
+
`engines: "auto"` only ever means Google → DuckDuckGo → Yandex. Avito is a *special* engine: it is off
|
|
48
|
+
by default and reachable only by naming it. To turn it on, set `engines.avito.enabled` to `true` (and
|
|
49
|
+
optionally `engines.avito.location`, a region slug such as `kaluga` or `all`) in
|
|
50
|
+
`~/.pi/agent/web-search.json` — ask your agent to do it, that is the intended path.
|
|
51
|
+
|
|
52
|
+
## Limits
|
|
53
|
+
|
|
54
|
+
No headless browsing, automation profile, captcha solving, proxy rotation, cookie export, telemetry or
|
|
55
|
+
paid search API. Google and Yandex forbid automated access in their terms. If a captcha appears, solve it
|
|
56
|
+
in the window or stop.
|
|
57
|
+
|
|
58
|
+
## Failures
|
|
59
|
+
|
|
60
|
+
Every failure is an `E_*` code with a stage, a retry flag and a hint you can act on. The full table is in
|
|
61
|
+
[errors.md](https://github.com/sh2nk/pi-chrome-search/blob/main/docs/errors.md). Start with
|
|
62
|
+
`/web-doctor` or `node src/cli/doctor.ts --json`.
|
|
63
|
+
|
|
64
|
+
Config reference: [config.md](https://github.com/sh2nk/pi-chrome-search/blob/main/docs/config.md).
|
|
65
|
+
|
|
66
|
+
MIT.
|
package/assets/anchor.html
CHANGED
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
</section>
|
|
84
84
|
|
|
85
85
|
<section class="card">
|
|
86
|
-
<h2>
|
|
86
|
+
<h2>Session counters</h2>
|
|
87
87
|
<table>
|
|
88
88
|
<tbody>
|
|
89
89
|
<tr><td>google</td><td class="num" id="count-google">—</td></tr>
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
var cell = counters[entry.engine];
|
|
136
136
|
if (!cell) continue;
|
|
137
137
|
seen[entry.engine] = true;
|
|
138
|
-
cell.textContent = String(entry.today)
|
|
138
|
+
cell.textContent = String(entry.today);
|
|
139
139
|
}
|
|
140
140
|
for (var name in counters) {
|
|
141
141
|
if (!seen[name] && counters[name]) counters[name].textContent = "—";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-chrome-search",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Free, keyless web search through the user's own visible Chrome (Google, DuckDuckGo, Yandex) for pi",
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "Free, keyless web search through the user's own visible Chrome (Google, DuckDuckGo, Yandex, and opt-in Avito listings) for pi",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"pi": {
|
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
"test": "node --test --test-timeout=15000 \"tests/unit/**/*.test.ts\"",
|
|
19
19
|
"test:live": "PI_CHROME_LIVE=1 node --test \"tests/live/**/*.test.ts\"",
|
|
20
20
|
"audit": "node scripts/audit.mjs",
|
|
21
|
+
"gen-config-doc": "node scripts/gen-config-doc.mjs",
|
|
22
|
+
"gen-errors-doc": "node scripts/gen-errors-doc.mjs",
|
|
23
|
+
"gen-readme": "node scripts/gen-readme.mjs",
|
|
21
24
|
"release": "node scripts/release.mjs"
|
|
22
25
|
},
|
|
23
26
|
"peerDependencies": {
|
|
@@ -42,7 +45,8 @@
|
|
|
42
45
|
"web-search",
|
|
43
46
|
"google",
|
|
44
47
|
"duckduckgo",
|
|
45
|
-
"yandex"
|
|
48
|
+
"yandex",
|
|
49
|
+
"avito"
|
|
46
50
|
],
|
|
47
51
|
"files": [
|
|
48
52
|
"index.ts",
|
package/src/cdp/bytes.ts
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
import { isWebSearchError } from "../runtime/error-codes.ts";
|
|
26
26
|
import { fail } from "../runtime/errors.ts";
|
|
27
|
-
import type {
|
|
27
|
+
import type { CdpClient } from "./types.ts";
|
|
28
28
|
import type { FetchedBytes, FetchBytesOptions } from "./types.ts";
|
|
29
29
|
|
|
30
30
|
export const DEFAULT_FETCH_TIMEOUT_MS = 20_000;
|
|
@@ -95,7 +95,7 @@ async function withDeadline<T>(work: Promise<T>, timeoutMs: number, url: string)
|
|
|
95
95
|
* is the final hop, and the tool re-runs the guard on it before using the body.
|
|
96
96
|
*/
|
|
97
97
|
export async function captureBytes(
|
|
98
|
-
|
|
98
|
+
client: CdpClient,
|
|
99
99
|
url: string,
|
|
100
100
|
options: FetchBytesOptions = {},
|
|
101
101
|
): Promise<FetchedBytes> {
|
|
@@ -113,15 +113,15 @@ export async function captureBytes(
|
|
|
113
113
|
try {
|
|
114
114
|
// `background: true` keeps the user's foreground tab where it is; Chrome
|
|
115
115
|
// 132+ accepts it and older builds ignore the flag rather than failing.
|
|
116
|
-
targetId = await
|
|
117
|
-
await
|
|
118
|
-
sessionId =
|
|
116
|
+
targetId = await client.createTarget("about:blank", { background: true });
|
|
117
|
+
await client.attach(targetId);
|
|
118
|
+
sessionId = client.sessionIdFor(targetId);
|
|
119
119
|
if (sessionId === undefined) {
|
|
120
120
|
throw fail("E_CDP_DISCONNECTED", "connect", { message: "the capture target is not attached" });
|
|
121
121
|
}
|
|
122
122
|
const captureSessionId = sessionId;
|
|
123
123
|
|
|
124
|
-
await
|
|
124
|
+
await client.send(
|
|
125
125
|
"Fetch.enable",
|
|
126
126
|
{ patterns: [{ urlPattern: "*", requestStage: "Response" }] },
|
|
127
127
|
captureSessionId,
|
|
@@ -157,7 +157,7 @@ export async function captureBytes(
|
|
|
157
157
|
|
|
158
158
|
// Redirects are part of the chain; keep following until the body hop.
|
|
159
159
|
if (status >= 300 && status < 400) {
|
|
160
|
-
void
|
|
160
|
+
void client
|
|
161
161
|
.send("Fetch.continueResponse", { requestId }, captureSessionId)
|
|
162
162
|
.catch(() => undefined);
|
|
163
163
|
return;
|
|
@@ -165,7 +165,7 @@ export async function captureBytes(
|
|
|
165
165
|
|
|
166
166
|
const declared = contentLength(headers);
|
|
167
167
|
if (declared !== undefined && declared > maxBytes) {
|
|
168
|
-
void
|
|
168
|
+
void client.send("Fetch.failRequest", { requestId, errorReason: "Aborted" }, captureSessionId).catch(
|
|
169
169
|
() => undefined,
|
|
170
170
|
);
|
|
171
171
|
finish(() =>
|
|
@@ -185,7 +185,7 @@ export async function captureBytes(
|
|
|
185
185
|
|
|
186
186
|
void (async () => {
|
|
187
187
|
try {
|
|
188
|
-
const body = await
|
|
188
|
+
const body = await client.send<Record<string, unknown>>(
|
|
189
189
|
"Fetch.getResponseBody",
|
|
190
190
|
{ requestId },
|
|
191
191
|
captureSessionId,
|
|
@@ -209,18 +209,18 @@ export async function captureBytes(
|
|
|
209
209
|
} catch (error) {
|
|
210
210
|
finish(() => reject(error));
|
|
211
211
|
} finally {
|
|
212
|
-
void
|
|
212
|
+
void client
|
|
213
213
|
.send("Fetch.failRequest", { requestId, errorReason: "Aborted" }, captureSessionId)
|
|
214
214
|
.catch(() => undefined);
|
|
215
215
|
}
|
|
216
216
|
})();
|
|
217
217
|
};
|
|
218
218
|
|
|
219
|
-
onEvent =
|
|
219
|
+
onEvent = client.on("Fetch.requestPaused", handler);
|
|
220
220
|
// The request is opened by the document, not by `Page.navigate`: the
|
|
221
221
|
// capture target never shows a PDF viewer, so there is no "load" to
|
|
222
222
|
// await and no viewer document to mistake for the payload.
|
|
223
|
-
void
|
|
223
|
+
void client
|
|
224
224
|
.send(
|
|
225
225
|
"Runtime.evaluate",
|
|
226
226
|
{
|
|
@@ -248,10 +248,10 @@ export async function captureBytes(
|
|
|
248
248
|
});
|
|
249
249
|
} finally {
|
|
250
250
|
if (sessionId !== undefined) {
|
|
251
|
-
await
|
|
251
|
+
await client.send("Fetch.disable", {}, sessionId).catch(() => undefined);
|
|
252
252
|
}
|
|
253
253
|
if (targetId !== undefined) {
|
|
254
|
-
await
|
|
254
|
+
await client.closeTarget(targetId).catch(() => undefined);
|
|
255
255
|
}
|
|
256
256
|
}
|
|
257
257
|
}
|