pi-lean-portal 0.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/LICENSE +661 -0
- package/README.md +608 -0
- package/backends/chromium/index.ts +50 -0
- package/backends/chromium-py/bridge.py +67 -0
- package/backends/firefox/index.ts +60 -0
- package/backends/firefox-py/bridge.py +64 -0
- package/backends/playwright-base/playwright-plugin.ts +1294 -0
- package/backends/python-adapter.ts +1141 -0
- package/backends/python-base/pi_browser_bridge/__init__.py +71 -0
- package/backends/python-base/pi_browser_bridge/accessibility.py +408 -0
- package/backends/python-base/pi_browser_bridge/bot_detection.py +115 -0
- package/backends/python-base/pi_browser_bridge/bridge.py +598 -0
- package/backends/python-base/pi_browser_bridge/playwright_base.py +1222 -0
- package/backends/python-base/pi_browser_bridge/transport.py +167 -0
- package/backends/python-base/pyproject.toml +15 -0
- package/browser-cookies.ts +88 -0
- package/browser-profile.ts +260 -0
- package/browser-status.ts +84 -0
- package/browser-toggle.ts +527 -0
- package/core/fetch-backend.ts +466 -0
- package/core/guides.ts +467 -0
- package/core/plugin-api.ts +302 -0
- package/core/plugin-config.ts +388 -0
- package/core/plugin-registry.ts +263 -0
- package/core/router.ts +1186 -0
- package/core/shared/accessibility-tree.ts +408 -0
- package/core/shared/bot-detection.ts +187 -0
- package/core/shared/browser-events.ts +111 -0
- package/core/shared/dom-extractor.ts +550 -0
- package/core/shared/nav-settle.ts +187 -0
- package/core/shared/paths.ts +56 -0
- package/core/shared/session-manager.ts +258 -0
- package/core/shared/settings-reader.ts +63 -0
- package/core/shared/snapshot-cache.ts +231 -0
- package/core/shared/storage-state.ts +560 -0
- package/core/shared/task-id.ts +77 -0
- package/core/shared/url-safety.ts +164 -0
- package/index.ts +253 -0
- package/package.json +63 -0
- package/ship-manifest.test.ts +12 -0
- package/tools/browser-back.ts +50 -0
- package/tools/browser-click.ts +74 -0
- package/tools/browser-console.ts +160 -0
- package/tools/browser-inspect.ts +136 -0
- package/tools/browser-navigate.ts +254 -0
- package/tools/browser-press.ts +80 -0
- package/tools/browser-scroll.ts +56 -0
- package/tools/browser-snapshot.ts +90 -0
- package/tools/browser-type.ts +60 -0
- package/tools/index.ts +19 -0
- package/tools/utils.ts +157 -0
- package/tools/web-fetch.ts +147 -0
- package/tools/web-guide.ts +55 -0
- package/tools/web-learn.ts +128 -0
- package/verify-ship-manifest.ts +126 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Registry — typed registration, validation, and lookup for BrowserPlugins.
|
|
3
|
+
*
|
|
4
|
+
* The registry holds all registered plugins and provides:
|
|
5
|
+
* - `register(name, plugin)` — with validation that all required operations exist
|
|
6
|
+
* - `get(name)` → BrowserPlugin | undefined
|
|
7
|
+
* - `getDefault()` → first enabled plugin from the configured `plugins` array
|
|
8
|
+
* - `getOrdered()` → all enabled plugins in array order (lower index = higher priority)
|
|
9
|
+
* - `available()` → list of registered plugin names
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { BrowserPlugin } from "./plugin-api.js";
|
|
13
|
+
import type { PluginConfig } from "./plugin-config.js";
|
|
14
|
+
|
|
15
|
+
// ─── Validation ───────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
/** The required operation method names (tool-mapped only; lifecycle, cookie, storage excluded) */
|
|
18
|
+
const REQUIRED_OPERATIONS: ReadonlyArray<keyof BrowserPlugin> = [
|
|
19
|
+
"navigate",
|
|
20
|
+
"snapshot",
|
|
21
|
+
"click",
|
|
22
|
+
"type",
|
|
23
|
+
"scroll",
|
|
24
|
+
"goBack",
|
|
25
|
+
"press",
|
|
26
|
+
"screenshot",
|
|
27
|
+
"getConsoleMessages",
|
|
28
|
+
"clearConsole",
|
|
29
|
+
"evaluate",
|
|
30
|
+
"cleanup",
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Validate that a BrowserPlugin implements all required operations.
|
|
35
|
+
* Returns an array of missing method names (empty if valid).
|
|
36
|
+
*/
|
|
37
|
+
export function validatePlugin(plugin: BrowserPlugin): string[] {
|
|
38
|
+
const missing: string[] = [];
|
|
39
|
+
for (const op of REQUIRED_OPERATIONS) {
|
|
40
|
+
if (typeof (plugin as any)[op] !== "function") {
|
|
41
|
+
missing.push(op);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return missing;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ─── Registry Entry ───────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
/** Internal tracking for a registered plugin */
|
|
50
|
+
interface RegistryEntry {
|
|
51
|
+
plugin: BrowserPlugin;
|
|
52
|
+
/** Position in the user's plugins config array (lower = higher priority, used for LLM escalation hints) */
|
|
53
|
+
level: number;
|
|
54
|
+
/** Whether this plugin is enabled */
|
|
55
|
+
enabled: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ─── PluginRegistry ───────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
export class PluginRegistry {
|
|
61
|
+
/** Map of plugin name → registry entry */
|
|
62
|
+
private entries = new Map<string, RegistryEntry>();
|
|
63
|
+
|
|
64
|
+
/** Ordered list of plugin names from config (defines escalation priority — lower index = recommended first) */
|
|
65
|
+
private orderedNames: string[] = [];
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Pre-populate the ordered plugin name list.
|
|
69
|
+
*
|
|
70
|
+
* Call this BEFORE any `register()` calls to ensure the config array order
|
|
71
|
+
* is preserved even when plugins are registered asynchronously (e.g. Node
|
|
72
|
+
* plugins loaded via dynamic `import()` vs Python plugins registered
|
|
73
|
+
* synchronously).
|
|
74
|
+
*
|
|
75
|
+
* If `register()` finds its name already in the seeded order, it uses the
|
|
76
|
+
* existing position as the priority level instead of appending.
|
|
77
|
+
*
|
|
78
|
+
* @param names - Plugin names in the desired priority order (typically
|
|
79
|
+
* the order from the user's `browser.plugins` config array).
|
|
80
|
+
*/
|
|
81
|
+
seedOrder(names: string[]): void {
|
|
82
|
+
this.orderedNames = [...names];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Register a plugin with its config.
|
|
87
|
+
*
|
|
88
|
+
* If the plugin name was pre-seeded via `seedOrder()`, its priority level
|
|
89
|
+
* is taken from that pre-determined position. Otherwise it is appended at
|
|
90
|
+
* the end.
|
|
91
|
+
*
|
|
92
|
+
* @throws if a plugin with the same name is already registered
|
|
93
|
+
* @throws if the plugin is missing required operations
|
|
94
|
+
*/
|
|
95
|
+
register(plugin: BrowserPlugin, config: PluginConfig): void {
|
|
96
|
+
if (this.entries.has(plugin.name)) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Plugin '${plugin.name}' is already registered. Remove the duplicate entry.`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Validate all required operations
|
|
103
|
+
const missing = validatePlugin(plugin);
|
|
104
|
+
if (missing.length > 0) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`Plugin '${plugin.name}' is missing required operations: ${missing.join(", ")}`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Determine the level from the pre-seeded orderedNames, or append
|
|
111
|
+
let level = this.orderedNames.indexOf(plugin.name);
|
|
112
|
+
if (level === -1) {
|
|
113
|
+
level = this.orderedNames.length;
|
|
114
|
+
this.orderedNames.push(plugin.name);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.entries.set(plugin.name, {
|
|
118
|
+
plugin,
|
|
119
|
+
level,
|
|
120
|
+
enabled: config.enabled,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get a plugin by name. Returns undefined if not registered or disabled.
|
|
126
|
+
*/
|
|
127
|
+
get(name: string): BrowserPlugin | undefined {
|
|
128
|
+
const entry = this.entries.get(name);
|
|
129
|
+
if (!entry || !entry.enabled) return undefined;
|
|
130
|
+
return entry.plugin;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get a plugin by name, even if disabled.
|
|
135
|
+
* Useful for error messages ("Plugin 'X' is disabled, not missing").
|
|
136
|
+
*/
|
|
137
|
+
private getAny(name: string): RegistryEntry | undefined {
|
|
138
|
+
return this.entries.get(name);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get the default plugin — the first enabled plugin in the config order.
|
|
143
|
+
* Returns undefined if no plugins are registered or enabled.
|
|
144
|
+
*/
|
|
145
|
+
getDefault(): BrowserPlugin | undefined {
|
|
146
|
+
for (const name of this.orderedNames) {
|
|
147
|
+
const entry = this.entries.get(name);
|
|
148
|
+
if (entry?.enabled) return entry.plugin;
|
|
149
|
+
}
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get all enabled plugins in config order (priority order).
|
|
155
|
+
* Each entry includes the plugin and its priority level (lower = recommended first).
|
|
156
|
+
*/
|
|
157
|
+
getOrdered(): Array<{ plugin: BrowserPlugin; level: number }> {
|
|
158
|
+
const result: Array<{ plugin: BrowserPlugin; level: number }> = [];
|
|
159
|
+
for (const name of this.orderedNames) {
|
|
160
|
+
const entry = this.entries.get(name);
|
|
161
|
+
if (entry?.enabled) {
|
|
162
|
+
result.push({ plugin: entry.plugin, level: entry.level });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* List all registered plugin names (enabled only).
|
|
170
|
+
*/
|
|
171
|
+
available(): string[] {
|
|
172
|
+
return this.getOrdered().map((e) => e.plugin.name);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* List all registered plugin names (including disabled).
|
|
177
|
+
*/
|
|
178
|
+
availableAll(): Array<{ name: string; enabled: boolean }> {
|
|
179
|
+
return this.orderedNames.map((name) => {
|
|
180
|
+
const entry = this.entries.get(name)!;
|
|
181
|
+
return { name, enabled: entry.enabled };
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get the priority level for a plugin (lower = recommended first).
|
|
187
|
+
* Used by the LLM to decide whether to escalate to a different backend.
|
|
188
|
+
* Returns undefined if the plugin is not registered.
|
|
189
|
+
*/
|
|
190
|
+
getLevel(name: string): number | undefined {
|
|
191
|
+
return this.entries.get(name)?.level;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get plugins at higher priority levels (further in the backup chain) than the given level.
|
|
196
|
+
* Used to suggest alternative backends when bot detection fires.
|
|
197
|
+
*/
|
|
198
|
+
getHigherStealth(currentLevel: number): Array<{
|
|
199
|
+
plugin: BrowserPlugin;
|
|
200
|
+
level: number;
|
|
201
|
+
}> {
|
|
202
|
+
return this.getOrdered().filter((e) => e.level > currentLevel);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Resolve a strategy value to a plugin.
|
|
207
|
+
*
|
|
208
|
+
* - "auto" → first enabled plugin (getDefault)
|
|
209
|
+
* - "<name>" → named plugin
|
|
210
|
+
*
|
|
211
|
+
* Returns { plugin, error? } — error is set if the plugin is not found.
|
|
212
|
+
*/
|
|
213
|
+
resolveStrategy(strategy: string): {
|
|
214
|
+
plugin?: BrowserPlugin;
|
|
215
|
+
error?: string;
|
|
216
|
+
} {
|
|
217
|
+
if (strategy === "auto") {
|
|
218
|
+
const plugin = this.getDefault();
|
|
219
|
+
if (!plugin) {
|
|
220
|
+
return { error: "No browser plugins are registered and enabled." };
|
|
221
|
+
}
|
|
222
|
+
return { plugin };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Check if the plugin exists at all (even disabled)
|
|
226
|
+
const anyEntry = this.getAny(strategy);
|
|
227
|
+
if (!anyEntry) {
|
|
228
|
+
const available = this.available();
|
|
229
|
+
return {
|
|
230
|
+
error: `Plugin '${strategy}' is not registered. Available: ${available.length > 0 ? available.join(", ") : "(none)"}`,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!anyEntry.enabled) {
|
|
235
|
+
const available = this.available();
|
|
236
|
+
return {
|
|
237
|
+
error: `Plugin '${strategy}' is disabled. Available: ${available.length > 0 ? available.join(", ") : "(none)"}`,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return { plugin: anyEntry.plugin };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Clear all registered plugins. Used for testing.
|
|
246
|
+
*/
|
|
247
|
+
clear(): void {
|
|
248
|
+
this.entries.clear();
|
|
249
|
+
this.orderedNames = [];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Number of registered plugins (enabled and disabled).
|
|
254
|
+
*/
|
|
255
|
+
get size(): number {
|
|
256
|
+
return this.entries.size;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ─── Singleton ────────────────────────────────────────────────────
|
|
261
|
+
|
|
262
|
+
/** Global plugin registry instance */
|
|
263
|
+
export const pluginRegistry = new PluginRegistry();
|