@hasna/browser 0.2.1 → 0.2.2

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
@@ -19363,14 +19363,24 @@ var init_dist = __esm(() => {
19363
19363
  var exports_login_scripts = {};
19364
19364
  __export(exports_login_scripts, {
19365
19365
  saveScript: () => saveScript,
19366
+ runScriptAsync: () => runScriptAsync,
19366
19367
  runScript: () => runScript,
19367
19368
  loadScript: () => loadScript,
19368
19369
  listScripts: () => listScripts,
19370
+ listJobs: () => listJobs,
19371
+ getJob: () => getJob,
19369
19372
  deleteScript: () => deleteScript,
19370
19373
  createUsestableScript: () => createUsestableScript
19371
19374
  });
19375
+ import { randomUUID as randomUUID10 } from "crypto";
19372
19376
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync6, mkdirSync as mkdirSync8, readdirSync as readdirSync4 } from "fs";
19373
19377
  import { join as join9 } from "path";
19378
+ function getJob(jobId) {
19379
+ return activeJobs.get(jobId) ?? null;
19380
+ }
19381
+ function listJobs() {
19382
+ return Array.from(activeJobs.values());
19383
+ }
19374
19384
  function getScriptsDir() {
19375
19385
  const dir = join9(getDataDir(), "scripts");
19376
19386
  mkdirSync8(dir, { recursive: true });
@@ -19417,15 +19427,34 @@ function interpolate(template, vars) {
19417
19427
  return vars[key] ?? match;
19418
19428
  });
19419
19429
  }
