chromeflow 0.10.24 → 0.10.25
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 +210 -22
- package/package.json +1 -1
package/bin/chromeflow.mjs
CHANGED
|
@@ -24755,10 +24755,153 @@ var WsBridge = class {
|
|
|
24755
24755
|
}
|
|
24756
24756
|
};
|
|
24757
24757
|
|
|
24758
|
+
// packages/mcp-server/src/flow-store.ts
|
|
24759
|
+
import { homedir } from "node:os";
|
|
24760
|
+
import { join, dirname } from "node:path";
|
|
24761
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from "node:fs";
|
|
24762
|
+
function originKey(url) {
|
|
24763
|
+
if (!url) return void 0;
|
|
24764
|
+
try {
|
|
24765
|
+
const u = new URL(url);
|
|
24766
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") return void 0;
|
|
24767
|
+
const path2 = u.pathname && u.pathname !== "/" ? u.pathname.replace(/\/+$/, "") : "";
|
|
24768
|
+
return u.origin + path2;
|
|
24769
|
+
} catch {
|
|
24770
|
+
return void 0;
|
|
24771
|
+
}
|
|
24772
|
+
}
|
|
24773
|
+
var FRAGILE_RE = /:nth-(of-type|child)\(|>\s*\w+:nth/;
|
|
24774
|
+
var FlowStore = class {
|
|
24775
|
+
path;
|
|
24776
|
+
data;
|
|
24777
|
+
version;
|
|
24778
|
+
// In-memory, per-session state (never persisted):
|
|
24779
|
+
buffer = /* @__PURE__ */ new Map();
|
|
24780
|
+
// notable atoms not yet committed, by origin
|
|
24781
|
+
surfaced = /* @__PURE__ */ new Set();
|
|
24782
|
+
// origins whose recall hint already fired this session
|
|
24783
|
+
lastOrigin;
|
|
24784
|
+
constructor(version2, baseDir) {
|
|
24785
|
+
this.version = version2;
|
|
24786
|
+
this.path = join(baseDir ?? join(homedir(), ".chromeflow"), "flows.json");
|
|
24787
|
+
this.data = this.load();
|
|
24788
|
+
}
|
|
24789
|
+
load() {
|
|
24790
|
+
try {
|
|
24791
|
+
if (existsSync(this.path)) {
|
|
24792
|
+
const parsed = JSON.parse(readFileSync(this.path, "utf-8"));
|
|
24793
|
+
if (parsed && parsed.version === 1 && parsed.origins) return parsed;
|
|
24794
|
+
}
|
|
24795
|
+
} catch {
|
|
24796
|
+
try {
|
|
24797
|
+
renameSync(this.path, this.path + ".corrupt");
|
|
24798
|
+
} catch {
|
|
24799
|
+
}
|
|
24800
|
+
}
|
|
24801
|
+
return { version: 1, origins: {} };
|
|
24802
|
+
}
|
|
24803
|
+
persist() {
|
|
24804
|
+
try {
|
|
24805
|
+
mkdirSync(dirname(this.path), { recursive: true });
|
|
24806
|
+
const tmp = this.path + ".tmp";
|
|
24807
|
+
writeFileSync(tmp, JSON.stringify(this.data, null, 2), "utf-8");
|
|
24808
|
+
renameSync(tmp, this.path);
|
|
24809
|
+
} catch {
|
|
24810
|
+
}
|
|
24811
|
+
}
|
|
24812
|
+
/** Update the "current origin" from any URL chromeflow observed. */
|
|
24813
|
+
noteUrl(url) {
|
|
24814
|
+
const k = originKey(url);
|
|
24815
|
+
if (k) this.lastOrigin = k;
|
|
24816
|
+
}
|
|
24817
|
+
/** Buffer a notable atom against an origin (defaults to last-seen origin). */
|
|
24818
|
+
observe(atom, url) {
|
|
24819
|
+
if (!atom) return;
|
|
24820
|
+
const k = originKey(url) ?? this.lastOrigin;
|
|
24821
|
+
if (!k) return;
|
|
24822
|
+
const list = this.buffer.get(k) ?? [];
|
|
24823
|
+
const sig = `${atom.tool}|${atom.target}|${atom.selector ?? ""}`;
|
|
24824
|
+
if (list.some((a) => `${a.tool}|${a.target}|${a.selector ?? ""}` === sig)) return;
|
|
24825
|
+
list.push(atom);
|
|
24826
|
+
this.buffer.set(k, list);
|
|
24827
|
+
}
|
|
24828
|
+
/** Compact recall hint for an origin, at most once per origin per session. */
|
|
24829
|
+
recallHint(url) {
|
|
24830
|
+
const k = originKey(url);
|
|
24831
|
+
if (!k || this.surfaced.has(k)) return "";
|
|
24832
|
+
const flows = this.data.origins[k];
|
|
24833
|
+
if (!flows || flows.length === 0) return "";
|
|
24834
|
+
this.surfaced.add(k);
|
|
24835
|
+
const best = [...flows].sort((a, b) => b.success_count - a.success_count).slice(0, 3);
|
|
24836
|
+
const lines = best.map((f) => {
|
|
24837
|
+
const steps = f.steps.map((s, i) => {
|
|
24838
|
+
const via = s.recovered_via ? ` [via ${s.recovered_via}]` : "";
|
|
24839
|
+
const sig = s.signal ? ` (${s.signal})` : "";
|
|
24840
|
+
const frag = s.fragile ? " \u26A0fragile-selector" : "";
|
|
24841
|
+
return ` ${i + 1}. ${s.tool} ${s.target}${sig}${via}${frag}`;
|
|
24842
|
+
}).join("\n");
|
|
24843
|
+
const stale = f.chromeflow_version !== this.version ? ` recorded on v${f.chromeflow_version}, re-verify` : "";
|
|
24844
|
+
return ` "${f.task_label}" (${f.steps.length} steps, ${f.success_count}x ok${stale}):
|
|
24845
|
+
${steps}`;
|
|
24846
|
+
});
|
|
24847
|
+
return `
|
|
24848
|
+
|
|
24849
|
+
\u2139 known_flow for ${k} \u2014 prefer these proven steps over rediscovery (verify each as usual):
|
|
24850
|
+
${lines.join("\n")}`;
|
|
24851
|
+
}
|
|
24852
|
+
/** Nudge to save buffered hard-won steps, when there are uncommitted ones. */
|
|
24853
|
+
capturableHint(url) {
|
|
24854
|
+
const k = originKey(url) ?? this.lastOrigin;
|
|
24855
|
+
if (!k) return "";
|
|
24856
|
+
const buf = this.buffer.get(k);
|
|
24857
|
+
if (!buf || buf.length === 0) return "";
|
|
24858
|
+
const reasons = [...new Set(buf.map((a) => a.reason))].slice(0, 2).join("; ");
|
|
24859
|
+
return `
|
|
24860
|
+
|
|
24861
|
+
\u2139 flow_capturable: ${buf.length} hard-won step(s) on ${k} not yet saved (${reasons}). Call save_flow("<task label>") to persist them so future runs skip the trial-and-error.`;
|
|
24862
|
+
}
|
|
24863
|
+
/** Commit the buffered atoms for an origin as a named flow. */
|
|
24864
|
+
commit(taskLabel, url) {
|
|
24865
|
+
const k = originKey(url) ?? this.lastOrigin;
|
|
24866
|
+
if (!k) return { saved: 0, origin: null, message: "No origin known yet \u2014 navigate or interact with a page first." };
|
|
24867
|
+
const buf = this.buffer.get(k) ?? [];
|
|
24868
|
+
if (buf.length === 0) {
|
|
24869
|
+
return { saved: 0, origin: k, message: `Nothing notable buffered for ${k}. Flows capture hard-won steps (a fallback fired, a verified submit, a field needing real keystrokes) \u2014 an ordinary first-try click isn't recorded.` };
|
|
24870
|
+
}
|
|
24871
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
24872
|
+
const sig = JSON.stringify(buf.map((a) => [a.tool, a.target, a.selector ?? ""]));
|
|
24873
|
+
const flows = this.data.origins[k] ?? [];
|
|
24874
|
+
const existing = flows.find((f) => f.task_label === taskLabel && JSON.stringify(f.steps.map((a) => [a.tool, a.target, a.selector ?? ""])) === sig);
|
|
24875
|
+
if (existing) {
|
|
24876
|
+
existing.success_count += 1;
|
|
24877
|
+
existing.last_verified = now;
|
|
24878
|
+
existing.chromeflow_version = this.version;
|
|
24879
|
+
} else {
|
|
24880
|
+
flows.push({
|
|
24881
|
+
id: `${k}#${flows.length + 1}`,
|
|
24882
|
+
task_label: taskLabel,
|
|
24883
|
+
steps: buf,
|
|
24884
|
+
created_at: now,
|
|
24885
|
+
last_verified: now,
|
|
24886
|
+
success_count: 1,
|
|
24887
|
+
fail_count: 0,
|
|
24888
|
+
chromeflow_version: this.version
|
|
24889
|
+
});
|
|
24890
|
+
}
|
|
24891
|
+
this.data.origins[k] = flows;
|
|
24892
|
+
this.buffer.delete(k);
|
|
24893
|
+
this.persist();
|
|
24894
|
+
return { saved: buf.length, origin: k, message: `Saved flow "${taskLabel}" (${buf.length} steps) for ${k}.` };
|
|
24895
|
+
}
|
|
24896
|
+
};
|
|
24897
|
+
function isFragileSelector(selector) {
|
|
24898
|
+
return !!selector && FRAGILE_RE.test(selector);
|
|
24899
|
+
}
|
|
24900
|
+
|
|
24758
24901
|
// packages/mcp-server/src/tools/browser.ts
|
|
24759
|
-
import { writeFileSync, copyFileSync, readFileSync } from "fs";
|
|
24760
|
-
import { tmpdir, homedir } from "os";
|
|
24761
|
-
import { join } from "path";
|
|
24902
|
+
import { writeFileSync as writeFileSync2, copyFileSync, readFileSync as readFileSync2 } from "fs";
|
|
24903
|
+
import { tmpdir, homedir as homedir2 } from "os";
|
|
24904
|
+
import { join as join2 } from "path";
|
|
24762
24905
|
import { execSync } from "child_process";
|
|
24763
24906
|
|
|
24764
24907
|
// packages/mcp-server/src/policy.ts
|
|
@@ -24787,7 +24930,7 @@ function isBlockedUrl(rawUrl) {
|
|
|
24787
24930
|
}
|
|
24788
24931
|
|
|
24789
24932
|
// packages/mcp-server/src/tools/browser.ts
|
|
24790
|
-
function registerBrowserTools(server, bridge) {
|
|
24933
|
+
function registerBrowserTools(server, bridge, flowStore) {
|
|
24791
24934
|
server.tool(
|
|
24792
24935
|
"open_page",
|
|
24793
24936
|
`Navigate to a URL. By default reuses the active tab. Set new_tab=true to open alongside the current tab without losing it. After navigating, call get_page_text to read the page \u2014 do NOT take a screenshot.
|
|
@@ -24836,6 +24979,8 @@ After tabs.onUpdated fires status=complete, chromeflow also runs a 6s settle che
|
|
|
24836
24979
|
|
|
24837
24980
|
\u2139 dismissed_beforeunload: true \u2014 the previous page had unsaved content (typed text in a composer, form draft, etc.) and Chrome's "Are you sure you want to leave?" dialog was auto-dismissed so navigation could proceed. If that draft was load-bearing, navigate back and re-capture before continuing.`;
|
|
24838
24981
|
}
|
|
24982
|
+
flowStore.noteUrl(r.current_url ?? url);
|
|
24983
|
+
text += flowStore.recallHint(r.current_url ?? url);
|
|
24839
24984
|
return { content: [{ type: "text", text }] };
|
|
24840
24985
|
}
|
|
24841
24986
|
);
|
|
@@ -24942,13 +25087,13 @@ Refuses fast on pages that are in fullscreen mode (captureVisibleTab hangs there
|
|
|
24942
25087
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
24943
25088
|
const filename = `chromeflow-${timestamp}.png`;
|
|
24944
25089
|
const imageBuffer = Buffer.from(response.image, "base64");
|
|
24945
|
-
const tmpPath =
|
|
25090
|
+
const tmpPath = join2(tmpdir(), filename);
|
|
24946
25091
|
const needTmp = !shouldInline || sharing;
|
|
24947
|
-
if (needTmp)
|
|
25092
|
+
if (needTmp) writeFileSync2(tmpPath, imageBuffer);
|
|
24948
25093
|
const notes = [];
|
|
24949
25094
|
let landedPath = tmpPath;
|
|
24950
25095
|
if (save_to !== "none") {
|
|
24951
|
-
const savePath = save_to === "cwd" ?
|
|
25096
|
+
const savePath = save_to === "cwd" ? join2(process.cwd(), filename) : join2(homedir2(), "Downloads", filename);
|
|
24952
25097
|
copyFileSync(tmpPath, savePath);
|
|
24953
25098
|
notes.push(`Saved to ${savePath}`);
|
|
24954
25099
|
landedPath = savePath;
|
|
@@ -24990,7 +25135,7 @@ The saved file path can be passed directly to set_file_input(hint, file_path) to
|
|
|
24990
25135
|
async ({ save_to = "downloads" }) => {
|
|
24991
25136
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
24992
25137
|
const filename = `terminal-${timestamp}.png`;
|
|
24993
|
-
const savePath = save_to === "cwd" ?
|
|
25138
|
+
const savePath = save_to === "cwd" ? join2(process.cwd(), filename) : join2(homedir2(), "Downloads", filename);
|
|
24994
25139
|
let captured = false;
|
|
24995
25140
|
try {
|
|
24996
25141
|
const bounds = execSync(`osascript -e '
|
|
@@ -25025,7 +25170,7 @@ The saved file path can be passed directly to set_file_input(hint, file_path) to
|
|
|
25025
25170
|
content: [{ type: "text", text: "Failed to capture terminal. Ensure Screen Recording permission is granted to your terminal app in System Settings > Privacy & Security > Screen Recording." }]
|
|
25026
25171
|
};
|
|
25027
25172
|
}
|
|
25028
|
-
const imageBuffer =
|
|
25173
|
+
const imageBuffer = readFileSync2(savePath);
|
|
25029
25174
|
const base642 = imageBuffer.toString("base64");
|
|
25030
25175
|
let clipboardNote = "";
|
|
25031
25176
|
try {
|
|
@@ -25135,8 +25280,20 @@ ${lines.join("\n")}${r.warning ?? ""}${captchaLine}${oauthLine}` }] };
|
|
|
25135
25280
|
timeoutMs
|
|
25136
25281
|
);
|
|
25137
25282
|
const r = response;
|
|
25283
|
+
let capturable = "";
|
|
25284
|
+
if (into_selector && r.success !== false) {
|
|
25285
|
+
flowStore.observe({
|
|
25286
|
+
tool: "type_text",
|
|
25287
|
+
target: into_selector,
|
|
25288
|
+
selector: into_selector,
|
|
25289
|
+
signal: clear_first ? "type_text(clear_first)" : "type_text",
|
|
25290
|
+
fragile: isFragileSelector(into_selector),
|
|
25291
|
+
reason: "field needs real keystrokes (type_text, not fill_input)"
|
|
25292
|
+
});
|
|
25293
|
+
capturable = flowStore.capturableHint(void 0);
|
|
25294
|
+
}
|
|
25138
25295
|
return {
|
|
25139
|
-
content: [{ type: "text", text: r.message ?? (r.success ? "Text typed successfully" : "Failed to type text") }]
|
|
25296
|
+
content: [{ type: "text", text: (r.message ?? (r.success ? "Text typed successfully" : "Failed to type text")) + capturable }]
|
|
25140
25297
|
};
|
|
25141
25298
|
}
|
|
25142
25299
|
);
|
|
@@ -25282,8 +25439,8 @@ Returns whether the element was found. Set valueToType only when the user must p
|
|
|
25282
25439
|
}
|
|
25283
25440
|
|
|
25284
25441
|
// packages/mcp-server/src/tools/capture.ts
|
|
25285
|
-
import { appendFileSync, mkdirSync, readFileSync as
|
|
25286
|
-
import { resolve, relative, isAbsolute, dirname } from "path";
|
|
25442
|
+
import { appendFileSync, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
25443
|
+
import { resolve, relative, isAbsolute, dirname as dirname2 } from "path";
|
|
25287
25444
|
function registerCaptureTools(server, bridge) {
|
|
25288
25445
|
server.tool(
|
|
25289
25446
|
"fill_input",
|
|
@@ -25471,7 +25628,7 @@ ${lines.join("\n")}` }] };
|
|
|
25471
25628
|
envPath = resolved;
|
|
25472
25629
|
let existing = "";
|
|
25473
25630
|
try {
|
|
25474
|
-
existing =
|
|
25631
|
+
existing = readFileSync3(envPath, "utf-8");
|
|
25475
25632
|
} catch {
|
|
25476
25633
|
}
|
|
25477
25634
|
const lines = existing.split("\n");
|
|
@@ -25479,7 +25636,7 @@ ${lines.join("\n")}` }] };
|
|
|
25479
25636
|
const existingIndex = lines.findIndex((l) => keyPattern.test(l));
|
|
25480
25637
|
if (existingIndex !== -1) {
|
|
25481
25638
|
lines[existingIndex] = `${key}=${value}`;
|
|
25482
|
-
|
|
25639
|
+
writeFileSync3(envPath, lines.join("\n"), "utf-8");
|
|
25483
25640
|
} else {
|
|
25484
25641
|
const toAppend = (existing && !existing.endsWith("\n") ? "\n" : "") + `${key}=${value}
|
|
25485
25642
|
`;
|
|
@@ -25613,9 +25770,9 @@ Set binary=true for non-text responses (PDFs, images, zips) \u2014 the body is r
|
|
|
25613
25770
|
`Refusing to write fetch_url body outside the project directory. Target "${resolved}" is not under "${cwd}".`
|
|
25614
25771
|
);
|
|
25615
25772
|
}
|
|
25616
|
-
|
|
25773
|
+
mkdirSync2(dirname2(resolved), { recursive: true });
|
|
25617
25774
|
const buf = r.body_base64 ? Buffer.from(r.body_base64, "base64") : Buffer.from(r.body_text ?? "", "utf-8");
|
|
25618
|
-
|
|
25775
|
+
writeFileSync3(resolved, buf);
|
|
25619
25776
|
const hdrLines = Object.keys(r.headers).sort().map((k) => ` ${k}: ${r.headers[k]}`).join("\n");
|
|
25620
25777
|
return {
|
|
25621
25778
|
content: [{
|
|
@@ -25644,7 +25801,7 @@ ${r.body_text}` : "";
|
|
|
25644
25801
|
}
|
|
25645
25802
|
|
|
25646
25803
|
// packages/mcp-server/src/tools/flow.ts
|
|
25647
|
-
function registerFlowTools(server, bridge) {
|
|
25804
|
+
function registerFlowTools(server, bridge, flowStore) {
|
|
25648
25805
|
server.tool(
|
|
25649
25806
|
"click_element",
|
|
25650
25807
|
`Click an interactive element by its visible text/aria-label (textHint) OR by direct CSS selector (selector). Pass exactly one.
|
|
@@ -25744,21 +25901,51 @@ Current URL: ${activeTab.url}`;
|
|
|
25744
25901
|
focusLine = `
|
|
25745
25902
|
\u2192 Focused: <${f.tag}${idBit}${nameBit}${aria}${valueBit}>`;
|
|
25746
25903
|
}
|
|
25904
|
+
const actionUrl = r.before_url ?? r.after_url;
|
|
25905
|
+
const nowUrl = r.after_url ?? r.before_url;
|
|
25906
|
+
const usedUntil = !!(until_selector || until_url_contains || until_text_contains || until_url_changes);
|
|
25907
|
+
if (r.success && (r.recovered_via || r.navigated || usedUntil)) {
|
|
25908
|
+
flowStore.observe({
|
|
25909
|
+
tool: "click_element",
|
|
25910
|
+
target: textHint ?? `selector=${selector}`,
|
|
25911
|
+
selector,
|
|
25912
|
+
recovered_via: r.recovered_via,
|
|
25913
|
+
signal: r.navigated ? "navigated" : until_url_changes ? "until_url_change" : usedUntil ? "until_*" : r.recovered_via,
|
|
25914
|
+
fragile: isFragileSelector(selector),
|
|
25915
|
+
reason: r.recovered_via ? `click recovered via ${r.recovered_via}` : r.navigated ? "navigating submit/link" : "verified terminal click"
|
|
25916
|
+
}, actionUrl);
|
|
25917
|
+
}
|
|
25918
|
+
flowStore.noteUrl(nowUrl);
|
|
25919
|
+
const recall = flowStore.recallHint(nowUrl);
|
|
25920
|
+
const capturable = flowStore.capturableHint(actionUrl);
|
|
25747
25921
|
if (!r.success) {
|
|
25748
25922
|
return {
|
|
25749
25923
|
content: [
|
|
25750
25924
|
{
|
|
25751
25925
|
type: "text",
|
|
25752
|
-
text: `Could not click "${targetLabel}": ${r.message}${navLine}${focusLine}`
|
|
25926
|
+
text: `Could not click "${targetLabel}": ${r.message}${navLine}${focusLine}${recall}`
|
|
25753
25927
|
}
|
|
25754
25928
|
]
|
|
25755
25929
|
};
|
|
25756
25930
|
}
|
|
25757
25931
|
return {
|
|
25758
|
-
content: [{ type: "text", text: `${r.message}${navLine}${focusLine}` }]
|
|
25932
|
+
content: [{ type: "text", text: `${r.message}${navLine}${focusLine}${recall}${capturable}` }]
|
|
25759
25933
|
};
|
|
25760
25934
|
}
|
|
25761
25935
|
);
|
|
25936
|
+
server.tool(
|
|
25937
|
+
"save_flow",
|
|
25938
|
+
`Persist the hard-won interaction steps chromeflow buffered for the current site as a reusable, named flow. chromeflow auto-buffers only NOTABLE resolutions (a click that needed a fallback, a verified submit, a field that needed real keystrokes) \u2014 so you just give the task a label and it commits whatever is buffered for the current origin. Next session, those steps are surfaced back as a known_flow hint so you skip the trial-and-error.
|
|
25939
|
+
|
|
25940
|
+
Call this when a response shows \`flow_capturable\`. Stored locally only (~/.chromeflow/flows.json), selectors/signals only \u2014 never typed text. Guidance, not autopilot: recalled steps are still verified on replay.`,
|
|
25941
|
+
{
|
|
25942
|
+
task_label: external_exports.string().describe('Short human label for what this flow accomplishes, e.g. "submit text post", "set flair and submit", "log report time".')
|
|
25943
|
+
},
|
|
25944
|
+
async ({ task_label }) => {
|
|
25945
|
+
const res = flowStore.commit(task_label);
|
|
25946
|
+
return { content: [{ type: "text", text: res.message }] };
|
|
25947
|
+
}
|
|
25948
|
+
);
|
|
25762
25949
|
server.tool(
|
|
25763
25950
|
"wait_for_click",
|
|
25764
25951
|
`Wait for the user to click (or interact with) the currently highlighted element, then return.
|
|
@@ -26072,21 +26259,22 @@ ${lines.join("\n")}${shadowSection}` }] };
|
|
|
26072
26259
|
}
|
|
26073
26260
|
|
|
26074
26261
|
// packages/mcp-server/src/index.ts
|
|
26075
|
-
var PACKAGE_VERSION = true ? "0.10.
|
|
26262
|
+
var PACKAGE_VERSION = true ? "0.10.25" : "dev";
|
|
26076
26263
|
main().catch((err) => {
|
|
26077
26264
|
console.error("[chromeflow] Fatal error:", err);
|
|
26078
26265
|
process.exit(1);
|
|
26079
26266
|
});
|
|
26080
26267
|
async function main() {
|
|
26081
26268
|
const bridge = new WsBridge();
|
|
26269
|
+
const flowStore = new FlowStore(PACKAGE_VERSION);
|
|
26082
26270
|
const server = new McpServer({
|
|
26083
26271
|
name: "chromeflow",
|
|
26084
26272
|
version: PACKAGE_VERSION
|
|
26085
26273
|
});
|
|
26086
|
-
registerBrowserTools(server, bridge);
|
|
26274
|
+
registerBrowserTools(server, bridge, flowStore);
|
|
26087
26275
|
registerHighlightTools(server, bridge);
|
|
26088
26276
|
registerCaptureTools(server, bridge);
|
|
26089
|
-
registerFlowTools(server, bridge);
|
|
26277
|
+
registerFlowTools(server, bridge, flowStore);
|
|
26090
26278
|
const registered = server._registeredTools ?? {};
|
|
26091
26279
|
const toolNames = Object.keys(registered).sort();
|
|
26092
26280
|
console.error(`[chromeflow] v${PACKAGE_VERSION} \u2014 registered ${toolNames.length} tools`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chromeflow",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.25",
|
|
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",
|