chromeflow 0.10.23 → 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 +211 -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;
|
|
@@ -24958,6 +25103,7 @@ Refuses fast on pages that are in fullscreen mode (captureVisibleTab hangs there
|
|
|
24958
25103
|
execSync(`osascript -e 'set the clipboard to (read (POSIX file "${tmpPath}") as \xABclass PNGf\xBB)'`);
|
|
24959
25104
|
notes.push("Copied to clipboard");
|
|
24960
25105
|
} catch {
|
|
25106
|
+
notes.push("Clipboard copy failed (macOS-only feature)");
|
|
24961
25107
|
}
|
|
24962
25108
|
}
|
|
24963
25109
|
const r = response;
|
|
@@ -24989,7 +25135,7 @@ The saved file path can be passed directly to set_file_input(hint, file_path) to
|
|
|
24989
25135
|
async ({ save_to = "downloads" }) => {
|
|
24990
25136
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
24991
25137
|
const filename = `terminal-${timestamp}.png`;
|
|
24992
|
-
const savePath = save_to === "cwd" ?
|
|
25138
|
+
const savePath = save_to === "cwd" ? join2(process.cwd(), filename) : join2(homedir2(), "Downloads", filename);
|
|
24993
25139
|
let captured = false;
|
|
24994
25140
|
try {
|
|
24995
25141
|
const bounds = execSync(`osascript -e '
|
|
@@ -25024,7 +25170,7 @@ The saved file path can be passed directly to set_file_input(hint, file_path) to
|
|
|
25024
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." }]
|
|
25025
25171
|
};
|
|
25026
25172
|
}
|
|
25027
|
-
const imageBuffer =
|
|
25173
|
+
const imageBuffer = readFileSync2(savePath);
|
|
25028
25174
|
const base642 = imageBuffer.toString("base64");
|
|
25029
25175
|
let clipboardNote = "";
|
|
25030
25176
|
try {
|
|
@@ -25134,8 +25280,20 @@ ${lines.join("\n")}${r.warning ?? ""}${captchaLine}${oauthLine}` }] };
|
|
|
25134
25280
|
timeoutMs
|
|
25135
25281
|
);
|
|
25136
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
|
+
}
|
|
25137
25295
|
return {
|
|
25138
|
-
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 }]
|
|
25139
25297
|
};
|
|
25140
25298
|
}
|
|
25141
25299
|
);
|
|
@@ -25281,8 +25439,8 @@ Returns whether the element was found. Set valueToType only when the user must p
|
|
|
25281
25439
|
}
|
|
25282
25440
|
|
|
25283
25441
|
// packages/mcp-server/src/tools/capture.ts
|
|
25284
|
-
import { appendFileSync, mkdirSync, readFileSync as
|
|
25285
|
-
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";
|
|
25286
25444
|
function registerCaptureTools(server, bridge) {
|
|
25287
25445
|
server.tool(
|
|
25288
25446
|
"fill_input",
|
|
@@ -25470,7 +25628,7 @@ ${lines.join("\n")}` }] };
|
|
|
25470
25628
|
envPath = resolved;
|
|
25471
25629
|
let existing = "";
|
|
25472
25630
|
try {
|
|
25473
|
-
existing =
|
|
25631
|
+
existing = readFileSync3(envPath, "utf-8");
|
|
25474
25632
|
} catch {
|
|
25475
25633
|
}
|
|
25476
25634
|
const lines = existing.split("\n");
|
|
@@ -25478,7 +25636,7 @@ ${lines.join("\n")}` }] };
|
|
|
25478
25636
|
const existingIndex = lines.findIndex((l) => keyPattern.test(l));
|
|
25479
25637
|
if (existingIndex !== -1) {
|
|
25480
25638
|
lines[existingIndex] = `${key}=${value}`;
|
|
25481
|
-
|
|
25639
|
+
writeFileSync3(envPath, lines.join("\n"), "utf-8");
|
|
25482
25640
|
} else {
|
|
25483
25641
|
const toAppend = (existing && !existing.endsWith("\n") ? "\n" : "") + `${key}=${value}
|
|
25484
25642
|
`;
|
|
@@ -25612,9 +25770,9 @@ Set binary=true for non-text responses (PDFs, images, zips) \u2014 the body is r
|
|
|
25612
25770
|
`Refusing to write fetch_url body outside the project directory. Target "${resolved}" is not under "${cwd}".`
|
|
25613
25771
|
);
|
|
25614
25772
|
}
|
|
25615
|
-
|
|
25773
|
+
mkdirSync2(dirname2(resolved), { recursive: true });
|
|
25616
25774
|
const buf = r.body_base64 ? Buffer.from(r.body_base64, "base64") : Buffer.from(r.body_text ?? "", "utf-8");
|
|
25617
|
-
|
|
25775
|
+
writeFileSync3(resolved, buf);
|
|
25618
25776
|
const hdrLines = Object.keys(r.headers).sort().map((k) => ` ${k}: ${r.headers[k]}`).join("\n");
|
|
25619
25777
|
return {
|
|
25620
25778
|
content: [{
|
|
@@ -25643,7 +25801,7 @@ ${r.body_text}` : "";
|
|
|
25643
25801
|
}
|
|
25644
25802
|
|
|
25645
25803
|
// packages/mcp-server/src/tools/flow.ts
|
|
25646
|
-
function registerFlowTools(server, bridge) {
|
|
25804
|
+
function registerFlowTools(server, bridge, flowStore) {
|
|
25647
25805
|
server.tool(
|
|
25648
25806
|
"click_element",
|
|
25649
25807
|
`Click an interactive element by its visible text/aria-label (textHint) OR by direct CSS selector (selector). Pass exactly one.
|
|
@@ -25743,21 +25901,51 @@ Current URL: ${activeTab.url}`;
|
|
|
25743
25901
|
focusLine = `
|
|
25744
25902
|
\u2192 Focused: <${f.tag}${idBit}${nameBit}${aria}${valueBit}>`;
|
|
25745
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);
|
|
25746
25921
|
if (!r.success) {
|
|
25747
25922
|
return {
|
|
25748
25923
|
content: [
|
|
25749
25924
|
{
|
|
25750
25925
|
type: "text",
|
|
25751
|
-
text: `Could not click "${targetLabel}": ${r.message}${navLine}${focusLine}`
|
|
25926
|
+
text: `Could not click "${targetLabel}": ${r.message}${navLine}${focusLine}${recall}`
|
|
25752
25927
|
}
|
|
25753
25928
|
]
|
|
25754
25929
|
};
|
|
25755
25930
|
}
|
|
25756
25931
|
return {
|
|
25757
|
-
content: [{ type: "text", text: `${r.message}${navLine}${focusLine}` }]
|
|
25932
|
+
content: [{ type: "text", text: `${r.message}${navLine}${focusLine}${recall}${capturable}` }]
|
|
25758
25933
|
};
|
|
25759
25934
|
}
|
|
25760
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
|
+
);
|
|
25761
25949
|
server.tool(
|
|
25762
25950
|
"wait_for_click",
|
|
25763
25951
|
`Wait for the user to click (or interact with) the currently highlighted element, then return.
|
|
@@ -26071,21 +26259,22 @@ ${lines.join("\n")}${shadowSection}` }] };
|
|
|
26071
26259
|
}
|
|
26072
26260
|
|
|
26073
26261
|
// packages/mcp-server/src/index.ts
|
|
26074
|
-
var PACKAGE_VERSION = true ? "0.10.
|
|
26262
|
+
var PACKAGE_VERSION = true ? "0.10.25" : "dev";
|
|
26075
26263
|
main().catch((err) => {
|
|
26076
26264
|
console.error("[chromeflow] Fatal error:", err);
|
|
26077
26265
|
process.exit(1);
|
|
26078
26266
|
});
|
|
26079
26267
|
async function main() {
|
|
26080
26268
|
const bridge = new WsBridge();
|
|
26269
|
+
const flowStore = new FlowStore(PACKAGE_VERSION);
|
|
26081
26270
|
const server = new McpServer({
|
|
26082
26271
|
name: "chromeflow",
|
|
26083
26272
|
version: PACKAGE_VERSION
|
|
26084
26273
|
});
|
|
26085
|
-
registerBrowserTools(server, bridge);
|
|
26274
|
+
registerBrowserTools(server, bridge, flowStore);
|
|
26086
26275
|
registerHighlightTools(server, bridge);
|
|
26087
26276
|
registerCaptureTools(server, bridge);
|
|
26088
|
-
registerFlowTools(server, bridge);
|
|
26277
|
+
registerFlowTools(server, bridge, flowStore);
|
|
26089
26278
|
const registered = server._registeredTools ?? {};
|
|
26090
26279
|
const toolNames = Object.keys(registered).sort();
|
|
26091
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",
|