chromeflow 0.9.9 → 0.9.10
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 +10 -8
- package/package.json +1 -1
package/bin/chromeflow.mjs
CHANGED
|
@@ -25580,14 +25580,15 @@ ANTI-BOT SUBMIT CEILING \u2014 synthetic clicks on social/auth platforms (Reddit
|
|
|
25580
25580
|
until_timeout_ms: external_exports.number().int().min(500).optional().describe("How long to wait for the until-condition, in milliseconds (default 5000). Only used if one of until_* is set."),
|
|
25581
25581
|
expect_submit: external_exports.boolean().optional().describe(`Broad anti-bot detector. After the click, watch up to 4s for ANY of: URL change, [role=alert] / [data-sonner-toast] / .toast / .notification / aria-live appearance, [role=dialog] / [aria-modal=true] appearance. Returns success=false with "submit silently rejected (likely anti-bot)" when no signal fires. Use on form submits when the until_* destination isn't known. Ignored when any until_* is set (those are more specific).`),
|
|
25582
25582
|
within_selector: external_exports.string().optional().describe(`Limit candidate matches to this CSS selector's subtree (mirrors find_text's scope_selector). Use to scope nth-counting to one section of a long form: click_element("Minor Issue(s)", nth=1, within_selector="#response-b-style"). Returns success=false with scope_missed=true if the selector does not match.`),
|
|
25583
|
-
near_text: external_exports.string().optional().describe("Find the nearest container whose heading starts with this text, then scope candidates to that container's subtree. Ignored when within_selector is set. Useful when the target section has no stable CSS selector but the heading is unique.")
|
|
25583
|
+
near_text: external_exports.string().optional().describe("Find the nearest container whose heading starts with this text, then scope candidates to that container's subtree. Ignored when within_selector is set. Useful when the target section has no stable CSS selector but the heading is unique."),
|
|
25584
|
+
try_fiber: external_exports.boolean().optional().describe(`Opt-in last-resort fallback when silently_rejected fires. After the 1500ms 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. Useful on React-heavy SPAs whose action buttons pass through isTrusted=true checks even on CDP events. Returns fiber_attempted=true in the response when the path was taken. Do NOT default to this \u2014 fiber-prop walking is undocumented and may misbehave on mangled production builds. Reserve for repeat silently_rejected on a known-safe React site.`)
|
|
25584
25585
|
},
|
|
25585
|
-
async ({ textHint, nth, until_selector, until_url_contains, until_text_contains, until_url_changes, until_timeout_ms, expect_submit, within_selector, near_text }) => {
|
|
25586
|
+
async ({ textHint, nth, until_selector, until_url_contains, until_text_contains, until_url_changes, until_timeout_ms, expect_submit, within_selector, near_text, try_fiber }) => {
|
|
25586
25587
|
const wsTimeout = Math.max(3e4, (until_timeout_ms ?? 0) + 1e4);
|
|
25587
25588
|
let response;
|
|
25588
25589
|
try {
|
|
25589
25590
|
response = await bridge.request(
|
|
25590
|
-
{ type: "click_element", textHint, nth, until_selector, until_url_contains, until_text_contains, until_url_changes, until_timeout_ms, expect_submit, within_selector, near_text },
|
|
25591
|
+
{ type: "click_element", textHint, nth, until_selector, until_url_contains, until_text_contains, until_url_changes, until_timeout_ms, expect_submit, within_selector, near_text, try_fiber },
|
|
25591
25592
|
wsTimeout
|
|
25592
25593
|
);
|
|
25593
25594
|
} catch (err) {
|
|
@@ -25682,7 +25683,7 @@ Clicked element: <${r.target.tag}>${r.target.text ? ` "${r.target.text}"` : ""}
|
|
|
25682
25683
|
);
|
|
25683
25684
|
server.tool(
|
|
25684
25685
|
"wait_for",
|
|
25685
|
-
`Wait for one of: a CSS selector to appear, a text substring to appear, or an existing element's subtree to mutate. Pass exactly one of \`selector\`, \`text\`, or \`change_in\`. Pierces open shadow roots. Pass \`shadow_root: true\` when waiting for the host's shadowRoot to attach (post-SPA-navigation hydration). \`scope_selector\` limits text-mode search; \`regex: true\` interprets text as a case-insensitive regex; \`frame: "iframe.selector"\` waits inside a same-origin iframe (text mode).`,
|
|
25686
|
+
`Wait for one of: a CSS selector to appear, a text substring to appear, or an existing element's subtree to mutate. Pass exactly one of \`selector\`, \`text\`, or \`change_in\`. Pierces open AND closed shadow roots (text \`scope_selector\` pierces too). Pass \`shadow_root: true\` when waiting for the host's shadowRoot to attach (post-SPA-navigation hydration). \`scope_selector\` limits text-mode search; \`regex: true\` interprets text as a case-insensitive regex; \`frame: "iframe.selector"\` waits inside a same-origin iframe (text mode). Pass \`since: "now"\` in text mode to skip the initial check and only resolve on text appearing in a NEW DOM mutation \u2014 defeats the "stale instruction panels still in DOM" false-positive.`,
|
|
25686
25687
|
{
|
|
25687
25688
|
selector: external_exports.string().optional().describe("CSS selector to wait for."),
|
|
25688
25689
|
text: external_exports.string().optional().describe("Text substring (or regex with regex=true) to wait for."),
|
|
@@ -25690,14 +25691,15 @@ Clicked element: <${r.target.tag}>${r.target.text ? ` "${r.target.text}"` : ""}
|
|
|
25690
25691
|
timeout_ms: external_exports.number().int().optional().describe("Max ms to wait (default 30000)."),
|
|
25691
25692
|
poll_interval_ms: external_exports.number().int().optional().describe("Selector-mode poll interval (default 500). Set to 15000 for slow server-side jobs."),
|
|
25692
25693
|
shadow_root: external_exports.boolean().optional().describe("Selector mode: require the matched host to have an attached shadowRoot. Default false."),
|
|
25693
|
-
scope_selector: external_exports.string().optional().describe("Text mode: limit search to this CSS selector's subtree."),
|
|
25694
|
+
scope_selector: external_exports.string().optional().describe("Text mode: limit search to this CSS selector's subtree. Pierces shadow roots."),
|
|
25694
25695
|
regex: external_exports.boolean().optional().describe("Text mode: interpret query as a case-insensitive regex."),
|
|
25695
25696
|
frame: external_exports.string().optional().describe("Same-origin iframe CSS selector to wait inside (text mode)."),
|
|
25697
|
+
since: external_exports.enum(["now"]).optional().describe(`Text mode: gate on a NEW mutation. Skips the initial check so already-present matches don't short-circuit. Use when the page keeps stale text in the DOM after a route change (e.g. stacked instruction panels) and you need to wait for the next render.`),
|
|
25696
25698
|
settle_ms: external_exports.number().int().optional().describe("change_in mode: ms to wait after the first mutation for batching (default 150)."),
|
|
25697
25699
|
max_chars: external_exports.number().int().min(50).optional().describe("change_in mode: cap the returned text content (default 1000). Chat-style mutations can dump huge text; agents that need more should opt in explicitly.")
|
|
25698
25700
|
},
|
|
25699
25701
|
async (args) => {
|
|
25700
|
-
const { selector, text, change_in, timeout_ms, poll_interval_ms, shadow_root, scope_selector, regex, frame, settle_ms, max_chars } = args;
|
|
25702
|
+
const { selector, text, change_in, timeout_ms, poll_interval_ms, shadow_root, scope_selector, regex, frame, since, settle_ms, max_chars } = args;
|
|
25701
25703
|
const set = [selector, text, change_in].filter((v) => v !== void 0 && v !== null && v !== "").length;
|
|
25702
25704
|
if (set !== 1) {
|
|
25703
25705
|
return { content: [{ type: "text", text: "wait_for: pass exactly one of selector, text, or change_in." }] };
|
|
@@ -25713,7 +25715,7 @@ Clicked element: <${r.target.tag}>${r.target.text ? ` "${r.target.text}"` : ""}
|
|
|
25713
25715
|
}
|
|
25714
25716
|
if (text !== void 0) {
|
|
25715
25717
|
const response2 = await bridge.request(
|
|
25716
|
-
{ type: "wait_for_text", query: text, timeout_ms: timeoutMs, scope_selector, regex, frame },
|
|
25718
|
+
{ type: "wait_for_text", query: text, timeout_ms: timeoutMs, scope_selector, regex, frame, since },
|
|
25717
25719
|
timeoutMs + 5e3
|
|
25718
25720
|
);
|
|
25719
25721
|
const r2 = response2;
|
|
@@ -25898,7 +25900,7 @@ ${lines.join("\n")}${shadowSection}` }] };
|
|
|
25898
25900
|
}
|
|
25899
25901
|
|
|
25900
25902
|
// packages/mcp-server/src/index.ts
|
|
25901
|
-
var PACKAGE_VERSION = true ? "0.9.
|
|
25903
|
+
var PACKAGE_VERSION = true ? "0.9.10" : "dev";
|
|
25902
25904
|
main().catch((err) => {
|
|
25903
25905
|
console.error("[chromeflow] Fatal error:", err);
|
|
25904
25906
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chromeflow",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.10",
|
|
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",
|