19420
- async function runScript(script, page, overrides = {}) {
19430
+ function stepDescription(step) {
19431
+ return step.description ?? `${step.type}${step.action ? `:${step.action}` : ""}${step.connector ? `:${step.connector}` : ""}`;
19432
+ }
19433
+ async function runScript(script, page, overrides = {}, jobId) {
19421
19434
  const t0 = Date.now();
19422
19435
  const vars = { ...script.variables, ...overrides };
19423
19436
  const errors = [];
19424
19437
  let executed = 0;
19425
19438
  let failed = 0;
19439
+ const job = jobId && activeJobs.has(jobId) ? activeJobs.get(jobId) : {
19440
+ id: jobId ?? randomUUID10(),
19441
+ script_name: script.name,
19442
+ status: "running",
19443
+ current_step: 0,
19444
+ total_steps: script.steps.length,
19445
+ current_step_description: "Starting...",
19446
+ steps_log: [],
19447
+ started_at: new Date().toISOString()
19448
+ };
19449
+ activeJobs.set(job.id, job);
19426
19450
  for (let i = 0;i < script.steps.length; i++) {
19427
19451
  const step = script.steps[i];
19452
+ const desc = stepDescription(step);
19428
19453
  executed++;
19454
+ job.current_step = i + 1;
19455
+ job.current_step_description = desc;
19456
+ job.steps_log.push({ step: i + 1, type: step.type, description: desc, status: "running" });
19457
+ const stepStart = Date.now();
19429
19458
  try {
19430
19459
  switch (step.type) {
19431
19460
  case "browser":
@@ -19462,15 +19491,22 @@ async function runScript(script, page, overrides = {}) {
19462
19491
  break;
19463
19492
  }
19464
19493
  }
19494
+ const logEntry = job.steps_log[job.steps_log.length - 1];
19495
+ logEntry.status = "ok";
19496
+ logEntry.duration_ms = Date.now() - stepStart;
19465
19497
  } catch (err) {
19466
19498
  failed++;
19467
19499
  const msg = `Step ${i + 1} (${step.type}/${step.action ?? step.connector ?? ""}): ${err instanceof Error ? err.message : String(err)}`;
19468
19500
  errors.push(msg);
19501
+ const logEntry = job.steps_log[job.steps_log.length - 1];
19502
+ logEntry.status = "failed";
19503
+ logEntry.error = err instanceof Error ? err.message : String(err);
19504
+ logEntry.duration_ms = Date.now() - stepStart;
19469
19505
  if (step.type === "browser" && step.action === "navigate")
19470
19506
  break;
19471
19507
  }
19472
19508
  }
19473
- return {
19509
+ const result = {
19474
19510
  success: failed === 0,
19475
19511
  steps_executed: executed,
19476
19512
  steps_failed: failed,
@@ -19478,6 +19514,28 @@ async function runScript(script, page, overrides = {}) {
19478
19514
  errors,
19479
19515
  duration_ms: Date.now() - t0
19480
19516
  };
19517
+ job.status = failed === 0 ? "completed" : "failed";
19518
+ job.result = result;
19519
+ return result;
19520
+ }
19521
+ function runScriptAsync(script, page, overrides = {}) {
19522
+ const jobId = randomUUID10();
19523
+ const job = {
19524
+ id: jobId,
19525
+ script_name: script.name,
19526
+ status: "running",
19527
+ current_step: 0,
19528
+ total_steps: script.steps.length,
19529
+ current_step_description: "Starting...",
19530
+ steps_log: [],
19531
+ started_at: new Date().toISOString()
19532
+ };
19533
+ activeJobs.set(jobId, job);
19534
+ runScript(script, page, overrides, jobId).catch((err) => {
19535
+ job.status = "failed";
19536
+ job.current_step_description = `Fatal error: ${err instanceof Error ? err.message : String(err)}`;
19537
+ });
19538
+ return jobId;
19481
19539
  }
19482
19540
  async function runBrowserStep(step, page, vars) {
19483
19541
  const action = step.action;
@@ -19637,8 +19695,10 @@ function createUsestableScript(email) {
19637
19695
  updated_at: new Date().toISOString()
19638
19696
  };
19639
19697
  }
19698
+ var activeJobs;
19640
19699
  var init_login_scripts = __esm(() => {
19641
19700
  init_schema();
19701
+ activeJobs = new Map;
19642
19702
  });
19643
19703
 
19644
19704
  // src/lib/daemon-client.ts
@@ -23767,7 +23827,7 @@ __export(exports_downloads, {
23767
23827
  deleteDownload: () => deleteDownload,
23768
23828
  cleanStaleDownloads: () => cleanStaleDownloads
23769
23829
  });
23770
- import { randomUUID as randomUUID10 } from "crypto";
23830
+ import { randomUUID as randomUUID11 } from "crypto";
23771
23831
  import { join as join11, basename, extname } from "path";
23772
23832
  import { mkdirSync as mkdirSync9, existsSync as existsSync8, readdirSync as readdirSync5, statSync as statSync2, unlinkSync as unlinkSync2, copyFileSync, writeFileSync as writeFileSync4, readFileSync as readFileSync6 } from "fs";
23773
23833
  import { homedir as homedir10 } from "os";
@@ -23788,7 +23848,7 @@ function metaPath(filePath) {
23788
23848
  }
23789
23849
  function saveToDownloads(buffer, filename, opts) {
23790
23850
  const dir = getDownloadsDir(opts?.sessionId);
23791
- const id = randomUUID10();
23851
+ const id = randomUUID11();
23792
23852
  const ext = extname(filename) || "";
23793
23853
  const stem = basename(filename, ext);
23794
23854
  const uniqueName = `${stem}-${id.slice(0, 8)}${ext}`;
@@ -24196,10 +24256,10 @@ __export(exports_workflows, {
24196
24256
  getWorkflow: () => getWorkflow,
24197
24257
  deleteWorkflow: () => deleteWorkflow
24198
24258
  });
24199
- import { randomUUID as randomUUID11 } from "crypto";
24259
+ import { randomUUID as randomUUID12 } from "crypto";
24200
24260
  function saveWorkflow(data) {
24201
24261
  const db = getDatabase();
24202
- const id = randomUUID11();
24262
+ const id = randomUUID12();
24203
24263
  db.prepare("INSERT OR REPLACE INTO workflows (id, name, description, steps, start_url) VALUES (?, ?, ?, ?, ?)").run(id, data.name, data.description ?? null, JSON.stringify(data.steps), data.startUrl ?? null);
24204
24264
  return getWorkflow(id);
24205
24265
  }
@@ -24359,10 +24419,10 @@ __export(exports_auth_flow, {
24359
24419
  getAuthFlow: () => getAuthFlow,
24360
24420
  deleteAuthFlow: () => deleteAuthFlow
24361
24421
  });
24362
- import { randomUUID as randomUUID12 } from "crypto";
24422
+ import { randomUUID as randomUUID13 } from "crypto";
24363
24423
  function saveAuthFlow(data) {
24364
24424
  const db = getDatabase();
24365
- const id = randomUUID12();
24425
+ const id = randomUUID13();
24366
24426
  db.prepare("INSERT OR REPLACE INTO auth_flows (id, name, domain, recording_id, storage_state_path) VALUES (?, ?, ?, ?, ?)").run(id, data.name, data.domain, data.recordingId ?? null, data.storageStatePath ?? null);
24367
24427
  return getAuthFlow(id);
24368
24428
  }
@@ -24542,13 +24602,13 @@ __export(exports_datasets, {
24542
24602
  exportDataset: () => exportDataset,
24543
24603
  deleteDataset: () => deleteDataset
24544
24604
  });
24545
- import { randomUUID as randomUUID13 } from "crypto";
24605
+ import { randomUUID as randomUUID14 } from "crypto";
24546
24606
  import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync11 } from "fs";
24547
24607
  import { join as join13 } from "path";
24548
24608
  import { homedir as homedir12 } from "os";
24549
24609
  function saveDataset(data) {
24550
24610
  const db = getDatabase();
24551
- const id = randomUUID13();
24611
+ const id = randomUUID14();
24552
24612
  const existing = db.query("SELECT id FROM datasets WHERE name = ?").get(data.name);
24553
24613
  if (existing) {
24554
24614
  db.prepare("UPDATE datasets SET data = ?, row_count = ?, source_url = ?, schema = ?, last_refresh = datetime('now'), updated_at = datetime('now') WHERE name = ?").run(JSON.stringify(data.rows), data.rows.length, data.sourceUrl ?? null, data.schema ? JSON.stringify(data.schema) : null, data.name);
@@ -29349,7 +29409,7 @@ import { Database as Database3 } from "bun:sqlite";
29349
29409
  import { mkdirSync as mkdirSync13 } from "fs";
29350
29410
  import { join as join15, dirname as dirname6 } from "path";
29351
29411
  import { homedir as homedir14 } from "os";
29352
- import { randomUUID as randomUUID14 } from "crypto";
29412
+ import { randomUUID as randomUUID15 } from "crypto";
29353
29413
  import { mkdirSync as mkdirSync23, copyFileSync as copyFileSync3, statSync as statSync3 } from "fs";
29354
29414
  import { join as join34 } from "path";
29355
29415
  import { homedir as homedir33 } from "os";
@@ -29729,7 +29789,7 @@ function guessMimeType(name) {
29729
29789
  function sendMessage(opts) {
29730
29790
  const db2 = getDb();
29731
29791
  const explicitSession = opts.session_id && opts.session_id.trim().length > 0 ? opts.session_id : undefined;
29732
- const sessionId = explicitSession ?? (opts.space ? `space:${opts.space}` : `${[opts.from, opts.to].sort().join("-")}-${randomUUID14().slice(0, 8)}`);
29792
+ const sessionId = explicitSession ?? (opts.space ? `space:${opts.space}` : `${[opts.from, opts.to].sort().join("-")}-${randomUUID15().slice(0, 8)}`);
29733
29793
  const metadata = opts.metadata ? JSON.stringify(opts.metadata) : null;
29734
29794
  const normalizedPriority = opts.priority === "low" || opts.priority === "normal" || opts.priority === "high" || opts.priority === "urgent" ? opts.priority : "normal";
29735
29795
  const blocking = opts.blocking ? 1 : 0;
@@ -41418,7 +41478,7 @@ __export(exports_cron_manager, {
41418
41478
  deleteCronJob: () => deleteCronJob,
41419
41479
  createCronJob: () => createCronJob
41420
41480
  });
41421
- import { randomUUID as randomUUID15 } from "crypto";
41481
+ import { randomUUID as randomUUID16 } from "crypto";
41422
41482
  function ensureCronTable() {
41423
41483
  const db2 = getDatabase();
41424
41484
  db2.exec(`
@@ -41447,7 +41507,7 @@ function ensureCronTable() {
41447
41507
  function createCronJob(schedule, task, name) {
41448
41508
  ensureCronTable();
41449
41509
  const db2 = getDatabase();
41450
- const id = randomUUID15();
41510
+ const id = randomUUID16();
41451
41511
  db2.prepare(`
41452
41512
  INSERT INTO cron_jobs (id, name, schedule, task_json, enabled)
41453
41513
  VALUES (?, ?, ?, ?, 1)
@@ -41505,7 +41565,7 @@ function getCronEvents(jobId, limit = 10) {
41505
41565
  async function executeCronJob(job) {
41506
41566
  ensureCronTable();
41507
41567
  const db2 = getDatabase();
41508
- const eventId = randomUUID15();
41568
+ const eventId = randomUUID16();
41509
41569
  const startedAt = new Date().toISOString();
41510
41570
  db2.prepare("INSERT INTO cron_events (id, job_id, started_at) VALUES (?, ?, ?)").run(eventId, job.id, startedAt);
41511
41571
  try {
@@ -41596,7 +41656,7 @@ __export(exports_url_watcher, {
41596
41656
  deleteWatchJob: () => deleteWatchJob,
41597
41657
  createWatchJob: () => createWatchJob
41598
41658
  });
41599
- import { randomUUID as randomUUID16 } from "crypto";
41659
+ import { randomUUID as randomUUID17 } from "crypto";
41600
41660
  import { createHash } from "crypto";
41601
41661
  function ensureWatchTables() {
41602
41662
  const db2 = getDatabase();
@@ -41628,7 +41688,7 @@ function ensureWatchTables() {
41628
41688
  function createWatchJob(url, schedule, opts) {
41629
41689
  ensureWatchTables();
41630
41690
  const db2 = getDatabase();
41631
- const id = randomUUID16();
41691
+ const id = randomUUID17();
41632
41692
  db2.prepare(`
41633
41693
  INSERT INTO watch_jobs (id, name, url, schedule, selector, extract_schema, enabled)
41634
41694
  VALUES (?, ?, ?, ?, ?, ?, 1)
@@ -41664,7 +41724,7 @@ function getWatchEvents(watchId, limit = 20) {
41664
41724
  async function checkWatchJob(job) {
41665
41725
  ensureWatchTables();
41666
41726
  const db2 = getDatabase();
41667
- const eventId = randomUUID16();
41727
+ const eventId = randomUUID17();
41668
41728
  const checkedAt = new Date().toISOString();
41669
41729
  let newContent = "";
41670
41730
  try {
@@ -43571,13 +43631,13 @@ var init_mcp = __esm(async () => {
43571
43631
  return err(e);
43572
43632
  }
43573
43633
  });
43574
- server.tool("browser_script_run", "Run a saved login script \u2014 multi-step workflow combining browser actions + connector calls (e.g. magic link login via Gmail). One command, fully automated.", {
43634
+ 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).", {
43575
43635
  name: exports_external.string().describe("Script name (e.g. 'usestable')"),
43576
43636
  session_id: exports_external.string().optional(),
43577
43637
  variables: exports_external.record(exports_external.string()).optional().describe("Override script variables (e.g. {email: 'foo@bar.com'})")
43578
43638
  }, async ({ name, session_id, variables }) => {
43579
43639
  try {
43580
- const { loadScript: loadScript2, runScript: runScript2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
43640
+ const { loadScript: loadScript2, runScriptAsync: runScriptAsync2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
43581
43641
  const script = loadScript2(name);
43582
43642
  if (!script)
43583
43643
  return err(new Error(`Script '${name}' not found. Use browser_script_list to see available scripts.`));
@@ -43587,12 +43647,29 @@ var init_mcp = __esm(async () => {
43587
43647
  sid = resolveSessionId(session_id);
43588
43648
  page = getSessionPage(sid);
43589
43649
  } else {
43590
- const result2 = await createSession2({ headless: true });
43591
- sid = result2.session.id;
43592
- page = result2.page;
43650
+ const result = await createSession2({ headless: true });
43651
+ sid = result.session.id;
43652
+ page = result.page;
43593
43653
  }
43594
- const result = await runScript2(script, page, variables ?? {});
43595
- return json({ ...result, session_id: sid, url: page.url() });
43654
+ const jobId = runScriptAsync2(script, page, variables ?? {});
43655
+ 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." });
43656
+ } catch (e) {
43657
+ return err(e);
43658
+ }
43659
+ });
43660
+ 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 }) => {
43661
+ try {
43662
+ const { getJob: getJob2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
43663
+ const job = getJob2(job_id);
43664
+ if (!job)
43665
+ return err(new Error(`Job '${job_id}' not found`));
43666
+ return json({
43667
+ status: job.status,
43668
+ progress: `${job.current_step}/${job.total_steps}`,
43669
+ current_step: job.current_step_description,
43670
+ steps_log: job.steps_log,
43671
+ result: job.result ?? undefined
43672
+ });
43596
43673
  } catch (e) {
43597
43674
  return err(e);
43598
43675
  }
@@ -44362,10 +44439,10 @@ __export(exports_snapshots, {
44362
44439
  deleteSnapshot: () => deleteSnapshot,
44363
44440
  createSnapshot: () => createSnapshot
44364
44441
  });
44365
- import { randomUUID as randomUUID17 } from "crypto";
44442
+ import { randomUUID as randomUUID18 } from "crypto";
44366
44443
  function createSnapshot(data) {
44367
44444
  const db2 = getDatabase();
44368
- const id = randomUUID17();
44445
+ const id = randomUUID18();
44369
44446
  db2.prepare("INSERT INTO snapshots (id, session_id, url, title, html, screenshot_path) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.session_id, data.url, data.title ?? null, data.html ?? null, data.screenshot_path ?? null);
44370
44447
  return getSnapshot(id);
44371
44448
  }
@@ -45,6 +45,26 @@ export interface ScriptRunResult {
45
45
  errors: string[];
46
46
  duration_ms: number;
47
47
  }
48
+ export interface ScriptJob {
49
+ id: string;
50
+ script_name: string;
51
+ status: "running" | "completed" | "failed";
52
+ current_step: number;
53
+ total_steps: number;
54
+ current_step_description: string;
55
+ steps_log: Array<{
56
+ step: number;
57
+ type: string;
58
+ description: string;
59
+ status: "ok" | "failed" | "running";
60
+ duration_ms?: number;
61
+ error?: string;
62
+ }>;
63
+ result?: ScriptRunResult;
64
+ started_at: string;
65
+ }
66
+ export declare function getJob(jobId: string): ScriptJob | null;
67
+ export declare function listJobs(): ScriptJob[];
48
68
  export declare function saveScript(script: LoginScript): string;
49
69
  export declare function loadScript(name: string): LoginScript | null;
50
70
  export declare function listScripts(): Array<{
@@ -54,6 +74,11 @@ export declare function listScripts(): Array<{
54
74
  steps: number;
55
75
  }>;
56
76
  export declare function deleteScript(name: string): boolean;
57
- export declare function runScript(script: LoginScript, page: Page, overrides?: Record<string, string>): Promise<ScriptRunResult>;
77
+ export declare function runScript(script: LoginScript, page: Page, overrides?: Record<string, string>, jobId?: string): Promise<ScriptRunResult>;
78
+ /**
79
+ * Run a script asynchronously — returns immediately with a job ID.
80
+ * Poll with getJob(jobId) for progress.
81
+ */
82
+ export declare function runScriptAsync(script: LoginScript, page: Page, overrides?: Record<string, string>): string;
58
83
  export declare function createUsestableScript(email: string): LoginScript;
59
84
  //# sourceMappingURL=login-scripts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"login-scripts.d.ts","sourceRoot":"","sources":["../../src/lib/login-scripts.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAIvC,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,YAAY,CAAC;AAEjG,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,QAAQ,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,MAAM,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,YAAY,GAAG,qBAAqB,GAAG,eAAe,GAAG,UAAU,CAAC;IAC7G,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IAGjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAGhB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IAGjB,OAAO,CAAC,EAAE,MAAM,CAAC;IAGjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IAGjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAUD,wBAAgB,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAOtD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAI3D;AAED,wBAAgB,WAAW,IAAI,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAY1G;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMlD;AAYD,wBAAsB,SAAS,CAC7B,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,IAAI,EACV,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACrC,OAAO,CAAC,eAAe,CAAC,CAmE1B;AAmID,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CA2ChE"}
1
+ {"version":3,"file":"login-scripts.d.ts","sourceRoot":"","sources":["../../src/lib/login-scripts.ts"],"names":[],"mappings":"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"}
package/dist/mcp/index.js CHANGED
@@ -17120,14 +17120,24 @@ var init_dist = __esm(() => {
17120
17120
  var exports_login_scripts = {};
17121
17121
  __export(exports_login_scripts, {
17122
17122
  saveScript: () => saveScript,
17123
+ runScriptAsync: () => runScriptAsync,
17123
17124
  runScript: () => runScript,
17124
17125
  loadScript: () => loadScript,
17125
17126
  listScripts: () => listScripts,
17127
+ listJobs: () => listJobs,
17128
+ getJob: () => getJob,
17126
17129
  deleteScript: () => deleteScript,
17127
17130
  createUsestableScript: () => createUsestableScript
17128
17131
  });
17132
+ import { randomUUID as randomUUID12 } from "crypto";
17129
17133
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync10, readdirSync as readdirSync5 } from "fs";
17130
17134
  import { join as join10 } from "path";
17135
+ function getJob(jobId) {
17136
+ return activeJobs.get(jobId) ?? null;
17137
+ }
17138
+ function listJobs() {
17139
+ return Array.from(activeJobs.values());
17140
+ }
17131
17141
  function getScriptsDir() {
17132
17142
  const dir = join10(getDataDir(), "scripts");
17133
17143
  mkdirSync10(dir, { recursive: true });
@@ -17174,15 +17184,34 @@ function interpolate(template, vars) {
17174
17184
  return vars[key] ?? match;
17175
17185
  });
17176
17186
  }
17177
- async function runScript(script, page, overrides = {}) {
17187
+ function stepDescription(step) {
17188
+ return step.description ?? `${step.type}${step.action ? `:${step.action}` : ""}${step.connector ? `:${step.connector}` : ""}`;
17189
+ }
17190
+ async function runScript(script, page, overrides = {}, jobId) {
17178
17191
  const t0 = Date.now();
17179
17192
  const vars = { ...script.variables, ...overrides };
17180
17193
  const errors2 = [];
17181
17194
  let executed = 0;
17182
17195
  let failed = 0;
17196
+ const job = jobId && activeJobs.has(jobId) ? activeJobs.get(jobId) : {
17197
+ id: jobId ?? randomUUID12(),
17198
+ script_name: script.name,
17199
+ status: "running",
17200
+ current_step: 0,
17201
+ total_steps: script.steps.length,
17202
+ current_step_description: "Starting...",
17203
+ steps_log: [],
17204
+ started_at: new Date().toISOString()
17205
+ };
17206
+ activeJobs.set(job.id, job);
17183
17207
  for (let i = 0;i < script.steps.length; i++) {
17184
17208
  const step = script.steps[i];
17209
+ const desc = stepDescription(step);
17185
17210
  executed++;
17211
+ job.current_step = i + 1;
17212
+ job.current_step_description = desc;
17213
+ job.steps_log.push({ step: i + 1, type: step.type, description: desc, status: "running" });
17214
+ const stepStart = Date.now();
17186
17215
  try {
17187
17216
  switch (step.type) {
17188
17217
  case "browser":
@@ -17219,15 +17248,22 @@ async function runScript(script, page, overrides = {}) {
17219
17248
  break;
17220
17249
  }
17221
17250
  }
17251
+ const logEntry = job.steps_log[job.steps_log.length - 1];
17252
+ logEntry.status = "ok";
17253
+ logEntry.duration_ms = Date.now() - stepStart;
17222
17254
  } catch (err) {
17223
17255
  failed++;
17224
17256
  const msg = `Step ${i + 1} (${step.type}/${step.action ?? step.connector ?? ""}): ${err instanceof Error ? err.message : String(err)}`;
17225
17257
  errors2.push(msg);
17258
+ const logEntry = job.steps_log[job.steps_log.length - 1];
17259
+ logEntry.status = "failed";
17260
+ logEntry.error = err instanceof Error ? err.message : String(err);
17261
+ logEntry.duration_ms = Date.now() - stepStart;
17226
17262
  if (step.type === "browser" && step.action === "navigate")
17227
17263
  break;
17228
17264
  }
17229
17265
  }
17230
- return {
17266
+ const result = {
17231
17267
  success: failed === 0,
17232
17268
  steps_executed: executed,
17233
17269
  steps_failed: failed,
@@ -17235,6 +17271,28 @@ async function runScript(script, page, overrides = {}) {
17235
17271
  errors: errors2,
17236
17272
  duration_ms: Date.now() - t0
17237
17273
  };
17274
+ job.status = failed === 0 ? "completed" : "failed";
17275
+ job.result = result;
17276
+ return result;
17277
+ }
17278
+ function runScriptAsync(script, page, overrides = {}) {
17279
+ const jobId = randomUUID12();
17280
+ const job = {
17281
+ id: jobId,
17282
+ script_name: script.name,
17283
+ status: "running",
17284
+ current_step: 0,
17285
+ total_steps: script.steps.length,
17286
+ current_step_description: "Starting...",
17287
+ steps_log: [],
17288
+ started_at: new Date().toISOString()
17289
+ };
17290
+ activeJobs.set(jobId, job);
17291
+ runScript(script, page, overrides, jobId).catch((err) => {
17292
+ job.status = "failed";
17293
+ job.current_step_description = `Fatal error: ${err instanceof Error ? err.message : String(err)}`;
17294
+ });
17295
+ return jobId;
17238
17296
  }
17239
17297
  async function runBrowserStep(step, page, vars) {
17240
17298
  const action = step.action;
@@ -17394,8 +17452,10 @@ function createUsestableScript(email) {
17394
17452
  updated_at: new Date().toISOString()
17395
17453
  };
17396
17454
  }
17455
+ var activeJobs;
17397
17456
  var init_login_scripts = __esm(() => {
17398
17457
  init_schema();
17458
+ activeJobs = new Map;
17399
17459
  });
17400
17460
 
17401
17461
  // src/lib/api-detector.ts
@@ -17404,7 +17464,7 @@ __export(exports_api_detector, {
17404
17464
  listDiscoveredAPIs: () => listDiscoveredAPIs,
17405
17465
  detectAPIs: () => detectAPIs
17406
17466
  });
17407
- import { randomUUID as randomUUID12 } from "crypto";
17467
+ import { randomUUID as randomUUID13 } from "crypto";
17408
17468
  function detectAPIs(sessionId) {
17409
17469
  const db = getDatabase();
17410
17470
  const requests = db.query(`SELECT method, url, status_code, response_headers, body_size
@@ -17440,7 +17500,7 @@ function detectAPIs(sessionId) {
17440
17500
  }
17441
17501
  const apis = Array.from(seen.values());
17442
17502
  for (const api of apis) {
17443
- const id = randomUUID12();
17503
+ const id = randomUUID13();
17444
17504
  db.prepare("INSERT OR IGNORE INTO api_endpoints (id, session_id, url, method, status_code, content_type) VALUES (?, ?, ?, ?, ?, ?)").run(id, sessionId, api.url, api.method, api.status_code, api.content_type);
17445
17505
  }
17446
17506
  return apis;
@@ -17545,13 +17605,13 @@ __export(exports_datasets, {
17545
17605
  exportDataset: () => exportDataset,
17546
17606
  deleteDataset: () => deleteDataset
17547
17607
  });
17548
- import { randomUUID as randomUUID13 } from "crypto";
17608
+ import { randomUUID as randomUUID14 } from "crypto";
17549
17609
  import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync11 } from "fs";
17550
17610
  import { join as join11 } from "path";
17551
17611
  import { homedir as homedir10 } from "os";
17552
17612
  function saveDataset(data) {
17553
17613
  const db = getDatabase();
17554
- const id = randomUUID13();
17614
+ const id = randomUUID14();
17555
17615
  const existing = db.query("SELECT id FROM datasets WHERE name = ?").get(data.name);
17556
17616
  if (existing) {
17557
17617
  db.prepare("UPDATE datasets SET data = ?, row_count = ?, source_url = ?, schema = ?, last_refresh = datetime('now'), updated_at = datetime('now') WHERE name = ?").run(JSON.stringify(data.rows), data.rows.length, data.sourceUrl ?? null, data.schema ? JSON.stringify(data.schema) : null, data.name);
@@ -22442,7 +22502,7 @@ import { Database as Database3 } from "bun:sqlite";
22442
22502
  import { mkdirSync as mkdirSync13 } from "fs";
22443
22503
  import { join as join14, dirname as dirname6 } from "path";
22444
22504
  import { homedir as homedir13 } from "os";
22445
- import { randomUUID as randomUUID14 } from "crypto";
22505
+ import { randomUUID as randomUUID15 } from "crypto";
22446
22506
  import { mkdirSync as mkdirSync23, copyFileSync as copyFileSync3, statSync as statSync3 } from "fs";
22447
22507
  import { join as join34 } from "path";
22448
22508
  import { homedir as homedir33 } from "os";
@@ -22822,7 +22882,7 @@ function guessMimeType(name) {
22822
22882
  function sendMessage(opts) {
22823
22883
  const db2 = getDb();
22824
22884
  const explicitSession = opts.session_id && opts.session_id.trim().length > 0 ? opts.session_id : undefined;
22825
- const sessionId = explicitSession ?? (opts.space ? `space:${opts.space}` : `${[opts.from, opts.to].sort().join("-")}-${randomUUID14().slice(0, 8)}`);
22885
+ const sessionId = explicitSession ?? (opts.space ? `space:${opts.space}` : `${[opts.from, opts.to].sort().join("-")}-${randomUUID15().slice(0, 8)}`);
22826
22886
  const metadata = opts.metadata ? JSON.stringify(opts.metadata) : null;
22827
22887
  const normalizedPriority = opts.priority === "low" || opts.priority === "normal" || opts.priority === "high" || opts.priority === "urgent" ? opts.priority : "normal";
22828
22888
  const blocking = opts.blocking ? 1 : 0;
@@ -34511,7 +34571,7 @@ __export(exports_cron_manager, {
34511
34571
  deleteCronJob: () => deleteCronJob,
34512
34572
  createCronJob: () => createCronJob
34513
34573
  });
34514
- import { randomUUID as randomUUID15 } from "crypto";
34574
+ import { randomUUID as randomUUID16 } from "crypto";
34515
34575
  function ensureCronTable() {
34516
34576
  const db2 = getDatabase();
34517
34577
  db2.exec(`
@@ -34540,7 +34600,7 @@ function ensureCronTable() {
34540
34600
  function createCronJob(schedule, task, name) {
34541
34601
  ensureCronTable();
34542
34602
  const db2 = getDatabase();
34543
- const id = randomUUID15();
34603
+ const id = randomUUID16();
34544
34604
  db2.prepare(`
34545
34605
  INSERT INTO cron_jobs (id, name, schedule, task_json, enabled)
34546
34606
  VALUES (?, ?, ?, ?, 1)
@@ -34598,7 +34658,7 @@ function getCronEvents(jobId, limit = 10) {
34598
34658
  async function executeCronJob(job) {
34599
34659
  ensureCronTable();
34600
34660
  const db2 = getDatabase();
34601
- const eventId = randomUUID15();
34661
+ const eventId = randomUUID16();
34602
34662
  const startedAt = new Date().toISOString();
34603
34663
  db2.prepare("INSERT INTO cron_events (id, job_id, started_at) VALUES (?, ?, ?)").run(eventId, job.id, startedAt);
34604
34664
  try {
@@ -34689,7 +34749,7 @@ __export(exports_url_watcher, {
34689
34749
  deleteWatchJob: () => deleteWatchJob,
34690
34750
  createWatchJob: () => createWatchJob
34691
34751
  });
34692
- import { randomUUID as randomUUID16 } from "crypto";
34752
+ import { randomUUID as randomUUID17 } from "crypto";
34693
34753
  import { createHash } from "crypto";
34694
34754
  function ensureWatchTables() {
34695
34755
  const db2 = getDatabase();
@@ -34721,7 +34781,7 @@ function ensureWatchTables() {
34721
34781
  function createWatchJob(url, schedule, opts) {
34722
34782
  ensureWatchTables();
34723
34783
  const db2 = getDatabase();
34724
- const id = randomUUID16();
34784
+ const id = randomUUID17();
34725
34785
  db2.prepare(`
34726
34786
  INSERT INTO watch_jobs (id, name, url, schedule, selector, extract_schema, enabled)
34727
34787
  VALUES (?, ?, ?, ?, ?, ?, 1)
@@ -34757,7 +34817,7 @@ function getWatchEvents(watchId, limit = 20) {
34757
34817
  async function checkWatchJob(job) {
34758
34818
  ensureWatchTables();
34759
34819
  const db2 = getDatabase();
34760
- const eventId = randomUUID16();
34820
+ const eventId = randomUUID17();
34761
34821
  const checkedAt = new Date().toISOString();
34762
34822
  let newContent = "";
34763
34823
  try {
@@ -41384,13 +41444,13 @@ server.tool("browser_profile_delete", "Delete a saved browser profile", { name:
41384
41444
  return err(e);
41385
41445
  }
41386
41446
  });
41387
- server.tool("browser_script_run", "Run a saved login script \u2014 multi-step workflow combining browser actions + connector calls (e.g. magic link login via Gmail). One command, fully automated.", {
41447
+ 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).", {
41388
41448
  name: exports_external.string().describe("Script name (e.g. 'usestable')"),
41389
41449
  session_id: exports_external.string().optional(),
41390
41450
  variables: exports_external.record(exports_external.string()).optional().describe("Override script variables (e.g. {email: 'foo@bar.com'})")
41391
41451
  }, async ({ name, session_id, variables }) => {
41392
41452
  try {
41393
- const { loadScript: loadScript2, runScript: runScript2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
41453
+ const { loadScript: loadScript2, runScriptAsync: runScriptAsync2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
41394
41454
  const script = loadScript2(name);
41395
41455
  if (!script)
41396
41456
  return err(new Error(`Script '${name}' not found. Use browser_script_list to see available scripts.`));
@@ -41400,12 +41460,29 @@ server.tool("browser_script_run", "Run a saved login script \u2014 multi-step wo
41400
41460
  sid = resolveSessionId(session_id);
41401
41461
  page = getSessionPage(sid);
41402
41462
  } else {
41403
- const result2 = await createSession2({ headless: true });
41404
- sid = result2.session.id;
41405
- page = result2.page;
41463
+ const result = await createSession2({ headless: true });
41464
+ sid = result.session.id;
41465
+ page = result.page;
41406
41466
  }
41407
- const result = await runScript2(script, page, variables ?? {});
41408
- return json({ ...result, session_id: sid, url: page.url() });
41467
+ const jobId = runScriptAsync2(script, page, variables ?? {});
41468
+ 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." });
41469
+ } catch (e) {
41470
+ return err(e);
41471
+ }
41472
+ });
41473
+ 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 }) => {
41474
+ try {
41475
+ const { getJob: getJob2 } = await Promise.resolve().then(() => (init_login_scripts(), exports_login_scripts));
41476
+ const job = getJob2(job_id);
41477
+ if (!job)
41478
+ return err(new Error(`Job '${job_id}' not found`));
41479
+ return json({
41480
+ status: job.status,
41481
+ progress: `${job.current_step}/${job.total_steps}`,
41482
+ current_step: job.current_step_description,
41483
+ steps_log: job.steps_log,
41484
+ result: job.result ?? undefined
41485
+ });
41409
41486
  } catch (e) {
41410
41487
  return err(e);
41411
41488
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/browser",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "General-purpose browser agent toolkit — Playwright, Chrome DevTools Protocol, Lightpanda with auto engine selection. CLI + MCP + REST + SDK.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",