chromeflow 0.1.31 → 0.1.32

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/CLAUDE.md CHANGED
@@ -41,8 +41,9 @@ Do NOT ask "should I open the browser?" — just do it. The user expects seamles
41
41
  a. Claude acts directly:
42
42
  click_element("Save") — press buttons/links Claude can press
43
43
  get_page_text() or wait_for_selector(".success") — ALWAYS confirm after click; click_element returns after 600ms regardless of outcome
44
- fill_input("Product name", "Pro") — fill fields Claude knows the answer to (works on React, CodeMirror, and contenteditable)
45
- clear_overlays() call this immediately after fill_input succeeds
44
+ fill_form([{label, value}, ...]) — fill multiple fields in one call; prefer over repeated fill_input
45
+ fill_input("Product name", "Pro") fill a single field (works on React, CodeMirror, and contenteditable)
46
+ clear_overlays() — call this immediately after fill_input/fill_form succeeds
46
47
  scroll_to_element("label text") — jump directly to a known field; prefer this over scroll_page when the target is known
47
48
  scroll_page("down") — reveal off-screen content when target location is unknown
48
49
  b. Check results with text, not vision:
@@ -51,8 +52,9 @@ Do NOT ask "should I open the browser?" — just do it. The user expects seamles
51
52
  execute_script("document.title") — query DOM state programmatically
52
53
  c. When an element can't be found or clicked:
53
54
  scroll_page("down") and retry — always try this first
54
- get_elements() — get EXACT DOM coords, use these in highlight_region
55
- highlight_region(x,y,w,h,msg) use exact coords from get_elements
55
+ get_elements() — get EXACT DOM coords when needed
56
+ highlight_region(selector,msg) highlight by CSS selector (preferred; scrolls element into view automatically)
57
+ highlight_region(x,y,w,h,msg) — highlight by coords only if no selector available (coords go stale on scroll)
56
58
  [absolute last resort] take_screenshot() — only if you genuinely can't identify the element from DOM
57
59
  d. Pause for the user when needed:
58
60
  find_and_highlight(text, msg) — show the user what to do
@@ -102,9 +104,14 @@ use `take_and_copy_screenshot()` — it saves a PNG to ~/Downloads and copies it
102
104
  - `get_form_fields()` includes `[type=file]` fields even when they are visually hidden behind
103
105
  custom drag-and-drop zones. File fields are marked "manual only" — highlight them and ask
104
106
  the user to select the file; they cannot be filled programmatically.
105
- - `fill_input` works on React-controlled inputs, contenteditable (Stripe, Notion), and
106
- **CodeMirror 6 editors** it auto-detects all three. No `execute_script` workaround needed.
107
- After filling, `fill_input` reads back the value and warns if React did not accept it.
107
+ - For forms with multiple fields, use `fill_form([{label, value}, ...])` to fill them all
108
+ in a single call. It returns a per-field success/failure report so you can immediately see
109
+ which fields weren't found. Use `fill_input` only for a single field.
110
+ - `fill_input` and `fill_form` work on React-controlled inputs, contenteditable (Stripe,
111
+ Notion), and **CodeMirror 6 editors** — auto-detected. After filling, the value is read
112
+ back and a warning is shown if React did not accept it.
113
+ - After any radio/checkbox click that reveals new fields, call `get_form_fields()` again —
114
+ the inventory will include the new fields and warn if more hidden ones still exist.
108
115
  - If a form has collapsible sections, expand them all before calling `get_form_fields()` so
109
116
  the field list is complete. Use the `[under: "section name"]` context in each field's entry
110
117
  to identify fields by section rather than by index — indices shift when sections expand.
package/dist/setup.js CHANGED
@@ -165,7 +165,9 @@ const CHROMEFLOW_TOOLS = [
165
165
  "save_page_state",
166
166
  "restore_page_state",
167
167
  // v0.1.25+
168
- "take_and_copy_screenshot"
168
+ "take_and_copy_screenshot",
169
+ // v0.1.32+
170
+ "fill_form"
169
171
  ].map((t) => `mcp__chromeflow__${t}`);
