pi-chrome 0.11.0 → 0.11.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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "Pi Chrome Connector",
|
|
4
|
-
"version": "0.11.
|
|
4
|
+
"version": "0.11.1",
|
|
5
5
|
"description": "Lets Pi control tabs in Chrome via a local connector at 127.0.0.1.",
|
|
6
6
|
"permissions": ["tabs", "scripting", "storage", "activeTab", "alarms", "webNavigation", "debugger"],
|
|
7
7
|
"host_permissions": ["<all_urls>", "http://127.0.0.1:17318/*"],
|
|
@@ -103,7 +103,7 @@ setInterval(() => {
|
|
|
103
103
|
}
|
|
104
104
|
}, 5000);
|
|
105
105
|
|
|
106
|
-
function
|
|
106
|
+
function cdpRaw(tabId, method, params) {
|
|
107
107
|
return new Promise((resolve, reject) => {
|
|
108
108
|
chrome.debugger.sendCommand({ tabId }, method, params || {}, (result) => {
|
|
109
109
|
if (chrome.runtime.lastError) reject(new Error(`${method}: ${chrome.runtime.lastError.message}`));
|
|
@@ -112,6 +112,24 @@ function cdp(tabId, method, params) {
|
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
// Wraps cdpRaw with one auto-recover on detached/closed sessions:
|
|
116
|
+
// chrome.debugger.attach can stay cached in attachedTabs even after Chrome killed
|
|
117
|
+
// the session (tab nav, devtools opened/closed, etc). Recover by detaching the
|
|
118
|
+
// stale entry and re-attaching, then retry the command once.
|
|
119
|
+
async function cdp(tabId, method, params) {
|
|
120
|
+
try {
|
|
121
|
+
return await cdpRaw(tabId, method, params);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
const msg = String(error?.message || error);
|
|
124
|
+
const isStale = /Debugger is not attached|Detached while|Target closed|No tab with id/i.test(msg);
|
|
125
|
+
if (!isStale) throw error;
|
|
126
|
+
attachedTabs.delete(tabId);
|
|
127
|
+
await chrome.debugger.attach({ tabId }, CDP_VERSION).catch(() => undefined);
|
|
128
|
+
attachedTabs.set(tabId, { detachAt: Date.now() + TRUSTED_IDLE_DETACH_MS, pointer: { x: 120 + Math.random() * 200, y: 80 + Math.random() * 120 } });
|
|
129
|
+
return cdpRaw(tabId, method, params);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
115
133
|
// Resolve target -> {x, y, rect} in viewport coords by running tiny script in tab.
|
|
116
134
|
async function resolveTargetInTab(tabId, params) {
|
|
117
135
|
const results = await chrome.scripting.executeScript({
|
|
@@ -349,20 +367,48 @@ async function trustedScroll(params) {
|
|
|
349
367
|
const x = resolved.rect ? resolved.rect.left + Math.min(resolved.rect.width, 800) / 2 : resolved.x;
|
|
350
368
|
const y = resolved.rect ? resolved.rect.top + Math.min(resolved.rect.height, 600) / 2 : resolved.y;
|
|
351
369
|
const totalY = params.deltaY || 0, totalX = params.deltaX || 0;
|
|
352
|
-
|
|
353
|
-
//
|
|
354
|
-
const
|
|
370
|
+
// Cap per-step delta so IntersectionObserver / scroll-driven animations get gradient samples.
|
|
371
|
+
// Real wheel notches ~50-120px; we aim ~40-90.
|
|
372
|
+
const MAX_STEP = 80;
|
|
373
|
+
const minStepsByDelta = Math.ceil(Math.max(Math.abs(totalY), Math.abs(totalX)) / MAX_STEP);
|
|
374
|
+
const n = Math.max(6, Math.min(60, params.steps || Math.max(minStepsByDelta, 12)));
|
|
375
|
+
// Momentum shape: ramp-up, plateau, decay. Each weight peaked mid-sequence then tapers.
|
|
376
|
+
const w = [];
|
|
377
|
+
for (let i = 0; i < n; i++) {
|
|
378
|
+
const t = i / Math.max(1, n - 1);
|
|
379
|
+
// bell-ish curve biased earlier so motion starts strong then decays (like a trackpad flick).
|
|
380
|
+
const base = Math.sin(Math.min(1, t * 1.4) * Math.PI);
|
|
381
|
+
const decay = Math.pow(1 - t * 0.6, 2);
|
|
382
|
+
w.push(0.2 + base * 0.8 * decay);
|
|
383
|
+
}
|
|
355
384
|
const sumW = w.reduce((a, b) => a + b, 0);
|
|
356
385
|
for (let i = 0; i < n; i++) {
|
|
357
386
|
const dy = totalY * (w[i] / sumW), dx = totalX * (w[i] / sumW);
|
|
358
387
|
await cdp(tab.id, "Input.dispatchMouseEvent", {
|
|
359
388
|
type: "mouseWheel", x, y, deltaX: dx, deltaY: dy, pointerType: "mouse",
|
|
360
389
|
});
|
|
361
|
-
|
|
390
|
+
// Sleep ~one frame+ so IntersectionObserver / rAF samples can run between events.
|
|
391
|
+
await sleep(rng(22, 52));
|
|
362
392
|
}
|
|
363
393
|
return { trusted: true, deltaX: totalX, deltaY: totalY, steps: n };
|
|
364
394
|
}
|
|
365
395
|
|
|
396
|
+
async function trustedTap(params) {
|
|
397
|
+
const tab = await getTabByParams(params);
|
|
398
|
+
if (params.foreground) await bringToFront(tab);
|
|
399
|
+
await attachDebugger(tab.id);
|
|
400
|
+
const resolved = (params.selector || params.uid || (typeof params.x === "number" && typeof params.y === "number"))
|
|
401
|
+
? await resolveTargetInTab(tab.id, params)
|
|
402
|
+
: null;
|
|
403
|
+
if (!resolved || !resolved.found) throw new Error("trusted.tap: target not found");
|
|
404
|
+
const point = resolved.rect ? pickInsideRect(resolved.rect) : { x: resolved.x, y: resolved.y };
|
|
405
|
+
const tp = { x: point.x, y: point.y, radiusX: 8, radiusY: 8, rotationAngle: 0, force: 0.5, id: 1 };
|
|
406
|
+
await cdp(tab.id, "Input.dispatchTouchEvent", { type: "touchStart", touchPoints: [tp] });
|
|
407
|
+
await sleep(rng(40, 110));
|
|
408
|
+
await cdp(tab.id, "Input.dispatchTouchEvent", { type: "touchEnd", touchPoints: [] });
|
|
409
|
+
return { trusted: true, x: point.x, y: point.y, tag: resolved.tag };
|
|
410
|
+
}
|
|
411
|
+
|
|
366
412
|
async function trustedDrag(params) {
|
|
367
413
|
const tab = await getTabByParams(params);
|
|
368
414
|
if (params.foreground) await bringToFront(tab);
|
|
@@ -537,6 +583,8 @@ async function dispatch(action, params) {
|
|
|
537
583
|
case "page.scroll":
|
|
538
584
|
if (await wantsTrusted(params)) return trustedScroll(params);
|
|
539
585
|
return executeActionInTab(params, scrollPage, [params.selector ?? null, params.uid ?? null, params.deltaY ?? 0, params.deltaX ?? 0, params.steps ?? null]);
|
|
586
|
+
case "page.tap":
|
|
587
|
+
return trustedTap(params);
|
|
540
588
|
case "trusted.mode":
|
|
541
589
|
return setTrustedMode(params.mode);
|
|
542
590
|
case "trusted.status":
|
|
@@ -1165,6 +1165,29 @@ Usage rules:
|
|
|
1165
1165
|
},
|
|
1166
1166
|
});
|
|
1167
1167
|
|
|
1168
|
+
pi.registerTool({
|
|
1169
|
+
name: "chrome_tap",
|
|
1170
|
+
label: "Chrome Tap (Touch)",
|
|
1171
|
+
description:
|
|
1172
|
+
"Dispatch a real browser-trusted touchstart/touchend tap via chrome.debugger (CDP Input.dispatchTouchEvent). Use for sites that gate on TouchEvent rather than MouseEvent (mobile-first PWAs, swipe carousels). Always uses the trusted CDP path — the yellow debugger banner appears.",
|
|
1173
|
+
promptSnippet: "Tap (real touch) a Chrome element by snapshot uid, selector, or coordinate.",
|
|
1174
|
+
parameters: Type.Object({
|
|
1175
|
+
uid: Type.Optional(Type.String()),
|
|
1176
|
+
selector: Type.Optional(Type.String()),
|
|
1177
|
+
x: Type.Optional(Type.Number()),
|
|
1178
|
+
y: Type.Optional(Type.Number()),
|
|
1179
|
+
targetId: Type.Optional(Type.String()),
|
|
1180
|
+
urlIncludes: Type.Optional(Type.String()),
|
|
1181
|
+
titleIncludes: Type.Optional(Type.String()),
|
|
1182
|
+
background: Type.Optional(Type.Boolean()),
|
|
1183
|
+
}),
|
|
1184
|
+
async execute(_id, params): Promise<ToolTextResult> {
|
|
1185
|
+
const result = await bridge.send("page.tap", withBackground(params), DEFAULT_TIMEOUT_MS);
|
|
1186
|
+
const target = params.uid ?? params.selector ?? `${params.x},${params.y}`;
|
|
1187
|
+
return { content: [{ type: "text", text: `Tapped ${target} (touch)` }], details: { result: result as Json } };
|
|
1188
|
+
},
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1168
1191
|
pi.registerTool({
|
|
1169
1192
|
name: "chrome_scroll",
|
|
1170
1193
|
label: "Chrome Scroll",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-chrome",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "Drive your existing logged-in Chrome from Pi — no re-login, no throwaway profile, watch the agent work in real time (or toggle quiet background mode).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|