chromeflow 0.1.13 → 0.1.16

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
@@ -49,6 +49,7 @@ Do NOT ask "should I open the browser?" — just do it. The user expects seamles
49
49
  3. For each step:
50
50
  a. Claude acts directly:
51
51
  click_element("Save") — press buttons/links Claude can press
52
+ wait_for_selector(".success") or get_page_text() — ALWAYS confirm after click; click_element returns after 600ms regardless of outcome
52
53
  fill_input("Product name", "Pro") — fill fields Claude knows the answer to
53
54
  clear_overlays() — call this immediately after fill_input succeeds
54
55
  scroll_page("down") — reveal off-screen content then retry
@@ -102,6 +103,7 @@ Use the absolute path for `envPath` — it's the Claude Code working directory +
102
103
  ## Error handling
103
104
  - After any action → `get_page_text()` to check for errors (not `take_screenshot`)
104
105
  - After `click_element("Save")` / form submission → use `get_page_text()` or `wait_for_selector` to confirm. Never use `wait_for_navigation` — most form saves don't navigate.
106
+ - After `click_element` → always confirm with `wait_for_selector(".selector")` or `get_page_text()`. `click_element` returns success after 600ms even if the action had no effect.
105
107
  - `click_element` not found → `scroll_page("down")` then retry
106
108
  - Still not found → `get_elements()` to get exact coords, then `highlight_region(x,y,w,h,msg)` using those coords. Only use `take_screenshot()` if you need to visually inspect the page.
107
109
  - `fill_input` not found → `click_element(hint)` to focus the field, then retry `fill_input`. If still failing, use `find_and_highlight(hint, "Click here — I'll fill it in")` (NO `valueToType`) then `wait_for_click()` then retry `fill_input` — after the user focuses the field by clicking, the active-element fallback fills it automatically. `find_and_highlight` uses DOM positioning (pixel-perfect) — only fall back to `take_screenshot` + `highlight_region` if `find_and_highlight` returns false. After `fill_input` succeeds, immediately call `clear_overlays()` to remove the highlight. Only use `valueToType` when the user genuinely must type the value themselves (e.g. password, personal data).
package/README.md CHANGED
@@ -19,7 +19,11 @@ Claude drives the flow. You only touch the browser for things that genuinely nee
19
19
  npx chromeflow setup
20
20
  ```
21
21
 
22
- This registers the MCP server in `~/.claude.json` and writes a `CLAUDE.md` into your project so Claude knows when and how to use Chromeflow.
22
+ This:
23
+ - Registers the MCP server in `~/.claude.json`
24
+ - Writes `CLAUDE.md` into your project so Claude knows when and how to use Chromeflow
25
+ - Adds a hint to `~/.claude/CLAUDE.md` so Claude will suggest `npx chromeflow setup` in any project that isn't yet configured
26
+ - Pre-approves Chromeflow tools in `.claude/settings.local.json` (no per-action prompts)
23
27
 
24
28
  **2. Load the Chrome extension** (one time):
25
29
 
@@ -48,13 +52,19 @@ Claude will navigate, highlight steps, click what it can, pause for anything sen
48
52
 
49
53
  ## Adding to another project
50
54
 
51
- Run the setup wizard from the new project's directory:
55
+ Run setup from the new project's directory — the MCP server is already registered globally, this just drops `CLAUDE.md` and tool permissions into the project:
52
56
 
53
57
  ```bash
54
58
  npx chromeflow setup
