chromeflow 0.1.13 → 0.1.17
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 +2 -0
- package/README.md +13 -3
- package/dist/index.js +7 -2
- package/dist/setup.js +139 -2
- package/dist/ws-bridge.js +3 -1
- package/package.json +1 -1
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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