skills
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import puppeteer from "puppeteer-core";
|
||||
|
||||
const message = process.argv.slice(2).join(" ");
|
||||
if (!message) {
|
||||
console.log("Usage: browser-pick.js 'message'");
|
||||
console.log("\nExample:");
|
||||
console.log(' browser-pick.js "Click the submit button"');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const b = await Promise.race([
|
||||
puppeteer.connect({
|
||||
browserURL: "http://localhost:9222",
|
||||
defaultViewport: null,
|
||||
}),
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 5000)),
|
||||
]).catch((e) => {
|
||||
console.error("✗ Could not connect to browser:", e.message);
|
||||
console.error(" Run: browser-start.js");
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
const p = (await b.pages()).at(-1);
|
||||
|
||||
if (!p) {
|
||||
console.error("✗ No active tab found");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Inject pick() helper into current page
|
||||
await p.evaluate(() => {
|
||||
if (!window.pick) {
|
||||
window.pick = async (message) => {
|
||||
if (!message) {
|
||||
throw new Error("pick() requires a message parameter");
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
const selections = [];
|
||||
const selectedElements = new Set();
|
||||
|
||||
const overlay = document.createElement("div");
|
||||
overlay.style.cssText =
|
||||
"position:fixed;top:0;left:0;width:100%;height:100%;z-index:2147483647;pointer-events:none";
|
||||
|
||||
const highlight = document.createElement("div");
|
||||
highlight.style.cssText =
|
||||
"position:absolute;border:2px solid #3b82f6;background:rgba(59,130,246,0.1);transition:all 0.1s";
|
||||
overlay.appendChild(highlight);
|
||||
|
||||
const banner = document.createElement("div");
|
||||
banner.style.cssText =
|
||||
"position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:#1f2937;color:white;padding:12px 24px;border-radius:8px;font:14px sans-serif;box-shadow:0 4px 12px rgba(0,0,0,0.3);pointer-events:auto;z-index:2147483647";
|
||||
|
||||
const updateBanner = () => {
|
||||
banner.textContent = `${message} (${selections.length} selected, Cmd/Ctrl+click to add, Enter to finish, ESC to cancel)`;
|
||||
};
|
||||
updateBanner();
|
||||
|
||||
document.body.append(banner, overlay);
|
||||
|
||||
const cleanup = () => {
|
||||
document.removeEventListener("mousemove", onMove, true);
|
||||
document.removeEventListener("click", onClick, true);
|
||||
document.removeEventListener("keydown", onKey, true);
|
||||
overlay.remove();
|
||||
banner.remove();
|
||||
selectedElements.forEach((el) => {
|
||||
el.style.outline = "";
|
||||
});
|
||||
};
|
||||
|
||||
const onMove = (e) => {
|
||||
const el = document.elementFromPoint(e.clientX, e.clientY);
|
||||
if (!el || overlay.contains(el) || banner.contains(el)) return;
|
||||
const r = el.getBoundingClientRect();
|
||||
highlight.style.cssText = `position:absolute;border:2px solid #3b82f6;background:rgba(59,130,246,0.1);top:${r.top}px;left:${r.left}px;width:${r.width}px;height:${r.height}px`;
|
||||
};
|
||||
|
||||
const buildElementInfo = (el) => {
|
||||
const parents = [];
|
||||
let current = el.parentElement;
|
||||
while (current && current !== document.body) {
|
||||
const parentInfo = current.tagName.toLowerCase();
|
||||
const id = current.id ? `#${current.id}` : "";
|
||||
const cls = current.className
|
||||
? `.${current.className.trim().split(/\s+/).join(".")}`
|
||||
: "";
|
||||
parents.push(parentInfo + id + cls);
|
||||
current = current.parentElement;
|
||||
}
|
||||
|
||||
return {
|
||||
tag: el.tagName.toLowerCase(),
|
||||
id: el.id || null,
|
||||
class: el.className || null,
|
||||
text: el.textContent?.trim().slice(0, 200) || null,
|
||||
html: el.outerHTML.slice(0, 500),
|
||||
parents: parents.join(" > "),
|
||||
};
|
||||
};
|
||||
|
||||
const onClick = (e) => {
|
||||
if (banner.contains(e.target)) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const el = document.elementFromPoint(e.clientX, e.clientY);
|
||||
if (!el || overlay.contains(el) || banner.contains(el)) return;
|
||||
|
||||
if (e.metaKey || e.ctrlKey) {
|
||||
if (!selectedElements.has(el)) {
|
||||
selectedElements.add(el);
|
||||
el.style.outline = "3px solid #10b981";
|
||||
selections.push(buildElementInfo(el));
|
||||
updateBanner();
|
||||
}
|
||||
} else {
|
||||
cleanup();
|
||||
const info = buildElementInfo(el);
|
||||
resolve(selections.length > 0 ? selections : info);
|
||||
}
|
||||
};
|
||||
|
||||
const onKey = (e) => {
|
||||
if (e.key === "Escape") {
|
||||
e.preventDefault();
|
||||
cleanup();
|
||||
resolve(null);
|
||||
} else if (e.key === "Enter" && selections.length > 0) {
|
||||
e.preventDefault();
|
||||
cleanup();
|
||||
resolve(selections);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("mousemove", onMove, true);
|
||||
document.addEventListener("click", onClick, true);
|
||||
document.addEventListener("keydown", onKey, true);
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const result = await p.evaluate((msg) => window.pick(msg), message);
|
||||
|
||||
if (Array.isArray(result)) {
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
if (i > 0) console.log("");
|
||||
for (const [key, value] of Object.entries(result[i])) {
|
||||
console.log(`${key}: ${value}`);
|
||||
}
|
||||
}
|
||||
} else if (typeof result === "object" && result !== null) {
|
||||
for (const [key, value] of Object.entries(result)) {
|
||||
console.log(`${key}: ${value}`);
|
||||
}
|
||||
} else {
|
||||
console.log(result);
|
||||
}
|
||||
|
||||
await b.disconnect();
|
||||
Reference in New Issue
Block a user