chromeflow 0.10.25 → 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.
- package/bin/chromeflow.mjs +43 -8
- package/package.json +1 -1
package/bin/chromeflow.mjs
CHANGED
|
@@ -24670,7 +24670,8 @@ var WsBridge = class {
|
|
|
24670
24670
|
return;
|
|
24671
24671
|
}
|
|
24672
24672
|
if (msg.type === "ready") {
|
|
24673
|
-
|
|
24673
|
+
const token = typeof msg.token === "string" ? msg.token : void 0;
|
|
24674
|
+
console.error(`[chromeflow] Extension ready${token ? " (token presented)" : ""}`);
|
|
24674
24675
|
const cwd = process.cwd();
|
|
24675
24676
|
const host = process.env.CHROMEFLOW_HOST ?? (process.env.CLAUDE_PLUGIN_ROOT ? "claude" : void 0);
|
|
24676
24677
|
ws.send(JSON.stringify({
|
|
@@ -25299,17 +25300,51 @@ ${lines.join("\n")}${r.warning ?? ""}${captchaLine}${oauthLine}` }] };
|
|
|
25299
25300
|
);
|
|
25300
25301
|
server.tool(
|
|
25301
25302
|
"set_file_input",
|
|
25302
|
-
|
|
25303
|
+
`Upload a file to a file input \u2014 works even when the input is hidden behind a custom drag-and-drop zone. Returns success=true only after an observable commit (file count goes up, input gets reset, or verify_selector appears within wait_ms). See CLAUDE.md for batch-upload guidance.
|
|
25304
|
+
|
|
25305
|
+
Two ways to supply the file:
|
|
25306
|
+
- file_path (CDP mode): an absolute path on the machine running this server. Reaches both open AND closed shadow roots.
|
|
25307
|
+
- file_content + file_name (inline-content mode): base64 file bytes plus a filename, materialized into the input directly. Use this when the server has no local disk access (e.g. a remote endpoint that can't see your filesystem). Caveat: inline-content mode reaches OPEN shadow roots only \u2014 if the input lives in a closed shadow root, use file_path instead.
|
|
25308
|
+
|
|
25309
|
+
Provide file_path OR file_content, not both.`,
|
|
25303
25310
|
{
|
|
25304
25311
|
hint: external_exports.string().describe("Label text, name, or surrounding text of the file input. Use empty string to target the first file input on the page."),
|
|
25305
|
-
file_path: external_exports.string().describe("Absolute path to the file to upload (e.g. /Users/you/Downloads/task.zip)"),
|
|
25312
|
+
file_path: external_exports.string().optional().describe("Absolute path to the file to upload (e.g. /Users/you/Downloads/task.zip). CDP mode \u2014 reaches closed shadow roots. Provide this OR file_content."),
|
|
25313
|
+
file_content: external_exports.string().optional().describe("Base64-encoded file bytes (no data: prefix) for inline-content mode. Use when the server has no local disk access. Requires file_name. Reaches OPEN shadow roots only. Provide this OR file_path."),
|
|
25314
|
+
file_name: external_exports.string().optional().describe('Filename to present to the page in inline-content mode (e.g. "report.pdf"). Required when file_content is set.'),
|
|
25315
|
+
mime_type: external_exports.string().optional().describe('Optional MIME type for inline-content mode (e.g. "application/pdf"). Inferred from file_name when omitted.'),
|
|
25306
25316
|
wait_ms: external_exports.number().int().min(0).optional().describe("How long to wait for an observable change after setting the file (default 3000). Increase for slow uploaders that take a moment to render thumbnails."),
|
|
25307
25317
|
verify_selector: external_exports.string().optional().describe('Optional CSS selector that should appear after a successful upload (e.g. ".photo-thumbnail", "[data-uploaded=true]"). When matched, set_file_input returns success immediately.')
|
|
25308
25318
|
},
|
|
25309
|
-
async ({ hint, file_path, wait_ms, verify_selector }) => {
|
|
25319
|
+
async ({ hint, file_path, file_content, file_name, mime_type, wait_ms, verify_selector }) => {
|
|
25320
|
+
if (!file_path && !file_content) {
|
|
25321
|
+
return {
|
|
25322
|
+
content: [{ type: "text", text: "Failed to set file: provide either file_path or file_content." }]
|
|
25323
|
+
};
|
|
25324
|
+
}
|
|
25325
|
+
if (file_path && file_content) {
|
|
25326
|
+
return {
|
|
25327
|
+
content: [{ type: "text", text: "Failed to set file: provide file_path OR file_content, not both." }]
|
|
25328
|
+
};
|
|
25329
|
+
}
|
|
25330
|
+
if (file_content && !file_name) {
|
|
25331
|
+
return {
|
|
25332
|
+
content: [{ type: "text", text: "Failed to set file: file_content requires file_name." }]
|
|
25333
|
+
};
|
|
25334
|
+
}
|
|
25310
25335
|
const wsTimeout = Math.max(3e4, (wait_ms ?? 3e3) + 1e4);
|
|
25311
25336
|
const response = await bridge.request(
|
|
25312
|
-
{
|
|
25337
|
+
{
|
|
25338
|
+
type: "set_file_input",
|
|
25339
|
+
hint,
|
|
25340
|
+
// snake -> camel, mirroring the existing file_path -> filePath mapping.
|
|
25341
|
+
filePath: file_path,
|
|
25342
|
+
fileContent: file_content,
|
|
25343
|
+
fileName: file_name,
|
|
25344
|
+
mimeType: mime_type,
|
|
25345
|
+
waitMs: wait_ms,
|
|
25346
|
+
verifySelector: verify_selector
|
|
25347
|
+
},
|
|
25313
25348
|
wsTimeout
|
|
25314
25349
|
);
|
|
25315
25350
|
const r = response;
|
|
@@ -25327,7 +25362,7 @@ ${lines.join("\n")}${r.warning ?? ""}${captchaLine}${oauthLine}` }] };
|
|
|
25327
25362
|
**Shadow-piercing helpers are pre-injected** into every script:
|
|
25328
25363
|
- \`$deep(selector, root?)\` \u2014 querySelector that walks open shadow roots
|
|
25329
25364
|
- \`$deepAll(selector, root?)\` \u2014 querySelectorAll equivalent, returns an array
|
|
25330
|
-
- \`shadowDocument\` \u2014 the open shadow root with the most interactive elements (buttons, inputs, links), or \`document\` if none. Useful when an SPA mounts ALL of its UI inside a single root shadow host (
|
|
25365
|
+
- \`shadowDocument\` \u2014 the open shadow root with the most interactive elements (buttons, inputs, links), or \`document\` if none. Useful when an SPA mounts ALL of its UI inside a single root shadow host (annotation-style dashboards that wrap the whole app in one web component): replace every \`document.querySelector*\` call with \`shadowDocument.querySelector*\` and the same code now reaches the SPA's content. On pages with multiple shadow roots (e.g. one for CSS theme vars, one for content), this picks the content root automatically.
|
|
25331
25366
|
- \`shadowDocuments\` \u2014 array of ALL open shadow roots on the page (in DOM order). Use when you need to search across multiple shadow roots or when the automatic pick is wrong.
|
|
25332
25367
|
|
|
25333
25368
|
The helpers pierce OPEN shadow roots only \u2014 MAIN world can't reach closed roots. For closed roots, use find_text / get_page_text / click_element / fill_input which pierce both kinds via chrome.dom.openOrClosedShadowRoot.
|
|
@@ -25843,7 +25878,7 @@ ANTI-BOT SUBMIT CEILING \u2014 synthetic clicks on social/auth platforms (Reddit
|
|
|
25843
25878
|
try_fiber: external_exports.boolean().optional().describe(`Opt-in last-resort fallback when silently_rejected fires. After the activity probe reports zero activity, chromeflow walks the React fiber tree from the matched element (up to 12 levels), finds the nearest \`__reactProps$.onClick\` prop, and invokes it with a minimal synthetic event. Now shadow-DOM-aware: when the element is inside a shadow root, the fiber walk searches for React root containers inside that shadow root instead of walking the light DOM. Returns fiber_attempted=true in the response when the path was taken. Do NOT default to this; reserve for repeat silently_rejected on a known-safe React site.`),
|
|
25844
25879
|
activity_timeout_ms: external_exports.number().int().min(500).optional().describe(`How long the activity probe watches for DOM mutations, focus changes, URL changes, or alert/toast/modal appearance after the click (default 1500ms). The probe returns early as soon as activity is detected, so the default adds only ~100ms on successful clicks. Increase to 2500-4000ms for buttons that trigger async API calls before producing visible DOM changes. The probe reports "silently_rejected" when zero activity is seen within this window; a higher value reduces false negatives but slows genuine rejection detection. For buttons where the click triggers a 3-5s API call with no immediate DOM change, use skip_activity_probe instead.`),
|
|
25845
25880
|
skip_activity_probe: external_exports.boolean().optional().describe(`Skip the 1500ms activity probe AND all automatic fallbacks (tap gesture, pointer chain, DOM .click(), fiber walk) after the CDP click. The click is dispatched and success is returned immediately without verifying page activity. Use for buttons that trigger slow async API calls (3-5s+) before producing visible DOM changes, where the activity probe would falsely report "silently_rejected" and the fallback chain would double-fire. Verify state yourself via find_text or execute_script after an appropriate delay. NOTE: this is set IMPLICITLY when any until_* clause is passed, since the until-clause already provides verification. WARNING: no automatic silent-rejection detection when this is set without an until-clause.`),
|
|
25846
|
-
via: external_exports.enum(["auto", "cdp", "fiber"]).optional().describe(`Click dispatch mode. "auto" (default): CDP click, then fiber fallback when try_fiber=true and the activity probe failed. "cdp": CDP click only, no fiber fallback ever. "fiber": skip the CDP bezier + activity probe entirely and invoke __reactProps$.onClick directly. Use "fiber" on React-heavy SPAs (
|
|
25881
|
+
via: external_exports.enum(["auto", "cdp", "fiber"]).optional().describe(`Click dispatch mode. "auto" (default): CDP click, then fiber fallback when try_fiber=true and the activity probe failed. "cdp": CDP click only, no fiber fallback ever. "fiber": skip the CDP bezier + activity probe entirely and invoke __reactProps$.onClick directly. Use "fiber" on React-heavy SPAs (fiber-only annotation dashboards) where you already know the site is fiber-only \u2014 cuts ~3 seconds of ceremony off the round trip. The fiber path is undocumented React internal access, prefer "auto" until you've confirmed the site needs it.`),
|
|
25847
25882
|
in_dialog: external_exports.boolean().optional().describe(`Scope candidate matches to the topmost open dialog (\`[role=dialog]\`, \`[role=alertdialog]\`, or \`<dialog open>\`), highest z-index wins. Use when Radix/Headless UI dialogs portal to document.body and a generic textHint like "Cancel" would otherwise match the wrong button. Returns scope_missed=true when no dialog is open.`),
|
|
25848
25883
|
dialog_query: external_exports.string().optional().describe(`Scope candidate matches to a specific dialog by heading or aria-label substring. Use when multiple dialogs are open and in_dialog (topmost) would pick the wrong one \u2014 e.g. click_element("Confirm", dialog_query="Delete account"). Mutually exclusive with in_dialog; dialog_query wins when both are set.`),
|
|
25849
25884
|
wait_until_enabled_ms: external_exports.number().int().min(0).optional().describe(`When the matched target is currently disabled (native disabled OR aria-disabled=true), poll for up to this many ms waiting for it to become enabled before clicking. Default 0 (do not wait \u2014 return target_disabled immediately). Use 2000-5000 for Submit-style buttons that briefly disable while an async copilot/validator/save is in flight. On timeout the response carries target_disabled=true plus a structured disabled_state snapshot (disabled, aria_disabled, pointer_events, opacity, visible) so the caller can decide between "wait more" or "field is genuinely missing \u2014 run get_form_fields(only_empty:true)".`)
|
|
@@ -26259,7 +26294,7 @@ ${lines.join("\n")}${shadowSection}` }] };
|
|
|
26259
26294
|
}
|
|
26260
26295
|
|
|
26261
26296
|
// packages/mcp-server/src/index.ts
|
|
26262
|
-
var PACKAGE_VERSION = true ? "0.
|
|
26297
|
+
var PACKAGE_VERSION = true ? "0.11.1" : "dev";
|
|
26263
26298
|
main().catch((err) => {
|
|
26264
26299
|
console.error("[chromeflow] Fatal error:", err);
|
|
26265
26300
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chromeflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "MCP server for chromeflow — lets Claude Code or Codex CLI drive your real Chrome browser with sessions intact. Plugin install recommended; npx chromeflow for manual MCP wiring.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./bin/chromeflow.mjs",
|