@hasna/browser 0.4.7 → 0.4.8

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.
Files changed (2) hide show
  1. package/dist/mcp/index.js +477 -234
  2. package/package.json +1 -1
package/dist/mcp/index.js CHANGED
@@ -11297,7 +11297,7 @@ function buildRowRefs(rows, method, totalRows, rowCount) {
11297
11297
  row: index,
11298
11298
  text,
11299
11299
  visible: method === "dom" ? true : index >= firstVisibleRow,
11300
- selector: method === "dom" ? `#takumi-tui-dom-root [data-row="${index}"]` : undefined
11300
+ selector: method === "dom" ? `#takumi-tui-dom-root .takumi-tui-dom-row[data-row="${index}"]` : undefined
11301
11301
  };
11302
11302
  });
11303
11303
  return refs;
@@ -11505,7 +11505,7 @@ async function readDomMirrorState(page) {
11505
11505
  const runtime = window.__takumiTuiDomRenderer;
11506
11506
  if (runtime?.sync)
11507
11507
  return runtime.sync();
11508
- const rowEls = Array.from(document.querySelectorAll("#takumi-tui-dom-root [data-row]"));
11508
+ const rowEls = Array.from(document.querySelectorAll("#takumi-tui-dom-root .takumi-tui-dom-row"));
11509
11509
  const rows = rowEls.map((row) => row.getAttribute("aria-label") ?? row.textContent ?? "");
11510
11510
  const term = window.term ?? window.terminal;
11511
11511
  const active = term?.buffer?.active;
@@ -11528,17 +11528,14 @@ function isDomMethod(method) {
11528
11528
  return method === "dom";
11529
11529
  }
11530
11530
  async function withTimeout(label, operation, timeoutMs = DEFAULT_TOOL_TIMEOUT_MS) {
11531
- let timedOut = false;
11532
- const timer = setTimeout(() => {
11533
- timedOut = true;
11534
- }, timeoutMs);
11531
+ let timer;
11532
+ const timeout = new Promise((_, reject) => {
11533
+ timer = setTimeout(() => {
11534
+ reject(new BrowserError(`${label} timed out after ${timeoutMs}ms \u2014 ttyd/playwright connection may be unhealthy. Try closing and re-opening the session.`, "TUI_TIMEOUT"));
11535
+ }, timeoutMs);
11536
+ });
11535
11537
  try {
11536
- return await operation();
11537
- } catch (err) {
11538
- if (timedOut) {
11539
- throw new BrowserError(`${label} timed out after ${timeoutMs}ms \u2014 ttyd/playwright connection may be unhealthy. Try closing and re-opening the session.`, "TUI_TIMEOUT");
11540
- }
11541
- throw err;
11538
+ return await Promise.race([operation(), timeout]);
11542
11539
  } finally {
11543
11540
  clearTimeout(timer);
11544
11541
  }
@@ -11808,7 +11805,7 @@ async function waitForTerminalText(page, text, timeoutMs = 30000, method = "buff
11808
11805
  return { found: true, elapsed_ms: Date.now() - start, stuck: false };
11809
11806
  await new Promise((r) => setTimeout(r, 250));
11810
11807
  }
11811
- return { found: false, elapsed_ms: timeoutMs, stuck: false };
11808
+ return { found: false, elapsed_ms: Date.now() - start, stuck: false };
11812
11809
  }
11813
11810
  async function closeTui(session) {
11814
11811
  await destroyDomRenderer(session.page);
@@ -11921,6 +11918,23 @@ var init_selector = __esm(() => {
11921
11918
  };
11922
11919
  });
11923
11920
 
11921
+ // src/lib/tui-recording.ts
11922
+ function trackTuiRecording(sessionId, intervalId) {
11923
+ stopTuiRecording(sessionId);
11924
+ recordingIntervals.set(sessionId, intervalId);
11925
+ }
11926
+ function stopTuiRecording(sessionId) {
11927
+ const intervalId = recordingIntervals.get(sessionId);
11928
+ if (!intervalId)
11929
+ return;
11930
+ clearInterval(intervalId);
11931
+ recordingIntervals.delete(sessionId);
11932
+ }
11933
+ var recordingIntervals;
11934
+ var init_tui_recording = __esm(() => {
11935
+ recordingIntervals = new Map;
11936
+ });
11937
+
11924
11938
  // src/db/network-log.ts
11925
11939
  import { randomUUID as randomUUID2 } from "crypto";
11926
11940
  function logRequest(data) {
@@ -12136,7 +12150,9 @@ async function applyStealthPatches(page) {
12136
12150
  "User-Agent": REALISTIC_USER_AGENT
12137
12151
  });
12138
12152
  }
12139
- var REALISTIC_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36", STEALTH_SCRIPT = `
12153
+ var REALISTIC_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36", STEALTH_SCRIPT;
12154
+ var init_stealth = __esm(() => {
12155
+ STEALTH_SCRIPT = `
12140
12156
  // \u2500\u2500 1. Remove navigator.webdriver flag \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
12141
12157
  Object.defineProperty(navigator, 'webdriver', {
12142
12158
  get: () => false,
@@ -12184,8 +12200,14 @@ if (!window.chrome.runtime) {
12184
12200
  id: undefined,
12185
12201
  };
12186
12202
  }
12203
+
12204
+ // \u2500\u2500 5. Override navigator.userAgent to match HTTP header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
12205
+ Object.defineProperty(navigator, 'userAgent', {
12206
+ get: () => "${REALISTIC_USER_AGENT}",
12207
+ configurable: true,
12208
+ });
12187
12209
  `;
12188
- var init_stealth = () => {};
12210
+ });
12189
12211
 
12190
12212
  // src/lib/dialogs.ts
12191
12213
  var exports_dialogs = {};
