@hasna/browser 0.2.5 → 0.3.0

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/dist/cli/index.js CHANGED
@@ -4,6 +4,7 @@ var __create = Object.create;
4
4
  var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
9
  var __toESM = (mod, isNodeMode, target) => {
9
10
  target = mod != null ? __create(__getProtoOf(mod)) : {};
@@ -16,6 +17,20 @@ var __toESM = (mod, isNodeMode, target) => {
16
17
  });
17
18
  return to;
18
19
  };
20
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
21
+ var __toCommonJS = (from) => {
22
+ var entry = __moduleCache.get(from), desc;
23
+ if (entry)
24
+ return entry;
25
+ entry = __defProp({}, "__esModule", { value: true });
26
+ if (from && typeof from === "object" || typeof from === "function")
27
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
28
+ get: () => from[key],
29
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
30
+ }));
31
+ __moduleCache.set(from, entry);
32
+ return entry;
33
+ };
19
34
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
20
35
  var __export = (target, all) => {
21
36
  for (var name in all)
@@ -2406,6 +2421,50 @@ function runMigrations(db) {
2406
2421
  );
2407
2422
  CREATE INDEX IF NOT EXISTS idx_api_endpoints_session ON api_endpoints(session_id);
2408
2423
  `
2424
+ },
2425
+ {
2426
+ version: 9,
2427
+ sql: `
2428
+ CREATE TABLE IF NOT EXISTS scripts (
2429
+ id TEXT PRIMARY KEY,
2430
+ name TEXT NOT NULL UNIQUE,
2431
+ domain TEXT NOT NULL DEFAULT '',
2432
+ description TEXT DEFAULT '',
2433
+ variables TEXT NOT NULL DEFAULT '{}',
2434
+ created_at TEXT DEFAULT (datetime('now')),
2435
+ updated_at TEXT DEFAULT (datetime('now')),
2436
+ last_run TEXT,
2437
+ run_count INTEGER DEFAULT 0
2438
+ );
2439
+
2440
+ CREATE TABLE IF NOT EXISTS script_steps (
2441
+ id TEXT PRIMARY KEY,
2442
+ script_id TEXT NOT NULL REFERENCES scripts(id) ON DELETE CASCADE,
2443
+ step_order INTEGER NOT NULL,
2444
+ type TEXT NOT NULL,
2445
+ config TEXT NOT NULL DEFAULT '{}',
2446
+ description TEXT DEFAULT '',
2447
+ ai_enabled INTEGER DEFAULT 0,
2448
+ ai_config TEXT DEFAULT '{}'
2449
+ );
2450
+ CREATE INDEX IF NOT EXISTS idx_script_steps_order ON script_steps(script_id, step_order);
2451
+
2452
+ CREATE TABLE IF NOT EXISTS script_runs (
2453
+ id TEXT PRIMARY KEY,
2454
+ script_id TEXT NOT NULL REFERENCES scripts(id) ON DELETE CASCADE,
2455
+ status TEXT NOT NULL DEFAULT 'running',
2456
+ current_step INTEGER DEFAULT 0,
2457
+ total_steps INTEGER DEFAULT 0,
2458
+ current_description TEXT DEFAULT '',
2459
+ variables TEXT DEFAULT '{}',
2460
+ steps_log TEXT DEFAULT '[]',
2461
+ errors TEXT DEFAULT '[]',
2462
+ started_at TEXT DEFAULT (datetime('now')),
2463
+ completed_at TEXT,
2464
+ duration_ms INTEGER
2465
+ );
2466
+ CREATE INDEX IF NOT EXISTS idx_script_runs_script ON script_runs(script_id, status);
2467
+ `
2409
2468
  }
2410
2469
  ];
2411
2470
  for (const m of migrations) {
@@ -4309,6 +4368,10 @@ var init_snapshot = __esm(() => {
4309
4368
  });
4310
4369
 
4311
4370
  // src/lib/self-heal.ts
4371
+ var exports_self_heal = {};
4372
+ __export(exports_self_heal, {
4373
+ healSelector: () => healSelector
4374
+ });
4312
4375
  async function healSelector(page, selector, sessionId) {
4313
4376
  const attempts = [];
4314
4377
  attempts.push(`selector: ${selector}`);
@@ -8621,7 +8684,7 @@ var require_operation = __commonJS((exports, module) => {
8621
8684
  // node_modules/@img/colour/color.cjs
8622
8685
  var require_color = __commonJS((exports, module) => {
8623
8686
  var __defProp2 = Object.defineProperty;
8624
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8687
+ var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
8625
8688
  var __getOwnPropNames2 = Object.getOwnPropertyNames;
8626
8689
  var __hasOwnProp2 = Object.prototype.hasOwnProperty;
8627
8690
  var __export2 = (target, all) => {
@@ -8632,16 +8695,16 @@ var require_color = __commonJS((exports, module) => {
8632
8695
  if (from && typeof from === "object" || typeof from === "function") {
8633
8696
  for (let key of __getOwnPropNames2(from))
8634
8697
  if (!__hasOwnProp2.call(to, key) && key !== except)
8635
- __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
8698
+ __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
8636
8699
  }
8637
8700
  return to;
8638
8701
  };
8639
- var __toCommonJS = (mod) => __copyProps(__defProp2({}, "__esModule", { value: true }), mod);
8702
+ var __toCommonJS2 = (mod) => __copyProps(__defProp2({}, "__esModule", { value: true }), mod);
8640
8703
  var index_exports = {};
8641
8704
  __export2(index_exports, {
8642
8705
  default: () => index_default
8643
8706
  });
8644
- module.exports = __toCommonJS(index_exports);
8707
+ module.exports = __toCommonJS2(index_exports);
8645
8708
  var colors = {
8646
8709
  aliceblue: [240, 248, 255],
8647
8710
  antiquewhite: [250, 235, 215],
@@ -12845,325 +12908,574 @@ async function loginWithCredentials(page, credentials, opts) {
12845
12908
  }
12846
12909
  var init_auth = () => {};
12847
12910
 
12848
- // src/lib/login-scripts.ts
12849
- var exports_login_scripts = {};
12850
- __export(exports_login_scripts, {
12851
- saveScript: () => saveScript,
12852
- runScriptAsync: () => runScriptAsync,
12853
- runScript: () => runScript,
12854
- loadScript: () => loadScript,
12911
+ // src/db/scripts.ts
12912
+ var exports_scripts = {};
12913
+ __export(exports_scripts, {
12914
+ upsertScript: () => upsertScript,
12915
+ updateRunProgress: () => updateRunProgress,
12916
+ startRun: () => startRun,
12917
+ migrateJsonScripts: () => migrateJsonScripts,
12855
12918
  listScripts: () => listScripts,
12856
- listJobs: () => listJobs,
12857
- getJob: () => getJob,
12919
+ listRuns: () => listRuns,
12920
+ getSteps: () => getSteps,
12921
+ getScriptByName: () => getScriptByName,
12922
+ getScript: () => getScript,
12923
+ getRun: () => getRun,
12924
+ deleteScriptByName: () => deleteScriptByName,
12858
12925
  deleteScript: () => deleteScript,
12859
- createScriptFromJSON: () => createScriptFromJSON,
12860
- createScriptFromFile: () => createScriptFromFile
12926
+ createScript: () => createScript,
12927
+ completeRun: () => completeRun
12861
12928
  });
12862
12929
  import { randomUUID as randomUUID10 } from "crypto";
12863
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync7, readdirSync as readdirSync3 } from "fs";
12864
- import { join as join8 } from "path";
12865
- function getJob(jobId) {
12866
- return activeJobs.get(jobId) ?? null;
12867
- }
12868
- function listJobs() {
12869
- return Array.from(activeJobs.values());
12930
+ function createScript(data) {
12931
+ const db = getDatabase();
12932
+ const id = randomUUID10();
12933
+ db.prepare("INSERT INTO scripts (id, name, domain, description, variables) VALUES (?, ?, ?, ?, ?)").run(id, data.name, data.domain ?? "", data.description ?? "", JSON.stringify(data.variables ?? {}));
12934
+ for (let i = 0;i < data.steps.length; i++) {
12935
+ const step = data.steps[i];
12936
+ db.prepare("INSERT INTO script_steps (id, script_id, step_order, type, config, description, ai_enabled, ai_config) VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run(randomUUID10(), id, i, step.type, JSON.stringify(step.config), step.description ?? "", step.ai_enabled ? 1 : 0, JSON.stringify(step.ai_config ?? {}));
12937
+ }
12938
+ return getScript(id);
12870
12939
  }
12871
- function getScriptsDir() {
12872
- const dir = join8(getDataDir(), "scripts");
12873
- mkdirSync7(dir, { recursive: true });
12874
- return dir;
12940
+ function upsertScript(data) {
12941
+ const existing = getScriptByName(data.name);
12942
+ if (existing) {
12943
+ deleteScript(existing.id);
12944
+ }
12945
+ return createScript(data);
12875
12946
  }
12876
- function saveScript(script) {
12877
- const dir = getScriptsDir();
12878
- const path = join8(dir, `${script.name}.json`);
12879
- script.updated_at = new Date().toISOString();
12880
- if (!script.created_at)
12881
- script.created_at = script.updated_at;
12882
- writeFileSync2(path, JSON.stringify(script, null, 2));
12883
- return path;
12947
+ function getScript(id) {
12948
+ const db = getDatabase();
12949
+ const row = db.query("SELECT * FROM scripts WHERE id = ?").get(id);
12950
+ if (!row)
12951
+ return null;
12952
+ return { ...row, variables: JSON.parse(row.variables), run_count: row.run_count ?? 0 };
12884
12953
  }
12885
- function loadScript(name) {
12886
- const path = join8(getScriptsDir(), `${name}.json`);
12887
- if (!existsSync5(path))
12954
+ function getScriptByName(name) {
12955
+ const db = getDatabase();
12956
+ const row = db.query("SELECT * FROM scripts WHERE name = ?").get(name);
12957
+ if (!row)
12888
12958
  return null;
12889
- return JSON.parse(readFileSync3(path, "utf8"));
12959
+ return { ...row, variables: JSON.parse(row.variables), run_count: row.run_count ?? 0 };
12890
12960
  }
12891
12961
  function listScripts() {
12892
- const dir = getScriptsDir();
12962
+ const db = getDatabase();
12963
+ return db.query("SELECT * FROM scripts ORDER BY updated_at DESC").all().map((row) => ({ ...row, variables: JSON.parse(row.variables), run_count: row.run_count ?? 0 }));
12964
+ }
12965
+ function deleteScript(id) {
12966
+ const db = getDatabase();
12967
+ return db.prepare("DELETE FROM scripts WHERE id = ?").run(id).changes > 0;
12968
+ }
12969
+ function deleteScriptByName(name) {
12970
+ const db = getDatabase();
12971
+ return db.prepare("DELETE FROM scripts WHERE name = ?").run(name).changes > 0;
12972
+ }
12973
+ function getSteps(scriptId) {
12974
+ const db = getDatabase();
12975
+ return db.query("SELECT * FROM script_steps WHERE script_id = ? ORDER BY step_order").all(scriptId).map((row) => ({
12976
+ ...row,
12977
+ config: JSON.parse(row.config),
12978
+ ai_enabled: !!row.ai_enabled,
12979
+ ai_config: JSON.parse(row.ai_config)
12980
+ }));
12981
+ }
12982
+ function startRun(scriptId, totalSteps) {
12983
+ const db = getDatabase();
12984
+ const id = randomUUID10();
12985
+ db.prepare("INSERT INTO script_runs (id, script_id, status, total_steps) VALUES (?, ?, 'running', ?)").run(id, scriptId, totalSteps);
12986
+ return getRun(id);
12987
+ }
12988
+ function updateRunProgress(runId, step, description, stepsLog, variables) {
12989
+ const db = getDatabase();
12990
+ db.prepare("UPDATE script_runs SET current_step = ?, current_description = ?, steps_log = ?, variables = ? WHERE id = ?").run(step, description, JSON.stringify(stepsLog), JSON.stringify(variables), runId);
12991
+ }
12992
+ function completeRun(runId, status, errors, durationMs) {
12993
+ const db = getDatabase();
12994
+ db.prepare("UPDATE script_runs SET status = ?, errors = ?, duration_ms = ?, completed_at = datetime('now') WHERE id = ?").run(status, JSON.stringify(errors), durationMs, runId);
12995
+ const run = getRun(runId);
12996
+ if (run) {
12997
+ db.prepare("UPDATE scripts SET last_run = datetime('now'), run_count = run_count + 1, updated_at = datetime('now') WHERE id = ?").run(run.script_id);
12998
+ }
12999
+ }
13000
+ function getRun(runId) {
13001
+ const db = getDatabase();
13002
+ const row = db.query("SELECT * FROM script_runs WHERE id = ?").get(runId);
13003
+ if (!row)
13004
+ return null;
13005
+ return {
13006
+ ...row,
13007
+ variables: JSON.parse(row.variables),
13008
+ steps_log: JSON.parse(row.steps_log),
13009
+ errors: JSON.parse(row.errors)
13010
+ };
13011
+ }
13012
+ function listRuns(scriptId) {
13013
+ const db = getDatabase();
13014
+ const query = scriptId ? db.query("SELECT * FROM script_runs WHERE script_id = ? ORDER BY started_at DESC LIMIT 20").all(scriptId) : db.query("SELECT * FROM script_runs ORDER BY started_at DESC LIMIT 20").all();
13015
+ return query.map((row) => ({
13016
+ ...row,
13017
+ variables: JSON.parse(row.variables),
13018
+ steps_log: JSON.parse(row.steps_log),
13019
+ errors: JSON.parse(row.errors)
13020
+ }));
13021
+ }
13022
+ function migrateJsonScripts() {
13023
+ const { existsSync: existsSync5, readdirSync: readdirSync3, readFileSync: readFileSync3 } = __require("fs");
13024
+ const { join: join8 } = __require("path");
13025
+ const { getDataDir: getDataDir3 } = (init_schema(), __toCommonJS(exports_schema));
13026
+ const dir = join8(getDataDir3(), "scripts");
12893
13027
  if (!existsSync5(dir))
12894
- return [];
12895
- return readdirSync3(dir).filter((f) => f.endsWith(".json")).map((f) => {
13028
+ return 0;
13029
+ const files = readdirSync3(dir).filter((f) => f.endsWith(".json"));
13030
+ let migrated = 0;
13031
+ for (const file of files) {
12896
13032
  try {
12897
- const script = JSON.parse(readFileSync3(join8(dir, f), "utf8"));
12898
- return { name: script.name, domain: script.domain, description: script.description, steps: script.steps.length };
12899
- } catch {
12900
- return null;
12901
- }
12902
- }).filter(Boolean);
13033
+ const raw = JSON.parse(readFileSync3(join8(dir, file), "utf8"));
13034
+ if (!raw.name || !raw.steps)
13035
+ continue;
13036
+ if (getScriptByName(raw.name))
13037
+ continue;
13038
+ const steps = raw.steps.map((s) => {
13039
+ const isAI = s.type === "ai";
13040
+ return {
13041
+ type: isAI ? "extract" : s.type,
13042
+ config: {
13043
+ action: s.action,
13044
+ selector: s.selector,
13045
+ url: s.url,
13046
+ value: s.value,
13047
+ text: s.text,
13048
+ timeout: s.timeout,
13049
+ connector: s.connector,
13050
+ args: s.args,
13051
+ format: s.format,
13052
+ pattern: s.pattern,
13053
+ json_path: s.json_path,
13054
+ check: s.check,
13055
+ source: s.check,
13056
+ seconds: s.seconds,
13057
+ equals: s.equals,
13058
+ contains: s.contains,
13059
+ skip_to: s.skip_to,
13060
+ name: s.name,
13061
+ save_as: s.save_as,
13062
+ ...isAI ? { prompt: s.prompt, source: s.check ?? "last_output" } : {}
13063
+ },
13064
+ description: s.description ?? "",
13065
+ ai_enabled: isAI || !!s.ai?.enabled,
13066
+ ai_config: isAI ? { provider: s.model === "haiku" || s.model === "sonnet" || s.model === "opus" ? "anthropic" : "cerebras", model: s.model ?? "fast" } : s.ai ?? {}
13067
+ };
13068
+ });
13069
+ createScript({
13070
+ name: raw.name,
13071
+ domain: raw.domain ?? "",
13072
+ description: raw.description ?? "",
13073
+ variables: raw.variables ?? {},
13074
+ steps
13075
+ });
13076
+ migrated++;
13077
+ } catch {}
13078
+ }
13079
+ return migrated;
12903
13080
  }
12904
- function deleteScript(name) {
12905
- const path = join8(getScriptsDir(), `${name}.json`);
12906
- if (!existsSync5(path))
12907
- return false;
12908
- const { unlinkSync: unlinkSync2 } = __require("fs");
12909
- unlinkSync2(path);
12910
- return true;
13081
+ var init_scripts = __esm(() => {
13082
+ init_schema();
13083
+ });
13084
+
13085
+ // src/lib/ai-inference.ts
13086
+ function resolve(opts) {
13087
+ if (opts?.model && ALIASES[opts.model])
13088
+ return ALIASES[opts.model];
13089
+ if (opts?.provider && opts?.model)
13090
+ return { provider: opts.provider, model: opts.model };
13091
+ if (opts?.provider === "anthropic")
13092
+ return ALIASES.haiku;
13093
+ return ALIASES.fast;
13094
+ }
13095
+ async function infer(prompt, opts) {
13096
+ const { provider, model } = resolve(opts);
13097
+ const maxTokens = opts?.maxTokens ?? 1024;
13098
+ if (provider === "anthropic") {
13099
+ const apiKey2 = process.env["ANTHROPIC_API_KEY"];
13100
+ if (!apiKey2)
13101
+ throw new Error("ANTHROPIC_API_KEY not set");
13102
+ const res2 = await fetch("https://api.anthropic.com/v1/messages", {
13103
+ method: "POST",
13104
+ headers: { "content-type": "application/json", "x-api-key": apiKey2, "anthropic-version": "2023-06-01" },
13105
+ body: JSON.stringify({ model, max_tokens: maxTokens, messages: [{ role: "user", content: prompt }] })
13106
+ });
13107
+ if (!res2.ok)
13108
+ throw new Error(`Anthropic API ${res2.status}: ${(await res2.text()).slice(0, 200)}`);
13109
+ const data2 = await res2.json();
13110
+ return data2.content?.[0]?.text ?? "";
13111
+ }
13112
+ const apiKey = process.env["CEREBRAS_API_KEY"];
13113
+ if (!apiKey)
13114
+ throw new Error("CEREBRAS_API_KEY not set");
13115
+ const res = await fetch("https://api.cerebras.ai/v1/chat/completions", {
13116
+ method: "POST",
13117
+ headers: { "content-type": "application/json", Authorization: `Bearer ${apiKey}` },
13118
+ body: JSON.stringify({ model, max_tokens: maxTokens, temperature: opts?.temperature ?? 0, messages: [{ role: "user", content: prompt }] })
13119
+ });
13120
+ if (!res.ok)
13121
+ throw new Error(`Cerebras API ${res.status}: ${(await res.text()).slice(0, 200)}`);
13122
+ const data = await res.json();
13123
+ return data.choices?.[0]?.message?.content ?? "";
13124
+ }
13125
+ var ALIASES;
13126
+ var init_ai_inference = __esm(() => {
13127
+ ALIASES = {
13128
+ fast: { provider: "cerebras", model: "llama-4-scout-17b-16e-instruct" },
13129
+ scout: { provider: "cerebras", model: "llama-4-scout-17b-16e-instruct" },
13130
+ maverick: { provider: "cerebras", model: "llama-4-maverick-17b-128e-instruct" },
13131
+ haiku: { provider: "anthropic", model: "claude-haiku-4-5-20251001" },
13132
+ sonnet: { provider: "anthropic", model: "claude-sonnet-4-5-20250929" },
13133
+ opus: { provider: "anthropic", model: "claude-opus-4-6" }
13134
+ };
13135
+ });
13136
+
13137
+ // src/lib/vision-fallback.ts
13138
+ var exports_vision_fallback = {};
13139
+ __export(exports_vision_fallback, {
13140
+ findElementByVision: () => findElementByVision,
13141
+ clickByVision: () => clickByVision
13142
+ });
13143
+ async function findElementByVision(page, description, opts) {
13144
+ const model = opts?.model ?? process.env["BROWSER_VISION_MODEL"] ?? DEFAULT_MODEL;
13145
+ const screenshot = await page.screenshot({ type: "jpeg", quality: 80 });
13146
+ const base64 = screenshot.toString("base64");
13147
+ const viewport = page.viewportSize() ?? { width: 1280, height: 720 };
13148
+ const apiKey = process.env["ANTHROPIC_API_KEY"];
13149
+ if (!apiKey) {
13150
+ return { found: false, x: 0, y: 0, confidence: "none", description, model, error: "ANTHROPIC_API_KEY not set" };
13151
+ }
13152
+ try {
13153
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
13154
+ method: "POST",
13155
+ headers: {
13156
+ "content-type": "application/json",
13157
+ "x-api-key": apiKey,
13158
+ "anthropic-version": "2023-06-01"
13159
+ },
13160
+ body: JSON.stringify({
13161
+ model,
13162
+ max_tokens: 256,
13163
+ messages: [{
13164
+ role: "user",
13165
+ content: [
13166
+ {
13167
+ type: "image",
13168
+ source: { type: "base64", media_type: "image/jpeg", data: base64 }
13169
+ },
13170
+ {
13171
+ type: "text",
13172
+ text: `Find the element matching this description: "${description}"
13173
+
13174
+ The screenshot is ${viewport.width}x${viewport.height} pixels.
13175
+
13176
+ Reply with ONLY a JSON object (no markdown, no explanation):
13177
+ {"found": true, "x": <pixel_x>, "y": <pixel_y>, "confidence": "high|medium|low", "description": "<what you found>"}
13178
+
13179
+ If you cannot find the element:
13180
+ {"found": false, "x": 0, "y": 0, "confidence": "none", "description": "not found"}`
13181
+ }
13182
+ ]
13183
+ }]
13184
+ })
13185
+ });
13186
+ const data = await response.json();
13187
+ const text = data.content?.[0]?.text ?? "";
13188
+ const jsonStr = text.replace(/```json?\n?/g, "").replace(/```/g, "").trim();
13189
+ const result = JSON.parse(jsonStr);
13190
+ result.model = model;
13191
+ return result;
13192
+ } catch (err) {
13193
+ return {
13194
+ found: false,
13195
+ x: 0,
13196
+ y: 0,
13197
+ confidence: "none",
13198
+ description,
13199
+ model,
13200
+ error: err instanceof Error ? err.message : String(err)
13201
+ };
13202
+ }
12911
13203
  }
13204
+ async function clickByVision(page, description, opts) {
13205
+ const result = await findElementByVision(page, description, opts);
13206
+ if (result.found && result.x > 0 && result.y > 0) {
13207
+ await page.mouse.click(result.x, result.y);
13208
+ }
13209
+ return result;
13210
+ }
13211
+ var DEFAULT_MODEL = "claude-sonnet-4-5-20250929";
13212
+
13213
+ // src/lib/script-engine.ts
13214
+ var exports_script_engine = {};
13215
+ __export(exports_script_engine, {
13216
+ executeScriptSync: () => executeScriptSync,
13217
+ executeScript: () => executeScript
13218
+ });
12912
13219
  function interpolate(template, vars) {
12913
- return template.replace(/\{\{(\w+(?:\.\w+)*)\}\}/g, (match, key) => {
12914
- return vars[key] ?? match;
13220
+ return template.replace(/\{\{(\w+(?:\.\w+)*)\}\}/g, (_, key) => vars[key] ?? `{{${key}}}`);
13221
+ }
13222
+ function interpolateConfig(config, vars) {
13223
+ const result = {};
13224
+ for (const [k, v] of Object.entries(config)) {
13225
+ if (typeof v === "string")
13226
+ result[k] = interpolate(v, vars);
13227
+ else if (Array.isArray(v))
13228
+ result[k] = v.map((item) => typeof item === "string" ? interpolate(item, vars) : item);
13229
+ else
13230
+ result[k] = v;
13231
+ }
13232
+ return result;
13233
+ }
13234
+ function executeScript(scriptId, page, overrides = {}) {
13235
+ const steps = getSteps(scriptId);
13236
+ const run = startRun(scriptId, steps.length);
13237
+ _runSteps(run.id, scriptId, steps, page, overrides).catch((err) => {
13238
+ completeRun(run.id, "failed", [err instanceof Error ? err.message : String(err)], 0);
12915
13239
  });
13240
+ return run.id;
12916
13241
  }
12917
- function stepDescription(step) {
12918
- return step.description ?? `${step.type}${step.action ? `:${step.action}` : ""}${step.connector ? `:${step.connector}` : ""}`;
13242
+ async function executeScriptSync(scriptId, page, overrides = {}) {
13243
+ const steps = getSteps(scriptId);
13244
+ const run = startRun(scriptId, steps.length);
13245
+ return _runSteps(run.id, scriptId, steps, page, overrides);
12919
13246
  }
12920
- async function runScript(script, page, overrides = {}, jobId) {
13247
+ async function _runSteps(runId, scriptId, steps, page, overrides) {
12921
13248
  const t0 = Date.now();
12922
- const vars = { ...script.variables, ...overrides };
13249
+ const { getScript: getScript2 } = await Promise.resolve().then(() => (init_scripts(), exports_scripts));
13250
+ const script = getScript2(scriptId);
13251
+ const vars = { ...script?.variables ?? {}, ...overrides };
12923
13252
  const errors = [];
13253
+ const stepsLog = [];
12924
13254
  let executed = 0;
12925
13255
  let failed = 0;
12926
- const job = jobId && activeJobs.has(jobId) ? activeJobs.get(jobId) : {
12927
- id: jobId ?? randomUUID10(),
12928
- script_name: script.name,
12929
- status: "running",
12930
- current_step: 0,
12931
- total_steps: script.steps.length,
12932
- current_step_description: "Starting...",
12933
- steps_log: [],
12934
- started_at: new Date().toISOString()
12935
- };
12936
- activeJobs.set(job.id, job);
12937
- for (let i = 0;i < script.steps.length; i++) {
12938
- const step = script.steps[i];
12939
- const desc = stepDescription(step);
13256
+ for (let i = 0;i < steps.length; i++) {
13257
+ const step = steps[i];
13258
+ const cfg = interpolateConfig(step.config, vars);
13259
+ const desc = step.description || `${step.type}`;
12940
13260
  executed++;
12941
- job.current_step = i + 1;
12942
- job.current_step_description = desc;
12943
- job.steps_log.push({ step: i + 1, type: step.type, description: desc, status: "running" });
13261
+ stepsLog.push({ step: i + 1, type: step.type, description: desc, status: "running" });
13262
+ updateRunProgress(runId, i + 1, desc, stepsLog, vars);
12944
13263
  const stepStart = Date.now();
12945
13264
  try {
12946
13265
  switch (step.type) {
12947
13266
  case "browser":
12948
- await runBrowserStep(step, page, vars);
13267
+ await execBrowser(cfg, step, page, vars);
12949
13268
  break;
12950
13269
  case "connector":
12951
- await runConnectorStep(step, vars);
13270
+ await execConnector(cfg, step, vars);
12952
13271
  break;
12953
13272
  case "extract":
12954
- runExtractStep(step, vars);
13273
+ await execExtract(cfg, step, vars);
12955
13274
  break;
12956
13275
  case "wait":
12957
- await new Promise((r) => setTimeout(r, (step.seconds ?? 3) * 1000));
13276
+ await new Promise((r) => setTimeout(r, (cfg.seconds ?? 3) * 1000));
12958
13277
  break;
12959
- case "condition": {
12960
- const checkVal = vars[step.check ?? ""];
12961
- let conditionMet = true;
12962
- if (step.equals !== undefined)
12963
- conditionMet = checkVal === interpolate(step.equals, vars);
12964
- if (step.contains !== undefined)
12965
- conditionMet = checkVal?.includes(interpolate(step.contains, vars)) ?? false;
12966
- if (!conditionMet && step.skip_to !== undefined) {
12967
- i = step.skip_to - 1;
12968
- }
13278
+ case "condition":
13279
+ i = execCondition(cfg, vars, i);
12969
13280
  break;
12970
- }
12971
- case "save_state": {
12972
- const stateName = interpolate(step.name ?? script.name, vars);
12973
- try {
12974
- const { saveStateFromPage: saveStateFromPage2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
12975
- const path = await saveStateFromPage2(page, stateName);
12976
- vars["saved_state_path"] = path;
12977
- } catch {}
13281
+ case "save_state":
13282
+ await execSaveState(cfg, page, vars);
12978
13283
  break;
12979
- }
12980
13284
  }
12981
- const logEntry = job.steps_log[job.steps_log.length - 1];
12982
- logEntry.status = "ok";
12983
- logEntry.duration_ms = Date.now() - stepStart;
13285
+ stepsLog[stepsLog.length - 1].status = "ok";
13286
+ stepsLog[stepsLog.length - 1].duration_ms = Date.now() - stepStart;
12984
13287
  } catch (err) {
12985
13288
  failed++;
12986
- const msg = `Step ${i + 1} (${step.type}/${step.action ?? step.connector ?? ""}): ${err instanceof Error ? err.message : String(err)}`;
13289
+ const msg = `Step ${i + 1} (${step.type}): ${err instanceof Error ? err.message : String(err)}`;
12987
13290
  errors.push(msg);
12988
- const logEntry = job.steps_log[job.steps_log.length - 1];
12989
- logEntry.status = "failed";
12990
- logEntry.error = err instanceof Error ? err.message : String(err);
12991
- logEntry.duration_ms = Date.now() - stepStart;
12992
- if (step.type === "browser" && step.action === "navigate")
13291
+ stepsLog[stepsLog.length - 1].status = "failed";
13292
+ stepsLog[stepsLog.length - 1].error = err instanceof Error ? err.message : String(err);
13293
+ stepsLog[stepsLog.length - 1].duration_ms = Date.now() - stepStart;
13294
+ if (step.type === "browser" && cfg.action === "navigate")
12993
13295
  break;
12994
13296
  }
13297
+ updateRunProgress(runId, i + 1, desc, stepsLog, vars);
12995
13298
  }
12996
- const result = {
12997
- success: failed === 0,
12998
- steps_executed: executed,
12999
- steps_failed: failed,
13000
- variables: vars,
13001
- errors,
13002
- duration_ms: Date.now() - t0
13003
- };
13004
- job.status = failed === 0 ? "completed" : "failed";
13005
- job.result = result;
13006
- return result;
13299
+ const durationMs = Date.now() - t0;
13300
+ const status = failed === 0 ? "completed" : "failed";
13301
+ completeRun(runId, status, errors, durationMs);
13302
+ return { run_id: runId, success: failed === 0, steps_executed: executed, steps_failed: failed, errors, duration_ms: durationMs, variables: vars };
13007
13303
  }
13008
- function runScriptAsync(script, page, overrides = {}) {
13009
- const jobId = randomUUID10();
13010
- const job = {
13011
- id: jobId,
13012
- script_name: script.name,
13013
- status: "running",
13014
- current_step: 0,
13015
- total_steps: script.steps.length,
13016
- current_step_description: "Starting...",
13017
- steps_log: [],
13018
- started_at: new Date().toISOString()
13019
- };
13020
- activeJobs.set(jobId, job);
13021
- runScript(script, page, overrides, jobId).catch((err) => {
13022
- job.status = "failed";
13023
- job.current_step_description = `Fatal error: ${err instanceof Error ? err.message : String(err)}`;
13024
- });
13025
- return jobId;
13026
- }
13027
- async function runBrowserStep(step, page, vars) {
13028
- const action = step.action;
13029
- if (!action)
13030
- throw new Error("Browser step missing action");
13304
+ async function execBrowser(cfg, step, page, vars) {
13305
+ const action = cfg.action;
13031
13306
  switch (action) {
13032
- case "navigate": {
13033
- const url = interpolate(step.url ?? "", vars);
13034
- await page.goto(url, { waitUntil: "domcontentloaded", timeout: step.timeout ?? 30000 });
13307
+ case "navigate":
13308
+ await page.goto(cfg.url, { waitUntil: "domcontentloaded", timeout: cfg.timeout ?? 30000 });
13035
13309
  await new Promise((r) => setTimeout(r, 1000));
13036
13310
  vars["current_url"] = page.url();
13037
13311
  vars["current_title"] = await page.title();
13038
13312
  break;
13039
- }
13040
13313
  case "type": {
13041
- const selector = interpolate(step.selector ?? "input", vars);
13042
- const value = interpolate(step.value ?? step.text ?? "", vars);
13043
- await page.fill(selector, value);
13314
+ const selector = cfg.selector ?? "input";
13315
+ const value = cfg.value ?? cfg.text ?? "";
13316
+ try {
13317
+ await page.fill(selector, value);
13318
+ } catch (origErr) {
13319
+ if (step.ai_enabled) {
13320
+ const healed = await aiSelfHeal(page, `input field for typing "${value}"`, step);
13321
+ if (healed) {
13322
+ await page.mouse.click(healed.x, healed.y);
13323
+ await page.keyboard.type(value);
13324
+ } else
13325
+ throw origErr;
13326
+ } else {
13327
+ const { healSelector: healSelector2 } = await Promise.resolve().then(() => exports_self_heal);
13328
+ const result = await healSelector2(page, selector);
13329
+ if (result.found && result.locator)
13330
+ await result.locator.fill(value);
13331
+ else
13332
+ throw origErr;
13333
+ }
13334
+ }
13044
13335
  break;
13045
13336
  }
13046
13337
  case "click": {
13047
- const selector = interpolate(step.selector ?? "", vars);
13048
- await page.click(selector, { timeout: step.timeout ?? 1e4 });
13338
+ const selector = cfg.selector;
13339
+ try {
13340
+ await page.click(selector, { timeout: cfg.timeout ?? 1e4 });
13341
+ } catch (origErr) {
13342
+ if (step.ai_enabled) {
13343
+ const healed = await aiSelfHeal(page, `clickable element matching "${selector}"`, step);
13344
+ if (healed)
13345
+ await page.mouse.click(healed.x, healed.y);
13346
+ else
13347
+ throw origErr;
13348
+ } else {
13349
+ const { healSelector: healSelector2 } = await Promise.resolve().then(() => exports_self_heal);
13350
+ const result = await healSelector2(page, selector);
13351
+ if (result.found && result.locator)
13352
+ await result.locator.click();
13353
+ else
13354
+ throw origErr;
13355
+ }
13356
+ }
13049
13357
  await new Promise((r) => setTimeout(r, 500));
13050
13358
  break;
13051
13359
  }
13052
13360
  case "click_text": {
13053
- const text = interpolate(step.text ?? "", vars);
13054
- await page.getByText(text, { exact: false }).first().click({ timeout: step.timeout ?? 1e4 });
13361
+ const text = cfg.text;
13362
+ try {
13363
+ await page.getByText(text, { exact: false }).first().click({ timeout: cfg.timeout ?? 1e4 });
13364
+ } catch (origErr) {
13365
+ if (step.ai_enabled) {
13366
+ const healed = await aiSelfHeal(page, `button or link with text "${text}"`, step);
13367
+ if (healed)
13368
+ await page.mouse.click(healed.x, healed.y);
13369
+ else
13370
+ throw origErr;
13371
+ } else
13372
+ throw origErr;
13373
+ }
13055
13374
  await new Promise((r) => setTimeout(r, 500));
13056
13375
  break;
13057
13376
  }
13058
- case "wait_for_navigation": {
13377
+ case "wait_for_navigation":
13059
13378
  try {
13060
- await page.waitForNavigation({ timeout: step.timeout ?? 15000 });
13379
+ await page.waitForNavigation({ timeout: cfg.timeout ?? 15000 });
13061
13380
  } catch {}
13062
13381
  await new Promise((r) => setTimeout(r, 1000));
13063
13382
  vars["current_url"] = page.url();
13064
13383
  break;
13065
- }
13066
13384
  case "wait_for_text": {
13067
- const text = interpolate(step.text ?? "", vars);
13068
- await page.waitForSelector(`text=${text}`, { timeout: step.timeout ?? 1e4 });
13385
+ const text = cfg.text;
13386
+ await page.waitForSelector(`text=${text}`, { timeout: cfg.timeout ?? 1e4 });
13069
13387
  break;
13070
13388
  }
13071
- case "snapshot": {
13389
+ case "snapshot":
13072
13390
  vars["page_text"] = await page.evaluate(() => document.body?.textContent?.trim() ?? "");
13073
13391
  break;
13074
- }
13075
13392
  }
13076
13393
  }
13077
- async function runConnectorStep(step, vars) {
13078
- const connectorName = step.connector;
13079
- if (!connectorName)
13080
- throw new Error("Connector step missing connector name");
13081
- const args = (step.args ?? []).map((a) => interpolate(a, vars));
13082
- let result;
13083
- try {
13084
- const bin = `connect-${connectorName}`;
13085
- const proc = Bun.spawn([bin, ...args], {
13086
- stdout: "pipe",
13087
- stderr: "pipe",
13088
- env: { ...process.env, HOME: process.env.HOME ?? "" }
13089
- });
13090
- const stdout = await new Response(proc.stdout).text();
13091
- const stderr = await new Response(proc.stderr).text();
13092
- const exitCode = await proc.exited;
13093
- result = { stdout, stderr, exitCode, success: exitCode === 0 };
13094
- } catch (e) {
13095
- result = { stdout: "", stderr: e.message ?? String(e), exitCode: 1, success: false };
13096
- }
13097
- vars["last_output"] = result.stdout;
13098
- vars["last_success"] = String(result.success);
13099
- vars["last_exit_code"] = String(result.exitCode);
13100
- try {
13101
- const parsed = JSON.parse(result.stdout);
13102
- if (typeof parsed === "object" && parsed !== null) {
13103
- for (const [k, v] of Object.entries(parsed)) {
13104
- if (typeof v === "string" || typeof v === "number") {
13105
- vars[`last.${k}`] = String(v);
13106
- }
13107
- }
13108
- }
13109
- } catch {}
13110
- if (step.save_as) {
13111
- vars[step.save_as] = result.stdout;
13394
+ async function execConnector(cfg, step, vars) {
13395
+ const connector = cfg.connector;
13396
+ if (!connector)
13397
+ throw new Error("Connector step missing 'connector' in config");
13398
+ const args = cfg.args ?? [];
13399
+ const proc = Bun.spawn([`connect-${connector}`, ...args], {
13400
+ stdout: "pipe",
13401
+ stderr: "pipe",
13402
+ env: { ...process.env, HOME: process.env.HOME ?? "" }
13403
+ });
13404
+ const stdout = await new Response(proc.stdout).text();
13405
+ const stderr = await new Response(proc.stderr).text();
13406
+ const exitCode = await proc.exited;
13407
+ if (exitCode !== 0 && !stdout)
13408
+ throw new Error(`Connector ${connector} failed: ${stderr.slice(0, 200)}`);
13409
+ const raw = stdout || stderr;
13410
+ vars["last_output"] = raw;
13411
+ if (step.ai_enabled && step.ai_config?.prompt) {
13412
+ const aiPrompt = interpolate(step.ai_config.prompt, { ...vars, last_output: raw });
13413
+ const provider = step.ai_config.provider ?? "cerebras";
13414
+ const model = step.ai_config.model ?? "fast";
13415
+ const parsed = await infer(aiPrompt, { provider, model });
13416
+ const saveTo = cfg.save_as ?? "last_output";
13417
+ vars[saveTo] = parsed.trim();
13418
+ } else if (cfg.save_as) {
13419
+ vars[cfg.save_as] = raw;
13420
+ }
13421
+ }
13422
+ async function execExtract(cfg, step, vars) {
13423
+ const saveTo = cfg.save_as ?? "extracted";
13424
+ if (step.ai_enabled || cfg.prompt) {
13425
+ const source = cfg.source ? vars[cfg.source] ?? "" : vars["last_output"] ?? "";
13426
+ const prompt = interpolate(cfg.prompt ?? `Extract the key information from this text:
13427
+
13428
+ ${source}`, { ...vars, source });
13429
+ const provider = step.ai_config?.provider ?? "cerebras";
13430
+ const model = step.ai_config?.model ?? "fast";
13431
+ const result = await infer(prompt, { provider, model });
13432
+ vars[saveTo] = result.trim();
13433
+ vars["last_output"] = result.trim();
13434
+ return;
13112
13435
  }
13113
- }
13114
- function decodeHtmlEntities(str) {
13115
- return str.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&#x27;/g, "'");
13116
- }
13117
- function runExtractStep(step, vars) {
13118
- const saveTo = step.save_as ?? "extracted";
13119
- if (step.pattern) {
13120
- const source = step.check ? vars[step.check] ?? "" : vars["last_output"] ?? "";
13121
- const regex = new RegExp(step.pattern);
13122
- const match = regex.exec(source);
13436
+ if (cfg.pattern) {
13437
+ const source = cfg.source ? vars[cfg.source] ?? "" : vars["last_output"] ?? "";
13438
+ const match = new RegExp(cfg.pattern).exec(source);
13123
13439
  if (match) {
13124
13440
  vars[saveTo] = decodeHtmlEntities(match[1] ?? match[0]);
13125
13441
  }
13126
13442
  }
13127
- if (step.json_path) {
13128
- const source = vars["last_output"] ?? "{}";
13129
- try {
13130
- let obj = JSON.parse(source);
13131
- for (const key of step.json_path.split(".")) {
13132
- obj = obj?.[key];
13133
- }
13134
- if (obj !== undefined)
13135
- vars[saveTo] = String(obj);
13136
- } catch {}
13137
- }
13138
13443
  }
13139
- function createScriptFromJSON(jsonStr) {
13140
- const parsed = JSON.parse(jsonStr);
13141
- if (!parsed.name)
13142
- throw new Error("Script must have a 'name' field");
13143
- if (!parsed.domain)
13144
- throw new Error("Script must have a 'domain' field");
13145
- if (!parsed.steps || !Array.isArray(parsed.steps) || parsed.steps.length === 0) {
13146
- throw new Error("Script must have a non-empty 'steps' array");
13147
- }
13148
- return {
13149
- name: parsed.name,
13150
- domain: parsed.domain,
13151
- description: parsed.description ?? "",
13152
- variables: parsed.variables ?? {},
13153
- steps: parsed.steps,
13154
- created_at: new Date().toISOString(),
13155
- updated_at: new Date().toISOString()
13156
- };
13444
+ function execCondition(cfg, vars, i) {
13445
+ const checkVal = vars[cfg.check ?? ""];
13446
+ let met = true;
13447
+ if (cfg.equals !== undefined)
13448
+ met = checkVal === interpolate(cfg.equals, vars);
13449
+ if (cfg.contains !== undefined)
13450
+ met = checkVal?.includes(interpolate(cfg.contains, vars)) ?? false;
13451
+ if (!met && cfg.skip_to !== undefined)
13452
+ return cfg.skip_to - 1;
13453
+ return i;
13157
13454
  }
13158
- function createScriptFromFile(filePath) {
13159
- if (!existsSync5(filePath))
13160
- throw new Error(`File not found: ${filePath}`);
13161
- return createScriptFromJSON(readFileSync3(filePath, "utf8"));
13455
+ async function execSaveState(cfg, page, vars) {
13456
+ const name = interpolate(cfg.name ?? "default", vars);
13457
+ try {
13458
+ const { saveStateFromPage: saveStateFromPage2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
13459
+ vars["saved_state_path"] = await saveStateFromPage2(page, name);
13460
+ } catch {}
13162
13461
  }
13163
- var activeJobs;
13164
- var init_login_scripts = __esm(() => {
13165
- init_schema();
13166
- activeJobs = new Map;
13462
+ async function aiSelfHeal(page, description, step) {
13463
+ try {
13464
+ const { findElementByVision: findElementByVision2 } = await Promise.resolve().then(() => exports_vision_fallback);
13465
+ const provider = step.ai_config?.provider ?? undefined;
13466
+ const model = step.ai_config?.model ?? undefined;
13467
+ const result = await findElementByVision2(page, description, { model: model ?? provider });
13468
+ if (result.found)
13469
+ return { x: result.x, y: result.y };
13470
+ } catch {}
13471
+ return null;
13472
+ }
13473
+ function decodeHtmlEntities(str) {
13474
+ return str.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&#x27;/g, "'");
13475
+ }
13476
+ var init_script_engine = __esm(() => {
13477
+ init_scripts();
13478
+ init_ai_inference();
13167
13479
  });
13168
13480
 
13169
13481
  // src/lib/daemon-client.ts
@@ -13175,8 +13487,8 @@ __export(exports_daemon_client, {
13175
13487
  getDaemonPidFile: () => getDaemonPidFile,
13176
13488
  getDaemonPid: () => getDaemonPid
13177
13489
  });
13178
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
13179
- import { join as join9 } from "path";
13490
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
13491
+ import { join as join8 } from "path";
13180
13492
  import { homedir as homedir8 } from "os";
13181
13493
  function getDaemonPidFile() {
13182
13494
  return PID_FILE;
@@ -13185,10 +13497,10 @@ function getDaemonPort() {
13185
13497
  return parseInt(process.env["BROWSER_DAEMON_PORT"] ?? String(DEFAULT_PORT), 10);
13186
13498
  }
13187
13499
  function isDaemonRunning() {
13188
- if (!existsSync6(PID_FILE))
13500
+ if (!existsSync5(PID_FILE))
13189
13501
  return false;
13190
13502
  try {
13191
- const pid = parseInt(readFileSync4(PID_FILE, "utf8").trim(), 10);
13503
+ const pid = parseInt(readFileSync3(PID_FILE, "utf8").trim(), 10);
13192
13504
  process.kill(pid, 0);
13193
13505
  return true;
13194
13506
  } catch {
@@ -13196,10 +13508,10 @@ function isDaemonRunning() {
13196
13508
  }
13197
13509
  }
13198
13510
  function getDaemonPid() {
13199
- if (!existsSync6(PID_FILE))
13511
+ if (!existsSync5(PID_FILE))
13200
13512
  return null;
13201
13513
  try {
13202
- return parseInt(readFileSync4(PID_FILE, "utf8").trim(), 10);
13514
+ return parseInt(readFileSync3(PID_FILE, "utf8").trim(), 10);
13203
13515
  } catch {
13204
13516
  return null;
13205
13517
  }
@@ -13219,7 +13531,7 @@ async function getDaemonStatus() {
13219
13531
  }
13220
13532
  var PID_FILE, DEFAULT_PORT = 7030;
13221
13533
  var init_daemon_client = __esm(() => {
13222
- PID_FILE = join9(process.env["BROWSER_DATA_DIR"] ?? join9(homedir8(), ".browser"), "daemon.pid");
13534
+ PID_FILE = join8(process.env["BROWSER_DATA_DIR"] ?? join8(homedir8(), ".browser"), "daemon.pid");
13223
13535
  });
13224
13536
 
13225
13537
  // node_modules/zod/v3/helpers/util.js
@@ -17293,16 +17605,16 @@ __export(exports_downloads, {
17293
17605
  cleanStaleDownloads: () => cleanStaleDownloads
17294
17606
  });
17295
17607
  import { randomUUID as randomUUID11 } from "crypto";
17296
- import { join as join10, basename, extname } from "path";
17297
- import { mkdirSync as mkdirSync8, existsSync as existsSync7, readdirSync as readdirSync4, statSync, unlinkSync as unlinkSync2, copyFileSync, writeFileSync as writeFileSync3, readFileSync as readFileSync5 } from "fs";
17608
+ import { join as join9, basename, extname } from "path";
17609
+ import { mkdirSync as mkdirSync7, existsSync as existsSync6, readdirSync as readdirSync3, statSync, unlinkSync as unlinkSync2, copyFileSync, writeFileSync as writeFileSync2, readFileSync as readFileSync4 } from "fs";
17298
17610
  import { homedir as homedir9 } from "os";
17299
17611
  function getDataDir3() {
17300
- return process.env["BROWSER_DATA_DIR"] ?? join10(homedir9(), ".browser");
17612
+ return process.env["BROWSER_DATA_DIR"] ?? join9(homedir9(), ".browser");
17301
17613
  }
17302
17614
  function getDownloadsDir(sessionId) {
17303
- const base = join10(getDataDir3(), "downloads");
17304
- const dir = sessionId ? join10(base, sessionId) : base;
17305
- mkdirSync8(dir, { recursive: true });
17615
+ const base = join9(getDataDir3(), "downloads");
17616
+ const dir = sessionId ? join9(base, sessionId) : base;
17617
+ mkdirSync7(dir, { recursive: true });
17306
17618
  return dir;
17307
17619
  }
17308
17620
  function ensureDownloadsDir() {
@@ -17317,8 +17629,8 @@ function saveToDownloads(buffer, filename, opts) {
17317
17629
  const ext = extname(filename) || "";
17318
17630
  const stem = basename(filename, ext);
17319
17631
  const uniqueName = `${stem}-${id.slice(0, 8)}${ext}`;
17320
- const filePath = join10(dir, uniqueName);
17321
- writeFileSync3(filePath, buffer);
17632
+ const filePath = join9(dir, uniqueName);
17633
+ writeFileSync2(filePath, buffer);
17322
17634
  const meta = {
17323
17635
  id,
17324
17636
  type: opts?.type ?? detectType(filename),
@@ -17329,7 +17641,7 @@ function saveToDownloads(buffer, filename, opts) {
17329
17641
  original_name: filename,
17330
17642
  ...opts?.metadata
17331
17643
  };
17332
- writeFileSync3(metaPath(filePath), JSON.stringify(meta, null, 2));
17644
+ writeFileSync2(metaPath(filePath), JSON.stringify(meta, null, 2));
17333
17645
  return {
17334
17646
  id,
17335
17647
  path: filePath,
@@ -17346,23 +17658,23 @@ function listDownloads(sessionId) {
17346
17658
  const dir = getDownloadsDir(sessionId);
17347
17659
  const results = [];
17348
17660
  function scanDir(d) {
17349
- if (!existsSync7(d))
17661
+ if (!existsSync6(d))
17350
17662
  return;
17351
- const entries = readdirSync4(d);
17663
+ const entries = readdirSync3(d);
17352
17664
  for (const entry of entries) {
17353
17665
  if (entry.endsWith(".meta.json"))
17354
17666
  continue;
17355
- const full = join10(d, entry);
17667
+ const full = join9(d, entry);
17356
17668
  const stat = statSync(full);
17357
17669
  if (stat.isDirectory()) {
17358
17670
  scanDir(full);
17359
17671
  continue;
17360
17672
  }
17361
17673
  const mpath = metaPath(full);
17362
- if (!existsSync7(mpath))
17674
+ if (!existsSync6(mpath))
17363
17675
  continue;
17364
17676
  try {
17365
- const meta = JSON.parse(readFileSync5(mpath, "utf8"));
17677
+ const meta = JSON.parse(readFileSync4(mpath, "utf8"));
17366
17678
  results.push({
17367
17679
  id: meta.id,
17368
17680
  path: full,
@@ -17390,7 +17702,7 @@ function deleteDownload(id, sessionId) {
17390
17702
  return false;
17391
17703
  try {
17392
17704
  unlinkSync2(file.path);
17393
- if (existsSync7(file.meta_path))
17705
+ if (existsSync6(file.meta_path))
17394
17706
  unlinkSync2(file.meta_path);
17395
17707
  return true;
17396
17708
  } catch {
@@ -17437,8 +17749,8 @@ function detectType(filename) {
17437
17749
  var init_downloads = () => {};
17438
17750
 
17439
17751
  // src/lib/files-integration.ts
17440
- import { join as join11 } from "path";
17441
- import { mkdirSync as mkdirSync9, copyFileSync as copyFileSync2 } from "fs";
17752
+ import { join as join10 } from "path";
17753
+ import { mkdirSync as mkdirSync8, copyFileSync as copyFileSync2 } from "fs";
17442
17754
  import { homedir as homedir10 } from "os";
17443
17755
  async function persistFile(localPath, opts) {
17444
17756
  try {
@@ -17448,12 +17760,12 @@ async function persistFile(localPath, opts) {
17448
17760
  return { id: ref.id, path: ref.path ?? localPath, permanent: true, provider: "open-files" };
17449
17761
  }
17450
17762
  } catch {}
17451
- const dataDir = process.env["BROWSER_DATA_DIR"] ?? join11(homedir10(), ".browser");
17763
+ const dataDir = process.env["BROWSER_DATA_DIR"] ?? join10(homedir10(), ".browser");
17452
17764
  const date = new Date().toISOString().split("T")[0];
17453
- const dir = join11(dataDir, "persistent", date);
17454
- mkdirSync9(dir, { recursive: true });
17765
+ const dir = join10(dataDir, "persistent", date);
17766
+ mkdirSync8(dir, { recursive: true });
17455
17767
  const filename = localPath.split("/").pop() ?? "file";
17456
- const targetPath = join11(dir, filename);
17768
+ const targetPath = join10(dir, filename);
17457
17769
  copyFileSync2(localPath, targetPath);
17458
17770
  return {
17459
17771
  id: `local-${Date.now()}`,
@@ -17930,9 +18242,9 @@ async function tryReplayAuth(page, domain) {
17930
18242
  return { replayed: false };
17931
18243
  if (flow.storage_state_path) {
17932
18244
  try {
17933
- const { existsSync: existsSync8, readFileSync: readFileSync6 } = await import("fs");
17934
- if (existsSync8(flow.storage_state_path)) {
17935
- const state = JSON.parse(readFileSync6(flow.storage_state_path, "utf8"));
18245
+ const { existsSync: existsSync7, readFileSync: readFileSync5 } = await import("fs");
18246
+ if (existsSync7(flow.storage_state_path)) {
18247
+ const state = JSON.parse(readFileSync5(flow.storage_state_path, "utf8"));
17936
18248
  if (state.cookies?.length) {
17937
18249
  await page.context().addCookies(state.cookies);
17938
18250
  await page.reload();
@@ -17981,82 +18293,6 @@ var init_auth_flow = __esm(() => {
17981
18293
  ];
17982
18294
  });
17983
18295
 
17984
- // src/lib/vision-fallback.ts
17985
- var exports_vision_fallback = {};
17986
- __export(exports_vision_fallback, {
17987
- findElementByVision: () => findElementByVision,
17988
- clickByVision: () => clickByVision
17989
- });
17990
- async function findElementByVision(page, description, opts) {
17991
- const model = opts?.model ?? process.env["BROWSER_VISION_MODEL"] ?? DEFAULT_MODEL;
17992
- const screenshot = await page.screenshot({ type: "jpeg", quality: 80 });
17993
- const base64 = screenshot.toString("base64");
17994
- const viewport = page.viewportSize() ?? { width: 1280, height: 720 };
17995
- const apiKey = process.env["ANTHROPIC_API_KEY"];
17996
- if (!apiKey) {
17997
- return { found: false, x: 0, y: 0, confidence: "none", description, model, error: "ANTHROPIC_API_KEY not set" };
17998
- }
17999
- try {
18000
- const response = await fetch("https://api.anthropic.com/v1/messages", {
18001
- method: "POST",
18002
- headers: {
18003
- "content-type": "application/json",
18004
- "x-api-key": apiKey,
18005
- "anthropic-version": "2023-06-01"
18006
- },
18007
- body: JSON.stringify({
18008
- model,
18009
- max_tokens: 256,
18010
- messages: [{
18011
- role: "user",
18012
- content: [
18013
- {
18014
- type: "image",
18015
- source: { type: "base64", media_type: "image/jpeg", data: base64 }
18016
- },
18017
- {
18018
- type: "text",
18019
- text: `Find the element matching this description: "${description}"
18020
-
18021
- The screenshot is ${viewport.width}x${viewport.height} pixels.
18022
-
18023
- Reply with ONLY a JSON object (no markdown, no explanation):
18024
- {"found": true, "x": <pixel_x>, "y": <pixel_y>, "confidence": "high|medium|low", "description": "<what you found>"}
18025
-
18026
- If you cannot find the element:
18027
- {"found": false, "x": 0, "y": 0, "confidence": "none", "description": "not found"}`
18028
- }
18029
- ]
18030
- }]
18031
- })
18032
- });
18033
- const data = await response.json();
18034
- const text = data.content?.[0]?.text ?? "";
18035
- const jsonStr = text.replace(/```json?\n?/g, "").replace(/```/g, "").trim();
18036
- const result = JSON.parse(jsonStr);
18037
- result.model = model;
18038
- return result;
18039
- } catch (err) {
18040
- return {
18041
- found: false,
18042
- x: 0,
18043
- y: 0,
18044
- confidence: "none",
18045
- description,
18046
- model,
18047
- error: err instanceof Error ? err.message : String(err)
18048
- };
18049
- }
18050
- }
18051
- async function clickByVision(page, description, opts) {
18052
- const result = await findElementByVision(page, description, opts);
18053
- if (result.found && result.x > 0 && result.y > 0) {
18054
- await page.mouse.click(result.x, result.y);
18055
- }
18056
- return result;
18057
- }
18058
- var DEFAULT_MODEL = "claude-sonnet-4-5-20250929";
18059
-
18060
18296
  // src/lib/datasets.ts
18061
18297
  var exports_datasets = {};
18062
18298
  __export(exports_datasets, {
@@ -18068,8 +18304,8 @@ __export(exports_datasets, {
18068
18304
  deleteDataset: () => deleteDataset
18069
18305
  });
18070
18306
  import { randomUUID as randomUUID14 } from "crypto";
18071
- import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync10 } from "fs";
18072
- import { join as join12 } from "path";
18307
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync9 } from "fs";
18308
+ import { join as join11 } from "path";
18073
18309
  import { homedir as homedir11 } from "os";
18074
18310
  function saveDataset(data) {
18075
18311
  const db = getDatabase();
@@ -18108,14 +18344,14 @@ function exportDataset(name, format) {
18108
18344
  const dataset = getDatasetByName(name);
18109
18345
  if (!dataset)
18110
18346
  throw new Error(`Dataset '${name}' not found`);
18111
- const dir = join12(process.env["BROWSER_DATA_DIR"] ?? join12(homedir11(), ".browser"), "exports");
18112
- mkdirSync10(dir, { recursive: true });
18347
+ const dir = join11(process.env["BROWSER_DATA_DIR"] ?? join11(homedir11(), ".browser"), "exports");
18348
+ mkdirSync9(dir, { recursive: true });
18113
18349
  const filename = `${name}.${format}`;
18114
- const path = join12(dir, filename);
18350
+ const path = join11(dir, filename);
18115
18351
  if (format === "csv") {
18116
18352
  const rows = dataset.data;
18117
18353
  if (rows.length === 0) {
18118
- writeFileSync4(path, "");
18354
+ writeFileSync3(path, "");
18119
18355
  return { path, size: 0 };
18120
18356
  }
18121
18357
  const headers = Object.keys(rows[0]);
@@ -18128,11 +18364,11 @@ function exportDataset(name, format) {
18128
18364
  }
18129
18365
  const content = csvLines.join(`
18130
18366
  `);
18131
- writeFileSync4(path, content);
18367
+ writeFileSync3(path, content);
18132
18368
  return { path, size: content.length };
18133
18369
  } else {
18134
18370
  const content = JSON.stringify(dataset.data, null, 2);
18135
- writeFileSync4(path, content);
18371
+ writeFileSync3(path, content);
18136
18372
  return { path, size: content.length };
18137
18373
  }
18138
18374
  }
@@ -18250,11 +18486,11 @@ __export(exports_dist, {
18250
18486
  DEFAULT_CONFIG: () => DEFAULT_CONFIG
18251
18487
  });
18252
18488
  import { Database as Database2 } from "bun:sqlite";
18253
- import { existsSync as existsSync8, mkdirSync as mkdirSync11 } from "fs";
18254
- import { dirname, join as join13, resolve } from "path";
18255
- import { existsSync as existsSync22, mkdirSync as mkdirSync22, readFileSync as readFileSync6, readdirSync as readdirSync5, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3 } from "fs";
18489
+ import { existsSync as existsSync7, mkdirSync as mkdirSync10 } from "fs";
18490
+ import { dirname, join as join12, resolve as resolve2 } from "path";
18491
+ import { existsSync as existsSync22, mkdirSync as mkdirSync22, readFileSync as readFileSync5, readdirSync as readdirSync4, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3 } from "fs";
18256
18492
  import { homedir as homedir12 } from "os";
18257
- import { basename as basename2, dirname as dirname2, join as join22, resolve as resolve2 } from "path";
18493
+ import { basename as basename2, dirname as dirname2, join as join22, resolve as resolve22 } from "path";
18258
18494
  import { existsSync as existsSync32, mkdirSync as mkdirSync32, readFileSync as readFileSync22, writeFileSync as writeFileSync22 } from "fs";
18259
18495
  import { homedir as homedir22 } from "os";
18260
18496
  import { join as join32 } from "path";
@@ -18265,10 +18501,10 @@ function isInMemoryDb(path) {
18265
18501
  return path === ":memory:" || path.startsWith("file::memory:");
18266
18502
  }
18267
18503
  function findNearestMementosDb(startDir) {
18268
- let dir = resolve(startDir);
18504
+ let dir = resolve2(startDir);
18269
18505
  while (true) {
18270
- const candidate = join13(dir, ".mementos", "mementos.db");
18271
- if (existsSync8(candidate))
18506
+ const candidate = join12(dir, ".mementos", "mementos.db");
18507
+ if (existsSync7(candidate))
18272
18508
  return candidate;
18273
18509
  const parent = dirname(dir);
18274
18510
  if (parent === dir)
@@ -18278,9 +18514,9 @@ function findNearestMementosDb(startDir) {
18278
18514
  return null;
18279
18515
  }
18280
18516
  function findGitRoot(startDir) {
18281
- let dir = resolve(startDir);
18517
+ let dir = resolve2(startDir);
18282
18518
  while (true) {
18283
- if (existsSync8(join13(dir, ".git")))
18519
+ if (existsSync7(join12(dir, ".git")))
18284
18520
  return dir;
18285
18521
  const parent = dirname(dir);
18286
18522
  if (parent === dir)
@@ -18300,18 +18536,18 @@ function getDbPath() {
18300
18536
  if (process.env["MEMENTOS_DB_SCOPE"] === "project") {
18301
18537
  const gitRoot = findGitRoot(cwd);
18302
18538
  if (gitRoot) {
18303
- return join13(gitRoot, ".mementos", "mementos.db");
18539
+ return join12(gitRoot, ".mementos", "mementos.db");
18304
18540
  }
18305
18541
  }
18306
18542
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
18307
- return join13(home, ".mementos", "mementos.db");
18543
+ return join12(home, ".mementos", "mementos.db");
18308
18544
  }
18309
18545
  function ensureDir2(filePath) {
18310
18546
  if (isInMemoryDb(filePath))
18311
18547
  return;
18312
- const dir = dirname(resolve(filePath));
18313
- if (!existsSync8(dir)) {
18314
- mkdirSync11(dir, { recursive: true });
18548
+ const dir = dirname(resolve2(filePath));
18549
+ if (!existsSync7(dir)) {
18550
+ mkdirSync10(dir, { recursive: true });
18315
18551
  }
18316
18552
  }
18317
18553
  function getDatabase2(dbPath) {
@@ -20165,7 +20401,7 @@ function loadConfig() {
20165
20401
  let fileConfig = {};
20166
20402
  if (existsSync22(configPath)) {
20167
20403
  try {
20168
- const raw = readFileSync6(configPath, "utf-8");
20404
+ const raw = readFileSync5(configPath, "utf-8");
20169
20405
  fileConfig = JSON.parse(raw);
20170
20406
  } catch {}
20171
20407
  }
@@ -20198,7 +20434,7 @@ function readGlobalConfig() {
20198
20434
  if (!existsSync22(p))
20199
20435
  return {};
20200
20436
  try {
20201
- return JSON.parse(readFileSync6(p, "utf-8"));
20437
+ return JSON.parse(readFileSync5(p, "utf-8"));
20202
20438
  } catch {
20203
20439
  return {};
20204
20440
  }
@@ -20206,7 +20442,7 @@ function readGlobalConfig() {
20206
20442
  function writeGlobalConfig(data) {
20207
20443
  const p = globalConfigPath();
20208
20444
  ensureDir22(dirname2(p));
20209
- writeFileSync5(p, JSON.stringify(data, null, 2), "utf-8");
20445
+ writeFileSync4(p, JSON.stringify(data, null, 2), "utf-8");
20210
20446
  }
20211
20447
  function getActiveProfile() {
20212
20448
  const envProfile = process.env["MEMENTOS_PROFILE"];
@@ -20228,7 +20464,7 @@ function listProfiles2() {
20228
20464
  const dir = profilesDir();
20229
20465
  if (!existsSync22(dir))
20230
20466
  return [];
20231
- return readdirSync5(dir).filter((f) => f.endsWith(".db")).map((f) => basename2(f, ".db")).sort();
20467
+ return readdirSync4(dir).filter((f) => f.endsWith(".db")).map((f) => basename2(f, ".db")).sort();
20232
20468
  }
20233
20469
  function deleteProfile2(name) {
20234
20470
  const dbPath = join22(profilesDir(), `${name}.db`);
@@ -22871,30 +23107,30 @@ __export(exports_dist2, {
22871
23107
  acquireLock: () => acquireLock2
22872
23108
  });
22873
23109
  import { Database as Database3 } from "bun:sqlite";
22874
- import { mkdirSync as mkdirSync12 } from "fs";
22875
- import { join as join14, dirname as dirname3 } from "path";
23110
+ import { mkdirSync as mkdirSync11 } from "fs";
23111
+ import { join as join13, dirname as dirname3 } from "path";
22876
23112
  import { homedir as homedir13 } from "os";
22877
23113
  import { randomUUID as randomUUID15 } from "crypto";
22878
23114
  import { mkdirSync as mkdirSync23, copyFileSync as copyFileSync3, statSync as statSync2 } from "fs";
22879
23115
  import { join as join33 } from "path";
22880
23116
  import { homedir as homedir33 } from "os";
22881
- import { readFileSync as readFileSync7 } from "fs";
23117
+ import { readFileSync as readFileSync6 } from "fs";
22882
23118
  import { join as join23 } from "path";
22883
23119
  import { homedir as homedir23 } from "os";
22884
23120
  import { randomUUID as randomUUID22 } from "crypto";
22885
- import { readFileSync as readFileSync23, writeFileSync as writeFileSync6, mkdirSync as mkdirSync33 } from "fs";
23121
+ import { readFileSync as readFileSync23, writeFileSync as writeFileSync5, mkdirSync as mkdirSync33 } from "fs";
22886
23122
  import { join as join43, dirname as dirname22 } from "path";
22887
23123
  import { homedir as homedir42 } from "os";
22888
23124
  function getDbPath2() {
22889
23125
  if (process.env.CONVERSATIONS_DB_PATH)
22890
23126
  return process.env.CONVERSATIONS_DB_PATH;
22891
- return join14(homedir13(), ".conversations", "messages.db");
23127
+ return join13(homedir13(), ".conversations", "messages.db");
22892
23128
  }
22893
23129
  function getDb() {
22894
23130
  if (db)
22895
23131
  return db;
22896
23132
  const dbPath = getDbPath2();
22897
- mkdirSync12(dirname3(dbPath), { recursive: true });
23133
+ mkdirSync11(dirname3(dbPath), { recursive: true });
22898
23134
  db = new Database3(dbPath, { create: true });
22899
23135
  db.exec("PRAGMA journal_mode = WAL");
22900
23136
  db.exec("PRAGMA busy_timeout = 5000");
@@ -23136,7 +23372,7 @@ function loadConfig2() {
23136
23372
  if (cachedConfig && now2 - configLoadedAt < CONFIG_CACHE_MS)
23137
23373
  return cachedConfig;
23138
23374
  try {
23139
- const raw = readFileSync7(getConfigPath(), "utf-8");
23375
+ const raw = readFileSync6(getConfigPath(), "utf-8");
23140
23376
  cachedConfig = JSON.parse(raw);
23141
23377
  configLoadedAt = now2;
23142
23378
  return cachedConfig;
@@ -24053,7 +24289,7 @@ function useSpaceMessages(spaceName) {
24053
24289
  }
24054
24290
  function isNameTaken(name) {
24055
24291
  try {
24056
- const { getDb: getDb2 } = (init_db(), __toCommonJS(exports_db));
24292
+ const { getDb: getDb2 } = (init_db(), __toCommonJS2(exports_db));
24057
24293
  const db2 = getDb2();
24058
24294
  const row = db2.prepare("SELECT agent FROM agent_presence WHERE agent = ?").get(name);
24059
24295
  return !!row;
@@ -24082,7 +24318,7 @@ function getAutoName() {
24082
24318
  cachedAutoName = name;
24083
24319
  try {
24084
24320
  mkdirSync33(dirname22(AGENT_ID_FILE), { recursive: true });
24085
- writeFileSync6(AGENT_ID_FILE, name + `
24321
+ writeFileSync5(AGENT_ID_FILE, name + `
24086
24322
  `, "utf-8");
24087
24323
  } catch {}