55
59
  ```
56
60
 
57
- The MCP server is already registered globally — this just adds `CLAUDE.md` to the project.
61
+ ## Commands
62
+
63
+ | Command | What it does |
64
+ |---------|-------------|
65
+ | `npx chromeflow setup` | Register MCP server, write project `CLAUDE.md`, pre-approve tools |
66
+ | `npx chromeflow update` | Refresh the project `CLAUDE.md` with the latest instructions |
67
+ | `npx chromeflow uninstall` | Remove all Chromeflow config (MCP entry, `CLAUDE.md` sections, tool permissions) |
58
68
 
59
69
  ## Development
60
70
 
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { registerBrowserTools } from "./tools/browser.js";
6
6
  import { registerHighlightTools } from "./tools/highlight.js";
7
7
  import { registerCaptureTools } from "./tools/capture.js";
8
8
  import { registerFlowTools } from "./tools/flow.js";
9
- import { runSetup, runUpdate } from "./setup.js";
9
+ import { runSetup, runUpdate, runUninstall } from "./setup.js";
10
10
  if (process.argv[2] === "setup") {
11
11
  runSetup().catch((err) => {
12
12
  console.error(err);
@@ -17,6 +17,11 @@ if (process.argv[2] === "setup") {
17
17
  console.error(err);
18
18
  process.exit(1);
19
19
  });
20
+ } else if (process.argv[2] === "uninstall") {
21
+ runUninstall().catch((err) => {
22
+ console.error(err);
23
+ process.exit(1);
24
+ });
20
25
  } else {
21
26
  main().catch((err) => {
22
27
  console.error("[chromeflow] Fatal error:", err);
@@ -27,7 +32,7 @@ async function main() {
27
32
  const bridge = new WsBridge();
28
33
  const server = new McpServer({
29
34
  name: "chromeflow",
30
- version: "0.1.13"
35
+ version: "0.1.14"
31
36
  });
32
37
  registerBrowserTools(server, bridge);
33
38
  registerHighlightTools(server, bridge);
package/dist/setup.js CHANGED
@@ -1,4 +1,4 @@
1
- import { readFileSync, writeFileSync, existsSync } from "fs";
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
2
2
  import { homedir } from "os";
3
3
  import { join, resolve, dirname } from "path";
4
4
  import { fileURLToPath } from "url";
@@ -139,6 +139,50 @@ function patchProjectClaudeMd(cwd, force = false) {
139
139
  writeFileSync(claudeMdPath, content);
140
140
  return "created";
141
141
  }
142
+ const CHROMEFLOW_TOOLS = [
143
+ "open_page",
144
+ "take_screenshot",
145
+ "clear_overlays",
146
+ "get_elements",
147
+ "execute_script",
148
+ "fill_input",
149
+ "read_element",
150
+ "get_page_text",
151
+ "write_to_env",
152
+ "scroll_page",
153
+ "click_element",
154
+ "wait_for_click",
155
+ "wait_for_selector",
156
+ "mark_step_done",
157
+ "find_and_highlight",
158
+ "highlight_region",
159
+ "show_guide_panel"
160
+ ].map((t) => `mcp__chromeflow__${t}`);
161
+ function patchSettingsLocalJson(cwd) {
162
+ const claudeDir = join(cwd, ".claude");
163
+ const settingsPath = join(claudeDir, "settings.local.json");
164
+ let settings = {};
165
+ if (existsSync(settingsPath)) {
166
+ try {
167
+ settings = JSON.parse(readFileSync(settingsPath, "utf8"));
168
+ } catch {
169
+ settings = {};
170
+ }
171
+ }
172
+ if (!settings.permissions || typeof settings.permissions !== "object") {
173
+ settings.permissions = {};
174
+ }
175
+ const perms = settings.permissions;
176
+ const existing = Array.isArray(perms.allow) ? perms.allow : [];
177
+ const withoutChromeflow = existing.filter((t) => !t.startsWith("mcp__chromeflow__"));
178
+ const merged = [...withoutChromeflow, ...CHROMEFLOW_TOOLS];
179
+ const changed = merged.length !== existing.length || CHROMEFLOW_TOOLS.some((t) => !existing.includes(t));
180
+ if (!changed) return "already-present";
181
+ perms.allow = merged;
182
+ if (!existsSync(claudeDir)) mkdirSync(claudeDir, { recursive: true });
183
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
184
+ return "updated";
185
+ }
142
186
  function tryOpenExtensionsPage() {
143
187
  try {
144
188
  execSync('open -a "Google Chrome" "chrome://extensions"', { stdio: "ignore" });
@@ -152,11 +196,31 @@ function tryOpenExtensionsPage() {
152
196
  }
153
197
  }
154
198
  }
199
+ function patchGlobalClaudeMd() {
200
+ const globalClaudeMdPath = join(HOME, ".claude", "CLAUDE.md");
201
+ const hint = `## Chromeflow
202
+
203
+ chromeflow is installed globally as an MCP server.
204
+
205
+ If you are working in a project and the project's CLAUDE.md does not contain chromeflow
206
+ instructions, tell the user: "Run \`npx chromeflow setup\` in this project directory to
207
+ configure chromeflow for it."
208
+ `;
209
+ if (existsSync(globalClaudeMdPath)) {
210
+ const existing = readFileSync(globalClaudeMdPath, "utf8");
211
+ if (existing.includes("chromeflow")) return "already-present";
212
+ writeFileSync(globalClaudeMdPath, existing.trimEnd() + "\n\n" + hint);
213
+ return "appended";
214
+ }
215
+ const dir = join(HOME, ".claude");
216
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
217
+ writeFileSync(globalClaudeMdPath, hint);
218
+ return "created";
219
+ }
155
220
  async function runSetup() {
156
221
  const scriptPath = fileURLToPath(import.meta.url);
157
222
  const distDir = dirname(scriptPath);
158
223
  const serverScriptPath = resolve(distDir, "index.js");
159
- const extensionDistPath = resolve(distDir, "..", "..", "extension", "dist");
160
224
  console.log("\nChromeflow Setup\n" + "\u2500".repeat(40));
161
225
  patchClaudeJson(serverScriptPath);
162
226
  const viaNpx = isRunningViaNpx();
@@ -164,6 +228,7 @@ async function runSetup() {
164
228
  console.log(viaNpx ? ` \u2192 npx -y chromeflow (auto-updates)` : ` \u2192 node ${serverScriptPath}`);
165
229
  const cwd = process.cwd();
166
230
  const mdResult = patchProjectClaudeMd(cwd);
231
+ const settingsResult = patchSettingsLocalJson(cwd);
167
232
  if (mdResult === "already-present") {
168
233
  console.log("\u2713 CLAUDE.md already has chromeflow instructions (run `npx chromeflow update` to refresh)");
169
234
  } else if (mdResult === "appended") {
@@ -171,6 +236,11 @@ async function runSetup() {
171
236
  } else {
172
237
  console.log(`\u2713 Created ${join(cwd, "CLAUDE.md")}`);
173
238
  }
239
+ if (settingsResult === "already-present") {
240
+ console.log("\u2713 .claude/settings.local.json already allows chromeflow tools");
241
+ } else {
242
+ console.log("\u2713 Added chromeflow tools to .claude/settings.local.json (no approval prompts)");
243
+ }
174
244
  console.log("\nChrome extension (one-time manual step):");
175
245
  const opened = tryOpenExtensionsPage();
176
246
  if (opened) {
@@ -178,11 +248,71 @@ async function runSetup() {
178
248
  } else {
179
249
  console.log(" Open chrome://extensions in Chrome.");
180
250
  }
251
+ const extensionDistPath = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "extension", "dist");
181
252
  console.log(" 1. Enable Developer mode (top-right toggle)");
182
253
  console.log(" 2. Click 'Load unpacked'");
183
254
  console.log(` 3. Select: ${extensionDistPath}`);
255
+ const globalResult = patchGlobalClaudeMd();
256
+ if (globalResult === "already-present") {
257
+ console.log("\u2713 ~/.claude/CLAUDE.md already has chromeflow hint");
258
+ } else if (globalResult === "appended") {
259
+ console.log("\u2713 Appended chromeflow hint to ~/.claude/CLAUDE.md");
260
+ } else {
261
+ console.log("\u2713 Created ~/.claude/CLAUDE.md with chromeflow hint");
262
+ }
184
263
  console.log("\nDone. Restart Claude Code to activate chromeflow.\n");
185
264
  }
265
+ async function runUninstall() {
266
+ const cwd = process.cwd();
267
+ console.log("\nChromeflow Uninstall\n" + "\u2500".repeat(40));
268
+ if (existsSync(CLAUDE_JSON_PATH)) {
269
+ try {
270
+ const config = JSON.parse(readFileSync(CLAUDE_JSON_PATH, "utf8"));
271
+ if (config.mcpServers && typeof config.mcpServers === "object") {
272
+ delete config.mcpServers.chromeflow;
273
+ }
274
+ writeFileSync(CLAUDE_JSON_PATH, JSON.stringify(config, null, 2) + "\n");
275
+ console.log("\u2713 Removed chromeflow MCP server from ~/.claude.json");
276
+ } catch {
277
+ console.log(" Could not update ~/.claude.json (skipping)");
278
+ }
279
+ }
280
+ const claudeMdPath = join(cwd, "CLAUDE.md");
281
+ if (existsSync(claudeMdPath)) {
282
+ const existing = readFileSync(claudeMdPath, "utf8");
283
+ if (existing.includes("# Chromeflow")) {
284
+ const idx = existing.indexOf("# Chromeflow");
285
+ const before = existing.slice(0, idx).trimEnd();
286
+ writeFileSync(claudeMdPath, before ? before + "\n" : "");
287
+ console.log(`\u2713 Removed chromeflow section from ${claudeMdPath}`);
288
+ }
289
+ }
290
+ const settingsPath = join(cwd, ".claude", "settings.local.json");
291
+ if (existsSync(settingsPath)) {
292
+ try {
293
+ const settings = JSON.parse(readFileSync(settingsPath, "utf8"));
294
+ const perms = settings.permissions;
295
+ if (perms && Array.isArray(perms.allow)) {
296
+ perms.allow = perms.allow.filter((t) => !t.startsWith("mcp__chromeflow__"));
297
+ }
298
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
299
+ console.log("\u2713 Removed chromeflow tools from .claude/settings.local.json");
300
+ } catch {
301
+ console.log(" Could not update .claude/settings.local.json (skipping)");
302
+ }
303
+ }
304
+ const globalClaudeMdPath = join(HOME, ".claude", "CLAUDE.md");
305
+ if (existsSync(globalClaudeMdPath)) {
306
+ const existing = readFileSync(globalClaudeMdPath, "utf8");
307
+ if (existing.includes("## Chromeflow")) {
308
+ const idx = existing.indexOf("## Chromeflow");
309
+ const before = existing.slice(0, idx).trimEnd();
310
+ writeFileSync(globalClaudeMdPath, before ? before + "\n" : "");
311
+ console.log("\u2713 Removed chromeflow hint from ~/.claude/CLAUDE.md");
312
+ }
313
+ }
314
+ console.log("\nDone. Restart Claude Code to complete removal.\n");
315
+ }
186
316
  async function runUpdate() {
187
317
  const cwd = process.cwd();
188
318
  console.log("\nChromeflow Update\n" + "\u2500".repeat(40));
@@ -194,9 +324,16 @@ async function runUpdate() {
194
324
  } else {
195
325
  console.log(`\u2713 Created ${join(cwd, "CLAUDE.md")}`);
196
326
  }
327
+ const settingsResult = patchSettingsLocalJson(cwd);
328
+ if (settingsResult === "already-present") {
329
+ console.log("\u2713 .claude/settings.local.json already up to date");
330
+ } else {
331
+ console.log("\u2713 Updated chromeflow tools in .claude/settings.local.json");
332
+ }
197
333
  console.log("Done.\n");
198
334
  }
199
335
  export {
200
336
  runSetup,
337
+ runUninstall,
201
338
  runUpdate
202
339
  };
package/dist/ws-bridge.js CHANGED
@@ -62,7 +62,9 @@ class WsBridge {
62
62
  this.client = null;
63
63
  for (const [id, pending] of this.pending) {
64
64
  clearTimeout(pending.timer);
65
- pending.reject(new Error("Extension disconnected"));
65
+ pending.reject(new Error(
66
+ "Chrome extension disconnected. Reload the chromeflow extension in Chrome and try again."
67
+ ));
66
68
  this.pending.delete(id);
67
69
  }
68
70
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chromeflow",
3
- "version": "0.1.13",
3
+ "version": "0.1.16",
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": {