@hasna/browser 0.2.6 → 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,395 +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
+ }
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;
12911
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);
12955
- break;
12956
- case "ai":
12957
- await runAIStep(step, vars);
13273
+ await execExtract(cfg, step, vars);
12958
13274
  break;
12959
13275
  case "wait":
12960
- await new Promise((r) => setTimeout(r, (step.seconds ?? 3) * 1000));
13276
+ await new Promise((r) => setTimeout(r, (cfg.seconds ?? 3) * 1000));
12961
13277
  break;
12962
- case "condition": {
12963
- const checkVal = vars[step.check ?? ""];
12964
- let conditionMet = true;
12965
- if (step.equals !== undefined)
12966
- conditionMet = checkVal === interpolate(step.equals, vars);
12967
- if (step.contains !== undefined)
12968
- conditionMet = checkVal?.includes(interpolate(step.contains, vars)) ?? false;
12969
- if (!conditionMet && step.skip_to !== undefined) {
12970
- i = step.skip_to - 1;
12971
- }
13278
+ case "condition":
13279
+ i = execCondition(cfg, vars, i);
12972
13280
  break;
12973
- }
12974
- case "save_state": {
12975
- const stateName = interpolate(step.name ?? script.name, vars);
12976
- try {
12977
- const { saveStateFromPage: saveStateFromPage2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
12978
- const path = await saveStateFromPage2(page, stateName);
12979
- vars["saved_state_path"] = path;
12980
- } catch {}
13281
+ case "save_state":
13282
+ await execSaveState(cfg, page, vars);
12981
13283
  break;
12982
- }
12983
13284
  }