170
172
  function patchSettingsLocalJson(cwd) {
171
173
  const claudeDir = join(cwd, ".claude");
@@ -153,9 +153,10 @@ Unlike get_elements, this includes ALL fields (even far below the fold) and is n
153
153
  async () => {
154
154
  const response = await bridge.request({ type: "get_form_fields" });
155
155
  if (response.type !== "form_fields_response") throw new Error("Unexpected response");
156
- const fields = response.fields;
156
+ const r = response;
157
+ const fields = r.fields;
157
158
  if (fields.length === 0) {
158
- return { content: [{ type: "text", text: "No form fields found on page." }] };
159
+ return { content: [{ type: "text", text: "No form fields found on page." + (r.warning ?? "") }] };
159
160
  }
160
161
  const lines = fields.map((f) => {
161
162
  const val = f.value ? ` [currently: "${f.value}"]` : "";
@@ -164,7 +165,7 @@ Unlike get_elements, this includes ALL fields (even far below the fold) and is n
164
165
  });
165
166
  return {
166
167
  content: [{ type: "text", text: `Form fields (${fields.length} total, sorted top-to-bottom):
167
- ${lines.join("\n")}` }]
168
+ ${lines.join("\n")}${r.warning ?? ""}` }]
168
169
  };
169
170
  }
170
171
  );
@@ -104,8 +104,36 @@ Examples: scroll_to_element("#submit-btn"), scroll_to_element("Billing address")
104
104
  query: z.string().describe("CSS selector (e.g. '#my-input', '.section-header') or visible text / label to search for")
105
105
  },
106
106
  async ({ query }) => {
107
- await bridge.request({ type: "scroll_to_element", query });
108
- return { content: [{ type: "text", text: `Scrolled to element matching "${query}".` }] };
107
+ const response = await bridge.request({ type: "scroll_to_element", query });
108
+ const msg = response.message ?? `Scrolled to element matching "${query}".`;
109
+ return { content: [{ type: "text", text: msg }] };
110
+ }
111
+ );
112
+ server.tool(
113
+ "fill_form",
114
+ `Fill multiple form fields in a single call by targeting each field by its label text.
115
+ Use this instead of calling fill_input repeatedly \u2014 it fills all fields in one round trip and returns a per-field success report.
116
+ Ideal for forms with many textareas or inputs where each fill would otherwise require a separate tool call.
117
+ fields is an array of {label, value} pairs. label should match the field's visible label, placeholder, or aria-label.`,
118
+ {
119
+ fields: z.array(
120
+ z.object({
121
+ label: z.string().describe("Visible label, placeholder, or aria-label of the field"),
122
+ value: z.string().describe("Value to fill in")
123
+ })
124
+ ).describe("List of fields to fill")
125
+ },
126
+ async ({ fields }) => {
127
+ const response = await bridge.request({ type: "fill_form", fields });
128
+ const r = response;
129
+ const lines = r.results.map((f) => `${f.success ? "\u2713" : "\u2717"} "${f.label}": ${f.message}`);
130
+ return {
131
+ content: [{
132
+ type: "text",
133
+ text: `Filled ${r.succeeded}/${r.total} fields:
134
+ ${lines.join("\n")}`
135
+ }]
136
+ };
109
137
  }
110
138
  );
111
139
  server.tool(
@@ -36,12 +36,15 @@ function registerHighlightTools(server, bridge) {
36
36
  );
37
37
  server.tool(
38
38
  "highlight_region",
39
- "Highlight a specific pixel region on the page with an instructional callout. Use the exact coordinates returned by get_elements \u2014 do not estimate positions. Only use take_screenshot first if get_elements cannot identify the element.",
39
+ `Highlight a region on the page with an instructional callout.
40
+ Prefer passing a CSS selector \u2014 the extension will find the element, scroll it into view, and highlight its exact bounds automatically. This is more robust than pixel coordinates, which go stale if the user scrolls.
41
+ Only pass x/y/width/height when you have no selector and already have fresh coordinates from get_elements.`,
40
42
  {
41
- x: z.number().describe("Left edge of the region in CSS pixels"),
42
- y: z.number().describe("Top edge of the region in CSS pixels"),
43
- width: z.number().describe("Width of the region in CSS pixels"),
44
- height: z.number().describe("Height of the region in CSS pixels"),
43
+ selector: z.string().optional().describe("CSS selector of the element to highlight (e.g. '#upload-zone', '.drop-area'). Preferred over raw coordinates."),
44
+ x: z.number().optional().describe("Left edge in CSS pixels \u2014 only needed if no selector"),
45
+ y: z.number().optional().describe("Top edge in CSS pixels \u2014 only needed if no selector"),
46
+ width: z.number().optional().describe("Width in CSS pixels \u2014 only needed if no selector"),
47
+ height: z.number().optional().describe("Height in CSS pixels \u2014 only needed if no selector"),
45
48
  message: z.string().describe(
46
49
  "Instruction to show the user in the callout. When the user needs to type something, use a short instruction like 'Type this in the field:' and pass the text as valueToType."
47
50
  ),
@@ -49,13 +52,13 @@ function registerHighlightTools(server, bridge) {
49
52
  `Only use when the user must personally type the value (password, email, personal data). Do NOT use when Claude will auto-fill after the click \u2014 in that case, omit this and use message: "Click here \u2014 I'll fill it in".`
50
53
  )
51
54
  },
52
- async ({ x, y, width, height, message, valueToType }) => {
53
- await bridge.request({ type: "highlight_region", x, y, width, height, message, valueToType });
55
+ async ({ selector, x, y, width, height, message, valueToType }) => {
56
+ await bridge.request({ type: "highlight_region", selector, x, y, width, height, message, valueToType });
54
57
  return {
55
58
  content: [
56
59
  {
57
60
  type: "text",
58
- text: `Region highlighted at (${x}, ${y}) ${width}\xD7${height}.`
61
+ text: selector ? `Highlighted element matching "${selector}".` : `Region highlighted at (${x ?? 0}, ${y ?? 0}) ${width ?? 0}\xD7${height ?? 0}.`
59
62
  }
60
63
  ]
61
64
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chromeflow",
3
- "version": "0.1.31",
3
+ "version": "0.1.32",
4
4
  "description": "Browser guidance MCP server for Claude Code — highlights, clicks, fills, and captures from the web so you don't have to.",
5
5
  "type": "module",
6
6
  "bin": {