24088
24324
  return name;
@@ -24688,7 +24924,7 @@ function getGraphStats() {
24688
24924
  map[r.relation] = r.c;
24689
24925
  return { total_edges: total, by_relation: map };
24690
24926
  }
24691
- var __create2, __getProtoOf2, __defProp3, __getOwnPropNames2, __getOwnPropDesc, __hasOwnProp2, __toESM2 = (mod, isNodeMode, target) => {
24927
+ var __create2, __getProtoOf2, __defProp3, __getOwnPropNames2, __getOwnPropDesc2, __hasOwnProp2, __toESM2 = (mod, isNodeMode, target) => {
24692
24928
  target = mod != null ? __create2(__getProtoOf2(mod)) : {};
24693
24929
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp3(target, "default", { value: mod, enumerable: true }) : target;
24694
24930
  for (let key of __getOwnPropNames2(mod))
@@ -24698,17 +24934,17 @@ var __create2, __getProtoOf2, __defProp3, __getOwnPropNames2, __getOwnPropDesc,
24698
24934
  enumerable: true
24699
24935
  });
24700
24936
  return to;
24701
- }, __moduleCache, __toCommonJS = (from) => {
24702
- var entry = __moduleCache.get(from), desc;
24937
+ }, __moduleCache2, __toCommonJS2 = (from) => {
24938
+ var entry = __moduleCache2.get(from), desc;
24703
24939
  if (entry)
24704
24940
  return entry;
24705
24941
  entry = __defProp3({}, "__esModule", { value: true });
24706
24942
  if (from && typeof from === "object" || typeof from === "function")
24707
24943
  __getOwnPropNames2(from).map((key) => !__hasOwnProp2.call(entry, key) && __defProp3(entry, key, {
24708
24944
  get: () => from[key],
24709
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
24945
+ enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable
24710
24946
  }));
24711
- __moduleCache.set(from, entry);
24947
+ __moduleCache2.set(from, entry);
24712
24948
  return entry;
24713
24949
  }, __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports), __export3 = (target, all) => {
24714
24950
  for (var name in all)
@@ -24724,9 +24960,9 @@ var init_dist2 = __esm(() => {
24724
24960
  __getProtoOf2 = Object.getPrototypeOf;
24725
24961
  __defProp3 = Object.defineProperty;
24726
24962
  __getOwnPropNames2 = Object.getOwnPropertyNames;
24727
- __getOwnPropDesc = Object.getOwnPropertyDescriptor;
24963
+ __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
24728
24964
  __hasOwnProp2 = Object.prototype.hasOwnProperty;
24729
- __moduleCache = /* @__PURE__ */ new WeakMap;
24965
+ __moduleCache2 = /* @__PURE__ */ new WeakMap;
24730
24966
  exports_db = {};
24731
24967
  __export3(exports_db, {
24732
24968
  getDbPath: () => getDbPath2,
@@ -27296,18 +27532,18 @@ __export(exports_dist3, {
27296
27532
  AgentNotFoundError: () => AgentNotFoundError2
27297
27533
  });
27298
27534
  import { Database as Database4 } from "bun:sqlite";
27299
- import { existsSync as existsSync9, mkdirSync as mkdirSync13 } from "fs";
27300
- import { dirname as dirname5, join as join15, resolve as resolve3 } from "path";
27535
+ import { existsSync as existsSync8, mkdirSync as mkdirSync12 } from "fs";
27536
+ import { dirname as dirname5, join as join14, resolve as resolve3 } from "path";
27301
27537
  import { existsSync as existsSync33 } from "fs";
27302
27538
  import { join as join34 } from "path";
27303
- import { existsSync as existsSync23, mkdirSync as mkdirSync24, readFileSync as readFileSync8, readdirSync as readdirSync6, statSync as statSync3, writeFileSync as writeFileSync7 } from "fs";
27539
+ import { existsSync as existsSync23, mkdirSync as mkdirSync24, readFileSync as readFileSync7, readdirSync as readdirSync5, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
27304
27540
  import { join as join24 } from "path";
27305
27541
  import { existsSync as existsSync43, readFileSync as readFileSync24, readdirSync as readdirSync22, writeFileSync as writeFileSync23 } from "fs";
27306
27542
  import { join as join44 } from "path";
27307
27543
  import { existsSync as existsSync52 } from "fs";
27308
27544
  import { join as join52 } from "path";
27309
27545
  import { readFileSync as readFileSync33, statSync as statSync22 } from "fs";
27310
- import { relative, resolve as resolve22, join as join62 } from "path";
27546
+ import { relative, resolve as resolve23, join as join62 } from "path";
27311
27547
  import { execSync as execSync2 } from "child_process";
27312
27548
 
27313
27549
  class TodosClient {
@@ -27530,8 +27766,8 @@ function isInMemoryDb2(path) {
27530
27766
  function findNearestTodosDb(startDir) {
27531
27767
  let dir = resolve3(startDir);
27532
27768
  while (true) {
27533
- const candidate = join15(dir, ".todos", "todos.db");
27534
- if (existsSync9(candidate))
27769
+ const candidate = join14(dir, ".todos", "todos.db");
27770
+ if (existsSync8(candidate))
27535
27771
  return candidate;
27536
27772
  const parent = dirname5(dir);
27537
27773
  if (parent === dir)
@@ -27543,7 +27779,7 @@ function findNearestTodosDb(startDir) {
27543
27779
  function findGitRoot2(startDir) {
27544
27780
  let dir = resolve3(startDir);
27545
27781
  while (true) {
27546
- if (existsSync9(join15(dir, ".git")))
27782
+ if (existsSync8(join14(dir, ".git")))
27547
27783
  return dir;
27548
27784
  const parent = dirname5(dir);
27549
27785
  if (parent === dir)
@@ -27563,18 +27799,18 @@ function getDbPath3() {
27563
27799
  if (process.env["TODOS_DB_SCOPE"] === "project") {
27564
27800
  const gitRoot = findGitRoot2(cwd);
27565
27801
  if (gitRoot) {
27566
- return join15(gitRoot, ".todos", "todos.db");
27802
+ return join14(gitRoot, ".todos", "todos.db");
27567
27803
  }
27568
27804
  }
27569
27805
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
27570
- return join15(home, ".todos", "todos.db");
27806
+ return join14(home, ".todos", "todos.db");
27571
27807
  }
27572
27808
  function ensureDir3(filePath) {
27573
27809
  if (isInMemoryDb2(filePath))
27574
27810
  return;
27575
27811
  const dir = dirname5(resolve3(filePath));
27576
- if (!existsSync9(dir)) {
27577
- mkdirSync13(dir, { recursive: true });
27812
+ if (!existsSync8(dir)) {
27813
+ mkdirSync12(dir, { recursive: true });
27578
27814
  }
27579
27815
  }
27580
27816
  function getDatabase3(dbPath) {
@@ -28040,28 +28276,28 @@ function ensureDir23(dir) {
28040
28276
  function listJsonFiles(dir) {
28041
28277
  if (!existsSync23(dir))
28042
28278
  return [];
28043
- return readdirSync6(dir).filter((f) => f.endsWith(".json"));
28279
+ return readdirSync5(dir).filter((f) => f.endsWith(".json"));
28044
28280
  }
28045
28281
  function readJsonFile(path) {
28046
28282
  try {
28047
- return JSON.parse(readFileSync8(path, "utf-8"));
28283
+ return JSON.parse(readFileSync7(path, "utf-8"));
28048
28284
  } catch {
28049
28285
  return null;
28050
28286
  }
28051
28287
  }
28052
28288
  function writeJsonFile(path, data) {
28053
- writeFileSync7(path, JSON.stringify(data, null, 2) + `
28289
+ writeFileSync6(path, JSON.stringify(data, null, 2) + `
28054
28290
  `);
28055
28291
  }
28056
28292
  function readHighWaterMark(dir) {
28057
28293
  const path = join24(dir, ".highwatermark");
28058
28294
  if (!existsSync23(path))
28059
28295
  return 1;
28060
- const val = parseInt(readFileSync8(path, "utf-8").trim(), 10);
28296
+ const val = parseInt(readFileSync7(path, "utf-8").trim(), 10);
28061
28297
  return isNaN(val) ? 1 : val;
28062
28298
  }
28063
28299
  function writeHighWaterMark(dir, value) {
28064
- writeFileSync7(join24(dir, ".highwatermark"), String(value));
28300
+ writeFileSync6(join24(dir, ".highwatermark"), String(value));
28065
28301
  }
28066
28302
  function getFileMtimeMs(path) {
28067
28303
  try {
@@ -31423,7 +31659,7 @@ function collectFiles(basePath, extensions) {
31423
31659
  return files.sort();
31424
31660
  }
31425
31661
  function extractTodos(options, db2) {
31426
- const basePath = resolve22(options.path);
31662
+ const basePath = resolve23(options.path);
31427
31663
  const tags = options.patterns || [...EXTRACT_TAGS];
31428
31664
  const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
31429
31665
  const files = collectFiles(basePath, extensions);
@@ -31432,7 +31668,7 @@ function extractTodos(options, db2) {
31432
31668
  const fullPath = statSync22(basePath).isFile() ? basePath : join62(basePath, file);
31433
31669
  try {
31434
31670
  const source = readFileSync33(fullPath, "utf-8");
31435
- const relPath = statSync22(basePath).isFile() ? relative(resolve22(basePath, ".."), fullPath) : file;
31671
+ const relPath = statSync22(basePath).isFile() ? relative(resolve23(basePath, ".."), fullPath) : file;
31436
31672
  const comments = extractFromSource(source, relPath, tags);
31437
31673
  allComments.push(...comments);
31438
31674
  } catch {}
@@ -32651,8 +32887,8 @@ __export(exports_dist4, {
32651
32887
  CATEGORIES: () => CATEGORIES,
32652
32888
  AGENT_TARGETS: () => AGENT_TARGETS
32653
32889
  });
32654
- import { existsSync as existsSync10, cpSync, mkdirSync as mkdirSync14, writeFileSync as writeFileSync8, rmSync as rmSync2, readdirSync as readdirSync7, statSync as statSync4, readFileSync as readFileSync9, accessSync, constants } from "fs";
32655
- import { join as join16, dirname as dirname6 } from "path";
32890
+ import { existsSync as existsSync9, cpSync, mkdirSync as mkdirSync13, writeFileSync as writeFileSync7, rmSync as rmSync2, readdirSync as readdirSync6, statSync as statSync4, readFileSync as readFileSync8, accessSync, constants } from "fs";
32891
+ import { join as join15, dirname as dirname6 } from "path";
32656
32892
  import { homedir as homedir14 } from "os";
32657
32893
  import { fileURLToPath } from "url";
32658
32894
  import { existsSync as existsSync24, readFileSync as readFileSync25, readdirSync as readdirSync23 } from "fs";
@@ -32764,35 +33000,35 @@ function normalizeSkillName(name) {
32764
33000
  function findSkillsDir() {
32765
33001
  let dir = __dirname2;
32766
33002
  for (let i = 0;i < 5; i++) {
32767
- const candidate = join16(dir, "skills");
32768
- if (existsSync10(candidate)) {
33003
+ const candidate = join15(dir, "skills");
33004
+ if (existsSync9(candidate)) {
32769
33005
  return candidate;
32770
33006
  }
32771
33007
  dir = dirname6(dir);
32772
33008
  }
32773
- return join16(__dirname2, "..", "skills");
33009
+ return join15(__dirname2, "..", "skills");
32774
33010
  }
32775
33011
  function getSkillPath(name) {
32776
33012
  const skillName = normalizeSkillName(name);
32777
- return join16(SKILLS_DIR, skillName);
33013
+ return join15(SKILLS_DIR, skillName);
32778
33014
  }
32779
33015
  function skillExists(name) {
32780
- return existsSync10(getSkillPath(name));
33016
+ return existsSync9(getSkillPath(name));
32781
33017
  }
32782
33018
  function installSkill(name, options = {}) {
32783
33019
  const { targetDir = process.cwd(), overwrite = false } = options;
32784
33020
  const skillName = normalizeSkillName(name);
32785
33021
  const sourcePath = getSkillPath(name);
32786
- const destDir = join16(targetDir, ".skills");
32787
- const destPath = join16(destDir, skillName);
32788
- if (!existsSync10(sourcePath)) {
33022
+ const destDir = join15(targetDir, ".skills");
33023
+ const destPath = join15(destDir, skillName);
33024
+ if (!existsSync9(sourcePath)) {
32789
33025
  return {
32790
33026
  skill: name,
32791
33027
  success: false,
32792
33028
  error: `Skill '${name}' not found`
32793
33029
  };
32794
33030
  }
32795
- if (existsSync10(destPath) && !overwrite) {
33031
+ if (existsSync9(destPath) && !overwrite) {
32796
33032
  return {
32797
33033
  skill: name,
32798
33034
  success: false,
@@ -32801,10 +33037,10 @@ function installSkill(name, options = {}) {
32801
33037
  };
32802
33038
  }
32803
33039
  try {
32804
- if (!existsSync10(destDir)) {
32805
- mkdirSync14(destDir, { recursive: true });
33040
+ if (!existsSync9(destDir)) {
33041
+ mkdirSync13(destDir, { recursive: true });
32806
33042
  }
32807
- if (existsSync10(destPath) && overwrite) {
33043
+ if (existsSync9(destPath) && overwrite) {
32808
33044
  rmSync2(destPath, { recursive: true, force: true });
32809
33045
  }
32810
33046
  cpSync(sourcePath, destPath, {
@@ -32843,10 +33079,10 @@ function installSkills(names, options = {}) {
32843
33079
  return names.map((name) => installSkill(name, options));
32844
33080
  }
32845
33081
  function updateSkillsIndex(skillsDir) {
32846
- const indexPath = join16(skillsDir, "index.ts");
33082
+ const indexPath = join15(skillsDir, "index.ts");
32847
33083
  const meta = loadMeta(skillsDir);
32848
33084
  const disabledSet = new Set(meta.disabled || []);
32849
- const skills = readdirSync7(skillsDir).filter((f) => f.startsWith("skill-") && !f.includes(".") && !disabledSet.has(f.replace("skill-", "")));
33085
+ const skills = readdirSync6(skillsDir).filter((f) => f.startsWith("skill-") && !f.includes(".") && !disabledSet.has(f.replace("skill-", "")));
32850
33086
  const exports = skills.map((s) => {
32851
33087
  const name = s.replace("skill-", "").replace(/-/g, "_");
32852
33088
  return `export * as ${name} from './${s}/src/index.js';`;
@@ -32859,31 +33095,31 @@ function updateSkillsIndex(skillsDir) {
32859
33095
 
32860
33096
  ${exports}
32861
33097
  `;
32862
- writeFileSync8(indexPath, content);
33098
+ writeFileSync7(indexPath, content);
32863
33099
  }
32864
33100
  function getMetaPath(skillsDir) {
32865
- return join16(skillsDir, ".meta.json");
33101
+ return join15(skillsDir, ".meta.json");
32866
33102
  }
32867
33103
  function loadMeta(skillsDir) {
32868
33104
  const metaPath2 = getMetaPath(skillsDir);
32869
- if (existsSync10(metaPath2)) {
33105
+ if (existsSync9(metaPath2)) {
32870
33106
  try {
32871
- return JSON.parse(readFileSync9(metaPath2, "utf-8"));
33107
+ return JSON.parse(readFileSync8(metaPath2, "utf-8"));
32872
33108
  } catch {}
32873
33109
  }
32874
33110
  return { skills: {} };
32875
33111
  }
32876
33112
  function saveMeta(skillsDir, meta) {
32877
- writeFileSync8(getMetaPath(skillsDir), JSON.stringify(meta, null, 2));
33113
+ writeFileSync7(getMetaPath(skillsDir), JSON.stringify(meta, null, 2));
32878
33114
  }
32879
33115
  function recordInstall(skillsDir, name) {
32880
33116
  const meta = loadMeta(skillsDir);
32881
33117
  const skillName = normalizeSkillName(name);
32882
33118
  let version = "unknown";
32883
33119
  try {
32884
- const pkgPath = join16(skillsDir, skillName, "package.json");
32885
- if (existsSync10(pkgPath)) {
32886
- const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
33120
+ const pkgPath = join15(skillsDir, skillName, "package.json");
33121
+ if (existsSync9(pkgPath)) {
33122
+ const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
32887
33123
  version = pkg.version || "unknown";
32888
33124
  }
32889
33125
  } catch {}
@@ -32896,12 +33132,12 @@ function recordRemove(skillsDir, name) {
32896
33132
  saveMeta(skillsDir, meta);
32897
33133
  }
32898
33134
  function getInstallMeta(targetDir = process.cwd()) {
32899
- return loadMeta(join16(targetDir, ".skills"));
33135
+ return loadMeta(join15(targetDir, ".skills"));
32900
33136
  }
32901
33137
  function disableSkill(name, targetDir = process.cwd()) {
32902
- const skillsDir = join16(targetDir, ".skills");
33138
+ const skillsDir = join15(targetDir, ".skills");
32903
33139
  const skillName = normalizeSkillName(name);
32904
- if (!existsSync10(join16(skillsDir, skillName)))
33140
+ if (!existsSync9(join15(skillsDir, skillName)))
32905
33141
  return false;
32906
33142
  const meta = loadMeta(skillsDir);
32907
33143
  const disabled = new Set(meta.disabled || []);
@@ -32914,7 +33150,7 @@ function disableSkill(name, targetDir = process.cwd()) {
32914
33150
  return true;
32915
33151
  }
32916
33152
  function enableSkill(name, targetDir = process.cwd()) {
32917
- const skillsDir = join16(targetDir, ".skills");
33153
+ const skillsDir = join15(targetDir, ".skills");
32918
33154
  const meta = loadMeta(skillsDir);
32919
33155
  const disabled = new Set(meta.disabled || []);
32920
33156
  if (!disabled.has(name))
@@ -32926,24 +33162,24 @@ function enableSkill(name, targetDir = process.cwd()) {
32926
33162
  return true;
32927
33163
  }
32928
33164
  function getDisabledSkills(targetDir = process.cwd()) {
32929
- const meta = loadMeta(join16(targetDir, ".skills"));
33165
+ const meta = loadMeta(join15(targetDir, ".skills"));
32930
33166
  return meta.disabled || [];
32931
33167
  }
32932
33168
  function getInstalledSkills(targetDir = process.cwd()) {
32933
- const skillsDir = join16(targetDir, ".skills");
32934
- if (!existsSync10(skillsDir)) {
33169
+ const skillsDir = join15(targetDir, ".skills");
33170
+ if (!existsSync9(skillsDir)) {
32935
33171
  return [];
32936
33172
  }
32937
- return readdirSync7(skillsDir).filter((f) => {
32938
- const fullPath = join16(skillsDir, f);
33173
+ return readdirSync6(skillsDir).filter((f) => {
33174
+ const fullPath = join15(skillsDir, f);
32939
33175
  return f.startsWith("skill-") && statSync4(fullPath).isDirectory();
32940
33176
  }).map((f) => f.replace("skill-", ""));
32941
33177
  }
32942
33178
  function removeSkill(name, targetDir = process.cwd()) {
32943
33179
  const skillName = normalizeSkillName(name);
32944
- const skillsDir = join16(targetDir, ".skills");
32945
- const skillPath = join16(skillsDir, skillName);
32946
- if (!existsSync10(skillPath)) {
33180
+ const skillsDir = join15(targetDir, ".skills");
33181
+ const skillPath = join15(skillsDir, skillName);
33182
+ if (!existsSync9(skillPath)) {
32947
33183
  return false;
32948
33184
  }
32949
33185
  rmSync2(skillPath, { recursive: true, force: true });
@@ -32954,25 +33190,25 @@ function removeSkill(name, targetDir = process.cwd()) {
32954
33190
  function getAgentSkillsDir(agent, scope = "global", projectDir) {
32955
33191
  const agentDir = `.${agent}`;
32956
33192
  if (scope === "project") {
32957
- return join16(projectDir || process.cwd(), agentDir, "skills");
33193
+ return join15(projectDir || process.cwd(), agentDir, "skills");
32958
33194
  }
32959
- return join16(homedir14(), agentDir, "skills");
33195
+ return join15(homedir14(), agentDir, "skills");
32960
33196
  }
32961
33197
  function getAgentSkillPath(name, agent, scope = "global", projectDir) {
32962
33198
  const skillName = normalizeSkillName(name);
32963
- return join16(getAgentSkillsDir(agent, scope, projectDir), skillName);
33199
+ return join15(getAgentSkillsDir(agent, scope, projectDir), skillName);
32964
33200
  }
32965
33201
  function installSkillForAgent(name, options, generateSkillMd) {
32966
33202
  const { agent, scope = "global", projectDir } = options;
32967
33203
  const skillName = normalizeSkillName(name);
32968
33204
  const sourcePath = getSkillPath(name);
32969
- if (!existsSync10(sourcePath)) {
33205
+ if (!existsSync9(sourcePath)) {
32970
33206
  return { skill: name, success: false, error: `Skill '${name}' not found` };
32971
33207
  }
32972
33208
  let skillMdContent = null;
32973
- const skillMdPath = join16(sourcePath, "SKILL.md");
32974
- if (existsSync10(skillMdPath)) {
32975
- skillMdContent = readFileSync9(skillMdPath, "utf-8");
33209
+ const skillMdPath = join15(sourcePath, "SKILL.md");
33210
+ if (existsSync9(skillMdPath)) {
33211
+ skillMdContent = readFileSync8(skillMdPath, "utf-8");
32976
33212
  } else if (generateSkillMd) {
32977
33213
  skillMdContent = generateSkillMd(name);
32978
33214
  }
@@ -32981,8 +33217,8 @@ function installSkillForAgent(name, options, generateSkillMd) {
32981
33217
  }
32982
33218
  const destDir = getAgentSkillPath(name, agent, scope, projectDir);
32983
33219
  if (scope === "global") {
32984
- const agentBaseDir2 = join16(homedir14(), `.${agent}`);
32985
- if (!existsSync10(agentBaseDir2)) {
33220
+ const agentBaseDir2 = join15(homedir14(), `.${agent}`);
33221
+ if (!existsSync9(agentBaseDir2)) {
32986
33222
  const agentLabels = {
32987
33223
  claude: "Claude Code",
32988
33224
  codex: "Codex CLI",
@@ -33005,8 +33241,8 @@ function installSkillForAgent(name, options, generateSkillMd) {
33005
33241
  }
33006
33242
  }
33007
33243
  try {
33008
- mkdirSync14(destDir, { recursive: true });
33009
- writeFileSync8(join16(destDir, "SKILL.md"), skillMdContent);
33244
+ mkdirSync13(destDir, { recursive: true });
33245
+ writeFileSync7(join15(destDir, "SKILL.md"), skillMdContent);
33010
33246
  return { skill: name, success: true, path: destDir };
33011
33247
  } catch (error) {
33012
33248
  return {
@@ -33019,7 +33255,7 @@ function installSkillForAgent(name, options, generateSkillMd) {
33019
33255
  function removeSkillForAgent(name, options) {
33020
33256
  const { agent, scope = "global", projectDir } = options;
33021
33257
  const destDir = getAgentSkillPath(name, agent, scope, projectDir);
33022
- if (!existsSync10(destDir)) {
33258
+ if (!existsSync9(destDir)) {
33023
33259
  return false;
33024
33260
  }
33025
33261
  rmSync2(destDir, { recursive: true, force: true });
@@ -35364,8 +35600,8 @@ var init_ai_task = () => {};
35364
35600
  var exports_mcp = {};
35365
35601
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
35366
35602
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
35367
- import { readFileSync as readFileSync10 } from "fs";
35368
- import { join as join17 } from "path";
35603
+ import { readFileSync as readFileSync9 } from "fs";
35604
+ import { join as join16 } from "path";
35369
35605
  function json(data) {
35370
35606
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
35371
35607
  }
@@ -35430,7 +35666,7 @@ var init_mcp = __esm(async () => {
35430
35666
  init_dialogs();
35431
35667
  init_profiles();
35432
35668
  init_types();
35433
- _pkg = JSON.parse(readFileSync10(join17(import.meta.dir, "../../package.json"), "utf8"));
35669
+ _pkg = JSON.parse(readFileSync9(join16(import.meta.dir, "../../package.json"), "utf8"));
35434
35670
  networkLogCleanup = new Map;
35435
35671
  consoleCaptureCleanup = new Map;
35436
35672
  harCaptures = new Map;
@@ -37096,14 +37332,17 @@ var init_mcp = __esm(async () => {
37096
37332
  return err(e);
37097
37333
  }
37098
37334
  });
37099
- server.tool("browser_script_run", "Run a saved login script asynchronously. Returns a job_id immediately \u2014 poll with browser_script_status for step-by-step progress. Combines browser actions + connector calls (e.g. magic link login via Gmail).", {
37100
- name: exports_external.string().describe("Script name (e.g. 'usestable')"),
37335
+ server.tool("browser_script_run", "Run a saved script asynchronously. Returns run_id immediately \u2014 poll with browser_script_status for step-by-step progress. Scripts combine browser actions + connector calls + AI reasoning. Works with any engine (Bun.WebView, Playwright, CDP).", {
37336
+ name: exports_external.string().describe("Script name"),
37101
37337
  session_id: exports_external.string().optional(),
37102
- variables: exports_external.record(exports_external.string()).optional().describe("Override script variables (e.g. {email: 'foo@bar.com'})")
37103
- }, async ({ name, session_id, variables }) => {
37338
+ engine: exports_external.enum(["playwright", "cdp", "lightpanda", "bun", "auto"]).optional().default("auto"),
37339
+ variables: exports_external.record(exports_external.string()).optional().describe("Override script variables")
37340
+ }, async ({ name, session_id, engine, variables }) => {
37104
37341
  try {
37105
- const { loadScript: loadScript2, runScriptAsync: runScriptAsync2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
37106
- const script = loadScript2(name);
37342
+ const { getScriptByName: getScriptByName2, migrateJsonScripts: migrateJsonScripts2, getSteps: getSteps2 } = await Promise.resolve().then(() => (init_scripts(), exports_scripts));
37343
+ const { executeScript: executeScript2 } = await Promise.resolve().then(() => (init_script_engine(), exports_script_engine));
37344
+ migrateJsonScripts2();
37345
+ const script = getScriptByName2(name);
37107
37346
  if (!script)
37108
37347
  return err(new Error(`Script '${name}' not found. Use browser_script_list to see available scripts.`));
37109
37348
  let sid;
@@ -37112,55 +37351,72 @@ var init_mcp = __esm(async () => {
37112
37351
  sid = resolveSessionId(session_id);
37113
37352
  page = getSessionPage(sid);
37114
37353
  } else {
37115
- const result = await createSession2({ headless: true });
37354
+ const result = await createSession2({ engine: engine ?? "auto", headless: true });
37116
37355
  sid = result.session.id;
37117
37356
  page = result.page;
37118
37357
  }
37119
- const jobId = runScriptAsync2(script, page, variables ?? {});
37120
- return json({ job_id: jobId, session_id: sid, script: name, total_steps: script.steps.length, message: "Script running in background. Poll with browser_script_status for progress." });
37358
+ const steps = getSteps2(script.id);
37359
+ const runId = executeScript2(script.id, page, variables ?? {});
37360
+ return json({ run_id: runId, session_id: sid, script: name, total_steps: steps.length, message: "Script running. Poll with browser_script_status." });
37121
37361
  } catch (e) {
37122
37362
  return err(e);
37123
37363
  }
37124
37364
  });
37125
- server.tool("browser_script_status", "Check the progress of a running login script. Shows current step, step-by-step log with durations, and final result when complete.", { job_id: exports_external.string() }, async ({ job_id }) => {
37365
+ server.tool("browser_script_status", "Check progress of a running script. Shows current step, step-by-step log with durations, and final result when complete.", { run_id: exports_external.string() }, async ({ run_id }) => {
37126
37366
  try {
37127
- const { getJob: getJob2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
37128
- const job = getJob2(job_id);
37129
- if (!job)
37130
- return err(new Error(`Job '${job_id}' not found`));
37367
+ const { getRun: getRun3 } = await Promise.resolve().then(() => (init_scripts(), exports_scripts));
37368
+ const run = getRun3(run_id);
37369
+ if (!run)
37370
+ return err(new Error(`Run '${run_id}' not found`));
37131
37371
  return json({
37132
- status: job.status,
37133
- progress: `${job.current_step}/${job.total_steps}`,
37134
- current_step: job.current_step_description,
37135
- steps_log: job.steps_log,
37136
- result: job.result ?? undefined
37372
+ status: run.status,
37373
+ progress: `${run.current_step}/${run.total_steps}`,
37374
+ current_step: run.current_description,
37375
+ steps_log: run.steps_log,
37376
+ errors: run.errors.length > 0 ? run.errors : undefined,
37377
+ duration_ms: run.duration_ms,
37378
+ completed: run.completed_at
37137
37379
  });
37138
37380
  } catch (e) {
37139
37381
  return err(e);
37140
37382
  }
37141
37383
  });
37142
- server.tool("browser_script_list", "List all saved login scripts", {}, async () => {
37384
+ server.tool("browser_script_list", "List all saved scripts", {}, async () => {
37143
37385
  try {
37144
- const { listScripts: listScripts2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
37145
- return json({ scripts: listScripts2() });
37386
+ const { listScripts: listScripts2, migrateJsonScripts: migrateJsonScripts2 } = await Promise.resolve().then(() => (init_scripts(), exports_scripts));
37387
+ migrateJsonScripts2();
37388
+ const scripts = listScripts2();
37389
+ return json({ scripts: scripts.map((s) => ({ name: s.name, domain: s.domain, description: s.description, run_count: s.run_count, last_run: s.last_run })), count: scripts.length });
37146
37390
  } catch (e) {
37147
37391
  return err(e);
37148
37392
  }
37149
37393
  });
37150
- server.tool("browser_script_save", "Save a login script from a JSON definition. Use for creating custom multi-step workflows that combine browser actions + connector calls.", { script: exports_external.string().describe("JSON string of the LoginScript object") }, async ({ script: scriptJson }) => {
37394
+ server.tool("browser_script_save", "Save a script. Steps are stored in SQLite. Each step has a type (browser/connector/extract/wait/condition/save_state), config, and optional AI config for intelligent fallbacks.", {
37395
+ name: exports_external.string(),
37396
+ domain: exports_external.string().optional().default(""),
37397
+ description: exports_external.string().optional().default(""),
37398
+ variables: exports_external.record(exports_external.string()).optional().default({}),
37399
+ steps: exports_external.array(exports_external.object({
37400
+ type: exports_external.enum(["browser", "connector", "extract", "wait", "condition", "save_state"]),
37401
+ config: exports_external.record(exports_external.unknown()).default({}),
37402
+ description: exports_external.string().optional().default(""),
37403
+ ai_enabled: exports_external.boolean().optional().default(false),
37404
+ ai_config: exports_external.record(exports_external.unknown()).optional().default({})
37405
+ }))
37406
+ }, async ({ name, domain, description, variables, steps }) => {
37151
37407
  try {
37152
- const { saveScript: saveScript2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
37153
- const script = JSON.parse(scriptJson);
37154
- const path = saveScript2(script);
37155
- return json({ saved: true, name: script.name, path, steps: script.steps.length });
37408
+ const { upsertScript: upsertScript2, getSteps: getSteps2 } = await Promise.resolve().then(() => (init_scripts(), exports_scripts));
37409
+ const script = upsertScript2({ name, domain, description, variables, steps });
37410
+ const savedSteps = getSteps2(script.id);
37411
+ return json({ id: script.id, name: script.name, steps: savedSteps.length });
37156
37412
  } catch (e) {
37157
37413
  return err(e);
37158
37414
  }
37159
37415
  });
37160
- server.tool("browser_script_delete", "Delete a saved login script", { name: exports_external.string() }, async ({ name }) => {
37416
+ server.tool("browser_script_delete", "Delete a saved script", { name: exports_external.string() }, async ({ name }) => {
37161
37417
  try {
37162
- const { deleteScript: deleteScript2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
37163
- return json({ deleted: deleteScript2(name) });
37418
+ const { deleteScriptByName: deleteScriptByName2 } = await Promise.resolve().then(() => (init_scripts(), exports_scripts));
37419
+ return json({ deleted: deleteScriptByName2(name) });
37164
37420
  } catch (e) {
37165
37421
  return err(e);
37166
37422
  }
@@ -37933,8 +38189,8 @@ var init_snapshots = __esm(() => {
37933
38189
 
37934
38190
  // src/server/index.ts
37935
38191
  var exports_server = {};
37936
- import { join as join18 } from "path";
37937
- import { existsSync as existsSync11 } from "fs";
38192
+ import { join as join17 } from "path";
38193
+ import { existsSync as existsSync10 } from "fs";
37938
38194
  function ok(data, status = 200) {
37939
38195
  return new Response(JSON.stringify(data), {
37940
38196
  status,
@@ -38193,14 +38449,14 @@ var init_server = __esm(() => {
38193
38449
  if (path.match(/^\/api\/gallery\/([^/]+)\/thumbnail$/) && method === "GET") {
38194
38450
  const id = path.split("/")[3];
38195
38451
  const entry = getEntry(id);
38196
- if (!entry?.thumbnail_path || !existsSync11(entry.thumbnail_path))
38452
+ if (!entry?.thumbnail_path || !existsSync10(entry.thumbnail_path))
38197
38453
  return notFound("Thumbnail not found");
38198
38454
  return new Response(Bun.file(entry.thumbnail_path), { headers: { ...CORS_HEADERS } });
38199
38455
  }
38200
38456
  if (path.match(/^\/api\/gallery\/([^/]+)\/image$/) && method === "GET") {
38201
38457
  const id = path.split("/")[3];
38202
38458
  const entry = getEntry(id);
38203
- if (!entry?.path || !existsSync11(entry.path))
38459
+ if (!entry?.path || !existsSync10(entry.path))
38204
38460
  return notFound("Image not found");
38205
38461
  return new Response(Bun.file(entry.path), { headers: { ...CORS_HEADERS } });
38206
38462
  }
@@ -38228,7 +38484,7 @@ var init_server = __esm(() => {
38228
38484
  if (path.match(/^\/api\/downloads\/([^/]+)\/raw$/) && method === "GET") {
38229
38485
  const id = path.split("/")[3];
38230
38486
  const file = getDownload(id);
38231
- if (!file || !existsSync11(file.path))
38487
+ if (!file || !existsSync10(file.path))
38232
38488
  return notFound("Download not found");
38233
38489
  return new Response(Bun.file(file.path), { headers: { ...CORS_HEADERS } });
38234
38490
  }
@@ -38236,13 +38492,13 @@ var init_server = __esm(() => {
38236
38492
  const id = path.split("/")[3];
38237
38493
  return ok({ deleted: deleteDownload(id) });
38238
38494
  }
38239
- const dashboardDist = join18(import.meta.dir, "../../dashboard/dist");
38240
- if (existsSync11(dashboardDist)) {
38241
- const filePath = path === "/" ? join18(dashboardDist, "index.html") : join18(dashboardDist, path);
38242
- if (existsSync11(filePath)) {
38495
+ const dashboardDist = join17(import.meta.dir, "../../dashboard/dist");
38496
+ if (existsSync10(dashboardDist)) {
38497
+ const filePath = path === "/" ? join17(dashboardDist, "index.html") : join17(dashboardDist, path);
38498
+ if (existsSync10(filePath)) {
38243
38499
  return new Response(Bun.file(filePath), { headers: CORS_HEADERS });
38244
38500
  }
38245
- return new Response(Bun.file(join18(dashboardDist, "index.html")), { headers: CORS_HEADERS });
38501
+ return new Response(Bun.file(join17(dashboardDist, "index.html")), { headers: CORS_HEADERS });
38246
38502
  }
38247
38503
  if (path === "/" || path === "") {
38248
38504
  return new Response("@hasna/browser REST API running. Dashboard not built.", {
@@ -38284,10 +38540,10 @@ init_projects();
38284
38540
  init_recorder();
38285
38541
  init_recordings();
38286
38542
  init_lightpanda();
38287
- import { readFileSync as readFileSync11 } from "fs";
38288
- import { join as join19 } from "path";
38543
+ import { readFileSync as readFileSync10 } from "fs";
38544
+ import { join as join18 } from "path";
38289
38545
  import chalk from "chalk";
38290
- var pkg = JSON.parse(readFileSync11(join19(import.meta.dir, "../../package.json"), "utf8"));
38546
+ var pkg = JSON.parse(readFileSync10(join18(import.meta.dir, "../../package.json"), "utf8"));
38291
38547
  var program2 = new Command;
38292
38548
  program2.name("browser").description("@hasna/browser \u2014 general-purpose browser agent CLI").version(pkg.version);
38293
38549
  program2.command("navigate <url>").description("Navigate to a URL and optionally take a screenshot").option("--engine <engine>", "Browser engine: playwright|cdp|lightpanda|auto", "auto").option("--screenshot", "Take a screenshot after navigation").option("--extract", "Extract page text after navigation").option("--headed", "Run in headed (visible) mode").option("--json", "Output as JSON").action(async (url, opts) => {
@@ -38734,18 +38990,19 @@ program2.command("login <url>").description("Login to a site: detect form, fill
38734
38990
  if (!opts.headed)
38735
38991
  await closeSession2(session.id);
38736
38992
  });
38737
- var scriptCmd = program2.command("script").description("Manage login scripts (browser + connector workflows)");
38738
- scriptCmd.command("run <name>").description("Run a saved login script").option("--email <email>", "Override email variable").option("--engine <engine>", "Browser engine", "auto").option("--headed", "Run in headed (visible) mode").option("--json", "Output as JSON").option("--var <pairs...>", "Set variables (key=value)").action(async (name, opts) => {
38739
- const { loadScript: loadScript2, runScript: runScript2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
38740
- const script = loadScript2(name);
38993
+ var scriptCmd = program2.command("script").description("Manage automation scripts (browser + connector + AI)");
38994
+ scriptCmd.command("run <name>").description("Run a saved script (sync, shows progress live)").option("--engine <engine>", "Browser engine", "auto").option("--headed", "Run in headed (visible) mode").option("--json", "Output as JSON").option("--var <pairs...>", "Set variables (key=value)").action(async (name, opts) => {
38995
+ const { getScriptByName: getScriptByName2, getSteps: getSteps2, migrateJsonScripts: migrateJsonScripts2 } = await Promise.resolve().then(() => (init_scripts(), exports_scripts));
38996
+ const { executeScriptSync: executeScriptSync2 } = await Promise.resolve().then(() => (init_script_engine(), exports_script_engine));
38997
+ migrateJsonScripts2();
38998
+ const script = getScriptByName2(name);
38741
38999
  if (!script) {
38742
- console.log(chalk.red(`Script '${name}' not found. Use 'browser script list' to see available scripts.`));
39000
+ console.log(chalk.red(`Script '${name}' not found.`));
38743
39001
  return;
38744
39002
  }
38745
39003
  const { session, page } = await createSession2({ engine: opts.engine, headless: !opts.headed });
39004
+ const steps = getSteps2(script.id);
38746
39005
  const overrides = {};
38747
- if (opts.email)
38748
- overrides.email = opts.email;
38749
39006
  if (opts.var) {
38750
39007
  for (const pair of opts.var) {
38751
39008
  const [k, ...v] = pair.split("=");
@@ -38754,83 +39011,92 @@ scriptCmd.command("run <name>").description("Run a saved login script").option("
38754
39011
  }
38755
39012
  }
38756
39013
  if (!opts.json) {
38757
- console.log(chalk.gray(`Running script: ${script.name} (${script.steps.length} steps)`));
38758
- console.log(chalk.gray(` Domain: ${script.domain}`));
39014
+ console.log(chalk.gray(`Running: ${script.name} (${steps.length} steps)`));
38759
39015
  if (script.description)
38760
39016
  console.log(chalk.gray(` ${script.description}
38761
39017
  `));
38762
39018
  }
38763
- const result = await runScript2(script, page, overrides);
39019
+ const result = await executeScriptSync2(script.id, page, overrides);
38764
39020
  if (opts.json) {
38765
39021
  console.log(JSON.stringify({ ...result, session_id: session.id }));
38766
39022
  } else {
38767
39023
  if (result.success) {
38768
39024
  console.log(chalk.green(`
38769
- \u2713 Script completed (${result.steps_executed} steps, ${result.duration_ms}ms)`));
39025
+ \u2713 Completed (${result.steps_executed} steps, ${result.duration_ms}ms)`));
38770
39026
  } else {
38771
39027
  console.log(chalk.red(`
38772
- \u2717 Script failed (${result.steps_failed}/${result.steps_executed} steps failed)`));
39028
+ \u2717 Failed (${result.steps_failed}/${result.steps_executed} failed)`));
38773
39029
  result.errors.forEach((e) => console.log(chalk.red(` ${e}`)));
38774
39030
  }
38775
- console.log(chalk.gray(` Session: ${session.id}`));
38776
- console.log(chalk.gray(` URL: ${result.variables.current_url ?? page.url()}`));
38777
39031
  }
38778
39032
  if (!opts.headed)
38779
39033
  await closeSession2(session.id);
38780
39034
  });
38781
- scriptCmd.command("list").description("List saved login scripts").option("--json", "Output as JSON").action(async (opts) => {
38782
- const { listScripts: listScripts2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
39035
+ scriptCmd.command("list").description("List saved scripts").option("--json", "Output as JSON").action(async (opts) => {
39036
+ const { listScripts: listScripts2, migrateJsonScripts: migrateJsonScripts2 } = await Promise.resolve().then(() => (init_scripts(), exports_scripts));
39037
+ migrateJsonScripts2();
38783
39038
  const scripts = listScripts2();
38784
39039
  if (opts.json) {
38785
39040
  console.log(JSON.stringify(scripts, null, 2));
38786
39041
  } else if (scripts.length === 0) {
38787
- console.log(chalk.gray("No scripts saved. Create one with 'browser script create-usestable'"));
39042
+ console.log(chalk.gray("No scripts. Import with: browser script import <file.json>"));
38788
39043
  } else {
38789
39044
  scripts.forEach((s) => {
38790
- console.log(`${chalk.bold(s.name)} ${chalk.gray(`(${s.domain})`)} \u2014 ${s.steps} steps`);
39045
+ console.log(`${chalk.bold(s.name)} ${chalk.gray(`(${s.domain})`)} \u2014 runs: ${s.run_count}`);
38791
39046
  if (s.description)
38792
39047
  console.log(chalk.gray(` ${s.description}`));
38793
39048
  });
38794
39049
  }
38795
39050
  });
38796
- scriptCmd.command("create <file>").description("Create a script from a JSON file (see docs for schema)").action(async (file) => {
38797
- const { createScriptFromFile: createScriptFromFile2, saveScript: saveScript2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
38798
- try {
38799
- const script = createScriptFromFile2(file);
38800
- const path = saveScript2(script);
38801
- console.log(chalk.green(`\u2713 Script saved: ${script.name}`));
38802
- console.log(chalk.gray(` Domain: ${script.domain}`));
38803
- console.log(chalk.gray(` Steps: ${script.steps.length}`));
38804
- console.log(chalk.gray(` Path: ${path}`));
38805
- console.log(chalk.gray(` Run with: browser script run ${script.name}`));
38806
- } catch (err2) {
38807
- console.log(chalk.red(`Error: ${err2 instanceof Error ? err2.message : String(err2)}`));
39051
+ scriptCmd.command("import <file>").description("Import a script from a JSON file into SQLite").action(async (file) => {
39052
+ const { readFileSync: readFileSync11, existsSync: existsSync11 } = await import("fs");
39053
+ if (!existsSync11(file)) {
39054
+ console.log(chalk.red(`File not found: ${file}`));
39055
+ return;
38808
39056
  }
39057
+ const raw = JSON.parse(readFileSync11(file, "utf8"));
39058
+ const { upsertScript: upsertScript2, getSteps: getSteps2 } = await Promise.resolve().then(() => (init_scripts(), exports_scripts));
39059
+ const steps = (raw.steps ?? []).map((s) => {
39060
+ const isAI = s.type === "ai";
39061
+ return {
39062
+ type: isAI ? "extract" : s.type,
39063
+ config: { action: s.action, selector: s.selector, url: s.url, value: s.value, text: s.text, timeout: s.timeout, connector: s.connector, args: s.args, seconds: s.seconds, check: s.check, equals: s.equals, contains: s.contains, skip_to: s.skip_to, name: s.name, save_as: s.save_as, pattern: s.pattern, ...isAI ? { prompt: s.prompt, source: s.check ?? "last_output" } : {} },
39064
+ description: s.description ?? "",
39065
+ ai_enabled: isAI || !!s.ai_enabled,
39066
+ ai_config: isAI ? { provider: "cerebras", model: s.model ?? "fast", prompt: s.prompt } : s.ai_config ?? {}
39067
+ };
39068
+ });
39069
+ const script = upsertScript2({ name: raw.name, domain: raw.domain ?? "", description: raw.description ?? "", variables: raw.variables ?? {}, steps });
39070
+ const saved = getSteps2(script.id);
39071
+ console.log(chalk.green(`\u2713 Imported: ${script.name} (${saved.length} steps)`));
38809
39072
  });
38810
39073
  scriptCmd.command("show <name>").description("Show script details").action(async (name) => {
38811
- const { loadScript: loadScript2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
38812
- const script = loadScript2(name);
39074
+ const { getScriptByName: getScriptByName2, getSteps: getSteps2, migrateJsonScripts: migrateJsonScripts2 } = await Promise.resolve().then(() => (init_scripts(), exports_scripts));
39075
+ migrateJsonScripts2();
39076
+ const script = getScriptByName2(name);
38813
39077
  if (!script) {
38814
39078
  console.log(chalk.red(`Script '${name}' not found`));
38815
39079
  return;
38816
39080
  }
39081
+ const steps = getSteps2(script.id);
38817
39082
  console.log(chalk.bold(`${script.name} (${script.domain})
38818
39083
  `));
38819
39084
  if (script.description)
38820
39085
  console.log(chalk.gray(` ${script.description}
38821
39086
  `));
38822
- console.log(chalk.gray(` Variables: ${Object.keys(script.variables).join(", ")}
39087
+ console.log(chalk.gray(` Variables: ${Object.keys(script.variables).join(", ")}`));
39088
+ console.log(chalk.gray(` Runs: ${script.run_count} Last: ${script.last_run ?? "never"}
38823
39089
  `));
38824
- script.steps.forEach((s, i) => {
38825
- const desc = s.description ?? `${s.type}${s.action ? `:${s.action}` : ""}${s.connector ? `:${s.connector}` : ""}`;
38826
- const detail = s.url ?? s.selector ?? s.text ?? s.connector ?? s.pattern ?? "";
38827
- console.log(` ${chalk.cyan(`${i + 1}.`)} [${s.type}] ${desc} ${chalk.gray(detail.slice(0, 60))}`);
39090
+ steps.forEach((s, i) => {
39091
+ const ai = s.ai_enabled ? chalk.yellow(" [AI]") : "";
39092
+ const detail = s.config.url ?? s.config.selector ?? s.config.connector ?? s.config.prompt?.slice(0, 40) ?? "";
39093
+ console.log(` ${chalk.cyan(`${i + 1}.`)} [${s.type}]${ai} ${s.description} ${chalk.gray(String(detail).slice(0, 50))}`);
38828
39094
  });
38829
39095
  });
38830
39096
  scriptCmd.command("delete <name>").description("Delete a saved script").action(async (name) => {
38831
- const { deleteScript: deleteScript2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
38832
- if (deleteScript2(name)) {
38833
- console.log(chalk.green(`\u2713 Script deleted: ${name}`));
39097
+ const { deleteScriptByName: deleteScriptByName2 } = await Promise.resolve().then(() => (init_scripts(), exports_scripts));
39098
+ if (deleteScriptByName2(name)) {
39099
+ console.log(chalk.green(`\u2713 Deleted: ${name}`));
38834
39100
  } else {
38835
39101
  console.log(chalk.red(`Script '${name}' not found`));
38836
39102
  }
@@ -38860,10 +39126,10 @@ daemonCmd.command("start").description("Start the browser daemon in the backgrou
38860
39126
  return;
38861
39127
  }
38862
39128
  const { spawn: spawn2 } = await import("child_process");
38863
- const { writeFileSync: writeFileSync9, mkdirSync: mkdirSync15 } = await import("fs");
39129
+ const { writeFileSync: writeFileSync8, mkdirSync: mkdirSync14 } = await import("fs");
38864
39130
  const { dirname: dirname4 } = await import("path");
38865
39131
  const pidFile = getDaemonPidFile2();
38866
- mkdirSync15(dirname4(pidFile), { recursive: true });
39132
+ mkdirSync14(dirname4(pidFile), { recursive: true });
38867
39133
  const child = spawn2(process.execPath, [import.meta.dir + "/../server/index.js"], {
38868
39134
  detached: true,
38869
39135
  stdio: "ignore",
@@ -38871,7 +39137,7 @@ daemonCmd.command("start").description("Start the browser daemon in the backgrou
38871
39137
  });
38872
39138
  child.unref();
38873
39139
  if (child.pid) {
38874
- writeFileSync9(pidFile, String(child.pid));
39140
+ writeFileSync8(pidFile, String(child.pid));
38875
39141
  await new Promise((r) => setTimeout(r, 1500));
38876
39142
  console.log(chalk.green(`\u2713 Daemon started`));
38877
39143
  console.log(chalk.gray(` PID: ${child.pid}, Port: ${opts.port}`));
@@ -39044,11 +39310,11 @@ galleryCmd.command("stats").description("Show gallery statistics").option("--pro
39044
39310
  });
39045
39311
  galleryCmd.command("clean").description("Delete gallery entries with missing files").action(async () => {
39046
39312
  const { listEntries: listEntries2, deleteEntry: deleteEntry2 } = await Promise.resolve().then(() => (init_gallery(), exports_gallery));
39047
- const { existsSync: existsSync12 } = await import("fs");
39313
+ const { existsSync: existsSync11 } = await import("fs");
39048
39314
  const entries = listEntries2({ limit: 9999 });
39049
39315
  let removed = 0;
39050
39316
  for (const e of entries) {
39051
- if (!existsSync12(e.path)) {
39317
+ if (!existsSync11(e.path)) {
39052
39318
  deleteEntry2(e.id);
39053
39319
  removed++;
39054
39320
  }