12984
- const logEntry = job.steps_log[job.steps_log.length - 1];
12985
- logEntry.status = "ok";
12986
- logEntry.duration_ms = Date.now() - stepStart;
13285
+ stepsLog[stepsLog.length - 1].status = "ok";
13286
+ stepsLog[stepsLog.length - 1].duration_ms = Date.now() - stepStart;
12987
13287
  } catch (err) {
12988
13288
  failed++;
12989
- 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)}`;
12990
13290
  errors.push(msg);
12991
- const logEntry = job.steps_log[job.steps_log.length - 1];
12992
- logEntry.status = "failed";
12993
- logEntry.error = err instanceof Error ? err.message : String(err);
12994
- logEntry.duration_ms = Date.now() - stepStart;
12995
- 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")
12996
13295
  break;
12997
13296
  }
13297
+ updateRunProgress(runId, i + 1, desc, stepsLog, vars);
12998
13298
  }
12999
- const result = {
13000
- success: failed === 0,
13001
- steps_executed: executed,
13002
- steps_failed: failed,
13003
- variables: vars,
13004
- errors,
13005
- duration_ms: Date.now() - t0
13006
- };
13007
- job.status = failed === 0 ? "completed" : "failed";
13008
- job.result = result;
13009
- return result;
13010
- }
13011
- function runScriptAsync(script, page, overrides = {}) {
13012
- const jobId = randomUUID10();
13013
- const job = {
13014
- id: jobId,
13015
- script_name: script.name,
13016
- status: "running",
13017
- current_step: 0,
13018
- total_steps: script.steps.length,
13019
- current_step_description: "Starting...",
13020
- steps_log: [],
13021
- started_at: new Date().toISOString()
13022
- };
13023
- activeJobs.set(jobId, job);
13024
- runScript(script, page, overrides, jobId).catch((err) => {
13025
- job.status = "failed";
13026
- job.current_step_description = `Fatal error: ${err instanceof Error ? err.message : String(err)}`;
13027
- });
13028
- return jobId;
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 };
13029
13303
  }
13030
- async function runBrowserStep(step, page, vars) {
13031
- const action = step.action;
13032
- if (!action)
13033
- throw new Error("Browser step missing action");
13304
+ async function execBrowser(cfg, step, page, vars) {
13305
+ const action = cfg.action;
13034
13306
  switch (action) {
13035
- case "navigate": {
13036
- const url = interpolate(step.url ?? "", vars);
13037
- 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 });
13038
13309
  await new Promise((r) => setTimeout(r, 1000));
13039
13310
  vars["current_url"] = page.url();
13040
13311
  vars["current_title"] = await page.title();
13041
13312
  break;
13042
- }
13043
13313
  case "type": {
13044
- const selector = interpolate(step.selector ?? "input", vars);
13045
- const value = interpolate(step.value ?? step.text ?? "", vars);
13046
- 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
+ }
13047
13335
  break;
13048
13336
  }
13049
13337
  case "click": {
13050
- const selector = interpolate(step.selector ?? "", vars);
13051
- 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
+ }
13052
13357
  await new Promise((r) => setTimeout(r, 500));
13053
13358
  break;
13054
13359
  }
13055
13360
  case "click_text": {
13056
- const text = interpolate(step.text ?? "", vars);
13057
- 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
+ }
13058
13374
  await new Promise((r) => setTimeout(r, 500));
13059
13375
  break;
13060
13376
  }
13061
- case "wait_for_navigation": {
13377
+ case "wait_for_navigation":
13062
13378
  try {
13063
- await page.waitForNavigation({ timeout: step.timeout ?? 15000 });
13379
+ await page.waitForNavigation({ timeout: cfg.timeout ?? 15000 });
13064
13380
  } catch {}
13065
13381
  await new Promise((r) => setTimeout(r, 1000));
13066
13382
  vars["current_url"] = page.url();
13067
13383
  break;
13068
- }
13069
13384
  case "wait_for_text": {
13070
- const text = interpolate(step.text ?? "", vars);
13071
- await page.waitForSelector(`text=${text}`, { timeout: step.timeout ?? 1e4 });
13385
+ const text = cfg.text;
13386
+ await page.waitForSelector(`text=${text}`, { timeout: cfg.timeout ?? 1e4 });
13072
13387
  break;
13073
13388
  }
13074
- case "snapshot": {
13389
+ case "snapshot":
13075
13390
  vars["page_text"] = await page.evaluate(() => document.body?.textContent?.trim() ?? "");
13076
13391
  break;
13077
- }
13078
13392
  }
13079
13393
  }
13080
- async function runConnectorStep(step, vars) {
13081
- const connectorName = step.connector;
13082
- if (!connectorName)
13083
- throw new Error("Connector step missing connector name");
13084
- const args = (step.args ?? []).map((a) => interpolate(a, vars));
13085
- let stdout = "";
13086
- try {
13087
- const bin = `connect-${connectorName}`;
13088
- const proc = Bun.spawn([bin, ...args], {
13089
- stdout: "pipe",
13090
- stderr: "pipe",
13091
- env: { ...process.env, HOME: process.env.HOME ?? "" }
13092
- });
13093
- stdout = await new Response(proc.stdout).text();
13094
- const stderr = await new Response(proc.stderr).text();
13095
- const exitCode = await proc.exited;
13096
- vars["last_success"] = String(exitCode === 0);
13097
- vars["last_exit_code"] = String(exitCode);
13098
- if (exitCode !== 0 && stderr)
13099
- stdout = stderr;
13100
- } catch (e) {
13101
- vars["last_success"] = "false";
13102
- throw new Error(`Connector ${connectorName} failed: ${e.message ?? String(e)}`);
13103
- }
13104
- vars["last_output"] = stdout;
13105
- try {
13106
- const parsed = JSON.parse(stdout);
13107
- if (typeof parsed === "object" && parsed !== null) {
13108
- for (const [k, v] of Object.entries(parsed)) {
13109
- if (typeof v === "string" || typeof v === "number") {
13110
- vars[`last.${k}`] = String(v);
13111
- }
13112
- }
13113
- }
13114
- } catch {}
13115
- if (step.save_as) {
13116
- vars[step.save_as] = stdout;
13117
- }
13118
- }
13119
- function getAIProvider(provider) {
13120
- if (provider === "anthropic") {
13121
- const apiKey2 = process.env["ANTHROPIC_API_KEY"];
13122
- if (!apiKey2)
13123
- throw new Error("ANTHROPIC_API_KEY not set");
13124
- return {
13125
- url: "https://api.anthropic.com/v1/messages",
13126
- headers: { "content-type": "application/json", "x-api-key": apiKey2, "anthropic-version": "2023-06-01" },
13127
- body: (model, prompt) => ({ model, max_tokens: 1024, messages: [{ role: "user", content: prompt }] }),
13128
- extract: (data) => data.content?.[0]?.text ?? ""
13129
- };
13130
- }
13131
- const apiKey = process.env["CEREBRAS_API_KEY"];
13132
- if (!apiKey)
13133
- throw new Error("CEREBRAS_API_KEY not set \u2014 required for AI steps (set CEREBRAS_API_KEY or use provider: 'anthropic')");
13134
- return {
13135
- url: "https://api.cerebras.ai/v1/chat/completions",
13136
- headers: { "content-type": "application/json", Authorization: `Bearer ${apiKey}` },
13137
- body: (model, prompt) => ({ model, max_tokens: 1024, messages: [{ role: "user", content: prompt }] }),
13138
- extract: (data) => data.choices?.[0]?.message?.content ?? ""
13139
- };
13140
- }
13141
- async function runAIStep(step, vars) {
13142
- const prompt = interpolate(step.prompt ?? "", vars);
13143
- if (!prompt)
13144
- throw new Error("AI step missing prompt");
13145
- const modelAlias = step.model ?? "fast";
13146
- const resolved = MODEL_MAP[modelAlias] ?? { provider: "cerebras", model: modelAlias };
13147
- const saveTo = step.save_as ?? "ai_response";
13148
- const provider = getAIProvider(resolved.provider);
13149
- const response = await fetch(provider.url, {
13150
- method: "POST",
13151
- headers: provider.headers,
13152
- body: JSON.stringify(provider.body(resolved.model, prompt))
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 ?? "" }
13153
13403
  });
13154
- if (!response.ok) {
13155
- const errBody = await response.text();
13156
- throw new Error(`AI API error (${response.status}): ${errBody.slice(0, 200)}`);
13157
- }
13158
- const data = await response.json();
13159
- const text = provider.extract(data);
13160
- vars[saveTo] = text;
13161
- vars["last_output"] = text;
13162
- if (step.response_format === "json") {
13163
- try {
13164
- const jsonStr = text.replace(/```json?\n?/g, "").replace(/```/g, "").trim();
13165
- const parsed = JSON.parse(jsonStr);
13166
- if (typeof parsed === "object" && parsed !== null) {
13167
- for (const [k, v] of Object.entries(parsed)) {
13168
- if (typeof v === "string" || typeof v === "number") {
13169
- vars[`${saveTo}.${k}`] = String(v);
13170
- }
13171
- }
13172
- }
13173
- } catch {}
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;
13174
13435
  }
13175
- }
13176
- function decodeHtmlEntities(str) {
13177
- return str.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&#x27;/g, "'");
13178
- }
13179
- function runExtractStep(step, vars) {
13180
- const saveTo = step.save_as ?? "extracted";
13181
- if (step.pattern) {
13182
- const source = step.check ? vars[step.check] ?? "" : vars["last_output"] ?? "";
13183
- const regex = new RegExp(step.pattern);
13184
- 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);
13185
13439
  if (match) {
13186
13440
  vars[saveTo] = decodeHtmlEntities(match[1] ?? match[0]);
13187
13441
  }
13188
13442
  }
13189
- if (step.json_path) {
13190
- const source = vars["last_output"] ?? "{}";
13191
- try {
13192
- let obj = JSON.parse(source);
13193
- for (const key of step.json_path.split(".")) {
13194
- obj = obj?.[key];
13195
- }
13196
- if (obj !== undefined)
13197
- vars[saveTo] = String(obj);
13198
- } catch {}
13199
- }
13200
13443
  }
13201
- function createScriptFromJSON(jsonStr) {
13202
- const parsed = JSON.parse(jsonStr);
13203
- if (!parsed.name)
13204
- throw new Error("Script must have a 'name' field");
13205
- if (!parsed.domain)
13206
- throw new Error("Script must have a 'domain' field");
13207
- if (!parsed.steps || !Array.isArray(parsed.steps) || parsed.steps.length === 0) {
13208
- throw new Error("Script must have a non-empty 'steps' array");
13209
- }
13210
- return {
13211
- name: parsed.name,
13212
- domain: parsed.domain,
13213
- description: parsed.description ?? "",
13214
- variables: parsed.variables ?? {},
13215
- steps: parsed.steps,
13216
- created_at: new Date().toISOString(),
13217
- updated_at: new Date().toISOString()
13218
- };
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;
13219
13454
  }
13220
- function createScriptFromFile(filePath) {
13221
- if (!existsSync5(filePath))
13222
- throw new Error(`File not found: ${filePath}`);
13223
- 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 {}
13224
13461
  }
13225
- var activeJobs, MODEL_MAP;
13226
- var init_login_scripts = __esm(() => {
13227
- init_schema();
13228
- activeJobs = new Map;
13229
- MODEL_MAP = {
13230
- fast: { provider: "cerebras", model: "llama-4-scout-17b-16e-instruct" },
13231
- scout: { provider: "cerebras", model: "llama-4-scout-17b-16e-instruct" },
13232
- maverick: { provider: "cerebras", model: "llama-4-maverick-17b-128e-instruct" },
13233
- haiku: { provider: "anthropic", model: "claude-haiku-4-5-20251001" },
13234
- sonnet: { provider: "anthropic", model: "claude-sonnet-4-5-20250929" },
13235
- opus: { provider: "anthropic", model: "claude-opus-4-6" }
13236
- };
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();
13237
13479
  });
13238
13480
 
13239
13481
  // src/lib/daemon-client.ts
@@ -13245,8 +13487,8 @@ __export(exports_daemon_client, {
13245
13487
  getDaemonPidFile: () => getDaemonPidFile,
13246
13488
  getDaemonPid: () => getDaemonPid
13247
13489
  });
13248
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
13249
- import { join as join9 } from "path";
13490
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
13491
+ import { join as join8 } from "path";
13250
13492
  import { homedir as homedir8 } from "os";
13251
13493
  function getDaemonPidFile() {
13252
13494
  return PID_FILE;
@@ -13255,10 +13497,10 @@ function getDaemonPort() {
13255
13497
  return parseInt(process.env["BROWSER_DAEMON_PORT"] ?? String(DEFAULT_PORT), 10);
13256
13498
  }
13257
13499
  function isDaemonRunning() {
13258
- if (!existsSync6(PID_FILE))
13500
+ if (!existsSync5(PID_FILE))
13259
13501
  return false;
13260
13502
  try {
13261
- const pid = parseInt(readFileSync4(PID_FILE, "utf8").trim(), 10);
13503
+ const pid = parseInt(readFileSync3(PID_FILE, "utf8").trim(), 10);
13262
13504
  process.kill(pid, 0);
13263
13505
  return true;
13264
13506
  } catch {
@@ -13266,10 +13508,10 @@ function isDaemonRunning() {
13266
13508
  }
13267
13509
  }
13268
13510
  function getDaemonPid() {
13269
- if (!existsSync6(PID_FILE))
13511
+ if (!existsSync5(PID_FILE))
13270
13512
  return null;
13271
13513
  try {
13272
- return parseInt(readFileSync4(PID_FILE, "utf8").trim(), 10);
13514
+ return parseInt(readFileSync3(PID_FILE, "utf8").trim(), 10);
13273
13515
  } catch {
13274
13516
  return null;
13275
13517
  }
@@ -13289,7 +13531,7 @@ async function getDaemonStatus() {
13289
13531
  }
13290
13532
  var PID_FILE, DEFAULT_PORT = 7030;
13291
13533
  var init_daemon_client = __esm(() => {
13292
- 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");
13293
13535
  });
13294
13536
 
13295
13537
  // node_modules/zod/v3/helpers/util.js
@@ -17363,16 +17605,16 @@ __export(exports_downloads, {
17363
17605
  cleanStaleDownloads: () => cleanStaleDownloads
17364
17606
  });
17365
17607
  import { randomUUID as randomUUID11 } from "crypto";
17366
- import { join as join10, basename, extname } from "path";
17367
- 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";
17368
17610
  import { homedir as homedir9 } from "os";
17369
17611
  function getDataDir3() {
17370
- return process.env["BROWSER_DATA_DIR"] ?? join10(homedir9(), ".browser");
17612
+ return process.env["BROWSER_DATA_DIR"] ?? join9(homedir9(), ".browser");
17371
17613
  }
17372
17614
  function getDownloadsDir(sessionId) {
17373
- const base = join10(getDataDir3(), "downloads");
17374
- const dir = sessionId ? join10(base, sessionId) : base;
17375
- mkdirSync8(dir, { recursive: true });
17615
+ const base = join9(getDataDir3(), "downloads");
17616
+ const dir = sessionId ? join9(base, sessionId) : base;
17617
+ mkdirSync7(dir, { recursive: true });
17376
17618
  return dir;
17377
17619
  }
17378
17620
  function ensureDownloadsDir() {
@@ -17387,8 +17629,8 @@ function saveToDownloads(buffer, filename, opts) {
17387
17629
  const ext = extname(filename) || "";
17388
17630
  const stem = basename(filename, ext);
17389
17631
  const uniqueName = `${stem}-${id.slice(0, 8)}${ext}`;
17390
- const filePath = join10(dir, uniqueName);
17391
- writeFileSync3(filePath, buffer);
17632
+ const filePath = join9(dir, uniqueName);
17633
+ writeFileSync2(filePath, buffer);
17392
17634
  const meta = {
17393
17635
  id,
17394
17636
  type: opts?.type ?? detectType(filename),
@@ -17399,7 +17641,7 @@ function saveToDownloads(buffer, filename, opts) {
17399
17641
  original_name: filename,
17400
17642
  ...opts?.metadata
17401
17643
  };
17402
- writeFileSync3(metaPath(filePath), JSON.stringify(meta, null, 2));
17644
+ writeFileSync2(metaPath(filePath), JSON.stringify(meta, null, 2));
17403
17645
  return {
17404
17646
  id,
17405
17647
  path: filePath,
@@ -17416,23 +17658,23 @@ function listDownloads(sessionId) {
17416
17658
  const dir = getDownloadsDir(sessionId);
17417
17659
  const results = [];
17418
17660
  function scanDir(d) {
17419
- if (!existsSync7(d))
17661
+ if (!existsSync6(d))
17420
17662
  return;
17421
- const entries = readdirSync4(d);
17663
+ const entries = readdirSync3(d);
17422
17664
  for (const entry of entries) {
17423
17665
  if (entry.endsWith(".meta.json"))
17424
17666
  continue;
17425
- const full = join10(d, entry);
17667
+ const full = join9(d, entry);
17426
17668
  const stat = statSync(full);
17427
17669
  if (stat.isDirectory()) {
17428
17670
  scanDir(full);
17429
17671
  continue;
17430
17672
  }
17431
17673
  const mpath = metaPath(full);
17432
- if (!existsSync7(mpath))
17674
+ if (!existsSync6(mpath))
17433
17675
  continue;
17434
17676
  try {
17435
- const meta = JSON.parse(readFileSync5(mpath, "utf8"));
17677
+ const meta = JSON.parse(readFileSync4(mpath, "utf8"));
17436
17678
  results.push({
17437
17679
  id: meta.id,
17438
17680
  path: full,
@@ -17460,7 +17702,7 @@ function deleteDownload(id, sessionId) {
17460
17702
  return false;
17461
17703
  try {
17462
17704
  unlinkSync2(file.path);
17463
- if (existsSync7(file.meta_path))
17705
+ if (existsSync6(file.meta_path))
17464
17706
  unlinkSync2(file.meta_path);
17465
17707
  return true;
17466
17708
  } catch {
@@ -17507,8 +17749,8 @@ function detectType(filename) {
17507
17749
  var init_downloads = () => {};
17508
17750
 
17509
17751
  // src/lib/files-integration.ts
17510
- import { join as join11 } from "path";
17511
- 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";
17512
17754
  import { homedir as homedir10 } from "os";
17513
17755
  async function persistFile(localPath, opts) {
17514
17756
  try {
@@ -17518,12 +17760,12 @@ async function persistFile(localPath, opts) {
17518
17760
  return { id: ref.id, path: ref.path ?? localPath, permanent: true, provider: "open-files" };
17519
17761
  }
17520
17762
  } catch {}
17521
- const dataDir = process.env["BROWSER_DATA_DIR"] ?? join11(homedir10(), ".browser");
17763
+ const dataDir = process.env["BROWSER_DATA_DIR"] ?? join10(homedir10(), ".browser");
17522
17764
  const date = new Date().toISOString().split("T")[0];
17523
- const dir = join11(dataDir, "persistent", date);
17524
- mkdirSync9(dir, { recursive: true });
17765
+ const dir = join10(dataDir, "persistent", date);
17766
+ mkdirSync8(dir, { recursive: true });
17525
17767
  const filename = localPath.split("/").pop() ?? "file";
17526
- const targetPath = join11(dir, filename);
17768
+ const targetPath = join10(dir, filename);
17527
17769
  copyFileSync2(localPath, targetPath);
17528
17770
  return {
17529
17771
  id: `local-${Date.now()}`,
@@ -18000,9 +18242,9 @@ async function tryReplayAuth(page, domain) {
18000
18242
  return { replayed: false };
18001
18243
  if (flow.storage_state_path) {
18002
18244
  try {
18003
- const { existsSync: existsSync8, readFileSync: readFileSync6 } = await import("fs");
18004
- if (existsSync8(flow.storage_state_path)) {
18005
- 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"));
18006
18248
  if (state.cookies?.length) {
18007
18249
  await page.context().addCookies(state.cookies);
18008
18250
  await page.reload();
@@ -18051,82 +18293,6 @@ var init_auth_flow = __esm(() => {
18051
18293
  ];
18052
18294
  });
18053
18295
 
18054
- // src/lib/vision-fallback.ts
18055
- var exports_vision_fallback = {};
18056
- __export(exports_vision_fallback, {
18057
- findElementByVision: () => findElementByVision,
18058
- clickByVision: () => clickByVision
18059
- });
18060
- async function findElementByVision(page, description, opts) {
18061
- const model = opts?.model ?? process.env["BROWSER_VISION_MODEL"] ?? DEFAULT_MODEL;
18062
- const screenshot = await page.screenshot({ type: "jpeg", quality: 80 });
18063
- const base64 = screenshot.toString("base64");
18064
- const viewport = page.viewportSize() ?? { width: 1280, height: 720 };
18065
- const apiKey = process.env["ANTHROPIC_API_KEY"];
18066
- if (!apiKey) {
18067
- return { found: false, x: 0, y: 0, confidence: "none", description, model, error: "ANTHROPIC_API_KEY not set" };
18068
- }
18069
- try {
18070
- const response = await fetch("https://api.anthropic.com/v1/messages", {
18071
- method: "POST",
18072
- headers: {
18073
- "content-type": "application/json",
18074
- "x-api-key": apiKey,
18075
- "anthropic-version": "2023-06-01"
18076
- },
18077
- body: JSON.stringify({
18078
- model,
18079
- max_tokens: 256,
18080
- messages: [{
18081
- role: "user",
18082
- content: [
18083
- {
18084
- type: "image",
18085
- source: { type: "base64", media_type: "image/jpeg", data: base64 }
18086
- },
18087
- {
18088
- type: "text",
18089
- text: `Find the element matching this description: "${description}"
18090
-
18091
- The screenshot is ${viewport.width}x${viewport.height} pixels.
18092
-
18093
- Reply with ONLY a JSON object (no markdown, no explanation):
18094
- {"found": true, "x": <pixel_x>, "y": <pixel_y>, "confidence": "high|medium|low", "description": "<what you found>"}
18095
-
18096
- If you cannot find the element:
18097
- {"found": false, "x": 0, "y": 0, "confidence": "none", "description": "not found"}`
18098
- }
18099
- ]
18100
- }]
18101
- })
18102
- });
18103
- const data = await response.json();
18104
- const text = data.content?.[0]?.text ?? "";
18105
- const jsonStr = text.replace(/```json?\n?/g, "").replace(/```/g, "").trim();
18106
- const result = JSON.parse(jsonStr);
18107
- result.model = model;
18108
- return result;
18109
- } catch (err) {
18110
- return {
18111
- found: false,
18112
- x: 0,
18113
- y: 0,
18114
- confidence: "none",
18115
- description,
18116
- model,
18117
- error: err instanceof Error ? err.message : String(err)
18118
- };
18119
- }
18120
- }
18121
- async function clickByVision(page, description, opts) {
18122
- const result = await findElementByVision(page, description, opts);
18123
- if (result.found && result.x > 0 && result.y > 0) {
18124
- await page.mouse.click(result.x, result.y);
18125
- }
18126
- return result;
18127
- }
18128
- var DEFAULT_MODEL = "claude-sonnet-4-5-20250929";
18129
-
18130
18296
  // src/lib/datasets.ts
18131
18297
  var exports_datasets = {};
18132
18298
  __export(exports_datasets, {
@@ -18138,8 +18304,8 @@ __export(exports_datasets, {
18138
18304
  deleteDataset: () => deleteDataset
18139
18305
  });
18140
18306
  import { randomUUID as randomUUID14 } from "crypto";
18141
- import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync10 } from "fs";
18142
- import { join as join12 } from "path";
18307
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync9 } from "fs";
18308
+ import { join as join11 } from "path";
18143
18309
  import { homedir as homedir11 } from "os";
18144
18310
  function saveDataset(data) {
18145
18311
  const db = getDatabase();
@@ -18178,14 +18344,14 @@ function exportDataset(name, format) {
18178
18344
  const dataset = getDatasetByName(name);
18179
18345
  if (!dataset)
18180
18346
  throw new Error(`Dataset '${name}' not found`);
18181
- const dir = join12(process.env["BROWSER_DATA_DIR"] ?? join12(homedir11(), ".browser"), "exports");
18182
- mkdirSync10(dir, { recursive: true });
18347
+ const dir = join11(process.env["BROWSER_DATA_DIR"] ?? join11(homedir11(), ".browser"), "exports");
18348
+ mkdirSync9(dir, { recursive: true });
18183
18349
  const filename = `${name}.${format}`;
18184
- const path = join12(dir, filename);
18350
+ const path = join11(dir, filename);
18185
18351
  if (format === "csv") {
18186
18352
  const rows = dataset.data;
18187
18353
  if (rows.length === 0) {
18188
- writeFileSync4(path, "");
18354
+ writeFileSync3(path, "");
18189
18355
  return { path, size: 0 };
18190
18356
  }
18191
18357
  const headers = Object.keys(rows[0]);
@@ -18198,11 +18364,11 @@ function exportDataset(name, format) {
18198
18364
  }
18199
18365
  const content = csvLines.join(`
18200
18366
  `);
18201
- writeFileSync4(path, content);
18367
+ writeFileSync3(path, content);
18202
18368
  return { path, size: content.length };
18203
18369
  } else {
18204
18370
  const content = JSON.stringify(dataset.data, null, 2);
18205
- writeFileSync4(path, content);
18371
+ writeFileSync3(path, content);
18206
18372
  return { path, size: content.length };
18207
18373
  }
18208
18374
  }
@@ -18320,11 +18486,11 @@ __export(exports_dist, {
18320
18486
  DEFAULT_CONFIG: () => DEFAULT_CONFIG
18321
18487
  });
18322
18488
  import { Database as Database2 } from "bun:sqlite";
18323
- import { existsSync as existsSync8, mkdirSync as mkdirSync11 } from "fs";
18324
- import { dirname, join as join13, resolve } from "path";
18325
- 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";
18326
18492
  import { homedir as homedir12 } from "os";
18327
- 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";
18328
18494
  import { existsSync as existsSync32, mkdirSync as mkdirSync32, readFileSync as readFileSync22, writeFileSync as writeFileSync22 } from "fs";
18329
18495
  import { homedir as homedir22 } from "os";
18330
18496
  import { join as join32 } from "path";
@@ -18335,10 +18501,10 @@ function isInMemoryDb(path) {
18335
18501
  return path === ":memory:" || path.startsWith("file::memory:");
18336
18502
  }
18337
18503
  function findNearestMementosDb(startDir) {
18338
- let dir = resolve(startDir);
18504
+ let dir = resolve2(startDir);
18339
18505
  while (true) {
18340
- const candidate = join13(dir, ".mementos", "mementos.db");
18341
- if (existsSync8(candidate))
18506
+ const candidate = join12(dir, ".mementos", "mementos.db");
18507
+ if (existsSync7(candidate))
18342
18508
  return candidate;
18343
18509
  const parent = dirname(dir);
18344
18510
  if (parent === dir)
@@ -18348,9 +18514,9 @@ function findNearestMementosDb(startDir) {
18348
18514
  return null;
18349
18515
  }
18350
18516
  function findGitRoot(startDir) {
18351
- let dir = resolve(startDir);
18517
+ let dir = resolve2(startDir);
18352
18518
  while (true) {
18353
- if (existsSync8(join13(dir, ".git")))
18519
+ if (existsSync7(join12(dir, ".git")))
18354
18520
  return dir;
18355
18521
  const parent = dirname(dir);
18356
18522
  if (parent === dir)
@@ -18370,18 +18536,18 @@ function getDbPath() {
18370
18536
  if (process.env["MEMENTOS_DB_SCOPE"] === "project") {
18371
18537
  const gitRoot = findGitRoot(cwd);
18372
18538
  if (gitRoot) {
18373
- return join13(gitRoot, ".mementos", "mementos.db");
18539
+ return join12(gitRoot, ".mementos", "mementos.db");
18374
18540
  }
18375
18541
  }
18376
18542
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
18377
- return join13(home, ".mementos", "mementos.db");
18543
+ return join12(home, ".mementos", "mementos.db");
18378
18544
  }
18379
18545
  function ensureDir2(filePath) {
18380
18546
  if (isInMemoryDb(filePath))
18381
18547
  return;
18382
- const dir = dirname(resolve(filePath));
18383
- if (!existsSync8(dir)) {
18384
- mkdirSync11(dir, { recursive: true });
18548
+ const dir = dirname(resolve2(filePath));
18549
+ if (!existsSync7(dir)) {
18550
+ mkdirSync10(dir, { recursive: true });
18385
18551
  }
18386
18552
  }
18387
18553
  function getDatabase2(dbPath) {
@@ -20235,7 +20401,7 @@ function loadConfig() {
20235
20401
  let fileConfig = {};
20236
20402
  if (existsSync22(configPath)) {
20237
20403
  try {
20238
- const raw = readFileSync6(configPath, "utf-8");
20404
+ const raw = readFileSync5(configPath, "utf-8");
20239
20405
  fileConfig = JSON.parse(raw);
20240
20406
  } catch {}
20241
20407
  }
@@ -20268,7 +20434,7 @@ function readGlobalConfig() {
20268
20434
  if (!existsSync22(p))
20269
20435
  return {};
20270
20436
  try {
20271
- return JSON.parse(readFileSync6(p, "utf-8"));
20437
+ return JSON.parse(readFileSync5(p, "utf-8"));
20272
20438
  } catch {
20273
20439
  return {};
20274
20440
  }
@@ -20276,7 +20442,7 @@ function readGlobalConfig() {
20276
20442
  function writeGlobalConfig(data) {
20277
20443
  const p = globalConfigPath();
20278
20444
  ensureDir22(dirname2(p));
20279
- writeFileSync5(p, JSON.stringify(data, null, 2), "utf-8");
20445
+ writeFileSync4(p, JSON.stringify(data, null, 2), "utf-8");
20280
20446
  }
20281
20447
  function getActiveProfile() {
20282
20448
  const envProfile = process.env["MEMENTOS_PROFILE"];
@@ -20298,7 +20464,7 @@ function listProfiles2() {
20298
20464
  const dir = profilesDir();
20299
20465
  if (!existsSync22(dir))
20300
20466
  return [];
20301
- 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();
20302
20468
  }
20303
20469
  function deleteProfile2(name) {
20304
20470
  const dbPath = join22(profilesDir(), `${name}.db`);
@@ -22941,30 +23107,30 @@ __export(exports_dist2, {
22941
23107
  acquireLock: () => acquireLock2
22942
23108
  });
22943
23109
  import { Database as Database3 } from "bun:sqlite";
22944
- import { mkdirSync as mkdirSync12 } from "fs";
22945
- 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";
22946
23112
  import { homedir as homedir13 } from "os";
22947
23113
  import { randomUUID as randomUUID15 } from "crypto";
22948
23114
  import { mkdirSync as mkdirSync23, copyFileSync as copyFileSync3, statSync as statSync2 } from "fs";
22949
23115
  import { join as join33 } from "path";
22950
23116
  import { homedir as homedir33 } from "os";
22951
- import { readFileSync as readFileSync7 } from "fs";
23117
+ import { readFileSync as readFileSync6 } from "fs";
22952
23118
  import { join as join23 } from "path";
22953
23119
  import { homedir as homedir23 } from "os";
22954
23120
  import { randomUUID as randomUUID22 } from "crypto";
22955
- 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";
22956
23122
  import { join as join43, dirname as dirname22 } from "path";
22957
23123
  import { homedir as homedir42 } from "os";
22958
23124
  function getDbPath2() {
22959
23125
  if (process.env.CONVERSATIONS_DB_PATH)
22960
23126
  return process.env.CONVERSATIONS_DB_PATH;
22961
- return join14(homedir13(), ".conversations", "messages.db");
23127
+ return join13(homedir13(), ".conversations", "messages.db");
22962
23128
  }
22963
23129
  function getDb() {
22964
23130
  if (db)
22965
23131
  return db;
22966
23132
  const dbPath = getDbPath2();
22967
- mkdirSync12(dirname3(dbPath), { recursive: true });
23133
+ mkdirSync11(dirname3(dbPath), { recursive: true });
22968
23134
  db = new Database3(dbPath, { create: true });
22969
23135
  db.exec("PRAGMA journal_mode = WAL");
22970
23136
  db.exec("PRAGMA busy_timeout = 5000");
@@ -23206,7 +23372,7 @@ function loadConfig2() {
23206
23372
  if (cachedConfig && now2 - configLoadedAt < CONFIG_CACHE_MS)
23207
23373
  return cachedConfig;
23208
23374
  try {
23209
- const raw = readFileSync7(getConfigPath(), "utf-8");
23375
+ const raw = readFileSync6(getConfigPath(), "utf-8");
23210
23376
  cachedConfig = JSON.parse(raw);
23211
23377
  configLoadedAt = now2;
23212
23378
  return cachedConfig;
@@ -24123,7 +24289,7 @@ function useSpaceMessages(spaceName) {
24123
24289
  }
24124
24290
  function isNameTaken(name) {
24125
24291
  try {
24126
- const { getDb: getDb2 } = (init_db(), __toCommonJS(exports_db));
24292
+ const { getDb: getDb2 } = (init_db(), __toCommonJS2(exports_db));
24127
24293
  const db2 = getDb2();
24128
24294
  const row = db2.prepare("SELECT agent FROM agent_presence WHERE agent = ?").get(name);
24129
24295
  return !!row;
@@ -24152,7 +24318,7 @@ function getAutoName() {
24152
24318
  cachedAutoName = name;
24153
24319
  try {
24154
24320
  mkdirSync33(dirname22(AGENT_ID_FILE), { recursive: true });
24155
- writeFileSync6(AGENT_ID_FILE, name + `
24321
+ writeFileSync5(AGENT_ID_FILE, name + `
24156
24322
  `, "utf-8");
24157
24323
  } catch {}