@@ -13249,6 +13271,29 @@ __export(exports_session, {
13249
13271
  function createBunProxy(view) {
13250
13272
  return view;
13251
13273
  }
13274
+ function attachPlaywrightListeners(page, sessionId, cleanups, opts = {}) {
13275
+ if (opts.captureNetwork !== false) {
13276
+ try {
13277
+ cleanups.push(enableNetworkLogging(page, sessionId));
13278
+ } catch {}
13279
+ }
13280
+ if (opts.captureConsole !== false) {
13281
+ try {
13282
+ cleanups.push(enableConsoleCapture(page, sessionId));
13283
+ } catch {}
13284
+ }
13285
+ try {
13286
+ cleanups.push(setupDialogHandler(page, sessionId));
13287
+ } catch {}
13288
+ }
13289
+ function detachPlaywrightListeners(cleanups) {
13290
+ while (cleanups.length > 1) {
13291
+ const cleanup = cleanups.pop();
13292
+ try {
13293
+ cleanup?.();
13294
+ } catch {}
13295
+ }
13296
+ }
13252
13297
  async function createSession2(opts = {}) {
13253
13298
  if (opts.cdpUrl) {
13254
13299
  const { connectToExistingBrowser: connectToExistingBrowser2 } = await Promise.resolve().then(() => (init_cdp(), exports_cdp));
@@ -13286,9 +13331,11 @@ async function createSession2(opts = {}) {
13286
13331
  let browser = null;
13287
13332
  let bunView = null;
13288
13333
  let page;
13334
+ let actualEngine = resolvedEngine;
13289
13335
  if (resolvedEngine === "bun") {
13290
13336
  if (!isBunWebViewAvailable()) {
13291
13337
  console.warn("[browser] Bun.WebView requested but not available \u2014 falling back to playwright. Run: bun upgrade --canary");
13338
+ actualEngine = "playwright";
13292
13339
  browser = await launchPlaywright({ headless: opts.headless ?? true, viewport: opts.viewport, userAgent: opts.userAgent });
13293
13340
  page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
13294
13341
  } else {
@@ -13308,9 +13355,11 @@ async function createSession2(opts = {}) {
13308
13355
  }
13309
13356
  if (!bunWorks) {
13310
13357
  console.warn("[browser] Bun.WebView exists but Chrome not available \u2014 falling back to playwright");
13358
+ actualEngine = "playwright";
13311
13359
  browser = await launchPlaywright({ headless: opts.headless ?? true, viewport: opts.viewport, userAgent: opts.userAgent });
13312
13360
  page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
13313
13361
  } else {
13362
+ actualEngine = "bun";
13314
13363
  bunView = testView;
13315
13364
  if (opts.stealth) {}
13316
13365
  page = createBunProxy(bunView);
@@ -13340,19 +13389,10 @@ async function createSession2(opts = {}) {
13340
13389
  });
13341
13390
  const cleanups2 = [];
13342
13391
  cleanups2.push(() => closeTui(tuiSess));
13343
- if (opts.captureNetwork !== false) {
13344
- try {
13345
- cleanups2.push(enableNetworkLogging(page, session2.id));
13346
- } catch {}
13347
- }
13348
- if (opts.captureConsole !== false) {
13349
- try {
13350
- cleanups2.push(enableConsoleCapture(page, session2.id));
13351
- } catch {}
13352
- }
13353
- try {
13354
- cleanups2.push(setupDialogHandler(page, session2.id));
13355
- } catch {}
13392
+ attachPlaywrightListeners(page, session2.id, cleanups2, {
13393
+ captureNetwork: opts.captureNetwork,
13394
+ captureConsole: opts.captureConsole
13395
+ });
13356
13396
  handles.set(session2.id, { browser, bunView: null, tuiSession: tuiSess, page, engine: "tui", cleanups: cleanups2, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false, startUrl: opts.startUrl ?? "bash" });
13357
13397
  return { session: session2, page };
13358
13398
  } else {
@@ -13382,7 +13422,7 @@ async function createSession2(opts = {}) {
13382
13422
  }
13383
13423
  })() : undefined);
13384
13424
  const session = createSession({
13385
- engine: bunView ? "bun" : browser ? resolvedEngine : resolvedEngine,
13425
+ engine: actualEngine,
13386
13426
  projectId: opts.projectId,
13387
13427
  agentId: opts.agentId,
13388
13428
  startUrl: opts.startUrl,
@@ -13395,37 +13435,12 @@ async function createSession2(opts = {}) {
13395
13435
  }
13396
13436
  const cleanups = [];
13397
13437
  if (!bunView) {
13398
- if (opts.captureNetwork !== false) {
13399
- try {
13400
- cleanups.push(enableNetworkLogging(page, session.id));
13401
- } catch {}
13402
- }
13403
- if (opts.captureConsole !== false) {
13404
- try {
13405
- cleanups.push(enableConsoleCapture(page, session.id));
13406
- } catch {}
13407
- }
13408
- try {
13409
- cleanups.push(setupDialogHandler(page, session.id));
13410
- } catch {}
13411
- } else {
13412
- if (opts.captureConsole !== false) {
13413
- try {
13414
- const { logConsoleMessage: logConsoleMessage2 } = await Promise.resolve().then(() => (init_console_log(), exports_console_log));
13415
- await bunView.addInitScript(`
13416
- (() => {
13417
- const orig = { log: console.log, warn: console.warn, error: console.error, debug: console.debug, info: console.info };
13418
- ['log','warn','error','debug','info'].forEach(level => {
13419
- console[level] = (...args) => {
13420
- orig[level](...args);
13421
- };
13422
- });
13423
- })()
13424
- `);
13425
- } catch {}
13426
- }
13438
+ attachPlaywrightListeners(page, session.id, cleanups, {
13439
+ captureNetwork: opts.captureNetwork,
13440
+ captureConsole: opts.captureConsole
13441
+ });
13427
13442
  }
13428
- handles.set(session.id, { browser, bunView, tuiSession: null, page, engine: bunView ? "bun" : resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false, startUrl: opts.startUrl ?? "" });
13443
+ handles.set(session.id, { browser, bunView, tuiSession: null, page, engine: actualEngine, cleanups, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false, startUrl: opts.startUrl ?? "" });
13429
13444
  if (opts.startUrl) {
13430
13445
  try {
13431
13446
  if (bunView) {
@@ -13458,7 +13473,8 @@ function getSessionBunView(sessionId) {
13458
13473
  return handles.get(sessionId)?.bunView ?? null;
13459
13474
  }
13460
13475
  function isBunSession(sessionId) {
13461
- return handles.get(sessionId)?.engine === "bun";
13476
+ const handle = handles.get(sessionId);
13477
+ return handle?.engine === "bun" && handle.bunView !== null;
13462
13478
  }
13463
13479
  function getSessionBrowser(sessionId) {
13464
13480
  const handle = handles.get(sessionId);
@@ -13484,11 +13500,16 @@ function setSessionTui(sessionId, tuiSess) {
13484
13500
  const handle = handles.get(sessionId);
13485
13501
  if (!handle)
13486
13502
  throw new SessionNotFoundError(sessionId);
13503
+ detachPlaywrightListeners(handle.cleanups);
13487
13504
  handle.tuiSession = tuiSess;
13488
13505
  handle.page = tuiSess.page;
13489
13506
  if (tuiSess.browser !== handle.browser) {
13490
13507
  handle.browser = tuiSess.browser;
13491
13508
  }
13509
+ attachPlaywrightListeners(tuiSess.page, sessionId, handle.cleanups, {
13510
+ captureNetwork: true,
13511
+ captureConsole: true
13512
+ });
13492
13513
  handle.lastActivity = Date.now();
13493
13514
  }
13494
13515
  function getSessionCommand(sessionId) {
@@ -13504,9 +13525,12 @@ async function closeSession2(sessionId) {
13504
13525
  const handle = handles.get(sessionId);
13505
13526
  try {
13506
13527
  if (handle) {
13528
+ if (handle.engine === "tui") {
13529
+ stopTuiRecording(sessionId);
13530
+ }
13507
13531
  for (const cleanup of handle.cleanups) {
13508
13532
  try {
13509
- cleanup();
13533
+ await cleanup();
13510
13534
  } catch {}
13511
13535
  }
13512
13536
  if (handle.bunView) {
@@ -13618,6 +13642,7 @@ var init_session = __esm(() => {
13618
13642
  init_bun_webview();
13619
13643
  init_selector();
13620
13644
  init_tui();
13645
+ init_tui_recording();
13621
13646
  init_network();
13622
13647
  init_console();
13623
13648
  init_stealth();
@@ -20245,10 +20270,17 @@ __export(exports_gallery, {
20245
20270
  createEntry: () => createEntry
20246
20271
  });
20247
20272
  import { randomUUID as randomUUID4 } from "crypto";
20273
+ import { join as join9, resolve, relative as relative2, isAbsolute } from "path";
20248
20274
  function validateDataPath(filePath) {
20249
20275
  if (filePath.includes("..")) {
20250
20276
  throw new Error(`File path must not contain '..': ${filePath}`);
20251
20277
  }
20278
+ const dataDir = resolve(getDataDir2());
20279
+ const resolved = resolve(isAbsolute(filePath) ? filePath : join9(dataDir, filePath));
20280
+ const rel = relative2(dataDir, resolved);
20281
+ if (rel.startsWith("..") || isAbsolute(rel)) {
20282
+ throw new Error(`File path must be within data directory: ${filePath}`);
20283
+ }
20252
20284
  return filePath;
20253
20285
  }
20254
20286
  function deserialize(row) {
@@ -20266,7 +20298,13 @@ function deserialize(row) {
20266
20298
  original_size_bytes: row.original_size_bytes ?? undefined,
20267
20299
  compressed_size_bytes: row.compressed_size_bytes ?? undefined,
20268
20300
  compression_ratio: row.compression_ratio ?? undefined,
20269
- tags: JSON.parse(row.tags),
20301
+ tags: (() => {
20302
+ try {
20303
+ return JSON.parse(row.tags);
20304
+ } catch {
20305
+ return [];
20306
+ }
20307
+ })(),
20270
20308
  notes: row.notes ?? undefined,
20271
20309
  is_favorite: row.is_favorite === 1,
20272
20310
  created_at: row.created_at
@@ -20456,6 +20494,7 @@ var exports_recorder = {};
20456
20494
  __export(exports_recorder, {
20457
20495
  stopRecording: () => stopRecording,
20458
20496
  startRecording: () => startRecording,
20497
+ resetRecorderState: () => resetRecorderState,
20459
20498
  replayRecording: () => replayRecording,
20460
20499
  recordStep: () => recordStep,
20461
20500
  listRecordings: () => listRecordings,
@@ -20463,6 +20502,14 @@ __export(exports_recorder, {
20463
20502
  exportRecording: () => exportRecording,
20464
20503
  attachPageListeners: () => attachPageListeners
20465
20504
  });
20505
+ function resetRecorderState() {
20506
+ for (const active of activeRecordings.values()) {
20507
+ try {
20508
+ active.cleanup();
20509
+ } catch {}
20510
+ }
20511
+ activeRecordings.clear();
20512
+ }
20466
20513
  function startRecording(sessionId, name, startUrl) {
20467
20514
  const steps = [];
20468
20515
  const recording = createRecording({ name, start_url: startUrl, steps });
@@ -20747,9 +20794,9 @@ var exports_gallery_diff = {};
20747
20794
  __export(exports_gallery_diff, {
20748
20795
  diffImages: () => diffImages
20749
20796
  });
20750
- import { join as join11 } from "path";
20797
+ import { join as join12 } from "path";
20751
20798
  import { mkdirSync as mkdirSync9 } from "fs";
20752
- async function diffImages(path1, path2) {
20799
+ async function diffImages(path1, path2, threshold = 10) {
20753
20800
  const img1 = import_sharp2.default(path1);
20754
20801
  const img2 = import_sharp2.default(path2);
20755
20802
  const [meta1, meta2] = await Promise.all([img1.metadata(), img2.metadata()]);
@@ -20768,7 +20815,7 @@ async function diffImages(path1, path2) {
20768
20815
  const dg = Math.abs(raw1[i + 1] - raw2[i + 1]);
20769
20816
  const db = Math.abs(raw1[i + 2] - raw2[i + 2]);
20770
20817
  const diff = (dr + dg + db) / 3;
20771
- if (diff > 10) {
20818
+ if (diff > threshold) {
20772
20819
  changedPixels++;
20773
20820
  diffBuffer[i] = 255;
20774
20821
  diffBuffer[i + 1] = 0;
@@ -20780,9 +20827,9 @@ async function diffImages(path1, path2) {
20780
20827
  }
20781
20828
  }
20782
20829
  const dataDir = getDataDir2();
20783
- const diffDir = join11(dataDir, "diffs");
20830
+ const diffDir = join12(dataDir, "diffs");
20784
20831
  mkdirSync9(diffDir, { recursive: true });
20785
- const diffPath = join11(diffDir, `diff-${Date.now()}.webp`);
20832
+ const diffPath = join12(diffDir, `diff-${Date.now()}.webp`);
20786
20833
  const diffImageBuffer = await import_sharp2.default(diffBuffer, { raw: { width: w, height: h, channels } }).webp({ quality: 85 }).toBuffer();
20787
20834
  await Bun.write(diffPath, diffImageBuffer);
20788
20835
  return {
@@ -20809,21 +20856,21 @@ __export(exports_profiles, {
20809
20856
  applyProfile: () => applyProfile
20810
20857
  });
20811
20858
  import { mkdirSync as mkdirSync11, existsSync as existsSync9, readdirSync as readdirSync7, rmSync, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
20812
- import { join as join13 } from "path";
20859
+ import { join as join14 } from "path";
20813
20860
  function getProfilesDir() {
20814
20861
  const dataDir = getDataDir2();
20815
- const dir = join13(dataDir, "profiles");
20862
+ const dir = join14(dataDir, "profiles");
20816
20863
  mkdirSync11(dir, { recursive: true });
20817
20864
  return dir;
20818
20865
  }
20819
20866
  function getProfileDir2(name) {
20820
- return join13(getProfilesDir(), name);
20867
+ return join14(getProfilesDir(), name);
20821
20868
  }
20822
20869
  async function saveProfile(page, name) {
20823
20870
  const dir = getProfileDir2(name);
20824
20871
  mkdirSync11(dir, { recursive: true });
20825
20872
  const cookies = await page.context().cookies();
20826
- writeFileSync3(join13(dir, "cookies.json"), JSON.stringify(cookies, null, 2));
20873
+ writeFileSync3(join14(dir, "cookies.json"), JSON.stringify(cookies, null, 2));
20827
20874
  let localStorage2 = {};
20828
20875
  try {
20829
20876
  localStorage2 = await page.evaluate(() => {
@@ -20835,11 +20882,11 @@ async function saveProfile(page, name) {
20835
20882
  return result;
20836
20883
  });
20837
20884
  } catch {}
20838
- writeFileSync3(join13(dir, "storage.json"), JSON.stringify(localStorage2, null, 2));
20885
+ writeFileSync3(join14(dir, "storage.json"), JSON.stringify(localStorage2, null, 2));
20839
20886
  const savedAt = new Date().toISOString();
20840
20887
  const url = page.url();
20841
20888
  const meta = { saved_at: savedAt, url };
20842
- writeFileSync3(join13(dir, "meta.json"), JSON.stringify(meta, null, 2));
20889
+ writeFileSync3(join14(dir, "meta.json"), JSON.stringify(meta, null, 2));
20843
20890
  return {
20844
20891
  name,
20845
20892
  saved_at: savedAt,
@@ -20853,9 +20900,9 @@ function loadProfile(name) {
20853
20900
  if (!existsSync9(dir)) {
20854
20901
  throw new Error(`Profile not found: ${name}`);
20855
20902
  }
20856
- const cookiesPath = join13(dir, "cookies.json");
20857
- const storagePath = join13(dir, "storage.json");
20858
- const metaPath2 = join13(dir, "meta.json");
20903
+ const cookiesPath = join14(dir, "cookies.json");
20904
+ const storagePath = join14(dir, "storage.json");
20905
+ const metaPath2 = join14(dir, "meta.json");
20859
20906
  const cookies = existsSync9(cookiesPath) ? JSON.parse(readFileSync3(cookiesPath, "utf8")) : [];
20860
20907
  const localStorage2 = existsSync9(storagePath) ? JSON.parse(readFileSync3(storagePath, "utf8")) : {};
20861
20908
  let savedAt = new Date().toISOString();
@@ -20896,24 +20943,24 @@ function listProfiles() {
20896
20943
  if (!entry.isDirectory())
20897
20944
  continue;
20898
20945
  const name = entry.name;
20899
- const profileDir = join13(dir, name);
20946
+ const profileDir = join14(dir, name);
20900
20947
  let savedAt = "";
20901
20948
  let url;
20902
20949
  let cookieCount = 0;
20903
20950
  let storageKeyCount = 0;
20904
20951
  try {
20905
- const metaPath2 = join13(profileDir, "meta.json");
20952
+ const metaPath2 = join14(profileDir, "meta.json");
20906
20953
  if (existsSync9(metaPath2)) {
20907
20954
  const meta = JSON.parse(readFileSync3(metaPath2, "utf8"));
20908
20955
  savedAt = meta.saved_at ?? "";
20909
20956
  url = meta.url;
20910
20957
  }
20911
- const cookiesPath = join13(profileDir, "cookies.json");
20958
+ const cookiesPath = join14(profileDir, "cookies.json");
20912
20959
  if (existsSync9(cookiesPath)) {
20913
20960
  const cookies = JSON.parse(readFileSync3(cookiesPath, "utf8"));
20914
20961
  cookieCount = Array.isArray(cookies) ? cookies.length : 0;
20915
20962
  }
20916
- const storagePath = join13(profileDir, "storage.json");
20963
+ const storagePath = join14(profileDir, "storage.json");
20917
20964
  if (existsSync9(storagePath)) {
20918
20965
  const storage = JSON.parse(readFileSync3(storagePath, "utf8"));
20919
20966
  storageKeyCount = Object.keys(storage).length;
@@ -21550,7 +21597,7 @@ __export(exports_dist, {
21550
21597
  AGENT_TARGETS: () => AGENT_TARGETS
21551
21598
  });
21552
21599
  import { existsSync as existsSync10, readFileSync as readFileSync4, readdirSync as readdirSync8 } from "fs";
21553
- import { join as join14 } from "path";
21600
+ import { join as join15 } from "path";
21554
21601
  import { homedir as homedir8 } from "os";
21555
21602
  import { existsSync as existsSync22, cpSync, mkdirSync as mkdirSync12, writeFileSync as writeFileSync4, rmSync as rmSync2, readdirSync as readdirSync22, statSync as statSync3, readFileSync as readFileSync22, accessSync, constants } from "fs";
21556
21603
  import { join as join22, dirname as dirname2 } from "path";
@@ -21600,7 +21647,7 @@ function discoverSkillsInDir(dir) {
21600
21647
  for (const entry of entries) {
21601
21648
  if (!entry.isDirectory())
21602
21649
  continue;
21603
- const skillMdPath = join14(dir, entry.name, "SKILL.md");
21650
+ const skillMdPath = join15(dir, entry.name, "SKILL.md");
21604
21651
  if (!existsSync10(skillMdPath))
21605
21652
  continue;
21606
21653
  let content;
@@ -21631,11 +21678,11 @@ function loadRegistry(cwd) {
21631
21678
  return _registryCache;
21632
21679
  }
21633
21680
  const official = SKILLS.map((s) => ({ ...s, source: "official" }));
21634
- const globalCustomNew = discoverSkillsInDir(join14(homedir8(), ".hasna", "skills", "custom"));
21635
- const globalCustomOld = discoverSkillsInDir(join14(homedir8(), ".skills"));
21681
+ const globalCustomNew = discoverSkillsInDir(join15(homedir8(), ".hasna", "skills", "custom"));
21682
+ const globalCustomOld = discoverSkillsInDir(join15(homedir8(), ".skills"));
21636
21683
  const oldNames = new Set(globalCustomNew.map((s) => s.name));
21637
21684
  const globalCustom = [...globalCustomNew, ...globalCustomOld.filter((s) => !oldNames.has(s.name))];
21638
- const projectCustom = discoverSkillsInDir(join14(cwd || process.cwd(), ".skills", "custom-skills"));
21685
+ const projectCustom = discoverSkillsInDir(join15(cwd || process.cwd(), ".skills", "custom-skills"));
21639
21686
  const customNames = new Set([...globalCustom, ...projectCustom].map((s) => s.name));
21640
21687
  const filtered = official.filter((s) => !customNames.has(s.name));
21641
21688
  _registryCache = [...filtered, ...globalCustom, ...projectCustom];
@@ -23999,13 +24046,14 @@ var init_dist2 = __esm(() => {
23999
24046
  // src/lib/auth.ts
24000
24047
  var exports_auth = {};
24001
24048
  __export(exports_auth, {
24049
+ lookupCredentials: () => lookupCredentials,
24002
24050
  loginWithCredentials: () => loginWithCredentials,
24003
24051
  getCredentials: () => getCredentials
24004
24052
  });
24005
24053
  import { existsSync as existsSync11, readFileSync as readFileSync6 } from "fs";
24006
- import { join as join15 } from "path";
24054
+ import { join as join16 } from "path";
24007
24055
  import { homedir as homedir9 } from "os";
24008
- async function getCredentials(service) {
24056
+ async function lookupCredentials(service) {
24009
24057
  try {
24010
24058
  const secretsVaultPath = process.env["BROWSER_SECRETS_VAULT_PATH"];
24011
24059
  if (secretsVaultPath) {
@@ -24013,11 +24061,14 @@ async function getCredentials(service) {
24013
24061
  const email = getSecret(`${service}_email`) ?? getSecret(`${service}_username`) ?? getSecret(`${service}_login`);
24014
24062
  const password = getSecret(`${service}_password`) ?? getSecret(`${service}_pass`);
24015
24063
  if (email?.value && password?.value) {
24016
- return { email: email.value, password: password.value };
24064
+ return {
24065
+ credential: { email: email.value, password: password.value },
24066
+ method: "secrets_vault"
24067
+ };
24017
24068
  }
24018
24069
  }
24019
24070
  } catch {}
24020
- const secretsPath = join15(homedir9(), ".secrets");
24071
+ const secretsPath = join16(homedir9(), ".secrets");
24021
24072
  if (existsSync11(secretsPath)) {
24022
24073
  let content;
24023
24074
  try {
@@ -24036,27 +24087,33 @@ async function getCredentials(service) {
24036
24087
  }
24037
24088
  const email = vars[`${prefix}_EMAIL`] ?? vars[`${prefix}_USERNAME`];
24038
24089
  const password = vars[`${prefix}_PASSWORD`];
24039
- if (email && password)
24040
- return { email, password };
24090
+ if (email && password) {
24091
+ return { credential: { email, password }, method: "env_file" };
24092
+ }
24041
24093
  }
24042
24094
  const envPrefix = service.toUpperCase().replace(/[^A-Z0-9]/g, "_");
24043
24095
  const envEmail = process.env[`${envPrefix}_EMAIL`] ?? process.env[`${envPrefix}_USERNAME`];
24044
24096
  const envPass = process.env[`${envPrefix}_PASSWORD`];
24045
- if (envEmail && envPass)
24046
- return { email: envEmail, password: envPass };
24047
- return null;
24097
+ if (envEmail && envPass) {
24098
+ return { credential: { email: envEmail, password: envPass }, method: "process_env" };
24099
+ }
24100
+ return { credential: null, method: "not_found" };
24101
+ }
24102
+ async function getCredentials(service) {
24103
+ return (await lookupCredentials(service)).credential;
24048
24104
  }
24049
24105
  async function loginWithCredentials(page, credentials, opts) {
24050
- const { fillForm: fillForm2 } = await Promise.resolve().then(() => (init_actions(), exports_actions));
24106
+ const { fillForm: fillForm2, waitForText: waitForText2 } = await Promise.resolve().then(() => (init_actions(), exports_actions));
24051
24107
  const { saveProfile: saveProfile2 } = await Promise.resolve().then(() => (init_profiles(), exports_profiles));
24052
24108
  try {
24053
24109
  if (opts?.loginUrl) {
24054
24110
  await page.goto(opts.loginUrl, { waitUntil: "domcontentloaded" });
24055
24111
  await new Promise((r) => setTimeout(r, 500));
24056
24112
  }
24113
+ const urlBefore = page.url();
24057
24114
  const emailSel = opts?.emailSelector ?? 'input[type="email"], input[name="email"], input[id*="email"], input[placeholder*="email" i]';
24058
24115
  const passSel = opts?.passwordSelector ?? 'input[type="password"]';
24059
- const submitSel = opts?.submitSelector ?? 'button[type="submit"], input[type="submit"], button:contains("Sign in"), button:contains("Log in"), button:contains("Login")';
24116
+ const submitSel = opts?.submitSelector ?? 'button[type="submit"], input[type="submit"]';
24060
24117
  const fields = {};
24061
24118
  if (credentials.email)
24062
24119
  fields[emailSel] = credentials.email;
@@ -24065,10 +24122,38 @@ async function loginWithCredentials(page, credentials, opts) {
24065
24122
  if (credentials.password)
24066
24123
  fields[passSel] = credentials.password;
24067
24124
  const fillResult = await fillForm2(page, fields, submitSel);
24068
- const successText = opts?.waitForText ?? "dashboard|profile|account|welcome|signed in|logout";
24069
- await new Promise((r) => setTimeout(r, 1500));
24070
- const currentUrl = page.url?.() ?? "";
24071
- const logged_in = fillResult.errors.length === 0;
24125
+ if (fillResult.errors.some((e) => e.startsWith("submit("))) {
24126
+ try {
24127
+ await page.getByRole("button", { name: /sign in|log in|login|submit/i }).first().click({ timeout: 5000 });
24128
+ } catch {
24129
+ try {
24130
+ await page.locator('input[type="submit"]').first().click({ timeout: 3000 });
24131
+ } catch {}
24132
+ }
24133
+ }
24134
+ const successPattern = opts?.waitForText ?? "dashboard|profile|account|welcome|signed in|logout";
24135
+ const patterns = successPattern.split("|").map((p) => p.trim()).filter(Boolean);
24136
+ let logged_in = false;
24137
+ for (const pattern of patterns) {
24138
+ try {
24139
+ await waitForText2(page, pattern, { timeout: 5000 });
24140
+ logged_in = fillResult.errors.length === 0;
24141
+ break;
24142
+ } catch {}
24143
+ }
24144
+ if (!logged_in) {
24145
+ await page.waitForLoadState("domcontentloaded").catch(() => {});
24146
+ await new Promise((r) => setTimeout(r, 1000));
24147
+ const currentUrl2 = page.url();
24148
+ const bodyText = await page.evaluate(() => document.body?.innerText?.toLowerCase() ?? "").catch(() => "");
24149
+ const urlChanged = currentUrl2 !== urlBefore;
24150
+ const textMatch = patterns.some((p) => {
24151
+ const needle = p.toLowerCase();
24152
+ return bodyText.includes(needle) || currentUrl2.toLowerCase().includes(needle);
24153
+ });
24154
+ logged_in = fillResult.errors.length === 0 && (urlChanged || textMatch);
24155
+ }
24156
+ const currentUrl = page.url();
24072
24157
  let profile_saved = false;
24073
24158
  if (opts?.saveProfile && logged_in) {
24074
24159
  try {
@@ -24080,14 +24165,14 @@ async function loginWithCredentials(page, credentials, opts) {
24080
24165
  logged_in,
24081
24166
  redirect_url: currentUrl,
24082
24167
  profile_saved,
24083
- method: "secrets_vault"
24168
+ method: opts?.method ?? "secrets_vault"
24084
24169
  };
24085
24170
  } catch (err2) {
24086
24171
  return {
24087
24172
  logged_in: false,
24088
24173
  redirect_url: "",
24089
24174
  profile_saved: false,
24090
- method: "not_found",
24175
+ method: opts?.method ?? "not_found",
24091
24176
  error: err2 instanceof Error ? err2.message : String(err2)
24092
24177
  };
24093
24178
  }
@@ -24194,11 +24279,11 @@ var init_skills_runner = __esm(() => {
24194
24279
  },
24195
24280
  login: async (page, params) => {
24196
24281
  const { service, login_url } = params;
24197
- const { getCredentials: getCredentials2, loginWithCredentials: loginWithCredentials2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
24198
- const creds = await getCredentials2(service);
24282
+ const { lookupCredentials: lookupCredentials2, loginWithCredentials: loginWithCredentials2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
24283
+ const { credential: creds, method } = await lookupCredentials2(service);
24199
24284
  if (!creds)
24200
24285
  return { logged_in: false, error: `No credentials found for ${service}` };
24201
- const result = await loginWithCredentials2(page, creds, { loginUrl: login_url, saveProfile: service });
24286
+ const result = await loginWithCredentials2(page, creds, { loginUrl: login_url, saveProfile: service, method });
24202
24287
  return result;
24203
24288
  },
24204
24289
  "extract-text": async (page, params) => {
@@ -24789,16 +24874,16 @@ function listRuns(scriptId) {
24789
24874
  }
24790
24875
  function migrateJsonScripts() {
24791
24876
  const { existsSync: existsSync12, readdirSync: readdirSync4, readFileSync: readFileSync7 } = __require("fs");
24792
- const { join: join16 } = __require("path");
24877
+ const { join: join17 } = __require("path");
24793
24878
  const { getDataDir: getDataDir4 } = (init_schema(), __toCommonJS(exports_schema));
24794
- const dir = join16(getDataDir4(), "scripts");
24879
+ const dir = join17(getDataDir4(), "scripts");
24795
24880
  if (!existsSync12(dir))
24796
24881
  return 0;
24797
24882
  const files = readdirSync4(dir).filter((f) => f.endsWith(".json"));
24798
24883
  let migrated = 0;
24799
24884
  for (const file of files) {
24800
24885
  try {
24801
- const raw = JSON.parse(readFileSync7(join16(dir, file), "utf8"));
24886
+ const raw = JSON.parse(readFileSync7(join17(dir, file), "utf8"));
24802
24887
  if (!raw.name || !raw.steps)
24803
24888
  continue;
24804
24889
  if (getScriptByName(raw.name))
@@ -24851,7 +24936,7 @@ var init_scripts = __esm(() => {
24851
24936
  });
24852
24937
 
24853
24938
  // src/lib/ai-inference.ts
24854
- function resolve(opts) {
24939
+ function resolve2(opts) {
24855
24940
  if (opts?.model && ALIASES[opts.model])
24856
24941
  return ALIASES[opts.model];
24857
24942
  if (opts?.provider && opts?.model)
@@ -24861,7 +24946,7 @@ function resolve(opts) {
24861
24946
  return ALIASES.fast;
24862
24947
  }
24863
24948
  async function infer(prompt, opts) {
24864
- const { provider, model } = resolve(opts);
24949
+ const { provider, model } = resolve2(opts);
24865
24950
  const maxTokens = opts?.maxTokens ?? 1024;
24866
24951
  if (provider === "anthropic") {
24867
24952
  const apiKey2 = process.env["ANTHROPIC_API_KEY"];
@@ -25252,7 +25337,7 @@ __export(exports_datasets, {
25252
25337
  });
25253
25338
  import { randomUUID as randomUUID15 } from "crypto";
25254
25339
  import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync13 } from "fs";
25255
- import { join as join16 } from "path";
25340
+ import { join as join17 } from "path";
25256
25341
  function saveDataset(data) {
25257
25342
  const db = getDatabase();
25258
25343
  const id = randomUUID15();
@@ -25290,10 +25375,10 @@ function exportDataset(name, format) {
25290
25375
  const dataset = getDatasetByName(name);
25291
25376
  if (!dataset)
25292
25377
  throw new Error(`Dataset '${name}' not found`);
25293
- const dir = join16(getDataDir2(), "exports");
25378
+ const dir = join17(getDataDir2(), "exports");
25294
25379
  mkdirSync13(dir, { recursive: true });
25295
25380
  const filename = `${name}.${format}`;
25296
- const path = join16(dir, filename);
25381
+ const path = join17(dir, filename);
25297
25382
  if (format === "csv") {
25298
25383
  const rows = dataset.data;
25299
25384
  if (rows.length === 0) {
@@ -25441,7 +25526,7 @@ import {
25441
25526
  copyFileSync as copyFileSync6
25442
25527
  } from "fs";
25443
25528
  import { homedir as homedir10 } from "os";
25444
- import { join as join17, relative as relative2 } from "path";
25529
+ import { join as join18, relative as relative3 } from "path";
25445
25530
  import { existsSync as existsSync23, mkdirSync as mkdirSync23, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
25446
25531
  import { homedir as homedir23 } from "os";
25447
25532
  import { join as join23 } from "path";
@@ -25453,7 +25538,7 @@ import { join as join43 } from "path";
25453
25538
  import { join as join62, dirname as dirname3 } from "path";
25454
25539
  import { homedir as homedir52, platform as platform2 } from "os";
25455
25540
  import { existsSync as existsSync43, mkdirSync as mkdirSync33, cpSync as cpSync2 } from "fs";
25456
- import { dirname as dirname23, join as join53, resolve as resolve2 } from "path";
25541
+ import { dirname as dirname23, join as join53, resolve as resolve3 } from "path";
25457
25542
  import { existsSync as existsSync62, mkdirSync as mkdirSync52, readFileSync as readFileSync23, readdirSync as readdirSync33, writeFileSync as writeFileSync23, unlinkSync as unlinkSync3, cpSync as cpSync22 } from "fs";
25458
25543
  import { homedir as homedir62 } from "os";
25459
25544
  import { basename as basename2, dirname as dirname32, join as join72, resolve as resolve22 } from "path";
@@ -26279,13 +26364,13 @@ function custom3(check, _params = {}, fatal) {
26279
26364
  return ZodAny3.create();
26280
26365
  }
26281
26366
  function getDataDir4(serviceName) {
26282
- const dir = join17(HASNA_DIR2, serviceName);
26367
+ const dir = join18(HASNA_DIR2, serviceName);
26283
26368
  mkdirSync14(dir, { recursive: true });
26284
26369
  return dir;
26285
26370
  }
26286
26371
  function getDbPath2(serviceName) {
26287
26372
  const dir = getDataDir4(serviceName);
26288
- return join17(dir, `${serviceName}.db`);
26373
+ return join18(dir, `${serviceName}.db`);
26289
26374
  }
26290
26375
  function getConfigDir2() {
26291
26376
  return CONFIG_DIR3;
@@ -26504,7 +26589,7 @@ function isInMemoryDb(path) {
26504
26589
  return path === ":memory:" || path.startsWith("file::memory:");
26505
26590
  }
26506
26591
  function findNearestMementosDb(startDir) {
26507
- let dir = resolve2(startDir);
26592
+ let dir = resolve3(startDir);
26508
26593
  while (true) {
26509
26594
  const candidate = join53(dir, ".mementos", "mementos.db");
26510
26595
  if (existsSync43(candidate))
@@ -26517,7 +26602,7 @@ function findNearestMementosDb(startDir) {
26517
26602
  return null;
26518
26603
  }
26519
26604
  function findGitRoot(startDir) {
26520
- let dir = resolve2(startDir);
26605
+ let dir = resolve3(startDir);
26521
26606
  while (true) {
26522
26607
  if (existsSync43(join53(dir, ".git")))
26523
26608
  return dir;
@@ -26559,7 +26644,7 @@ function getDbPath22() {
26559
26644
  function ensureDir2(filePath) {
26560
26645
  if (isInMemoryDb(filePath))
26561
26646
  return;
26562
- const dir = dirname23(resolve2(filePath));
26647
+ const dir = dirname23(resolve3(filePath));
26563
26648
  if (!existsSync43(dir)) {
26564
26649
  mkdirSync33(dir, { recursive: true });
26565
26650
  }
@@ -32797,7 +32882,7 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
32797
32882
  function parse(stream, callback) {
32798
32883
  const parser = new parser_1.Parser;
32799
32884
  stream.on("data", (buffer) => parser.parse(buffer, callback));
32800
- return new Promise((resolve3) => stream.on("end", () => resolve3()));
32885
+ return new Promise((resolve4) => stream.on("end", () => resolve4()));
32801
32886
  }
32802
32887
  exports.parse = parse;
32803
32888
  });
@@ -33462,12 +33547,12 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
33462
33547
  this._connect(callback);
33463
33548
  return;
33464
33549
  }
33465
- return new this._Promise((resolve3, reject) => {
33550
+ return new this._Promise((resolve4, reject) => {
33466
33551
  this._connect((error) => {
33467
33552
  if (error) {
33468
33553
  reject(error);
33469
33554
  } else {
33470
- resolve3(this);
33555
+ resolve4(this);
33471
33556
  }
33472
33557
  });
33473
33558
  });
@@ -33799,8 +33884,8 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
33799
33884
  readTimeout = config.query_timeout || this.connectionParameters.query_timeout;
33800
33885
  query = new Query3(config, values, callback);
33801
33886
  if (!query.callback) {
33802
- result = new this._Promise((resolve3, reject) => {
33803
- query.callback = (err2, res) => err2 ? reject(err2) : resolve3(res);
33887
+ result = new this._Promise((resolve4, reject) => {
33888
+ query.callback = (err2, res) => err2 ? reject(err2) : resolve4(res);
33804
33889
  }).catch((err2) => {
33805
33890
  Error.captureStackTrace(err2);
33806
33891
  throw err2;
@@ -33875,8 +33960,8 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
33875
33960
  if (cb) {
33876
33961
  this.connection.once("end", cb);
33877
33962
  } else {
33878
- return new this._Promise((resolve3) => {
33879
- this.connection.once("end", resolve3);
33963
+ return new this._Promise((resolve4) => {
33964
+ this.connection.once("end", resolve4);
33880
33965
  });
33881
33966
  }
33882
33967
  }
@@ -33921,8 +34006,8 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
33921
34006
  const cb = function(err2, client) {
33922
34007
  err2 ? rej(err2) : res(client);
33923
34008
  };
33924
- const result = new Promise2(function(resolve3, reject) {
33925
- res = resolve3;
34009
+ const result = new Promise2(function(resolve4, reject) {
34010
+ res = resolve4;
33926
34011
  rej = reject;
33927
34012
  }).catch((err2) => {
33928
34013
  Error.captureStackTrace(err2);
@@ -33983,7 +34068,7 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
33983
34068
  if (typeof Promise2.try === "function") {
33984
34069
  return Promise2.try(f);
33985
34070
  }
33986
- return new Promise2((resolve3) => resolve3(f()));
34071
+ return new Promise2((resolve4) => resolve4(f()));
33987
34072
  }
33988
34073
  _isFull() {
33989
34074
  return this._clients.length >= this.options.max;
@@ -34357,8 +34442,8 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
34357
34442
  NativeQuery.prototype._getPromise = function() {
34358
34443
  if (this._promise)
34359
34444
  return this._promise;
34360
- this._promise = new Promise(function(resolve3, reject) {
34361
- this._once("end", resolve3);
34445
+ this._promise = new Promise(function(resolve4, reject) {
34446
+ this._once("end", resolve4);
34362
34447
  this._once("error", reject);
34363
34448
  }.bind(this));
34364
34449
  return this._promise;
@@ -34532,12 +34617,12 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
34532
34617
  this._connect(callback);
34533
34618
  return;
34534
34619
  }
34535
- return new this._Promise((resolve3, reject) => {
34620
+ return new this._Promise((resolve4, reject) => {
34536
34621
  this._connect((error) => {
34537
34622
  if (error) {
34538
34623
  reject(error);
34539
34624
  } else {
34540
- resolve3(this);
34625
+ resolve4(this);
34541
34626
  }
34542
34627
  });
34543
34628
  });
@@ -34561,8 +34646,8 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
34561
34646
  query = new NativeQuery(config, values, callback);
34562
34647
  if (!query.callback) {
34563
34648
  let resolveOut, rejectOut;
34564
- result = new this._Promise((resolve3, reject) => {
34565
- resolveOut = resolve3;
34649
+ result = new this._Promise((resolve4, reject) => {
34650
+ resolveOut = resolve4;
34566
34651
  rejectOut = reject;
34567
34652
  }).catch((err2) => {
34568
34653
  Error.captureStackTrace(err2);
@@ -34620,8 +34705,8 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
34620
34705
  }
34621
34706
  let result;
34622
34707
  if (!cb) {
34623
- result = new this._Promise(function(resolve3, reject) {
34624
- cb = (err2) => err2 ? reject(err2) : resolve3();
34708
+ result = new this._Promise(function(resolve4, reject) {
34709
+ cb = (err2) => err2 ? reject(err2) : resolve4();
34625
34710
  });
34626
34711
  }
34627
34712
  this.native.end(function() {
@@ -37909,7 +37994,7 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
37909
37994
  init_external2();
37910
37995
  });
37911
37996
  init_dotfile2 = __esm22(() => {
37912
- HASNA_DIR2 = join17(homedir10(), ".hasna");
37997
+ HASNA_DIR2 = join18(homedir10(), ".hasna");
37913
37998
  });
37914
37999
  exports_config2 = {};
37915
38000
  __export22(exports_config2, {
@@ -39485,7 +39570,7 @@ import {
39485
39570
  copyFileSync as copyFileSync7
39486
39571
  } from "fs";
39487
39572
  import { homedir as homedir11 } from "os";
39488
- import { join as join18, relative as relative3 } from "path";
39573
+ import { join as join19, relative as relative4 } from "path";
39489
39574
  import { existsSync as existsSync24, mkdirSync as mkdirSync24, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
39490
39575
  import { homedir as homedir24 } from "os";
39491
39576
  import { join as join24 } from "path";
@@ -40328,13 +40413,13 @@ function custom4(check, _params = {}, fatal) {
40328
40413
  return ZodAny4.create();
40329
40414
  }
40330
40415
  function getDataDir5(serviceName) {
40331
- const dir = join18(HASNA_DIR3, serviceName);
40416
+ const dir = join19(HASNA_DIR3, serviceName);
40332
40417
  mkdirSync15(dir, { recursive: true });
40333
40418
  return dir;
40334
40419
  }
40335
40420
  function getDbPath3(serviceName) {
40336
40421
  const dir = getDataDir5(serviceName);
40337
- return join18(dir, `${serviceName}.db`);
40422
+ return join19(dir, `${serviceName}.db`);
40338
40423
  }
40339
40424
  function getConfigDir3() {
40340
40425
  return CONFIG_DIR5;
@@ -45793,7 +45878,7 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
45793
45878
  function parse(stream, callback) {
45794
45879
  const parser = new parser_1.Parser;
45795
45880
  stream.on("data", (buffer) => parser.parse(buffer, callback));
45796
- return new Promise((resolve3) => stream.on("end", () => resolve3()));
45881
+ return new Promise((resolve4) => stream.on("end", () => resolve4()));
45797
45882
  }
45798
45883
  exports.parse = parse;
45799
45884
  });
@@ -46458,12 +46543,12 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
46458
46543
  this._connect(callback);
46459
46544
  return;
46460
46545
  }
46461
- return new this._Promise((resolve3, reject) => {
46546
+ return new this._Promise((resolve4, reject) => {
46462
46547
  this._connect((error) => {
46463
46548
  if (error) {
46464
46549
  reject(error);
46465
46550
  } else {
46466
- resolve3(this);
46551
+ resolve4(this);
46467
46552
  }
46468
46553
  });
46469
46554
  });
@@ -46795,8 +46880,8 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
46795
46880
  readTimeout = config.query_timeout || this.connectionParameters.query_timeout;
46796
46881
  query = new Query4(config, values, callback);
46797
46882
  if (!query.callback) {
46798
- result = new this._Promise((resolve3, reject) => {
46799
- query.callback = (err2, res) => err2 ? reject(err2) : resolve3(res);
46883
+ result = new this._Promise((resolve4, reject) => {
46884
+ query.callback = (err2, res) => err2 ? reject(err2) : resolve4(res);
46800
46885
  }).catch((err2) => {
46801
46886
  Error.captureStackTrace(err2);
46802
46887
  throw err2;
@@ -46871,8 +46956,8 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
46871
46956
  if (cb) {
46872
46957
  this.connection.once("end", cb);
46873
46958
  } else {
46874
- return new this._Promise((resolve3) => {
46875
- this.connection.once("end", resolve3);
46959
+ return new this._Promise((resolve4) => {
46960
+ this.connection.once("end", resolve4);
46876
46961
  });
46877
46962
  }
46878
46963
  }
@@ -46917,8 +47002,8 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
46917
47002
  const cb = function(err2, client) {
46918
47003
  err2 ? rej(err2) : res(client);
46919
47004
  };
46920
- const result = new Promise2(function(resolve3, reject) {
46921
- res = resolve3;
47005
+ const result = new Promise2(function(resolve4, reject) {
47006
+ res = resolve4;
46922
47007
  rej = reject;
46923
47008
  }).catch((err2) => {
46924
47009
  Error.captureStackTrace(err2);
@@ -46979,7 +47064,7 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
46979
47064
  if (typeof Promise2.try === "function") {
46980
47065
  return Promise2.try(f);
46981
47066
  }
46982
- return new Promise2((resolve3) => resolve3(f()));
47067
+ return new Promise2((resolve4) => resolve4(f()));
46983
47068
  }
46984
47069
  _isFull() {
46985
47070
  return this._clients.length >= this.options.max;
@@ -47353,8 +47438,8 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
47353
47438
  NativeQuery.prototype._getPromise = function() {
47354
47439
  if (this._promise)
47355
47440
  return this._promise;
47356
- this._promise = new Promise(function(resolve3, reject) {
47357
- this._once("end", resolve3);
47441
+ this._promise = new Promise(function(resolve4, reject) {
47442
+ this._once("end", resolve4);
47358
47443
  this._once("error", reject);
47359
47444
  }.bind(this));
47360
47445
  return this._promise;
@@ -47528,12 +47613,12 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
47528
47613
  this._connect(callback);
47529
47614
  return;
47530
47615
  }
47531
- return new this._Promise((resolve3, reject) => {
47616
+ return new this._Promise((resolve4, reject) => {
47532
47617
  this._connect((error) => {
47533
47618
  if (error) {
47534
47619
  reject(error);
47535
47620
  } else {
47536
- resolve3(this);
47621
+ resolve4(this);
47537
47622
  }
47538
47623
  });
47539
47624
  });
@@ -47557,8 +47642,8 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
47557
47642
  query = new NativeQuery(config, values, callback);
47558
47643
  if (!query.callback) {
47559
47644
  let resolveOut, rejectOut;
47560
- result = new this._Promise((resolve3, reject) => {
47561
- resolveOut = resolve3;
47645
+ result = new this._Promise((resolve4, reject) => {
47646
+ resolveOut = resolve4;
47562
47647
  rejectOut = reject;
47563
47648
  }).catch((err2) => {
47564
47649
  Error.captureStackTrace(err2);
@@ -47616,8 +47701,8 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
47616
47701
  }
47617
47702
  let result;
47618
47703
  if (!cb) {
47619
- result = new this._Promise(function(resolve3, reject) {
47620
- cb = (err2) => err2 ? reject(err2) : resolve3();
47704
+ result = new this._Promise(function(resolve4, reject) {
47705
+ cb = (err2) => err2 ? reject(err2) : resolve4();
47621
47706
  });
47622
47707
  }
47623
47708
  this.native.end(function() {
@@ -50905,7 +50990,7 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
50905
50990
  init_external3();
50906
50991
  });
50907
50992
  init_dotfile3 = __esm23(() => {
50908
- HASNA_DIR3 = join18(homedir11(), ".hasna");
50993
+ HASNA_DIR3 = join19(homedir11(), ".hasna");
50909
50994
  });
50910
50995
  exports_config3 = {};
50911
50996
  __export23(exports_config3, {
@@ -52667,14 +52752,14 @@ Check the top-level render call using <` + parentName + ">.";
52667
52752
  var thenableResult = result;
52668
52753
  var wasAwaited = false;
52669
52754
  var thenable = {
52670
- then: function(resolve3, reject) {
52755
+ then: function(resolve4, reject) {
52671
52756
  wasAwaited = true;
52672
52757
  thenableResult.then(function(returnValue2) {
52673
52758
  popActScope(prevActScopeDepth);
52674
52759
  if (actScopeDepth === 0) {
52675
- recursivelyFlushAsyncActWork(returnValue2, resolve3, reject);
52760
+ recursivelyFlushAsyncActWork(returnValue2, resolve4, reject);
52676
52761
  } else {
52677
- resolve3(returnValue2);
52762
+ resolve4(returnValue2);
52678
52763
  }
52679
52764
  }, function(error2) {
52680
52765
  popActScope(prevActScopeDepth);
@@ -52703,20 +52788,20 @@ Check the top-level render call using <` + parentName + ">.";
52703
52788
  ReactCurrentActQueue.current = null;
52704
52789
  }
52705
52790
  var _thenable = {
52706
- then: function(resolve3, reject) {
52791
+ then: function(resolve4, reject) {
52707
52792
  if (ReactCurrentActQueue.current === null) {
52708
52793
  ReactCurrentActQueue.current = [];
52709
- recursivelyFlushAsyncActWork(returnValue, resolve3, reject);
52794
+ recursivelyFlushAsyncActWork(returnValue, resolve4, reject);
52710
52795
  } else {
52711
- resolve3(returnValue);
52796
+ resolve4(returnValue);
52712
52797
  }
52713
52798
  }
52714
52799
  };
52715
52800
  return _thenable;
52716
52801
  } else {
52717
52802
  var _thenable2 = {
52718
- then: function(resolve3, reject) {
52719
- resolve3(returnValue);
52803
+ then: function(resolve4, reject) {
52804
+ resolve4(returnValue);
52720
52805
  }
52721
52806
  };
52722
52807
  return _thenable2;
@@ -52732,7 +52817,7 @@ Check the top-level render call using <` + parentName + ">.";
52732
52817
  actScopeDepth = prevActScopeDepth;
52733
52818
  }
52734
52819
  }
52735
- function recursivelyFlushAsyncActWork(returnValue, resolve3, reject) {
52820
+ function recursivelyFlushAsyncActWork(returnValue, resolve4, reject) {
52736
52821
  {
52737
52822
  var queue = ReactCurrentActQueue.current;
52738
52823
  if (queue !== null) {
@@ -52741,16 +52826,16 @@ Check the top-level render call using <` + parentName + ">.";
52741
52826
  enqueueTask(function() {
52742
52827
  if (queue.length === 0) {
52743
52828
  ReactCurrentActQueue.current = null;
52744
- resolve3(returnValue);
52829
+ resolve4(returnValue);
52745
52830
  } else {
52746
- recursivelyFlushAsyncActWork(returnValue, resolve3, reject);
52831
+ recursivelyFlushAsyncActWork(returnValue, resolve4, reject);
52747
52832
  }
52748
52833
  });
52749
52834
  } catch (error2) {
52750
52835
  reject(error2);
52751
52836
  }
52752
52837
  } else {
52753
- resolve3(returnValue);
52838
+ resolve4(returnValue);
52754
52839
  }
52755
52840
  }
52756
52841
  }
@@ -53670,7 +53755,7 @@ __export(exports_dist4, {
53670
53755
  import { hostname as osHostname, platform as osPlatform } from "os";
53671
53756
  import { Database as Database4 } from "bun:sqlite";
53672
53757
  import { existsSync as existsSync14, mkdirSync as mkdirSync16 } from "fs";
53673
- import { dirname as dirname5, join as join19, resolve as resolve3 } from "path";
53758
+ import { dirname as dirname5, join as join20, resolve as resolve4 } from "path";
53674
53759
  import { existsSync as existsSync35 } from "fs";
53675
53760
  import { join as join35 } from "path";
53676
53761
  import { existsSync as existsSync25, mkdirSync as mkdirSync25, readFileSync as readFileSync10, readdirSync as readdirSync11, statSync as statSync5, writeFileSync as writeFileSync8 } from "fs";
@@ -53691,7 +53776,7 @@ import {
53691
53776
  copyFileSync as copyFileSync8
53692
53777
  } from "fs";
53693
53778
  import { homedir as homedir25 } from "os";
53694
- import { join as join74, relative as relative4 } from "path";
53779
+ import { join as join74, relative as relative5 } from "path";
53695
53780
  import { existsSync as existsSync222, mkdirSync as mkdirSync222, readFileSync as readFileSync45, writeFileSync as writeFileSync43 } from "fs";
53696
53781
  import { homedir as homedir222 } from "os";
53697
53782
  import { join as join222 } from "path";
@@ -54149,9 +54234,9 @@ function isInMemoryDb2(path) {
54149
54234
  return path === ":memory:" || path.startsWith("file::memory:");
54150
54235
  }
54151
54236
  function findNearestTodosDb(startDir) {
54152
- let dir = resolve3(startDir);
54237
+ let dir = resolve4(startDir);
54153
54238
  while (true) {
54154
- const candidate = join19(dir, ".todos", "todos.db");
54239
+ const candidate = join20(dir, ".todos", "todos.db");
54155
54240
  if (existsSync14(candidate))
54156
54241
  return candidate;
54157
54242
  const parent = dirname5(dir);
@@ -54162,9 +54247,9 @@ function findNearestTodosDb(startDir) {
54162
54247
  return null;
54163
54248
  }
54164
54249
  function findGitRoot2(startDir) {
54165
- let dir = resolve3(startDir);
54250
+ let dir = resolve4(startDir);
54166
54251
  while (true) {
54167
- if (existsSync14(join19(dir, ".git")))
54252
+ if (existsSync14(join20(dir, ".git")))
54168
54253
  return dir;
54169
54254
  const parent = dirname5(dir);
54170
54255
  if (parent === dir)
@@ -54187,12 +54272,12 @@ function getDbPath4() {
54187
54272
  if (process.env["TODOS_DB_SCOPE"] === "project") {
54188
54273
  const gitRoot = findGitRoot2(cwd);
54189
54274
  if (gitRoot) {
54190
- return join19(gitRoot, ".todos", "todos.db");
54275
+ return join20(gitRoot, ".todos", "todos.db");
54191
54276
  }
54192
54277
  }
54193
54278
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
54194
- const newPath = join19(home, ".hasna", "todos", "todos.db");
54195
- const legacyPath = join19(home, ".todos", "todos.db");
54279
+ const newPath = join20(home, ".hasna", "todos", "todos.db");
54280
+ const legacyPath = join20(home, ".todos", "todos.db");
54196
54281
  if (!existsSync14(newPath) && existsSync14(legacyPath)) {
54197
54282
  return legacyPath;
54198
54283
  }
@@ -54201,7 +54286,7 @@ function getDbPath4() {
54201
54286
  function ensureDir3(filePath) {
54202
54287
  if (isInMemoryDb2(filePath))
54203
54288
  return;
54204
- const dir = dirname5(resolve3(filePath));
54289
+ const dir = dirname5(resolve4(filePath));
54205
54290
  if (!existsSync14(dir)) {
54206
54291
  mkdirSync16(dir, { recursive: true });
54207
54292
  }
@@ -70827,7 +70912,7 @@ init_dist();
70827
70912
  import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
70828
70913
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
70829
70914
  import { readFileSync as readFileSync11 } from "fs";
70830
- import { join as join20 } from "path";
70915
+ import { join as join21 } from "path";
70831
70916
 
70832
70917
  // node_modules/zod/v3/external.js
70833
70918
  var exports_external2 = {};
@@ -74812,12 +74897,12 @@ init_types2();
74812
74897
  init_gallery();
74813
74898
  init_schema();
74814
74899
  var import_sharp = __toESM(require_lib3(), 1);
74815
- import { join as join9 } from "path";
74900
+ import { join as join10 } from "path";
74816
74901
  import { mkdirSync as mkdirSync7 } from "fs";
74817
74902
  function getScreenshotDir(projectId) {
74818
- const base = join9(getDataDir2(), "screenshots");
74903
+ const base = join10(getDataDir2(), "screenshots");
74819
74904
  const date = new Date().toISOString().split("T")[0];
74820
- const dir = projectId ? join9(base, projectId, date) : join9(base, date);
74905
+ const dir = projectId ? join10(base, projectId, date) : join10(base, date);
74821
74906
  mkdirSync7(dir, { recursive: true });
74822
74907
  return dir;
74823
74908
  }
@@ -74833,7 +74918,7 @@ async function compressBuffer(raw, format, quality, maxWidth) {
74833
74918
  }
74834
74919
  }
74835
74920
  async function generateThumbnail(raw, dir, stem) {
74836
- const thumbPath = join9(dir, `${stem}.thumb.webp`);
74921
+ const thumbPath = join10(dir, `${stem}.thumb.webp`);
74837
74922
  const thumbBuffer = await import_sharp.default(raw).resize({ width: 200, withoutEnlargement: true }).webp({ quality: 70, effort: 3 }).toBuffer();
74838
74923
  await Bun.write(thumbPath, thumbBuffer);
74839
74924
  return { path: thumbPath, base64: thumbBuffer.toString("base64") };
@@ -74901,7 +74986,7 @@ async function takeScreenshot(page, opts) {
74901
74986
  const compressedSizeBytes = finalBuffer.length;
74902
74987
  const compressionRatio = originalSizeBytes > 0 ? compressedSizeBytes / originalSizeBytes : 1;
74903
74988
  const ext = format;
74904
- const screenshotPath = opts?.path ?? join9(dir, `${stem}.${ext}`);
74989
+ const screenshotPath = opts?.path ?? join10(dir, `${stem}.${ext}`);
74905
74990
  await Bun.write(screenshotPath, finalBuffer);
74906
74991
  let thumbnailPath;
74907
74992
  let thumbnailBase64;
@@ -74961,12 +75046,12 @@ async function takeScreenshot(page, opts) {
74961
75046
  }
74962
75047
  async function generatePDF(page, opts) {
74963
75048
  try {
74964
- const base = join9(getDataDir2(), "pdfs");
75049
+ const base = join10(getDataDir2(), "pdfs");
74965
75050
  const date = new Date().toISOString().split("T")[0];
74966
- const dir = opts?.projectId ? join9(base, opts.projectId, date) : join9(base, date);
75051
+ const dir = opts?.projectId ? join10(base, opts.projectId, date) : join10(base, date);
74967
75052
  mkdirSync7(dir, { recursive: true });
74968
75053
  const timestamp = Date.now();
74969
- const pdfPath = opts?.path ?? join9(dir, `${timestamp}.pdf`);
75054
+ const pdfPath = opts?.path ?? join10(dir, `${timestamp}.pdf`);
74970
75055
  const buffer = await page.pdf({
74971
75056
  path: pdfPath,
74972
75057
  format: opts?.format ?? "A4",
@@ -75218,11 +75303,11 @@ init_gallery();
75218
75303
  // src/lib/downloads.ts
75219
75304
  init_schema();
75220
75305
  import { randomUUID as randomUUID9 } from "crypto";
75221
- import { join as join10, basename, extname } from "path";
75306
+ import { join as join11, basename, extname } from "path";
75222
75307
  import { mkdirSync as mkdirSync8, existsSync as existsSync7, readdirSync as readdirSync6, statSync as statSync2, unlinkSync as unlinkSync2, copyFileSync as copyFileSync3, writeFileSync as writeFileSync2, readFileSync as readFileSync2 } from "fs";
75223
75308
  function getDownloadsDir(sessionId) {
75224
- const base = join10(getDataDir2(), "downloads");
75225
- const dir = sessionId ? join10(base, sessionId) : base;
75309
+ const base = join11(getDataDir2(), "downloads");
75310
+ const dir = sessionId ? join11(base, sessionId) : base;
75226
75311
  mkdirSync8(dir, { recursive: true });
75227
75312
  return dir;
75228
75313
  }
@@ -75235,7 +75320,7 @@ function saveToDownloads(buffer, filename, opts) {
75235
75320
  const ext = extname(filename) || "";
75236
75321
  const stem = basename(filename, ext);
75237
75322
  const uniqueName = `${stem}-${id.slice(0, 8)}${ext}`;
75238
- const filePath = join10(dir, uniqueName);
75323
+ const filePath = join11(dir, uniqueName);
75239
75324
  writeFileSync2(filePath, buffer);
75240
75325
  const meta = {
75241
75326
  id,
@@ -75270,7 +75355,7 @@ function listDownloads(sessionId) {
75270
75355
  for (const entry of entries) {
75271
75356
  if (entry.endsWith(".meta.json"))
75272
75357
  continue;
75273
- const full = join10(d, entry);
75358
+ const full = join11(d, entry);
75274
75359
  const stat = statSync2(full);
75275
75360
  if (stat.isDirectory()) {
75276
75361
  scanDir(full);
@@ -75359,7 +75444,7 @@ init_snapshot();
75359
75444
 
75360
75445
  // src/lib/files-integration.ts
75361
75446
  init_schema();
75362
- import { join as join12 } from "path";
75447
+ import { join as join13 } from "path";
75363
75448
  import { mkdirSync as mkdirSync10, copyFileSync as copyFileSync4 } from "fs";
75364
75449
  async function persistFile(localPath, opts) {
75365
75450
  try {
@@ -75371,10 +75456,10 @@ async function persistFile(localPath, opts) {
75371
75456
  } catch {}
75372
75457
  const dataDir = getDataDir2();
75373
75458
  const date = new Date().toISOString().split("T")[0];
75374
- const dir = join12(dataDir, "persistent", date);
75459
+ const dir = join13(dataDir, "persistent", date);
75375
75460
  mkdirSync10(dir, { recursive: true });
75376
75461
  const filename = localPath.split("/").pop() ?? "file";
75377
- const targetPath = join12(dir, filename);
75462
+ const targetPath = join13(dir, filename);
75378
75463
  copyFileSync4(localPath, targetPath);
75379
75464
  return {
75380
75465
  id: `local-${Date.now()}`,
@@ -76117,7 +76202,7 @@ function register2(server) {
76117
76202
  page.on("response", onResponse);
76118
76203
  page.on("requestfailed", onFailed);
76119
76204
  try {
76120
- await new Promise((resolve, reject) => {
76205
+ await new Promise((resolve2, reject) => {
76121
76206
  const check = () => {
76122
76207
  const now = Date.now();
76123
76208
  if (now - t0 > timeout) {
@@ -76125,7 +76210,7 @@ function register2(server) {
76125
76210
  return;
76126
76211
  }
76127
76212
  if (pending === 0 && now - lastActivity >= idle_time) {
76128
- resolve();
76213
+ resolve2();
76129
76214
  return;
76130
76215
  }
76131
76216
  setTimeout(check, 100);
@@ -76647,7 +76732,7 @@ function register3(server) {
76647
76732
  await new Promise((r) => setTimeout(r, wait_ms));
76648
76733
  const ss2 = await takeScreenshot(page, { maxWidth: 1280, compress: true, track: false });
76649
76734
  const { diffImages: diffImages2 } = await Promise.resolve().then(() => (init_gallery_diff(), exports_gallery_diff));
76650
- const diff = await diffImages2(ss1.path, ss2.path);
76735
+ const diff = await diffImages2(ss1.path, ss2.path, threshold);
76651
76736
  logEvent(sid, "diff", { url1, url2, changed_percent: diff.changed_percent });
76652
76737
  return json({
76653
76738
  url1,
@@ -77623,13 +77708,14 @@ function registerIntegrationAndMeta(server) {
77623
77708
  try {
77624
77709
  const sid = resolveSessionId(session_id);
77625
77710
  const page = getSessionPage(sid);
77626
- const { getCredentials: getCredentials2, loginWithCredentials: loginWithCredentials2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
77627
- const creds = await getCredentials2(service);
77711
+ const { lookupCredentials: lookupCredentials2, loginWithCredentials: loginWithCredentials2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
77712
+ const { credential: creds, method } = await lookupCredentials2(service);
77628
77713
  if (!creds)
77629
77714
  return err(new Error(`No credentials found for '${service}'. Add them: secrets set ${service}_email yourlogin && secrets set ${service}_password yourpass`));
77630
77715
  const result = await loginWithCredentials2(page, creds, {
77631
77716
  loginUrl: login_url,
77632
- saveProfile: save_profile ? service : undefined
77717
+ saveProfile: save_profile ? service : undefined,
77718
+ method
77633
77719
  });
77634
77720
  return json(result);
77635
77721
  } catch (e) {
@@ -77994,6 +78080,7 @@ function register8(server) {
77994
78080
  // src/mcp/tui.ts
77995
78081
  init_tui();
77996
78082
  init_session();
78083
+ init_tui_recording();
77997
78084
  var DEFAULT_TOOL_TIMEOUT_MS2 = 15000;
77998
78085
  var RECONNECT_ON_STUCK = true;
77999
78086
  var KEY_MAP = {
@@ -78048,7 +78135,11 @@ function assertTuiSession(sessionId) {
78048
78135
  }
78049
78136
  }
78050
78137
  function getTuiSession(sessionId) {
78051
- return getSessionTuiSession(sessionId);
78138
+ const session = getSessionTuiSession(sessionId);
78139
+ if (!session) {
78140
+ throw new Error(`TUI session handle missing for session ${sessionId}. Close and re-open with engine="tui".`);
78141
+ }
78142
+ return session;
78052
78143
  }
78053
78144
  function getTuiMeta(sessionId) {
78054
78145
  const session = getTuiSession(sessionId);
@@ -78095,19 +78186,19 @@ async function withTuiHealth(sessionId, operation, options = {}) {
78095
78186
  } else if (!health.healthy) {
78096
78187
  throw Object.assign(new Error(`TUI session is unhealthy: ${health.reason}. Close and reopen the session.`), { code: "TUI_UNHEALTHY" });
78097
78188
  }
78098
- let timedOut = false;
78099
- const timer = setTimeout(() => {
78100
- timedOut = true;
78101
- }, timeoutMs);
78102
- try {
78103
- return await operation(page, session);
78104
- } catch (error) {
78105
- if (timedOut) {
78189
+ let timer;
78190
+ const timeout = new Promise((_, reject) => {
78191
+ timer = setTimeout(() => {
78106
78192
  const err2 = new Error(`${operationName} timed out after ${timeoutMs}ms \u2014 ttyd/playwright connection may be unhealthy. Status: ${health.healthy ? "was healthy before op" : "was already unhealthy"}. Try closing and re-opening the session.`);
78107
78193
  Object.assign(err2, { code: "TUI_TIMEOUT" });
78108
- throw err2;
78109
- }
78110
- throw error;
78194
+ reject(err2);
78195
+ }, timeoutMs);
78196
+ });
78197
+ try {
78198
+ return await Promise.race([
78199
+ operation(page, session),
78200
+ timeout
78201
+ ]);
78111
78202
  } finally {
78112
78203
  clearTimeout(timer);
78113
78204
  }
@@ -78406,6 +78497,7 @@ CONDITION SYNTAX:
78406
78497
  }, interval_ms)
78407
78498
  };
78408
78499
  activeRecordings2.set(sid, recording);
78500
+ trackTuiRecording(sid, recording.intervalId);
78409
78501
  return json({
78410
78502
  recording: true,
78411
78503
  session_id: sid,
@@ -78425,6 +78517,7 @@ CONDITION SYNTAX:
78425
78517
  if (!recording)
78426
78518
  return err(new Error("No active recording for this session"));
78427
78519
  clearInterval(recording.intervalId);
78520
+ stopTuiRecording(sid);
78428
78521
  activeRecordings2.delete(sid);
78429
78522
  const duration = (Date.now() - recording.startTime) / 1000;
78430
78523
  const header = {
@@ -78471,20 +78564,170 @@ CONDITION SYNTAX:
78471
78564
  });
78472
78565
  }
78473
78566
 
78567
+ // src/mcp/http.ts
78568
+ import { createServer } from "http";
78569
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
78570
+ var MCP_HTTP_SERVICE_NAME = "browser";
78571
+ var DEFAULT_MCP_HTTP_PORT = 8802;
78572
+ function isHttpMode(argv = process.argv, env = process.env) {
78573
+ return argv.includes("--http") || env.MCP_HTTP === "1";
78574
+ }
78575
+ function resolveMcpHttpPort(argv = process.argv, env = process.env) {
78576
+ const portIdx = argv.indexOf("--port");
78577
+ if (portIdx !== -1 && argv[portIdx + 1]) {
78578
+ return parsePort(argv[portIdx + 1], "--port");
78579
+ }
78580
+ if (env.MCP_HTTP_PORT) {
78581
+ return parsePort(env.MCP_HTTP_PORT, "MCP_HTTP_PORT");
78582
+ }
78583
+ return DEFAULT_MCP_HTTP_PORT;
78584
+ }
78585
+ function parsePort(raw, source) {
78586
+ const parsed = Number(raw);
78587
+ if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65535) {
78588
+ throw new Error(`Invalid ${source} value "${raw}". Expected 0-65535.`);
78589
+ }
78590
+ return parsed;
78591
+ }
78592
+ async function readJsonBody(req) {
78593
+ const chunks = [];
78594
+ for await (const chunk of req) {
78595
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
78596
+ }
78597
+ const text = Buffer.concat(chunks).toString("utf8");
78598
+ if (!text)
78599
+ return;
78600
+ return JSON.parse(text);
78601
+ }
78602
+ async function startMcpHttpServer(buildServer, options) {
78603
+ const host = options?.host ?? "127.0.0.1";
78604
+ const requestedPort = options?.port ?? resolveMcpHttpPort();
78605
+ const serviceName = options?.serviceName ?? MCP_HTTP_SERVICE_NAME;
78606
+ const httpServer = createServer(async (req, res) => {
78607
+ try {
78608
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
78609
+ if (req.method === "GET" && url.pathname === "/health") {
78610
+ res.writeHead(200, { "Content-Type": "application/json" });
78611
+ res.end(JSON.stringify({ status: "ok", name: serviceName }));
78612
+ return;
78613
+ }
78614
+ if (url.pathname !== "/mcp") {
78615
+ res.writeHead(404, { "Content-Type": "text/plain" });
78616
+ res.end("Not Found");
78617
+ return;
78618
+ }
78619
+ const server = buildServer();
78620
+ const transport = new StreamableHTTPServerTransport({
78621
+ sessionIdGenerator: undefined
78622
+ });
78623
+ await server.connect(transport);
78624
+ let parsedBody;
78625
+ if (req.method === "POST") {
78626
+ parsedBody = await readJsonBody(req);
78627
+ }
78628
+ await transport.handleRequest(req, res, parsedBody);
78629
+ res.on("close", () => {
78630
+ transport.close();
78631
+ server.close();
78632
+ });
78633
+ } catch (error) {
78634
+ console.error(`[${serviceName}-mcp] HTTP error:`, error);
78635
+ if (!res.headersSent) {
78636
+ res.writeHead(500, { "Content-Type": "application/json" });
78637
+ res.end(JSON.stringify({
78638
+ jsonrpc: "2.0",
78639
+ error: { code: -32603, message: "Internal server error" },
78640
+ id: null
78641
+ }));
78642
+ }
78643
+ }
78644
+ });
78645
+ await new Promise((resolve5, reject) => {
78646
+ httpServer.once("error", reject);
78647
+ httpServer.listen(requestedPort, host, () => resolve5());
78648
+ });
78649
+ const addr = httpServer.address();
78650
+ const port = typeof addr === "object" && addr ? addr.port : requestedPort;
78651
+ console.error(`[${serviceName}-mcp] Streamable HTTP listening on http://${host}:${port}/mcp`);
78652
+ return {
78653
+ port,
78654
+ host,
78655
+ close: () => new Promise((resolve5, reject) => {
78656
+ httpServer.close((err2) => err2 ? reject(err2) : resolve5());
78657
+ })
78658
+ };
78659
+ }
78660
+
78474
78661
  // src/mcp/index.ts
78475
- var _pkg = JSON.parse(readFileSync11(join20(import.meta.dir, "../../package.json"), "utf8"));
78476
- var server = new McpServer2({
78477
- name: "@hasna/browser",
78478
- version: _pkg.version
78479
- });
78480
- register(server);
78481
- register2(server);
78482
- register3(server);
78483
- register4(server);
78484
- register8(server);
78485
- register9(server);
78486
- registerCloudTools(server, "browser");
78487
- var _startupToolCount = Object.keys(server._registeredTools ?? {}).length;
78488
- console.error(`@hasna/browser v${_pkg.version} \u2014 ${_startupToolCount} tools | data: ${(await Promise.resolve().then(() => (init_schema(), exports_schema))).getDataDir()}`);
78489
- var transport = new StdioServerTransport;
78490
- await server.connect(transport);
78662
+ var _pkg = JSON.parse(readFileSync11(join21(import.meta.dir, "../../package.json"), "utf8"));
78663
+ function buildServer() {
78664
+ const server = new McpServer2({
78665
+ name: "@hasna/browser",
78666
+ version: _pkg.version
78667
+ });
78668
+ register(server);
78669
+ register2(server);
78670
+ register3(server);
78671
+ register4(server);
78672
+ register8(server);
78673
+ register9(server);
78674
+ registerCloudTools(server, "browser");
78675
+ return server;
78676
+ }
78677
+ function hasFlag(...flags) {
78678
+ return process.argv.some((arg) => flags.includes(arg));
78679
+ }
78680
+ function printHelp() {
78681
+ process.stdout.write(`Usage: browser-mcp [options]
78682
+
78683
+ Browser MCP server (stdio transport by default)
78684
+
78685
+ Options:
78686
+ --http Serve MCP over Streamable HTTP (127.0.0.1)
78687
+ --port <number> HTTP port (default: 8802, env: MCP_HTTP_PORT)
78688
+ -h, --help Show help
78689
+ -V, --version Show version
78690
+ `);
78691
+ }
78692
+ async function logStartup(server) {
78693
+ const startupToolCount = Object.keys(server._registeredTools ?? {}).length;
78694
+ const { getDataDir: getDataDir7 } = await Promise.resolve().then(() => (init_schema(), exports_schema));
78695
+ console.error(`@hasna/browser v${_pkg.version} \u2014 ${startupToolCount} tools | data: ${getDataDir7()}`);
78696
+ }
78697
+ async function main() {
78698
+ if (hasFlag("--help", "-h")) {
78699
+ printHelp();
78700
+ return;
78701
+ }
78702
+ if (hasFlag("--version", "-V")) {
78703
+ process.stdout.write(`${_pkg.version}
78704
+ `);
78705
+ return;
78706
+ }
78707
+ if (isHttpMode()) {
78708
+ const handle = await startMcpHttpServer(buildServer, {
78709
+ port: resolveMcpHttpPort()
78710
+ });
78711
+ await logStartup(buildServer());
78712
+ process.on("SIGINT", () => {
78713
+ handle.close().finally(() => process.exit(0));
78714
+ });
78715
+ process.on("SIGTERM", () => {
78716
+ handle.close().finally(() => process.exit(0));
78717
+ });
78718
+ return;
78719
+ }
78720
+ const server = buildServer();
78721
+ await logStartup(server);
78722
+ const transport = new StdioServerTransport;
78723
+ await server.connect(transport);
78724
+ }
78725
+ if (import.meta.main) {
78726
+ main().catch((error) => {
78727
+ console.error("MCP server error:", error);
78728
+ process.exit(1);
78729
+ });
78730
+ }
78731
+ export {
78732
+ buildServer
78733
+ };