pi-lean-dimension 0.1.0 → 0.2.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 +96 -36
- package/node_modules/pi-lean-portal/AGENTS.md +146 -4
- package/node_modules/pi-lean-portal/README.md +112 -50
- package/node_modules/pi-lean-portal/__tests__/browser-data.test.ts +124 -0
- package/node_modules/pi-lean-portal/__tests__/browser-inspect.test.ts +192 -16
- package/node_modules/pi-lean-portal/__tests__/browser-toggle-profile.test.ts +3 -3
- package/node_modules/pi-lean-portal/__tests__/browser-toggle.test.ts +49 -35
- package/node_modules/pi-lean-portal/__tests__/chromium-py-persistence.test.ts +21 -195
- package/node_modules/pi-lean-portal/__tests__/chromium-py.test.ts +17 -81
- package/node_modules/pi-lean-portal/__tests__/chromium.test.ts +25 -0
- package/node_modules/pi-lean-portal/__tests__/contributed/invisible-py/invisible-py.test.ts +299 -0
- package/node_modules/pi-lean-portal/__tests__/cookie-persistence.test.ts +22 -182
- package/node_modules/pi-lean-portal/__tests__/fetch-backend.test.ts +1 -1
- package/node_modules/pi-lean-portal/__tests__/firefox-py-persistence.test.ts +21 -184
- package/node_modules/pi-lean-portal/__tests__/firefox-py.test.ts +17 -101
- package/node_modules/pi-lean-portal/__tests__/firefox.test.ts +2 -18
- package/node_modules/pi-lean-portal/__tests__/helpers/__pycache__/mock-python-bridge.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/__tests__/helpers/create-py-backend-harness.ts +105 -0
- package/node_modules/pi-lean-portal/__tests__/helpers/load-plugin-config-from-file.ts +53 -0
- package/node_modules/pi-lean-portal/__tests__/helpers/mock-plugin.ts +11 -7
- package/node_modules/pi-lean-portal/__tests__/helpers/mock-python-bridge.py +4 -0
- package/node_modules/pi-lean-portal/__tests__/helpers/persistence-suite.ts +218 -0
- package/node_modules/pi-lean-portal/__tests__/helpers/plugin-contract.ts +198 -318
- package/node_modules/pi-lean-portal/__tests__/helpers/probe-user-backend.ts +198 -0
- package/node_modules/pi-lean-portal/__tests__/helpers/test-server.ts +14 -0
- package/node_modules/pi-lean-portal/__tests__/plugin-config-browser.test.ts +150 -15
- package/node_modules/pi-lean-portal/__tests__/plugin-loading.test.ts +120 -18
- package/node_modules/pi-lean-portal/__tests__/plugin-registry.test.ts +6 -67
- package/node_modules/pi-lean-portal/__tests__/probe-user-backend.test.ts +236 -0
- package/node_modules/pi-lean-portal/__tests__/python-adapter.test.ts +401 -11
- package/node_modules/pi-lean-portal/__tests__/router-session.test.ts +4 -1
- package/node_modules/pi-lean-portal/__tests__/run-contributed-suites.test.ts +318 -0
- package/node_modules/pi-lean-portal/__tests__/session-manager.test.ts +50 -0
- package/node_modules/pi-lean-portal/__tests__/snapshot-cache.test.ts +2 -2
- package/node_modules/pi-lean-portal/__tests__/url-safety.test.ts +1 -1
- package/node_modules/pi-lean-portal/backends/chromium/index.ts +5 -13
- package/node_modules/pi-lean-portal/backends/chromium-py/__pycache__/bridge.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/chromium-py/bridge.py +0 -2
- package/node_modules/pi-lean-portal/backends/firefox/index.ts +6 -5
- package/node_modules/pi-lean-portal/backends/firefox-py/__pycache__/bridge.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/firefox-py/bridge.py +7 -6
- package/node_modules/pi-lean-portal/backends/playwright-base/playwright-plugin.ts +241 -398
- package/node_modules/pi-lean-portal/backends/python-adapter.ts +182 -83
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__init__.py +1 -42
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/__init__.cpython-312.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/__init__.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/accessibility.cpython-312.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/accessibility.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/bot_detection.cpython-312.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/bot_detection.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/bridge.cpython-312.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/bridge.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/browser_data.cpython-312.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/browser_data.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/patch_playwright.cpython-312.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/patch_playwright.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/playwright_base.cpython-312.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/playwright_base.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/transport.cpython-312.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/transport.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/accessibility.py +12 -147
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/bot_detection.py +12 -37
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/bridge.py +249 -322
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/browser_data.py +92 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/patch_playwright.py +321 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/playwright_base.py +511 -299
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/conftest.cpython-313-pytest-9.1.1.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/{test_accessibility.cpython-313-pytest-9.1.0.pyc → test_accessibility.cpython-313-pytest-9.1.1.pyc} +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_accessibility.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_bot_detection.cpython-313-pytest-9.1.1.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_bot_detection.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_browser_data.cpython-313-pytest-9.1.1.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_browser_data.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/{test_chromium_py_bridge.cpython-313-pytest-9.1.0.pyc → test_chromium_py_bridge.cpython-313-pytest-9.1.1.pyc} +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/{test_firefox_py_bridge.cpython-313-pytest-9.1.0.pyc → test_firefox_py_bridge.cpython-313-pytest-9.1.1.pyc} +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_playwright_base_quirks.cpython-313-pytest-9.1.1.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_playwright_base_quirks.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_py_bridges.cpython-313-pytest-9.1.1.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/{test_transport.cpython-313-pytest-9.1.0.pyc → test_transport.cpython-313-pytest-9.1.1.pyc} +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/conftest.py +95 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/test_accessibility.py +8 -132
- package/node_modules/pi-lean-portal/backends/python-base/tests/test_bot_detection.py +8 -147
- package/node_modules/pi-lean-portal/backends/python-base/tests/test_browser_data.py +131 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/test_playwright_base_quirks.py +768 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/test_py_bridges.py +198 -0
- package/node_modules/pi-lean-portal/browser-toggle.ts +33 -69
- package/node_modules/pi-lean-portal/contributed/CHOOSING.md +126 -0
- package/node_modules/pi-lean-portal/contributed/README.md +304 -0
- package/node_modules/pi-lean-portal/contributed/camoufox-py/bridge.py +216 -0
- package/node_modules/pi-lean-portal/contributed/invisible-py/__pycache__/bridge.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/contributed/invisible-py/bridge.py +434 -0
- package/node_modules/pi-lean-portal/core/fetch-backend.ts +0 -5
- package/node_modules/pi-lean-portal/core/plugin-api.ts +6 -33
- package/node_modules/pi-lean-portal/core/plugin-config.ts +75 -58
- package/node_modules/pi-lean-portal/core/plugin-registry.ts +11 -49
- package/node_modules/pi-lean-portal/core/router.ts +59 -98
- package/node_modules/pi-lean-portal/core/shared/accessibility-tree.ts +10 -143
- package/node_modules/pi-lean-portal/core/shared/bot-detection.ts +31 -77
- package/node_modules/pi-lean-portal/core/shared/browser-data.json +183 -0
- package/node_modules/pi-lean-portal/core/shared/browser-data.ts +50 -0
- package/node_modules/pi-lean-portal/core/shared/browser-events.ts +6 -6
- package/node_modules/pi-lean-portal/core/shared/dom-extractor.ts +97 -36
- package/node_modules/pi-lean-portal/core/shared/nav-settle.ts +12 -15
- package/node_modules/pi-lean-portal/core/shared/paths.ts +3 -0
- package/node_modules/pi-lean-portal/core/shared/session-manager.ts +7 -21
- package/node_modules/pi-lean-portal/{verify-ship-manifest.ts → core/shared/ship-manifest.ts} +34 -9
- package/node_modules/pi-lean-portal/core/shared/snapshot-cache.ts +7 -6
- package/node_modules/pi-lean-portal/core/shared/storage-state.ts +40 -9
- package/node_modules/pi-lean-portal/index.ts +42 -7
- package/node_modules/pi-lean-portal/package.json +8 -3
- package/node_modules/pi-lean-portal/ship-manifest.test.ts +8 -3
- package/node_modules/pi-lean-portal/tools/browser-inspect.ts +2 -6
- package/node_modules/pi-lean-portal/tools/browser-navigate.ts +7 -5
- package/node_modules/pi-lean-portal/tools/browser-snapshot.ts +2 -5
- package/node_modules/pi-lean-portal/tools/utils.ts +22 -4
- package/node_modules/pi-lean-portal/tools/web-fetch.ts +3 -4
- package/node_modules/pi-lean-search/README.md +6 -2
- package/node_modules/pi-lean-search/__tests__/web-search.test.ts +11 -0
- package/node_modules/pi-lean-search/index.ts +4 -21
- package/node_modules/pi-lean-search/package.json +2 -2
- package/node_modules/pi-lean-search/verify-ship-manifest.ts +6 -92
- package/node_modules/pi-lean-search/web-search-tool.ts +19 -13
- package/package.json +4 -4
- package/node_modules/pi-lean-portal/__tests__/helpers/reddit-fixture.ts +0 -264
- package/node_modules/pi-lean-portal/__tests__/helpers/toggle-test-utils.ts +0 -31
- package/node_modules/pi-lean-portal/__tests__/reddit-dialog.test.ts +0 -302
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/occlusion.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_bot_detection.cpython-313-pytest-9.1.0.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/test_chromium_py_bridge.py +0 -281
- package/node_modules/pi-lean-portal/backends/python-base/tests/test_firefox_py_bridge.py +0 -212
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import { verifyShipManifest } from "./
|
|
1
|
+
import { verifyShipManifest } from "./core/shared/ship-manifest.js";
|
|
2
2
|
import { describe, expect, it } from "vitest";
|
|
3
3
|
|
|
4
4
|
describe("publish manifest", () => {
|
|
5
5
|
it("`package.json` `files` array covers every production .ts module across the tree", () => {
|
|
6
|
-
expect(
|
|
6
|
+
expect(
|
|
7
|
+
verifyShipManifest(import.meta.url, { skipDirs: ["contributed"] })
|
|
8
|
+
.missing,
|
|
9
|
+
).toEqual([]);
|
|
7
10
|
});
|
|
8
11
|
|
|
9
12
|
it("every `files` entry points at something on disk — a stale entry ships nothing", () => {
|
|
10
|
-
expect(
|
|
13
|
+
expect(
|
|
14
|
+
verifyShipManifest(import.meta.url, { skipDirs: ["contributed"] }).stale,
|
|
15
|
+
).toEqual([]);
|
|
11
16
|
});
|
|
12
17
|
});
|
|
@@ -7,6 +7,7 @@ import { Type } from "@earendil-works/pi-ai";
|
|
|
7
7
|
import { Text } from "@earendil-works/pi-tui";
|
|
8
8
|
import * as router from "../core/router.js";
|
|
9
9
|
import { taskId } from "../core/shared/task-id.js";
|
|
10
|
+
import { renderExpandedText } from "./utils.js";
|
|
10
11
|
|
|
11
12
|
export const browserInspectTool = defineTool({
|
|
12
13
|
name: "browser-inspect",
|
|
@@ -124,13 +125,8 @@ export const browserInspectTool = defineTool({
|
|
|
124
125
|
const staleness = d?.stalenessWarning
|
|
125
126
|
? ` ${theme.fg("warning", "(stale)")}`
|
|
126
127
|
: "";
|
|
127
|
-
const preview = content.replace(/\n{3,}/g, "\n\n").slice(0, 100);
|
|
128
128
|
let text = theme.fg("accent", `🔍 ${content.length} chars`) + staleness;
|
|
129
|
-
|
|
130
|
-
text += `\n${theme.fg("dim", preview)}`;
|
|
131
|
-
if (content.length > 100)
|
|
132
|
-
text += `\n${theme.fg("muted", `… ${content.length - 100} more chars`)}`;
|
|
133
|
-
}
|
|
129
|
+
text = renderExpandedText(text, theme, content, 100);
|
|
134
130
|
return new Text(text, 0, 0);
|
|
135
131
|
},
|
|
136
132
|
});
|
|
@@ -12,7 +12,11 @@ import { getConversationDefaultProfile } from "../browser-toggle.js";
|
|
|
12
12
|
import { sessionManager } from "../core/shared/session-manager.js";
|
|
13
13
|
import { removeSnapshotFiles } from "../core/shared/snapshot-cache.js";
|
|
14
14
|
import { taskId } from "../core/shared/task-id.js";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
updateFooterStatus,
|
|
17
|
+
profileLine,
|
|
18
|
+
renderExpandedText,
|
|
19
|
+
} from "./utils.js";
|
|
16
20
|
|
|
17
21
|
export const browserNavigateTool = defineTool({
|
|
18
22
|
name: "browser-navigate",
|
|
@@ -242,10 +246,8 @@ export const browserNavigateTool = defineTool({
|
|
|
242
246
|
|
|
243
247
|
const content = (result.content?.[0] as any)?.text ?? "";
|
|
244
248
|
if (expanded) {
|
|
245
|
-
|
|
246
|
-
text
|
|
247
|
-
if (content.length > 500)
|
|
248
|
-
text += `\n${theme.fg("muted", `… ${content.length - 500} more chars`)}`;
|
|
249
|
+
text += "\n";
|
|
250
|
+
text = renderExpandedText(text, theme, content, 500);
|
|
249
251
|
} else {
|
|
250
252
|
text += `\n${theme.fg("muted", `${content.length} chars (expand)`)}`;
|
|
251
253
|
}
|
|
@@ -7,7 +7,7 @@ import { Type } from "@earendil-works/pi-ai";
|
|
|
7
7
|
import { Text } from "@earendil-works/pi-tui";
|
|
8
8
|
import * as router from "../core/router.js";
|
|
9
9
|
import { taskId } from "../core/shared/task-id.js";
|
|
10
|
-
import { updateFooterStatus } from "./utils.js";
|
|
10
|
+
import { updateFooterStatus, renderExpandedText } from "./utils.js";
|
|
11
11
|
|
|
12
12
|
export const browserSnapshotTool = defineTool({
|
|
13
13
|
name: "browser-snapshot",
|
|
@@ -72,12 +72,9 @@ export const browserSnapshotTool = defineTool({
|
|
|
72
72
|
const content = (result.content?.[0] as any)?.text ?? "";
|
|
73
73
|
const isFull = !!(d?.full as boolean);
|
|
74
74
|
if (expanded) {
|
|
75
|
-
const preview = content.slice(0, 400);
|
|
76
75
|
let text = theme.fg("accent", `📋 ${ec} elements`);
|
|
77
76
|
text += isFull ? "" : theme.fg("dim", " (compact)");
|
|
78
|
-
text
|
|
79
|
-
if (content.length > 400)
|
|
80
|
-
text += `\n${theme.fg("muted", `… ${content.length - 400} more chars`)}`;
|
|
77
|
+
text = renderExpandedText(text, theme, content, 400);
|
|
81
78
|
return new Text(text, 0, 0);
|
|
82
79
|
}
|
|
83
80
|
const label = isFull ? " (full)" : " (compact)";
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Includes taskId resolution, status bar updates, and profile line formatting.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import type { ExtensionAPI, ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { getToggleState, getLearnState } from "../browser-toggle.js";
|
|
10
10
|
import { sessionManager } from "../core/shared/session-manager.js";
|
|
11
11
|
import { taskId } from "../core/shared/task-id.js";
|
|
@@ -21,7 +21,7 @@ import { taskId } from "../core/shared/task-id.js";
|
|
|
21
21
|
let _lastCtx: {
|
|
22
22
|
ui: {
|
|
23
23
|
setStatus: (key: string, label: string) => void;
|
|
24
|
-
theme: { fg: (c:
|
|
24
|
+
theme: { fg: (c: ThemeColor, t: string) => string };
|
|
25
25
|
};
|
|
26
26
|
} | null = null;
|
|
27
27
|
|
|
@@ -31,7 +31,7 @@ let _lastCtx: {
|
|
|
31
31
|
export function updateFooterStatus(ctx: {
|
|
32
32
|
ui: {
|
|
33
33
|
setStatus: (key: string, label: string) => void;
|
|
34
|
-
theme: { fg: (c:
|
|
34
|
+
theme: { fg: (c: ThemeColor, t: string) => string };
|
|
35
35
|
};
|
|
36
36
|
}): void {
|
|
37
37
|
_lastCtx = ctx;
|
|
@@ -109,7 +109,7 @@ export async function executeInteractionTool<
|
|
|
109
109
|
ctx: {
|
|
110
110
|
ui: {
|
|
111
111
|
setStatus: (key: string, label: string) => void;
|
|
112
|
-
theme: { fg: (c:
|
|
112
|
+
theme: { fg: (c: ThemeColor, t: string) => string };
|
|
113
113
|
};
|
|
114
114
|
sessionManager?: { getSessionId?(): string };
|
|
115
115
|
},
|
|
@@ -154,4 +154,22 @@ export async function executeInteractionTool<
|
|
|
154
154
|
};
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Append a dim-styled content preview to an in-progress result string,
|
|
159
|
+
* with a "more chars" suffix when the content exceeds the given limit.
|
|
160
|
+
*/
|
|
161
|
+
export function renderExpandedText(
|
|
162
|
+
text: string,
|
|
163
|
+
theme: { fg: (c: ThemeColor, t: string) => string },
|
|
164
|
+
content: string,
|
|
165
|
+
limit: number,
|
|
166
|
+
): string {
|
|
167
|
+
const preview = content.replace(/\n{3,}/g, "\n\n").slice(0, limit);
|
|
168
|
+
if (!preview) return text;
|
|
169
|
+
text += `\n${theme.fg("dim", preview)}`;
|
|
170
|
+
if (content.length > limit)
|
|
171
|
+
text += `\n${theme.fg("muted", `… ${content.length - limit} more chars`)}`;
|
|
172
|
+
return text;
|
|
173
|
+
}
|
|
174
|
+
|
|
157
175
|
export type { ExtensionAPI };
|
|
@@ -7,6 +7,7 @@ import { Type } from "@earendil-works/pi-ai";
|
|
|
7
7
|
import { Text } from "@earendil-works/pi-tui";
|
|
8
8
|
import { webFetch } from "../core/fetch-backend.js";
|
|
9
9
|
import { taskId } from "../core/shared/task-id.js";
|
|
10
|
+
import { renderExpandedText } from "./utils.js";
|
|
10
11
|
|
|
11
12
|
export const webFetchTool = defineTool({
|
|
12
13
|
name: "web-fetch",
|
|
@@ -135,10 +136,8 @@ export const webFetchTool = defineTool({
|
|
|
135
136
|
|
|
136
137
|
const content = (result.content?.[0] as any)?.text ?? "";
|
|
137
138
|
if (expanded) {
|
|
138
|
-
|
|
139
|
-
text
|
|
140
|
-
if (content.length > 500)
|
|
141
|
-
text += `\n${theme.fg("muted", `… ${content.length - 500} more chars`)}`;
|
|
139
|
+
text += "\n";
|
|
140
|
+
text = renderExpandedText(text, theme, content, 500);
|
|
142
141
|
} else {
|
|
143
142
|
text += `\n${theme.fg("muted", `${content.length} chars (expand)`)}`;
|
|
144
143
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# pi-lean-search
|
|
2
2
|
|
|
3
|
-
> SearXNG search tool for Pi.
|
|
3
|
+
> SearXNG search tool for Pi. Pairs with [pi-lean-portal](https://www.npmjs.com/package/pi-lean-portal)'s
|
|
4
|
+
> `/web` toggle — search-only installs are valid, or add it to a portal install
|
|
5
|
+
> for the full web-tools suite. Part of the
|
|
6
|
+
> [pi-lean-dimension](https://github.com/coreyryanhanson/pi-lean-dimension)
|
|
7
|
+
> web-tools suite.
|
|
4
8
|
|
|
5
9
|
## Quick start
|
|
6
10
|
|
|
@@ -19,7 +23,7 @@ pi install npm:pi-lean-dimension # portal + search, requires SearXNG server
|
|
|
19
23
|
|
|
20
24
|
| Command / Tool | Description |
|
|
21
25
|
|---|---|
|
|
22
|
-
| `web-search` tool | Search the web via your SearXNG instance. Agents use this automatically. |
|
|
26
|
+
| `web-search` tool | Search the web via your SearXNG instance. Supports `count` (1–100) and `pageno` (1-indexed) for deeper result pagination. Agents use this automatically. |
|
|
23
27
|
| `/searxng-status` | Test and diagnose the SearXNG connection. |
|
|
24
28
|
|
|
25
29
|
The `web-search` tool is automatically included in `/web on` / `/web off` toggling
|
|
@@ -128,6 +128,17 @@ describe("webSearchTool", () => {
|
|
|
128
128
|
expect(schema).toBeDefined();
|
|
129
129
|
});
|
|
130
130
|
|
|
131
|
+
it("defines pageno parameter with minimum of 1", () => {
|
|
132
|
+
const schema = webSearchTool.parameters;
|
|
133
|
+
expect(schema).toBeDefined();
|
|
134
|
+
const pageno = (
|
|
135
|
+
schema as Record<string, unknown> & {
|
|
136
|
+
properties?: Record<string, unknown>;
|
|
137
|
+
}
|
|
138
|
+
)?.properties?.pageno;
|
|
139
|
+
expect(pageno).toBeDefined();
|
|
140
|
+
});
|
|
141
|
+
|
|
131
142
|
it("has execute function", () => {
|
|
132
143
|
expect(typeof webSearchTool.execute).toBe("function");
|
|
133
144
|
});
|
|
@@ -44,8 +44,8 @@ async function checkServerReachable(
|
|
|
44
44
|
let res: Response;
|
|
45
45
|
try {
|
|
46
46
|
const mergedSignal = signal
|
|
47
|
-
?
|
|
48
|
-
:
|
|
47
|
+
? AbortSignal.any([signal, controller.signal])
|
|
48
|
+
: controller.signal;
|
|
49
49
|
res = await fetch(url, { signal: mergedSignal });
|
|
50
50
|
} finally {
|
|
51
51
|
clearTimeout(timeoutId);
|
|
@@ -75,8 +75,8 @@ async function checkSearchReachable(
|
|
|
75
75
|
let res: Response;
|
|
76
76
|
try {
|
|
77
77
|
const mergedSignal = signal
|
|
78
|
-
?
|
|
79
|
-
:
|
|
78
|
+
? AbortSignal.any([signal, controller.signal])
|
|
79
|
+
: controller.signal;
|
|
80
80
|
res = await fetch(searchUrl, {
|
|
81
81
|
signal: mergedSignal,
|
|
82
82
|
headers: { Accept: "application/json" },
|
|
@@ -95,23 +95,6 @@ async function checkSearchReachable(
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
/**
|
|
99
|
-
* Combine two AbortSignals into one that aborts when either aborts.
|
|
100
|
-
*/
|
|
101
|
-
function combineAbortSignals(...signals: AbortSignal[]): AbortSignal {
|
|
102
|
-
const controller = new AbortController();
|
|
103
|
-
for (const sig of signals) {
|
|
104
|
-
if (sig.aborted) {
|
|
105
|
-
controller.abort(sig.reason);
|
|
106
|
-
return controller.signal;
|
|
107
|
-
}
|
|
108
|
-
sig.addEventListener("abort", () => controller.abort(sig.reason), {
|
|
109
|
-
once: true,
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
return controller.signal;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
98
|
// ─── Status slot helpers ──────────────────────────────────────────
|
|
116
99
|
|
|
117
100
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-lean-search",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "Pi extension. SearXNG search tool for Pi
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Pi extension. SearXNG search tool for Pi — pairs with pi-lean-portal's /web toggle; search-only installs are valid, or add it to a portal install for the full web-tools suite.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
7
7
|
"pi-extension",
|
|
@@ -1,96 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Ship-manifest verification helper.
|
|
2
|
+
* Ship-manifest verification helper (thin re-export).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* actually contains the modules it imports at runtime.
|
|
8
|
-
*
|
|
9
|
-
* Ported from pi-lean-portal's verify-ship-manifest.ts (rpiv-mono pattern).
|
|
4
|
+
* Re-exports the shared {@link verifyShipManifest} from pi-lean-portal's
|
|
5
|
+
* core/shared/ship-manifest.ts. Kept so `ship-manifest.test.ts` can import
|
|
6
|
+
* from a local path without depending on a specific cross-package layout.
|
|
10
7
|
*/
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
import { fileURLToPath } from "node:url";
|
|
15
|
-
|
|
16
|
-
const SKIP_DIRS = new Set(["node_modules", "docs", "__tests__"]);
|
|
17
|
-
const SKIP_FILES = new Set(["test-fixtures.ts"]);
|
|
18
|
-
/** Entries that won't exist on disk at rest but are valid (generated at pack time, e.g. by prepack). */
|
|
19
|
-
const SKIP_STALE = new Set(["LICENSE"]);
|
|
20
|
-
|
|
21
|
-
export interface ShipManifestResult {
|
|
22
|
-
declared: readonly string[];
|
|
23
|
-
onDisk: readonly string[];
|
|
24
|
-
missing: readonly string[];
|
|
25
|
-
stale: readonly string[];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function verifyShipManifest(
|
|
29
|
-
packageDirOrUrl: string,
|
|
30
|
-
): ShipManifestResult {
|
|
31
|
-
const packageDir = packageDirOrUrl.startsWith("file:")
|
|
32
|
-
? dirname(fileURLToPath(packageDirOrUrl))
|
|
33
|
-
: packageDirOrUrl;
|
|
34
|
-
const pkgRaw = readFileSync(resolve(packageDir, "package.json"), "utf8");
|
|
35
|
-
const pkg = JSON.parse(pkgRaw) as { files?: string[] };
|
|
36
|
-
const declared = pkg.files ?? [];
|
|
37
|
-
const exactFiles = new Set<string>();
|
|
38
|
-
const dirPrefixes: string[] = [];
|
|
39
|
-
for (const entry of declared) {
|
|
40
|
-
// Negation patterns (`!foo/`) are npm `files`-array syntax for excluding
|
|
41
|
-
// sub-paths within an included directory. They have no on-disk counterpart;
|
|
42
|
-
// skip them for existence checks and staleness detection.
|
|
43
|
-
if (entry.startsWith("!")) continue;
|
|
44
|
-
if (entry.endsWith("/")) dirPrefixes.push(entry);
|
|
45
|
-
else if (isDirOnDisk(packageDir, entry)) dirPrefixes.push(`${entry}/`);
|
|
46
|
-
else exactFiles.add(entry);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const onDisk = walkProductionTs(packageDir, packageDir);
|
|
50
|
-
const missing = onDisk.filter((f) => !isCovered(f, exactFiles, dirPrefixes));
|
|
51
|
-
const stale = declared.filter(
|
|
52
|
-
(entry) =>
|
|
53
|
-
!entry.startsWith("!") &&
|
|
54
|
-
!SKIP_STALE.has(entry) &&
|
|
55
|
-
!existsSync(resolve(packageDir, entry)),
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
return { declared, onDisk, missing, stale };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function isDirOnDisk(packageDir: string, entry: string): boolean {
|
|
62
|
-
try {
|
|
63
|
-
return statSync(resolve(packageDir, entry)).isDirectory();
|
|
64
|
-
} catch {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function isCovered(
|
|
70
|
-
file: string,
|
|
71
|
-
exactFiles: Set<string>,
|
|
72
|
-
dirPrefixes: readonly string[],
|
|
73
|
-
): boolean {
|
|
74
|
-
if (exactFiles.has(file)) return true;
|
|
75
|
-
for (const prefix of dirPrefixes) {
|
|
76
|
-
if (file.startsWith(prefix)) return true;
|
|
77
|
-
}
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function walkProductionTs(root: string, dir: string): string[] {
|
|
82
|
-
const out: string[] = [];
|
|
83
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
84
|
-
if (entry.name.startsWith(".")) continue;
|
|
85
|
-
if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) continue;
|
|
86
|
-
const abs = resolve(dir, entry.name);
|
|
87
|
-
if (entry.isDirectory()) {
|
|
88
|
-
out.push(...walkProductionTs(root, abs));
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
if (!entry.isFile() || !entry.name.endsWith(".ts")) continue;
|
|
92
|
-
if (entry.name.endsWith(".test.ts") || SKIP_FILES.has(entry.name)) continue;
|
|
93
|
-
out.push(relative(root, abs));
|
|
94
|
-
}
|
|
95
|
-
return out;
|
|
96
|
-
}
|
|
9
|
+
export type { ShipManifestResult } from "../pi-lean-portal/core/shared/ship-manifest.js";
|
|
10
|
+
export { verifyShipManifest } from "../pi-lean-portal/core/shared/ship-manifest.js";
|
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* web-search tool
|
|
3
|
-
*
|
|
4
|
-
* Searches the web via a SearXNG instance. Degrades gracefully when
|
|
5
|
-
* no SearXNG URL is configured — returns a setup message on first call.
|
|
6
|
-
*
|
|
7
|
-
* Adapted from the prototype at:
|
|
8
|
-
* /root/lab/startup_scripts/firecracker/config/pi/extensions/searxng-search/index.ts
|
|
9
|
-
*
|
|
10
|
-
* Changes from prototype:
|
|
11
|
-
* - Config read from Pi settings.json (searxng.url) instead of env vars
|
|
12
|
-
* - No injectUnavailabilityNotice (graceful degradation via tool output only)
|
|
13
|
-
* - Health state management lives in index.ts, not in the tool
|
|
2
|
+
* web-search tool for pi-lean-search — SearXNG web search with graceful degradation.
|
|
3
|
+
* Config read from Pi settings.json; health state managed in index.ts.
|
|
14
4
|
*/
|
|
15
5
|
|
|
16
6
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
@@ -41,6 +31,7 @@ function buildSearchUrl(
|
|
|
41
31
|
query: string,
|
|
42
32
|
options: {
|
|
43
33
|
count: number;
|
|
34
|
+
pageno: number;
|
|
44
35
|
language: string;
|
|
45
36
|
safesearch: string;
|
|
46
37
|
time_range: string;
|
|
@@ -60,6 +51,7 @@ function buildSearchUrl(
|
|
|
60
51
|
if (options.safesearch) params.set("safesearch", options.safesearch);
|
|
61
52
|
if (options.time_range) params.set("time_range", options.time_range);
|
|
62
53
|
if (options.category) params.set("categories", options.category);
|
|
54
|
+
if (options.pageno > 1) params.set("pageno", String(options.pageno));
|
|
63
55
|
if (options.engines) params.set("engines", options.engines);
|
|
64
56
|
|
|
65
57
|
return `${normalized}/search?${params.toString()}`;
|
|
@@ -75,8 +67,10 @@ export const webSearchTool = defineTool({
|
|
|
75
67
|
"Use for finding current information, research, news, and fact-checking.",
|
|
76
68
|
promptSnippet:
|
|
77
69
|
"Search the web via a local SearXNG instance — use for up-to-date facts, verification, or research.",
|
|
78
|
-
promptGuidelines:
|
|
70
|
+
promptGuidelines: [
|
|
79
71
|
'Use when you need recent/current information not already known. Increase `count` for broad research; keep it small for quick lookups. Filter by time_range="day" for breaking news, category="news" for journalism. Set language to match the query (e.g. "de" for German, "es" for Spanish).',
|
|
72
|
+
"Use `pageno` (1-indexed) to fetch deeper pages when `count` results are not enough.",
|
|
73
|
+
],
|
|
80
74
|
|
|
81
75
|
parameters: Type.Object({
|
|
82
76
|
query: Type.String({ description: "The search query" }),
|
|
@@ -128,6 +122,13 @@ export const webSearchTool = defineTool({
|
|
|
128
122
|
},
|
|
129
123
|
),
|
|
130
124
|
),
|
|
125
|
+
pageno: Type.Optional(
|
|
126
|
+
Type.Number({
|
|
127
|
+
description:
|
|
128
|
+
"Page number, 1-indexed (default: 1). Combine with count to page through results.",
|
|
129
|
+
minimum: 1,
|
|
130
|
+
}),
|
|
131
|
+
),
|
|
131
132
|
engines: Type.Optional(
|
|
132
133
|
Type.String({
|
|
133
134
|
description:
|
|
@@ -140,6 +141,7 @@ export const webSearchTool = defineTool({
|
|
|
140
141
|
const {
|
|
141
142
|
query,
|
|
142
143
|
count = 5,
|
|
144
|
+
pageno = 1,
|
|
143
145
|
timeout: userTimeout,
|
|
144
146
|
language = "",
|
|
145
147
|
safesearch = "0",
|
|
@@ -174,6 +176,7 @@ export const webSearchTool = defineTool({
|
|
|
174
176
|
// ── Build URL ──
|
|
175
177
|
const url = buildSearchUrl(searxngUrl, query, {
|
|
176
178
|
count,
|
|
179
|
+
pageno,
|
|
177
180
|
language,
|
|
178
181
|
safesearch,
|
|
179
182
|
time_range,
|
|
@@ -359,6 +362,7 @@ export const webSearchTool = defineTool({
|
|
|
359
362
|
details: {
|
|
360
363
|
resultCount: results.length,
|
|
361
364
|
query,
|
|
365
|
+
pageno,
|
|
362
366
|
timeout: timeoutSeconds,
|
|
363
367
|
results: results.map((r) => ({
|
|
364
368
|
title: r.title,
|
|
@@ -392,6 +396,8 @@ export const webSearchTool = defineTool({
|
|
|
392
396
|
const parts: string[] = [theme.fg("toolTitle", theme.bold("web-search "))];
|
|
393
397
|
parts.push(theme.fg("accent", `"${args.query}"`));
|
|
394
398
|
if (args.count) parts.push(theme.fg("dim", `count=${args.count}`));
|
|
399
|
+
if (args.pageno && args.pageno > 1)
|
|
400
|
+
parts.push(theme.fg("dim", `p${args.pageno}`));
|
|
395
401
|
if (args.category) parts.push(theme.fg("dim", `cat:${args.category}`));
|
|
396
402
|
if (args.time_range) parts.push(theme.fg("dim", `time:${args.time_range}`));
|
|
397
403
|
return new Text(parts.join(" "), 0, 0);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-lean-dimension",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "Pi extension. The full
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Pi extension. The full Pi web-tools suite in one install — interactive browsing (pi-lean-portal) plus SearXNG search (pi-lean-search), unified under a /web toggle that removes the tools from context when switched off.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
7
7
|
"pi-extension",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
]
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"pi-lean-portal": "^0.1
|
|
36
|
-
"pi-lean-search": "^0.1
|
|
35
|
+
"pi-lean-portal": "^0.2.1",
|
|
36
|
+
"pi-lean-search": "^0.2.1"
|
|
37
37
|
},
|
|
38
38
|
"bundledDependencies": [
|
|
39
39
|
"pi-lean-portal",
|