24158
24324
  return name;
@@ -24758,7 +24924,7 @@ function getGraphStats() {
24758
24924
  map[r.relation] = r.c;
24759
24925
  return { total_edges: total, by_relation: map };
24760
24926
  }
24761
- var __create2, __getProtoOf2, __defProp3, __getOwnPropNames2, __getOwnPropDesc, __hasOwnProp2, __toESM2 = (mod, isNodeMode, target) => {
24927
+ var __create2, __getProtoOf2, __defProp3, __getOwnPropNames2, __getOwnPropDesc2, __hasOwnProp2, __toESM2 = (mod, isNodeMode, target) => {
24762
24928
  target = mod != null ? __create2(__getProtoOf2(mod)) : {};
24763
24929
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp3(target, "default", { value: mod, enumerable: true }) : target;
24764
24930
  for (let key of __getOwnPropNames2(mod))
@@ -24768,17 +24934,17 @@ var __create2, __getProtoOf2, __defProp3, __getOwnPropNames2, __getOwnPropDesc,
24768
24934
  enumerable: true
24769
24935
  });
24770
24936
  return to;
24771
- }, __moduleCache, __toCommonJS = (from) => {
24772
- var entry = __moduleCache.get(from), desc;
24937
+ }, __moduleCache2, __toCommonJS2 = (from) => {
24938
+ var entry = __moduleCache2.get(from), desc;
24773
24939
  if (entry)
24774
24940
  return entry;
24775
24941
  entry = __defProp3({}, "__esModule", { value: true });
24776
24942
  if (from && typeof from === "object" || typeof from === "function")
24777
24943
  __getOwnPropNames2(from).map((key) => !__hasOwnProp2.call(entry, key) && __defProp3(entry, key, {
24778
24944
  get: () => from[key],
24779
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
24945
+ enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable
24780
24946
  }));
24781
- __moduleCache.set(from, entry);
24947
+ __moduleCache2.set(from, entry);
24782
24948
  return entry;
24783
24949
  }, __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports), __export3 = (target, all) => {
24784
24950
  for (var name in all)
@@ -24794,9 +24960,9 @@ var init_dist2 = __esm(() => {
24794
24960
  __getProtoOf2 = Object.getPrototypeOf;
24795
24961
  __defProp3 = Object.defineProperty;
24796
24962
  __getOwnPropNames2 = Object.getOwnPropertyNames;
24797
- __getOwnPropDesc = Object.getOwnPropertyDescriptor;
24963
+ __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
24798
24964
  __hasOwnProp2 = Object.prototype.hasOwnProperty;
24799
- __moduleCache = /* @__PURE__ */ new WeakMap;
24965
+ __moduleCache2 = /* @__PURE__ */ new WeakMap;
24800
24966
  exports_db = {};
24801
24967
  __export3(exports_db, {
24802
24968
  getDbPath: () => getDbPath2,
@@ -27366,18 +27532,18 @@ __export(exports_dist3, {
27366
27532
  AgentNotFoundError: () => AgentNotFoundError2
27367
27533
  });
27368
27534
  import { Database as Database4 } from "bun:sqlite";
27369
- import { existsSync as existsSync9, mkdirSync as mkdirSync13 } from "fs";
27370
- 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";
27371
27537
  import { existsSync as existsSync33 } from "fs";
27372
27538
  import { join as join34 } from "path";
27373
- 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";
27374
27540
  import { join as join24 } from "path";
27375
27541
  import { existsSync as existsSync43, readFileSync as readFileSync24, readdirSync as readdirSync22, writeFileSync as writeFileSync23 } from "fs";
27376
27542
  import { join as join44 } from "path";
27377
27543
  import { existsSync as existsSync52 } from "fs";
27378
27544
  import { join as join52 } from "path";
27379
27545
  import { readFileSync as readFileSync33, statSync as statSync22 } from "fs";
27380
- import { relative, resolve as resolve22, join as join62 } from "path";
27546
+ import { relative, resolve as resolve23, join as join62 } from "path";
27381
27547
  import { execSync as execSync2 } from "child_process";
27382
27548
 
27383
27549
  class TodosClient {
@@ -27600,8 +27766,8 @@ function isInMemoryDb2(path) {
27600
27766
  function findNearestTodosDb(startDir) {
27601
27767
  let dir = resolve3(startDir);
27602
27768
  while (true) {
27603
- const candidate = join15(dir, ".todos", "todos.db");
27604
- if (existsSync9(candidate))
27769
+ const candidate = join14(dir, ".todos", "todos.db");
27770
+ if (existsSync8(candidate))
27605
27771
  return candidate;
27606
27772
  const parent = dirname5(dir);
27607
27773
  if (parent === dir)
@@ -27613,7 +27779,7 @@ function findNearestTodosDb(startDir) {
27613
27779
  function findGitRoot2(startDir) {
27614
27780
  let dir = resolve3(startDir);
27615
27781
  while (true) {
27616
- if (existsSync9(join15(dir, ".git")))
27782
+ if (existsSync8(join14(dir, ".git")))
27617
27783
  return dir;
27618
27784
  const parent = dirname5(dir);
27619
27785
  if (parent === dir)
@@ -27633,18 +27799,18 @@ function getDbPath3() {
27633
27799
  if (process.env["TODOS_DB_SCOPE"] === "project") {
27634
27800
  const gitRoot = findGitRoot2(cwd);
27635
27801
  if (gitRoot) {
27636
- return join15(gitRoot, ".todos", "todos.db");
27802
+ return join14(gitRoot, ".todos", "todos.db");
27637
27803
  }
27638
27804
  }
27639
27805
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
27640
- return join15(home, ".todos", "todos.db");
27806
+ return join14(home, ".todos", "todos.db");
27641
27807
  }
27642
27808
  function ensureDir3(filePath) {
27643
27809
  if (isInMemoryDb2(filePath))
27644
27810
  return;
27645
27811
  const dir = dirname5(resolve3(filePath));
27646
- if (!existsSync9(dir)) {
27647
- mkdirSync13(dir, { recursive: true });
27812
+ if (!existsSync8(dir)) {
27813
+ mkdirSync12(dir, { recursive: true });
27648
27814
  }
27649
27815
  }
27650
27816
  function getDatabase3(dbPath) {
@@ -28110,28 +28276,28 @@ function ensureDir23(dir) {
28110
28276
  function listJsonFiles(dir) {
28111
28277
  if (!existsSync23(dir))
28112
28278
  return [];
28113
- return readdirSync6(dir).filter((f) => f.endsWith(".json"));
28279
+ return readdirSync5(dir).filter((f) => f.endsWith(".json"));
28114
28280
  }
28115
28281
  function readJsonFile(path) {
28116
28282
  try {
28117
- return JSON.parse(readFileSync8(path, "utf-8"));
28283
+ return JSON.parse(readFileSync7(path, "utf-8"));
28118
28284
  } catch {
28119
28285
  return null;
28120
28286
  }
28121
28287
  }
28122
28288
  function writeJsonFile(path, data) {
28123
- writeFileSync7(path, JSON.stringify(data, null, 2) + `
28289
+ writeFileSync6(path, JSON.stringify(data, null, 2) + `
28124
28290
  `);
28125
28291
  }
28126
28292
  function readHighWaterMark(dir) {
28127
28293
  const path = join24(dir, ".highwatermark");
28128
28294
  if (!existsSync23(path))
28129
28295
  return 1;
28130
- const val = parseInt(readFileSync8(path, "utf-8").trim(), 10);
28296
+ const val = parseInt(readFileSync7(path, "utf-8").trim(), 10);
28131
28297
  return isNaN(val) ? 1 : val;
28132
28298
  }
28133
28299
  function writeHighWaterMark(dir, value) {
28134
- writeFileSync7(join24(dir, ".highwatermark"), String(value));
28300
+ writeFileSync6(join24(dir, ".highwatermark"), String(value));
28135
28301
  }
28136
28302
  function getFileMtimeMs(path) {
28137
28303
  try {
@@ -31493,7 +31659,7 @@ function collectFiles(basePath, extensions) {
31493
31659
  return files.sort();
31494
31660
  }
31495
31661
  function extractTodos(options, db2) {
31496
- const basePath = resolve22(options.path);
31662
+ const basePath = resolve23(options.path);
31497
31663
  const tags = options.patterns || [...EXTRACT_TAGS];
31498
31664
  const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
31499
31665
  const files = collectFiles(basePath, extensions);
@@ -31502,7 +31668,7 @@ function extractTodos(options, db2) {
31502
31668
  const fullPath = statSync22(basePath).isFile() ? basePath : join62(basePath, file);
31503
31669
  try {
31504
31670
  const source = readFileSync33(fullPath, "utf-8");
31505
- const relPath = statSync22(basePath).isFile() ? relative(resolve22(basePath, ".."), fullPath) : file;
31671
+ const relPath = statSync22(basePath).isFile() ? relative(resolve23(basePath, ".."), fullPath) : file;
31506
31672
  const comments = extractFromSource(source, relPath, tags);
31507
31673
  allComments.push(...comments);
31508
31674
  } catch {}
@@ -32721,8 +32887,8 @@ __export(exports_dist4, {
32721
32887
  CATEGORIES: () => CATEGORIES,
32722
32888
  AGENT_TARGETS: () => AGENT_TARGETS
32723
32889
  });
32724
- 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";
32725
- 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";
32726
32892
  import { homedir as homedir14 } from "os";
32727
32893
  import { fileURLToPath } from "url";
32728
32894
  import { existsSync as existsSync24, readFileSync as readFileSync25, readdirSync as readdirSync23 } from "fs";
@@ -32834,35 +33000,35 @@ function normalizeSkillName(name) {
32834
33000
  function findSkillsDir() {
32835
33001
  let dir = __dirname2;
32836
33002
  for (let i = 0;i < 5; i++) {
32837
- const candidate = join16(dir, "skills");
32838
- if (existsSync10(candidate)) {
33003
+ const candidate = join15(dir, "skills");
33004
+ if (existsSync9(candidate)) {
32839
33005
  return candidate;
32840
33006
  }
32841
33007
  dir = dirname6(dir);
32842
33008
  }
32843
- return join16(__dirname2, "..", "skills");
33009
+ return join15(__dirname2, "..", "skills");
32844
33010
  }
32845
33011
  function getSkillPath(name) {
32846
33012
  const skillName = normalizeSkillName(name);
32847
- return join16(SKILLS_DIR, skillName);
33013
+ return join15(SKILLS_DIR, skillName);
32848
33014
  }
32849
33015
  function skillExists(name) {
32850
- return existsSync10(getSkillPath(name));
33016
+ return existsSync9(getSkillPath(name));
32851
33017
  }
32852
33018
  function installSkill(name, options = {}) {
32853
33019
  const { targetDir = process.cwd(), overwrite = false } = options;
32854
33020
  const skillName = normalizeSkillName(name);
32855
33021
  const sourcePath = getSkillPath(name);
32856
- const destDir = join16(targetDir, ".skills");
32857
- const destPath = join16(destDir, skillName);
32858
- if (!existsSync10(sourcePath)) {
33022
+ const destDir = join15(targetDir, ".skills");
33023
+ const destPath = join15(destDir, skillName);
33024
+ if (!existsSync9(sourcePath)) {
32859
33025
  return {
32860
33026
  skill: name,
32861
33027
  success: false,
32862
33028
  error: `Skill '${name}' not found`
32863
33029
  };
32864
33030
  }
32865
- if (existsSync10(destPath) && !overwrite) {
33031
+ if (existsSync9(destPath) && !overwrite) {
32866
33032
  return {
32867
33033
  skill: name,
32868
33034
  success: false,
@@ -32871,10 +33037,10 @@ function installSkill(name, options = {}) {
32871
33037
  };
32872
33038
  }
32873
33039
  try {
32874
- if (!existsSync10(destDir)) {
32875
- mkdirSync14(destDir, { recursive: true });
33040
+ if (!existsSync9(destDir)) {
33041
+ mkdirSync13(destDir, { recursive: true });
32876
33042
  }
32877
- if (existsSync10(destPath) && overwrite) {
33043
+ if (existsSync9(destPath) && overwrite) {
32878
33044
  rmSync2(destPath, { recursive: true, force: true });
32879
33045
  }
32880
33046
  cpSync(sourcePath, destPath, {
@@ -32913,10 +33079,10 @@ function installSkills(names, options = {}) {
32913
33079
  return names.map((name) => installSkill(name, options));
32914
33080
  }
32915
33081
  function updateSkillsIndex(skillsDir) {
32916
- const indexPath = join16(skillsDir, "index.ts");
33082
+ const indexPath = join15(skillsDir, "index.ts");
32917
33083
  const meta = loadMeta(skillsDir);
32918
33084
  const disabledSet = new Set(meta.disabled || []);
32919
- 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-", "")));
32920
33086
  const exports = skills.map((s) => {
32921
33087
  const name = s.replace("skill-", "").replace(/-/g, "_");
32922
33088
  return `export * as ${name} from './${s}/src/index.js';`;
@@ -32929,31 +33095,31 @@ function updateSkillsIndex(skillsDir) {
32929
33095
 
32930
33096
  ${exports}
32931
33097
  `;
32932
- writeFileSync8(indexPath, content);
33098
+ writeFileSync7(indexPath, content);
32933
33099
  }
32934
33100
  function getMetaPath(skillsDir) {
32935
- return join16(skillsDir, ".meta.json");
33101
+ return join15(skillsDir, ".meta.json");
32936
33102
  }
32937
33103
  function loadMeta(skillsDir) {
32938
33104
  const metaPath2 = getMetaPath(skillsDir);
32939
- if (existsSync10(metaPath2)) {
33105
+ if (existsSync9(metaPath2)) {
32940
33106
  try {
32941
- return JSON.parse(readFileSync9(metaPath2, "utf-8"));
33107
+ return JSON.parse(readFileSync8(metaPath2, "utf-8"));
32942
33108
  } catch {}
32943
33109
  }
32944
33110
  return { skills: {} };
32945
33111
  }
32946
33112
  function saveMeta(skillsDir, meta) {
32947
- writeFileSync8(getMetaPath(skillsDir), JSON.stringify(meta, null, 2));
33113
+ writeFileSync7(getMetaPath(skillsDir), JSON.stringify(meta, null, 2));
32948
33114
  }
32949
33115
  function recordInstall(skillsDir, name) {
32950
33116
  const meta = loadMeta(skillsDir);
32951
33117
  const skillName = normalizeSkillName(name);
32952
33118
  let version = "unknown";
32953
33119
  try {
32954
- const pkgPath = join16(skillsDir, skillName, "package.json");
32955
- if (existsSync10(pkgPath)) {
32956
- 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"));
32957
33123
  version = pkg.version || "unknown";
32958
33124
  }
32959
33125
  } catch {}
@@ -32966,12 +33132,12 @@ function recordRemove(skillsDir, name) {
32966
33132
  saveMeta(skillsDir, meta);
32967
33133
  }
32968
33134
  function getInstallMeta(targetDir = process.cwd()) {
32969
- return loadMeta(join16(targetDir, ".skills"));
33135
+ return loadMeta(join15(targetDir, ".skills"));
32970
33136
  }
32971
33137
  function disableSkill(name, targetDir = process.cwd()) {
32972
- const skillsDir = join16(targetDir, ".skills");
33138
+ const skillsDir = join15(targetDir, ".skills");
32973
33139
  const skillName = normalizeSkillName(name);
32974
- if (!existsSync10(join16(skillsDir, skillName)))
33140
+ if (!existsSync9(join15(skillsDir, skillName)))
32975
33141
  return false;
32976
33142
  const meta = loadMeta(skillsDir);
32977
33143
  const disabled = new Set(meta.disabled || []);
@@ -32984,7 +33150,7 @@ function disableSkill(name, targetDir = process.cwd()) {
32984
33150
  return true;
32985
33151
  }
32986
33152
  function enableSkill(name, targetDir = process.cwd()) {
32987
- const skillsDir = join16(targetDir, ".skills");
33153
+ const skillsDir = join15(targetDir, ".skills");
32988
33154
  const meta = loadMeta(skillsDir);
32989
33155
  const disabled = new Set(meta.disabled || []);
32990
33156
  if (!disabled.has(name))
@@ -32996,24 +33162,24 @@ function enableSkill(name, targetDir = process.cwd()) {
32996
33162
  return true;
32997
33163
  }
32998
33164
  function getDisabledSkills(targetDir = process.cwd()) {
32999
- const meta = loadMeta(join16(targetDir, ".skills"));
33165
+ const meta = loadMeta(join15(targetDir, ".skills"));
33000
33166
  return meta.disabled || [];
33001
33167
  }
33002
33168
  function getInstalledSkills(targetDir = process.cwd()) {
33003
- const skillsDir = join16(targetDir, ".skills");
33004
- if (!existsSync10(skillsDir)) {
33169
+ const skillsDir = join15(targetDir, ".skills");
33170
+ if (!existsSync9(skillsDir)) {
33005
33171
  return [];
33006
33172
  }
33007
- return readdirSync7(skillsDir).filter((f) => {
33008
- const fullPath = join16(skillsDir, f);
33173
+ return readdirSync6(skillsDir).filter((f) => {
33174
+ const fullPath = join15(skillsDir, f);
33009
33175
  return f.startsWith("skill-") && statSync4(fullPath).isDirectory();
33010
33176
  }).map((f) => f.replace("skill-", ""));
33011
33177
  }
33012
33178
  function removeSkill(name, targetDir = process.cwd()) {
33013
33179
  const skillName = normalizeSkillName(name);
33014
- const skillsDir = join16(targetDir, ".skills");
33015
- const skillPath = join16(skillsDir, skillName);
33016
- if (!existsSync10(skillPath)) {
33180
+ const skillsDir = join15(targetDir, ".skills");
33181
+ const skillPath = join15(skillsDir, skillName);
33182
+ if (!existsSync9(skillPath)) {
33017
33183
  return false;
33018
33184
  }
33019
33185
  rmSync2(skillPath, { recursive: true, force: true });
@@ -33024,25 +33190,25 @@ function removeSkill(name, targetDir = process.cwd()) {
33024
33190
  function getAgentSkillsDir(agent, scope = "global", projectDir) {
33025
33191
  const agentDir = `.${agent}`;
33026
33192
  if (scope === "project") {
33027
- return join16(projectDir || process.cwd(), agentDir, "skills");
33193
+ return join15(projectDir || process.cwd(), agentDir, "skills");
33028
33194
  }
33029
- return join16(homedir14(), agentDir, "skills");
33195
+ return join15(homedir14(), agentDir, "skills");
33030
33196
  }
33031
33197
  function getAgentSkillPath(name, agent, scope = "global", projectDir) {
33032
33198
  const skillName = normalizeSkillName(name);
33033
- return join16(getAgentSkillsDir(agent, scope, projectDir), skillName);
33199
+ return join15(getAgentSkillsDir(agent, scope, projectDir), skillName);
33034
33200
  }
33035
33201
  function installSkillForAgent(name, options, generateSkillMd) {
33036
33202
  const { agent, scope = "global", projectDir } = options;
33037
33203
  const skillName = normalizeSkillName(name);
33038
33204
  const sourcePath = getSkillPath(name);
33039
- if (!existsSync10(sourcePath)) {
33205
+ if (!existsSync9(sourcePath)) {
33040
33206
  return { skill: name, success: false, error: `Skill '${name}' not found` };
33041
33207
  }
33042
33208
  let skillMdContent = null;
33043
- const skillMdPath = join16(sourcePath, "SKILL.md");
33044
- if (existsSync10(skillMdPath)) {
33045
- skillMdContent = readFileSync9(skillMdPath, "utf-8");
33209
+ const skillMdPath = join15(sourcePath, "SKILL.md");
33210
+ if (existsSync9(skillMdPath)) {
33211
+ skillMdContent = readFileSync8(skillMdPath, "utf-8");
33046
33212
  } else if (generateSkillMd) {
33047
33213
  skillMdContent = generateSkillMd(name);
33048
33214
  }
@@ -33051,8 +33217,8 @@ function installSkillForAgent(name, options, generateSkillMd) {
33051
33217
  }
33052
33218
  const destDir = getAgentSkillPath(name, agent, scope, projectDir);
33053
33219
  if (scope === "global") {
33054
- const agentBaseDir2 = join16(homedir14(), `.${agent}`);
33055
- if (!existsSync10(agentBaseDir2)) {
33220
+ const agentBaseDir2 = join15(homedir14(), `.${agent}`);
33221
+ if (!existsSync9(agentBaseDir2)) {
33056
33222
  const agentLabels = {
33057
33223
  claude: "Claude Code",
33058
33224
  codex: "Codex CLI",
@@ -33075,8 +33241,8 @@ function installSkillForAgent(name, options, generateSkillMd) {
33075
33241
  }
33076
33242
  }
33077
33243
  try {
33078
- mkdirSync14(destDir, { recursive: true });
33079
- writeFileSync8(join16(destDir, "SKILL.md"), skillMdContent);
33244
+ mkdirSync13(destDir, { recursive: true });
33245
+ writeFileSync7(join15(destDir, "SKILL.md"), skillMdContent);
33080
33246
  return { skill: name, success: true, path: destDir };
33081
33247
  } catch (error) {
33082
33248
  return {
@@ -33089,7 +33255,7 @@ function installSkillForAgent(name, options, generateSkillMd) {
33089
33255
  function removeSkillForAgent(name, options) {
33090
33256
  const { agent, scope = "global", projectDir } = options;
33091
33257
  const destDir = getAgentSkillPath(name, agent, scope, projectDir);
33092
- if (!existsSync10(destDir)) {
33258
+ if (!existsSync9(destDir)) {
33093
33259
  return false;
33094
33260
  }
33095
33261
  rmSync2(destDir, { recursive: true, force: true });
@@ -35434,8 +35600,8 @@ var init_ai_task = () => {};
35434
35600
  var exports_mcp = {};
35435
35601
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
35436
35602
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
35437
- import { readFileSync as readFileSync10 } from "fs";
35438
- import { join as join17 } from "path";
35603
+ import { readFileSync as readFileSync9 } from "fs";
35604
+ import { join as join16 } from "path";
35439
35605
  function json(data) {
35440
35606
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
35441
35607
  }
@@ -35500,7 +35666,7 @@ var init_mcp = __esm(async () => {
35500
35666
  init_dialogs();
35501
35667
  init_profiles();
35502
35668
  init_types();
35503
- _pkg = JSON.parse(readFileSync10(join17(import.meta.dir, "../../package.json"), "utf8"));
35669
+ _pkg = JSON.parse(readFileSync9(join16(import.meta.dir, "../../package.json"), "utf8"));
35504
35670
  networkLogCleanup = new Map;
35505
35671
  consoleCaptureCleanup = new Map;
35506
35672
  harCaptures = new Map;
@@ -37166,14 +37332,17 @@ var init_mcp = __esm(async () => {
37166
37332
  return err(e);
37167
37333
  }
37168
37334
  });
37169
- 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).", {
37170
- 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"),
37171
37337
  session_id: exports_external.string().optional(),
37172
- variables: exports_external.record(exports_external.string()).optional().describe("Override script variables (e.g. {email: 'foo@bar.com'})")
37173
- }, 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 }) => {
37174
37341
  try {
37175
- const { loadScript: loadScript2, runScriptAsync: runScriptAsync2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
37176
- 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);
37177
37346
  if (!script)
37178
37347
  return err(new Error(`Script '${name}' not found. Use browser_script_list to see available scripts.`));
37179
37348
  let sid;
@@ -37182,55 +37351,72 @@ var init_mcp = __esm(async () => {
37182
37351
  sid = resolveSessionId(session_id);
37183
37352
  page = getSessionPage(sid);
37184
37353
  } else {
37185
- const result = await createSession2({ headless: true });
37354
+ const result = await createSession2({ engine: engine ?? "auto", headless: true });
37186
37355
  sid = result.session.id;
37187
37356
  page = result.page;
37188
37357
  }
37189
- const jobId = runScriptAsync2(script, page, variables ?? {});
37190
- 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." });
37191
37361
  } catch (e) {
37192
37362
  return err(e);
37193
37363
  }
37194
37364
  });
37195
- 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 }) => {
37196
37366
  try {
37197
- const { getJob: getJob2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
37198
- const job = getJob2(job_id);
37199
- if (!job)
37200
- 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`));
37201
37371
  return json({
37202
- status: job.status,
37203
- progress: `${job.current_step}/${job.total_steps}`,
37204
- current_step: job.current_step_description,
37205
- steps_log: job.steps_log,
37206
- 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
37207
37379
  });
37208
37380
  } catch (e) {
37209
37381
  return err(e);
37210
37382
  }
37211
37383
  });
37212
- server.tool("browser_script_list", "List all saved login scripts", {}, async () => {
37384
+ server.tool("browser_script_list", "List all saved scripts", {}, async () => {
37213
37385
  try {
37214
- const { listScripts: listScripts2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
37215
- 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 });
37216
37390
  } catch (e) {
37217
37391
  return err(e);
37218
37392
  }
37219
37393
  });
37220
- 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 }) => {
37221
37407
  try {
37222
- const { saveScript: saveScript2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
37223
- const script = JSON.parse(scriptJson);
37224
- const path = saveScript2(script);
37225
- 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 });
37226
37412
  } catch (e) {
37227
37413
  return err(e);
37228
37414
  }
37229
37415
  });
37230
- 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 }) => {
37231
37417
  try {
37232
- const { deleteScript: deleteScript2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
37233
- return json({ deleted: deleteScript2(name) });
37418
+ const { deleteScriptByName: deleteScriptByName2 } = await Promise.resolve().then(() => (init_scripts(), exports_scripts));
37419
+ return json({ deleted: deleteScriptByName2(name) });
37234
37420
  } catch (e) {
37235
37421
  return err(e);
37236
37422
  }
@@ -38003,8 +38189,8 @@ var init_snapshots = __esm(() => {
38003
38189
 
38004
38190
  // src/server/index.ts
38005
38191
  var exports_server = {};
38006
- import { join as join18 } from "path";
38007
- import { existsSync as existsSync11 } from "fs";
38192
+ import { join as join17 } from "path";
38193
+ import { existsSync as existsSync10 } from "fs";
38008
38194
  function ok(data, status = 200) {
38009
38195
  return new Response(JSON.stringify(data), {
38010
38196
  status,
@@ -38263,14 +38449,14 @@ var init_server = __esm(() => {
38263
38449
  if (path.match(/^\/api\/gallery\/([^/]+)\/thumbnail$/) && method === "GET") {
38264
38450
  const id = path.split("/")[3];
38265
38451
  const entry = getEntry(id);
38266
- if (!entry?.thumbnail_path || !existsSync11(entry.thumbnail_path))
38452
+ if (!entry?.thumbnail_path || !existsSync10(entry.thumbnail_path))
38267
38453
  return notFound("Thumbnail not found");
38268
38454
  return new Response(Bun.file(entry.thumbnail_path), { headers: { ...CORS_HEADERS } });
38269
38455
  }
38270
38456
  if (path.match(/^\/api\/gallery\/([^/]+)\/image$/) && method === "GET") {
38271
38457
  const id = path.split("/")[3];
38272
38458
  const entry = getEntry(id);
38273
- if (!entry?.path || !existsSync11(entry.path))
38459
+ if (!entry?.path || !existsSync10(entry.path))
38274
38460
  return notFound("Image not found");
38275
38461
  return new Response(Bun.file(entry.path), { headers: { ...CORS_HEADERS } });
38276
38462
  }
@@ -38298,7 +38484,7 @@ var init_server = __esm(() => {
38298
38484
  if (path.match(/^\/api\/downloads\/([^/]+)\/raw$/) && method === "GET") {
38299
38485
  const id = path.split("/")[3];
38300
38486
  const file = getDownload(id);
38301
- if (!file || !existsSync11(file.path))
38487
+ if (!file || !existsSync10(file.path))
38302
38488
  return notFound("Download not found");
38303
38489
  return new Response(Bun.file(file.path), { headers: { ...CORS_HEADERS } });
38304
38490
  }
@@ -38306,13 +38492,13 @@ var init_server = __esm(() => {
38306
38492
  const id = path.split("/")[3];
38307
38493
  return ok({ deleted: deleteDownload(id) });
38308
38494
  }
38309
- const dashboardDist = join18(import.meta.dir, "../../dashboard/dist");
38310
- if (existsSync11(dashboardDist)) {
38311
- const filePath = path === "/" ? join18(dashboardDist, "index.html") : join18(dashboardDist, path);
38312
- 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)) {
38313
38499
  return new Response(Bun.file(filePath), { headers: CORS_HEADERS });
38314
38500
  }
38315
- 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 });
38316
38502
  }
38317
38503
  if (path === "/" || path === "") {
38318
38504
  return new Response("@hasna/browser REST API running. Dashboard not built.", {
@@ -38354,10 +38540,10 @@ init_projects();
38354
38540
  init_recorder();
38355
38541
  init_recordings();
38356
38542
  init_lightpanda();
38357
- import { readFileSync as readFileSync11 } from "fs";
38358
- import { join as join19 } from "path";
38543
+ import { readFileSync as readFileSync10 } from "fs";
38544
+ import { join as join18 } from "path";
38359
38545
  import chalk from "chalk";
38360
- 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"));
38361
38547
  var program2 = new Command;
38362
38548
  program2.name("browser").description("@hasna/browser \u2014 general-purpose browser agent CLI").version(pkg.version);
38363
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) => {
@@ -38804,18 +38990,19 @@ program2.command("login <url>").description("Login to a site: detect form, fill
38804
38990
  if (!opts.headed)
38805
38991
  await closeSession2(session.id);
38806
38992
  });
38807
- var scriptCmd = program2.command("script").description("Manage login scripts (browser + connector workflows)");
38808
- 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) => {
38809
- const { loadScript: loadScript2, runScript: runScript2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
38810
- 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);
38811
38999
  if (!script) {
38812
- 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.`));
38813
39001
  return;
38814
39002
  }
38815
39003
  const { session, page } = await createSession2({ engine: opts.engine, headless: !opts.headed });
39004
+ const steps = getSteps2(script.id);
38816
39005
  const overrides = {};
38817
- if (opts.email)
38818
- overrides.email = opts.email;
38819
39006
  if (opts.var) {
38820
39007
  for (const pair of opts.var) {
38821
39008
  const [k, ...v] = pair.split("=");
@@ -38824,83 +39011,92 @@ scriptCmd.command("run <name>").description("Run a saved login script").option("
38824
39011
  }
38825
39012
  }
38826
39013
  if (!opts.json) {
38827
- console.log(chalk.gray(`Running script: ${script.name} (${script.steps.length} steps)`));
38828
- console.log(chalk.gray(` Domain: ${script.domain}`));
39014
+ console.log(chalk.gray(`Running: ${script.name} (${steps.length} steps)`));
38829
39015
  if (script.description)
38830
39016
  console.log(chalk.gray(` ${script.description}
38831
39017
  `));
38832
39018
  }
38833
- const result = await runScript2(script, page, overrides);
39019
+ const result = await executeScriptSync2(script.id, page, overrides);
38834
39020
  if (opts.json) {
38835
39021
  console.log(JSON.stringify({ ...result, session_id: session.id }));
38836
39022
  } else {
38837
39023
  if (result.success) {
38838
39024
  console.log(chalk.green(`
38839
- \u2713 Script completed (${result.steps_executed} steps, ${result.duration_ms}ms)`));
39025
+ \u2713 Completed (${result.steps_executed} steps, ${result.duration_ms}ms)`));
38840
39026
  } else {
38841
39027
  console.log(chalk.red(`
38842
- \u2717 Script failed (${result.steps_failed}/${result.steps_executed} steps failed)`));
39028
+ \u2717 Failed (${result.steps_failed}/${result.steps_executed} failed)`));
38843
39029
  result.errors.forEach((e) => console.log(chalk.red(` ${e}`)));
38844
39030
  }
38845
- console.log(chalk.gray(` Session: ${session.id}`));
38846
- console.log(chalk.gray(` URL: ${result.variables.current_url ?? page.url()}`));
38847
39031
  }
38848
39032
  if (!opts.headed)
38849
39033
  await closeSession2(session.id);
38850
39034
  });
38851
- scriptCmd.command("list").description("List saved login scripts").option("--json", "Output as JSON").action(async (opts) => {
38852
- 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();
38853
39038
  const scripts = listScripts2();
38854
39039
  if (opts.json) {
38855
39040
  console.log(JSON.stringify(scripts, null, 2));
38856
39041
  } else if (scripts.length === 0) {
38857
- 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>"));
38858
39043
  } else {
38859
39044
  scripts.forEach((s) => {
38860
- 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}`);
38861
39046
  if (s.description)
38862
39047
  console.log(chalk.gray(` ${s.description}`));
38863
39048
  });
38864
39049
  }
38865
39050
  });
38866
- scriptCmd.command("create <file>").description("Create a script from a JSON file (see docs for schema)").action(async (file) => {
38867
- const { createScriptFromFile: createScriptFromFile2, saveScript: saveScript2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
38868
- try {
38869
- const script = createScriptFromFile2(file);
38870
- const path = saveScript2(script);
38871
- console.log(chalk.green(`\u2713 Script saved: ${script.name}`));
38872
- console.log(chalk.gray(` Domain: ${script.domain}`));
38873
- console.log(chalk.gray(` Steps: ${script.steps.length}`));
38874
- console.log(chalk.gray(` Path: ${path}`));
38875
- console.log(chalk.gray(` Run with: browser script run ${script.name}`));
38876
- } catch (err2) {
38877
- 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;
38878
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)`));
38879
39072
  });
38880
39073
  scriptCmd.command("show <name>").description("Show script details").action(async (name) => {
38881
- const { loadScript: loadScript2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
38882
- 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);
38883
39077
  if (!script) {
38884
39078
  console.log(chalk.red(`Script '${name}' not found`));
38885
39079
  return;
38886
39080
  }
39081
+ const steps = getSteps2(script.id);
38887
39082
  console.log(chalk.bold(`${script.name} (${script.domain})
38888
39083
  `));
38889
39084
  if (script.description)
38890
39085
  console.log(chalk.gray(` ${script.description}
38891
39086
  `));
38892
- 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"}
38893
39089
  `));
38894
- script.steps.forEach((s, i) => {
38895
- const desc = s.description ?? `${s.type}${s.action ? `:${s.action}` : ""}${s.connector ? `:${s.connector}` : ""}`;
38896
- const detail = s.url ?? s.selector ?? s.text ?? s.connector ?? s.pattern ?? "";
38897
- 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))}`);
38898
39094
  });
38899
39095
  });
38900
39096
  scriptCmd.command("delete <name>").description("Delete a saved script").action(async (name) => {
38901
- const { deleteScript: deleteScript2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
38902
- if (deleteScript2(name)) {
38903
- 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}`));
38904
39100
  } else {
38905
39101
  console.log(chalk.red(`Script '${name}' not found`));
38906
39102
  }
@@ -38930,10 +39126,10 @@ daemonCmd.command("start").description("Start the browser daemon in the backgrou
38930
39126
  return;
38931
39127
  }
38932
39128
  const { spawn: spawn2 } = await import("child_process");
38933
- const { writeFileSync: writeFileSync9, mkdirSync: mkdirSync15 } = await import("fs");
39129
+ const { writeFileSync: writeFileSync8, mkdirSync: mkdirSync14 } = await import("fs");
38934
39130
  const { dirname: dirname4 } = await import("path");
38935
39131
  const pidFile = getDaemonPidFile2();
38936
- mkdirSync15(dirname4(pidFile), { recursive: true });
39132
+ mkdirSync14(dirname4(pidFile), { recursive: true });
38937
39133
  const child = spawn2(process.execPath, [import.meta.dir + "/../server/index.js"], {
38938
39134
  detached: true,
38939
39135
  stdio: "ignore",
@@ -38941,7 +39137,7 @@ daemonCmd.command("start").description("Start the browser daemon in the backgrou
38941
39137
  });
38942
39138
  child.unref();
38943
39139
  if (child.pid) {
38944
- writeFileSync9(pidFile, String(child.pid));
39140
+ writeFileSync8(pidFile, String(child.pid));
38945
39141
  await new Promise((r) => setTimeout(r, 1500));
38946
39142
  console.log(chalk.green(`\u2713 Daemon started`));
38947
39143
  console.log(chalk.gray(` PID: ${child.pid}, Port: ${opts.port}`));
@@ -39114,11 +39310,11 @@ galleryCmd.command("stats").description("Show gallery statistics").option("--pro
39114
39310
  });
39115
39311
  galleryCmd.command("clean").description("Delete gallery entries with missing files").action(async () => {
39116
39312
  const { listEntries: listEntries2, deleteEntry: deleteEntry2 } = await Promise.resolve().then(() => (init_gallery(), exports_gallery));
39117
- const { existsSync: existsSync12 } = await import("fs");
39313
+ const { existsSync: existsSync11 } = await import("fs");
39118
39314
  const entries = listEntries2({ limit: 9999 });
39119
39315
  let removed = 0;
39120
39316
  for (const e of entries) {
39121
- if (!existsSync12(e.path)) {
39317
+ if (!existsSync11(e.path)) {
39122
39318
  deleteEntry2(e.id);
39123
39319
  removed++;
39124
39320
  }