viyv-browser-mcp 0.5.4 → 0.5.6

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/index.js CHANGED
@@ -27,7 +27,15 @@ var TIMEOUTS = {
27
27
  /** CDP idle detach delay */
28
28
  CDP_IDLE_DETACH: 5e3,
29
29
  /** Tab lock TTL (deadlock prevention) */
30
- TAB_LOCK_TTL: 6e4
30
+ TAB_LOCK_TTL: 6e4,
31
+ /** Default scenario max_duration_ms (30 min) */
32
+ SCENARIO_MAX_DURATION: 18e5,
33
+ /** Buffer added to scenario max_duration for MCP timeout */
34
+ MCP_SCENARIO_BUFFER: 6e4,
35
+ /** MCP timeout for sm_job_run with wait_for_completion (4 hours) */
36
+ MCP_JOB_WAIT: 4 * 60 * 60 * 1e3,
37
+ /** Dialog pause timeout (2 min) — auto-fail if no resume */
38
+ DIALOG_PAUSE_TIMEOUT: 12e4
31
39
  };
32
40
  var LIMITS = {
33
41
  /** Native Messaging max message size (Chrome limit) */
@@ -93,12 +101,13 @@ function decompressPayload(data, isCompressed) {
93
101
  }
94
102
 
95
103
  // src/native-host/transport.ts
96
- var MAX_MESSAGE_SIZE = 1024 * 1024;
104
+ var MAX_OUTBOUND_SIZE = 1024 * 1024;
105
+ var MAX_INBOUND_SIZE = 100 * 1024 * 1024;
97
106
  function encodeMessage(message) {
98
107
  const json = JSON.stringify(message);
99
108
  const body = Buffer.from(json, "utf-8");
100
- if (body.length > MAX_MESSAGE_SIZE) {
101
- throw new Error(`Message too large: ${body.length} bytes (max ${MAX_MESSAGE_SIZE})`);
109
+ if (body.length > MAX_OUTBOUND_SIZE) {
110
+ throw new Error(`Message too large: ${body.length} bytes (max ${MAX_OUTBOUND_SIZE})`);
102
111
  }
103
112
  const header = Buffer.alloc(4);
104
113
  header.writeUInt32LE(body.length, 0);
@@ -114,8 +123,8 @@ function createMessageReader(stream, onMessage, onError, onClose) {
114
123
  if (buffer.length < 4) break;
115
124
  expectedLength = buffer.readUInt32LE(0);
116
125
  buffer = buffer.subarray(4);
117
- if (expectedLength > MAX_MESSAGE_SIZE) {
118
- onError?.(new Error(`Message too large: ${expectedLength} bytes`));
126
+ if (expectedLength > MAX_INBOUND_SIZE) {
127
+ onError?.(new Error(`Message too large: ${expectedLength} bytes (max ${MAX_INBOUND_SIZE})`));
119
128
  expectedLength = null;
120
129
  buffer = Buffer.alloc(0);
121
130
  break;
@@ -297,16 +306,27 @@ function startBridge(options) {
297
306
  const chrome = chromeConnections.get(chromeId);
298
307
  if (chrome) {
299
308
  chrome.send(message);
309
+ return;
310
+ }
311
+ const fallback = chromeConnections.values().next().value;
312
+ if (fallback) {
313
+ process.stderr.write(
314
+ `${LOG_PREFIX2} WARNING: '${chromeId}' not found, using ${fallback.chromeId}
315
+ `
316
+ );
317
+ fallback.send(message);
300
318
  } else {
301
319
  process.stderr.write(
302
- `${LOG_PREFIX2} WARNING: chromeId '${chromeId}' not found, falling back to primary
320
+ `${LOG_PREFIX2} WARNING: No Chrome connections available, dropping message
303
321
  `
304
322
  );
305
- chromeConnections.get(primaryChromeId)?.send(message);
306
323
  }
307
324
  }
308
325
  function getChromeForAgent(agentId) {
309
- return agentToChrome.get(agentId) ?? primaryChromeId;
326
+ const mapped = agentToChrome.get(agentId);
327
+ if (mapped && chromeConnections.has(mapped)) return mapped;
328
+ const first = chromeConnections.keys().next().value;
329
+ return first ?? primaryChromeId;
310
330
  }
311
331
  function connIdForAgent(agentId) {
312
332
  return agentToConn.get(agentId);
@@ -373,12 +393,17 @@ function startBridge(options) {
373
393
  agentToConn.set(agentId, connId);
374
394
  mcpConnections.get(connId)?.agentIds.add(agentId);
375
395
  const chromeProfile = message.chromeProfile;
376
- const resolvedChrome = chromeProfile && chromeConnections.has(chromeProfile) ? chromeProfile : primaryChromeId;
377
- if (chromeProfile && !chromeConnections.has(chromeProfile)) {
378
- process.stderr.write(
379
- `${LOG_PREFIX2} WARNING: chromeProfile '${chromeProfile}' not found, using primary
396
+ let resolvedChrome;
397
+ if (chromeProfile && chromeConnections.has(chromeProfile)) {
398
+ resolvedChrome = chromeProfile;
399
+ } else {
400
+ if (chromeProfile) {
401
+ process.stderr.write(
402
+ `${LOG_PREFIX2} WARNING: chromeProfile '${chromeProfile}' not found, using default
380
403
  `
381
- );
404
+ );
405
+ }
406
+ resolvedChrome = chromeConnections.keys().next().value ?? primaryChromeId;
382
407
  }
383
408
  agentToChrome.set(agentId, resolvedChrome);
384
409
  chromeConnections.get(resolvedChrome)?.agentIds.add(agentId);
@@ -455,6 +480,75 @@ function startBridge(options) {
455
480
  `);
456
481
  }
457
482
  }
483
+ function removeChromeConnection(chromeId) {
484
+ const chrome = chromeConnections.get(chromeId);
485
+ if (!chrome) return;
486
+ for (const agentId of [...chrome.agentIds]) {
487
+ const connId = connIdForAgent(agentId);
488
+ if (connId) {
489
+ sendToMcp(connId, {
490
+ id: randomUUID(),
491
+ type: "session_close",
492
+ agentId,
493
+ timestamp: Date.now()
494
+ });
495
+ cleanupAgent(agentId, connId);
496
+ } else {
497
+ agentToChrome.delete(agentId);
498
+ }
499
+ }
500
+ chromeConnections.delete(chromeId);
501
+ broadcastChromeProfiles();
502
+ }
503
+ let primaryStdinClosed = false;
504
+ const ORPHAN_TIMEOUT = 6e4;
505
+ let orphanTimer = null;
506
+ function gracefulExit(reason) {
507
+ process.stderr.write(`${LOG_PREFIX2} ${reason}, exiting
508
+ `);
509
+ if (orphanTimer) {
510
+ clearTimeout(orphanTimer);
511
+ orphanTimer = null;
512
+ }
513
+ const closingMsg = JSON.stringify({ type: "bridge_closing", timestamp: Date.now() });
514
+ for (const [, conn] of mcpConnections) {
515
+ try {
516
+ conn.socket.write(`${closingMsg}
517
+ `);
518
+ } catch {
519
+ }
520
+ }
521
+ for (const [, conn] of mcpConnections) {
522
+ conn.socket.destroy();
523
+ }
524
+ mcpConnections.clear();
525
+ agentToConn.clear();
526
+ agentToChrome.clear();
527
+ chromeConnections.clear();
528
+ requestOrigin.clear();
529
+ clearInterval(cleanupTimer2);
530
+ server?.close();
531
+ process.exit(0);
532
+ }
533
+ function maybeExitIfEmpty() {
534
+ if (!primaryStdinClosed) return;
535
+ if (chromeConnections.size > 0 && orphanTimer) {
536
+ clearTimeout(orphanTimer);
537
+ orphanTimer = null;
538
+ return;
539
+ }
540
+ if (chromeConnections.size === 0 && mcpConnections.size === 0) {
541
+ gracefulExit("No connections remaining");
542
+ }
543
+ if (chromeConnections.size === 0 && !orphanTimer) {
544
+ orphanTimer = setTimeout(() => {
545
+ orphanTimer = null;
546
+ if (chromeConnections.size === 0) {
547
+ gracefulExit(`No Chrome reconnected within ${ORPHAN_TIMEOUT}ms`);
548
+ }
549
+ }, ORPHAN_TIMEOUT);
550
+ }
551
+ }
458
552
  function setupRelayConnection(socket) {
459
553
  const chromeId = `chrome-${nextChromeIndex++}`;
460
554
  const chrome = {
@@ -483,6 +577,7 @@ function startBridge(options) {
483
577
  chrome.send({ type: "bridge_status", connected: true, timestamp: Date.now() });
484
578
  }
485
579
  broadcastChromeProfiles();
580
+ maybeExitIfEmpty();
486
581
  const processData = createTcpLineReader((message) => handleChromeMessage(message), onError);
487
582
  socket.on("data", processData);
488
583
  socket.on("error", (error) => {
@@ -494,22 +589,8 @@ function startBridge(options) {
494
589
  `${LOG_PREFIX2} Chrome relay disconnected: ${chromeId} (remaining chromes: ${chromeConnections.size - 1})
495
590
  `
496
591
  );
497
- for (const agentId of [...chrome.agentIds]) {
498
- const connId = connIdForAgent(agentId);
499
- if (connId) {
500
- sendToMcp(connId, {
501
- id: randomUUID(),
502
- type: "session_close",
503
- agentId,
504
- timestamp: Date.now()
505
- });
506
- cleanupAgent(agentId, connId);
507
- } else {
508
- agentToChrome.delete(agentId);
509
- }
510
- }
511
- chromeConnections.delete(chromeId);
512
- broadcastChromeProfiles();
592
+ removeChromeConnection(chromeId);
593
+ maybeExitIfEmpty();
513
594
  });
514
595
  }
515
596
  function handleTcpConnection(socket) {
@@ -616,6 +697,7 @@ function startBridge(options) {
616
697
  if (mcpConnections.size === 0) {
617
698
  notifyChromeConnected(false);
618
699
  }
700
+ maybeExitIfEmpty();
619
701
  });
620
702
  }
621
703
  server = createServer((socket) => handleTcpConnection(socket));
@@ -643,49 +725,24 @@ function startBridge(options) {
643
725
  onError
644
726
  );
645
727
  notifyChromeConnected(false);
646
- function shutdown() {
647
- const closingMsg = JSON.stringify({ type: "bridge_closing", timestamp: Date.now() });
648
- for (const [, conn] of mcpConnections) {
649
- try {
650
- conn.socket.write(`${closingMsg}
651
- `);
652
- } catch {
653
- }
654
- }
655
- for (const [, conn] of mcpConnections) {
656
- conn.socket.destroy();
657
- }
658
- mcpConnections.clear();
659
- agentToConn.clear();
660
- agentToChrome.clear();
661
- chromeConnections.clear();
662
- requestOrigin.clear();
663
- clearInterval(cleanupTimer2);
664
- server?.close();
665
- server = null;
666
- }
667
- process.on("SIGINT", () => {
668
- shutdown();
669
- process.exit(0);
670
- });
671
- process.on("SIGTERM", () => {
672
- shutdown();
673
- process.exit(0);
674
- });
728
+ process.on("SIGINT", () => gracefulExit("SIGINT received"));
729
+ process.on("SIGTERM", () => gracefulExit("SIGTERM received"));
675
730
  process.stdin.on("end", () => {
676
- process.stderr.write(`${LOG_PREFIX2} stdin closed, shutting down
731
+ process.stderr.write(`${LOG_PREFIX2} Primary Chrome stdin closed
677
732
  `);
678
- shutdown();
679
- process.exit(0);
733
+ primaryStdinClosed = true;
734
+ removeChromeConnection(primaryChromeId);
735
+ maybeExitIfEmpty();
680
736
  });
681
737
  });
682
738
  }
683
739
 
684
740
  // src/server.ts
685
741
  import { randomUUID as randomUUID3 } from "crypto";
686
- import { existsSync, statSync } from "fs";
742
+ import { existsSync, mkdirSync, statSync, writeFileSync } from "fs";
687
743
  import http from "http";
688
744
  import { createConnection as createConnection2 } from "net";
745
+ import { dirname } from "path";
689
746
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
690
747
  import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
691
748
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -919,10 +976,16 @@ Options:
919
976
  - maxChars: Limit output size (default: 50000). Use 100000 for full page content.`;
920
977
 
921
978
  // src/tools/core/handle-dialog.ts
922
- var HANDLE_DIALOG_DESCRIPTION = `Handle JavaScript dialogs such as alert, confirm, and prompt.
923
- Allows accepting or dismissing the dialog, and optionally providing
924
- input text for prompt dialogs. Must be called while a dialog
925
- is actively displayed on the page.`;
979
+ var HANDLE_DIALOG_DESCRIPTION = `Handle JavaScript dialogs (alert, confirm, prompt).
980
+
981
+ Two modes:
982
+ 1. Handle current dialog: provide action ('accept'/'dismiss') and optional text
983
+ 2. Set next policy: provide nextPolicy to pre-configure how the next
984
+ auto-handled dialog will be processed. The policy is one-shot \u2014
985
+ consumed after a single dialog, then reverts to default behavior.
986
+
987
+ Use mode 2 after seeing _auto_handled_dialogs in a tool result to change
988
+ how the next dialog will be handled when you re-trigger the action.`;
926
989
 
927
990
  // src/tools/core/hover.ts
928
991
  var HOVER_DESCRIPTION = `Move mouse to coordinates or element without clicking.
@@ -1039,38 +1102,6 @@ Returns details of HTTP requests and responses including URL, method,
1039
1102
  status code, headers, and timing information. Supports filtering
1040
1103
  by URL pattern or resource type.`;
1041
1104
 
1042
- // src/tools/sheets/descriptions.ts
1043
- var SHEETS_READ_DESCRIPTION = `Read data from the active Google Sheets spreadsheet.
1044
-
1045
- Uses the gviz/tq endpoint with session cookies \u2014 works with private sheets without OAuth.
1046
- Supports range selection, tq query language for filtering/aggregation, and row limits.
1047
-
1048
- tq query examples:
1049
- - "SELECT A, B WHERE C > 100" \u2014 filter rows
1050
- - "SELECT A, SUM(B) GROUP BY A" \u2014 aggregate
1051
- - "SELECT * ORDER BY C DESC LIMIT 5" \u2014 sort and limit
1052
-
1053
- Set formulas=true to read raw formulas (e.g., =IMAGE(), =HYPERLINK()) instead of computed values.
1054
- Formula mode reads cell-by-cell via the formula bar, limited to 200 cells.
1055
- The tab must be on a Google Sheets page (docs.google.com/spreadsheets/).`;
1056
- var SHEETS_WRITE_DESCRIPTION = `Write data to the active Google Sheets spreadsheet.
1057
-
1058
- For single cell: navigates to the cell via Name Box and types the value.
1059
- For multiple cells: converts 2D array to TSV, copies to clipboard via Offscreen Document, then pastes with Ctrl+V. Sheets auto-expands TSV into multiple cells.
1060
-
1061
- Provide either "value" (single cell string) or "values" (2D array), not both.
1062
- The tab must be on a Google Sheets page.`;
1063
- var SHEETS_INFO_DESCRIPTION = `Get metadata about the active Google Sheets spreadsheet.
1064
-
1065
- Returns spreadsheet ID, document title, sheet list (name, index, row/col counts, active status), active cell reference, and URL.
1066
- Uses DOM inspection and gviz/tq queries for sheet dimensions.
1067
- The tab must be on a Google Sheets page.`;
1068
- var SHEETS_NAVIGATE_DESCRIPTION = `Navigate within the active Google Sheets spreadsheet.
1069
-
1070
- Jump to a specific cell (e.g., "A1", "Z100") via the Name Box, or switch to a different sheet tab by name.
1071
- Both cell and sheet can be specified together.
1072
- The tab must be on a Google Sheets page.`;
1073
-
1074
1105
  // src/tools/semantic/sm-add-action.ts
1075
1106
  var SM_ADD_ACTION_DESCRIPTION = `Define a semantic action (step sequence) for a registered page.
1076
1107
 
@@ -1151,6 +1182,10 @@ var SM_CAPABILITIES_DESCRIPTION = `Query semantic capabilities available for the
1151
1182
  Returns the matched page definition, available actions, and fetches for the active tab.
1152
1183
  Uses page signature matching (URL pattern + DOM markers) to identify the current page state.
1153
1184
 
1185
+ Parameters:
1186
+ - tabId: target tab
1187
+ - include_other_pages (optional): include list of other registered pages on the same domain (default: false). Enable when looking for related pages to navigate to.
1188
+
1154
1189
  Returns:
1155
1190
  - ok: whether a page match was found
1156
1191
  - domain: hostname of the current tab
@@ -1158,7 +1193,7 @@ Returns:
1158
1193
  - match_score: confidence score of the match
1159
1194
  - actions[]: available actions with their parameters
1160
1195
  - fetches[]: available data extractions with their parameters
1161
- - other_pages[]: other registered pages on the same domain (for cross-page navigation)
1196
+ - other_pages[]: (only when include_other_pages=true) other registered pages on the same domain
1162
1197
  - hint: guidance message when no page matches
1163
1198
 
1164
1199
  Use this before sm_invoke or sm_fetch to discover what operations are available.
@@ -1245,15 +1280,52 @@ Returns:
1245
1280
  - cascaded: (page deletion) lists of deleted targets, actions, fetches
1246
1281
  - warnings: (target deletion) referencing actions/fetches that may break`;
1247
1282
 
1283
+ // src/tools/semantic/sm-execution-resume.ts
1284
+ var SM_EXECUTION_RESUME_DESCRIPTION = `Resume a paused scenario/job execution.
1285
+
1286
+ When execution is paused (e.g. a browser dialog appeared), use this tool to decide how to proceed.
1287
+ Check sm_execution_status or sm_job_report_get (status 'dialog_paused') to detect pauses.
1288
+
1289
+ Actions:
1290
+ - continue: Dialog already handled, continue to next step
1291
+ - retry: Set dialog policy and re-execute the step that triggered the dialog
1292
+ - skip_iteration: Skip to next loop iteration (only inside a loop)
1293
+ - skip_scenario: Skip entire scenario (marks as cancelled)
1294
+ - fail: Fail the current step (scenario continues or stops based on on_error)
1295
+
1296
+ Returns execution result or another dialog_paused if a new dialog appears.`;
1297
+
1298
+ // src/tools/semantic/sm-execution-status.ts
1299
+ var SM_EXECUTION_STATUS_DESCRIPTION = `Check what is currently executing on a tab.
1300
+
1301
+ Returns the execution type (job/scenario/none), IDs, progress, and pause state.
1302
+ Use this to see if a tab is busy before starting new work, or to get IDs for polling.
1303
+
1304
+ Parameters:
1305
+ - tabId: tab to check
1306
+
1307
+ Returns:
1308
+ - type: 'job' | 'scenario' | 'none'
1309
+ - label: execution label
1310
+ - started_at: execution start timestamp
1311
+ - job_id, job_report_id: (job only) IDs for polling with sm_job_report_get
1312
+ - scenario_id, report_id: (standalone scenario only) IDs for polling with sm_report_get
1313
+ - current_scenario: (job only) currently running scenario {scenario_id, scenario_label, report_id}
1314
+ - progress: (job only) {current_entry_index, total_entries, entries_completed}
1315
+ - is_paused: whether execution is paused due to dialog
1316
+ - pause_info: dialog details if paused`;
1317
+
1248
1318
  // src/tools/semantic/sm-export.ts
1249
1319
  var SM_EXPORT_DESCRIPTION = `Export semantic configuration as JSON.
1250
1320
 
1251
- Exports pages, targets, actions, and fetches. Optionally filter by domain.
1321
+ Exports pages, targets, actions, and fetches scoped by the current tab's page.
1252
1322
  Secret parameter default values are stripped from the export.
1253
1323
  Includes a SHA-256 checksum for integrity verification on import.
1254
1324
 
1255
1325
  Parameters:
1256
- - domain (optional): export only pages matching this domain (subdomain-aware). If omitted, exports all.
1326
+ - tabId (required): Tab ID to identify the current page
1327
+ - scope (optional): "page" (default) exports only the current page's config, "domain" exports all pages for the tab's domain, "all" exports everything
1328
+ - file_path (optional): save to this path and return metadata only (keeps data out of context)
1257
1329
 
1258
1330
  Returns:
1259
1331
  - SemanticExport JSON with schema_version, checksum, pages[], targets[], actions[], fetches[]`;
@@ -1378,7 +1450,10 @@ Parameters:
1378
1450
 
1379
1451
  Returns:
1380
1452
  - job_id, job_label
1381
- - snapshots[]: list of {snapshot_id, status, started_at, completed_at, duration_ms, scenario_count}`;
1453
+ - snapshots[]: list of {snapshot_id, job_report_id, status, started_at, completed_at, duration_ms, scenario_count, scenarios[]}
1454
+ - job_report_id: use with sm_job_report_get for detailed results
1455
+ - scenarios[]: per-scenario {scenario_id, scenario_label, report_id, status}
1456
+ - report_id: use with sm_report_get or sm_report_export for individual scenario data`;
1382
1457
  var SM_JOB_COMPARE_DESCRIPTION = `Compare two job executions to see data changes between them.
1383
1458
 
1384
1459
  Matches scenarios by scenario_id, data groups by result_key, and rows by loop_params. Computes per-cell diffs with numeric deltas.
@@ -1398,9 +1473,10 @@ Use this to inspect the full captured data for a single execution, including all
1398
1473
 
1399
1474
  Parameters:
1400
1475
  - snapshot_id: the snapshot to retrieve (SNP_xxxx)
1476
+ - summary_only: if true, omit data_groups from scenario snapshots to reduce payload size (default: false)
1401
1477
 
1402
1478
  Returns:
1403
- - snapshot: full JobSnapshot with scenario_snapshots and data_groups`;
1479
+ - snapshot: full JobSnapshot with scenario_snapshots and data_groups (or empty data_groups if summary_only)`;
1404
1480
 
1405
1481
  // src/tools/semantic/sm-job-list.ts
1406
1482
  var SM_JOB_LIST_DESCRIPTION = `List all defined jobs with entry counts and configuration.
@@ -1429,6 +1505,11 @@ var SM_JOB_REPORT_GET_DESCRIPTION = `Get job execution status and results.
1429
1505
  Shows overall progress and per-scenario report summaries.
1430
1506
  Use sm_report_get with individual report_ids for detailed scenario data.
1431
1507
 
1508
+ Poll this after sm_job_run to track progress:
1509
+ - status 'running': job is still executing
1510
+ - status 'dialog_paused': a browser dialog appeared, use sm_execution_resume to continue
1511
+ - status 'completed'/'failed': job finished
1512
+
1432
1513
  Parameters:
1433
1514
  - job_report_id: job report ID (JRP_xxxx)
1434
1515
 
@@ -1439,8 +1520,7 @@ Returns:
1439
1520
  // src/tools/semantic/sm-job-run.ts
1440
1521
  var SM_JOB_RUN_DESCRIPTION = `Execute a job on a browser tab. Scenarios run sequentially.
1441
1522
 
1442
- Returns job_report_id immediately by default. Poll with sm_job_report_get to check progress.
1443
- Set wait_for_completion=true to block until all scenarios finish.
1523
+ Always returns immediately with job_report_id. Poll sm_job_report_get to check progress.
1444
1524
 
1445
1525
  Only one job or scenario can run per tab at a time.
1446
1526
 
@@ -1448,12 +1528,16 @@ Parameters:
1448
1528
  - tabId: target tab for execution
1449
1529
  - job_id: the job to execute
1450
1530
  - params: optional per-scenario param overrides keyed by scenario_id
1451
- - wait_for_completion: if true, wait for all scenarios to complete (default: false)
1452
1531
 
1453
1532
  Returns:
1454
- - job_report_id: ID of the created job report (JRP_xxxx)
1533
+ - job_report_id: ID of the created job report (JRP_xxxx) \u2014 use with sm_job_report_get to poll
1455
1534
  - job_id: the executed job
1456
- - status: 'running' (async) or 'completed'/'failed' (when wait_for_completion=true)`;
1535
+ - status: 'running' | 'failed' (failed = could not start)
1536
+
1537
+ Workflow:
1538
+ 1. sm_job_run \u2192 get job_report_id
1539
+ 2. sm_job_report_get(job_report_id) \u2192 poll until status is 'completed'/'failed'
1540
+ 3. If status is 'dialog_paused', use sm_execution_resume to continue`;
1457
1541
 
1458
1542
  // src/tools/semantic/sm-job-update.ts
1459
1543
  var SM_JOB_UPDATE_DESCRIPTION = `Update an existing job definition.
@@ -1627,12 +1711,17 @@ Parameters:
1627
1711
  - scenario_id: the scenario to execute
1628
1712
  - params: parameter values for template substitution
1629
1713
  - wait_for_completion: if true, wait for scenario to finish (up to max_duration_ms)
1714
+ - max_duration_ms (optional): override scenario timeout in milliseconds (default: scenario's own value, typically 1800000ms / 30min)
1630
1715
 
1631
1716
  Returns:
1632
1717
  - report_id: ID of the created report (RPT_xxxx)
1633
1718
  - scenario_id: the executed scenario
1634
1719
  - status: 'running' (async) or 'completed'/'failed' (when wait_for_completion=true)
1635
- - error: error message if startup failed`;
1720
+ - error: error message if startup failed
1721
+
1722
+ When wait_for_completion=true, may return status 'dialog_paused' if a browser dialog
1723
+ appeared during execution. The dialog was auto-handled to unblock the page.
1724
+ Use sm_execution_resume to decide how to proceed.`;
1636
1725
 
1637
1726
  // src/tools/semantic/sm-scenario-update.ts
1638
1727
  var SM_SCENARIO_UPDATE_DESCRIPTION = `Update an existing scenario.
@@ -1663,14 +1752,16 @@ Parameters:
1663
1752
  - page_id (optional): filter results to a specific page
1664
1753
  - domain (optional): filter results to pages matching this domain
1665
1754
  - test_resolve (optional): if true, resolve all targets on the current tab
1755
+ - summary_only (optional): return only summary counts and page list, omitting targets/actions/fetches/integrity arrays (default: true)
1666
1756
 
1667
1757
  Returns:
1668
1758
  - summary: {total_pages, total_targets, total_actions, total_fetches}
1669
1759
  - pages[]: page definitions with page_type, domains, url_patterns, and counts
1670
- - targets[]: targets with locator info and optional resolve_result
1671
- - actions[]: actions with step count and param names
1672
- - fetches[]: fetches with field count
1673
- - integrity[]: referential integrity warnings`;
1760
+ - When summary_only is false, also includes:
1761
+ - targets[]: targets with locator info and optional resolve_result
1762
+ - actions[]: actions with step count and param names
1763
+ - fetches[]: fetches with field count
1764
+ - integrity[]: referential integrity warnings`;
1674
1765
 
1675
1766
  // src/tools/semantic/sm-test-target.ts
1676
1767
  var SM_TEST_TARGET_DESCRIPTION = `Test a specific target's locators against the current page.
@@ -1688,6 +1779,38 @@ Returns:
1688
1779
  - element: resolved element info {tag, text, rect} if found
1689
1780
  - test_status: updated test status saved to storage`;
1690
1781
 
1782
+ // src/tools/sheets/descriptions.ts
1783
+ var SHEETS_READ_DESCRIPTION = `Read data from the active Google Sheets spreadsheet.
1784
+
1785
+ Uses the gviz/tq endpoint with session cookies \u2014 works with private sheets without OAuth.
1786
+ Supports range selection, tq query language for filtering/aggregation, and row limits.
1787
+
1788
+ tq query examples:
1789
+ - "SELECT A, B WHERE C > 100" \u2014 filter rows
1790
+ - "SELECT A, SUM(B) GROUP BY A" \u2014 aggregate
1791
+ - "SELECT * ORDER BY C DESC LIMIT 5" \u2014 sort and limit
1792
+
1793
+ Set formulas=true to read raw formulas (e.g., =IMAGE(), =HYPERLINK()) instead of computed values.
1794
+ Formula mode reads cell-by-cell via the formula bar, limited to 200 cells.
1795
+ The tab must be on a Google Sheets page (docs.google.com/spreadsheets/).`;
1796
+ var SHEETS_WRITE_DESCRIPTION = `Write data to the active Google Sheets spreadsheet.
1797
+
1798
+ For single cell: navigates to the cell via Name Box and types the value.
1799
+ For multiple cells: converts 2D array to TSV, copies to clipboard via Offscreen Document, then pastes with Ctrl+V. Sheets auto-expands TSV into multiple cells.
1800
+
1801
+ Provide either "value" (single cell string) or "values" (2D array), not both.
1802
+ The tab must be on a Google Sheets page.`;
1803
+ var SHEETS_INFO_DESCRIPTION = `Get metadata about the active Google Sheets spreadsheet.
1804
+
1805
+ Returns spreadsheet ID, document title, sheet list (name, index, row/col counts, active status), active cell reference, and URL.
1806
+ Uses DOM inspection and gviz/tq queries for sheet dimensions.
1807
+ The tab must be on a Google Sheets page.`;
1808
+ var SHEETS_NAVIGATE_DESCRIPTION = `Navigate within the active Google Sheets spreadsheet.
1809
+
1810
+ Jump to a specific cell (e.g., "A1", "Z100") via the Name Box, or switch to a different sheet tab by name.
1811
+ Both cell and sheet can be specified together.
1812
+ The tab must be on a Google Sheets page.`;
1813
+
1691
1814
  // src/tools/tabs/select-tab.ts
1692
1815
  var SELECT_TAB_DESCRIPTION = `Switch focus to a specific tab by its identifier.
1693
1816
  Makes the target tab the active tab in the agent's group,
@@ -1930,8 +2053,12 @@ var handleDialogTool = {
1930
2053
  description: HANDLE_DIALOG_DESCRIPTION,
1931
2054
  inputSchema: z.object({
1932
2055
  tabId: z.coerce.number().describe("Tab ID"),
1933
- action: z.enum(["accept", "dismiss"]).describe("Dialog action"),
1934
- text: z.string().optional().describe("Text for prompt dialog")
2056
+ action: z.enum(["accept", "dismiss"]).optional().describe("Dialog action (required when handling current dialog)"),
2057
+ text: z.string().optional().describe("Text for prompt dialog"),
2058
+ nextPolicy: z.object({
2059
+ action: z.enum(["accept", "dismiss"]).describe("Action for next dialog"),
2060
+ text: z.string().optional().describe("Text for prompt dialog")
2061
+ }).optional().describe("Set one-shot policy for the next auto-handled dialog. Consumed after one use.")
1935
2062
  })
1936
2063
  };
1937
2064
  var tabsContextTool = {
@@ -2116,7 +2243,8 @@ var smCapabilitiesTool = {
2116
2243
  name: "sm_capabilities",
2117
2244
  description: SM_CAPABILITIES_DESCRIPTION,
2118
2245
  inputSchema: z.object({
2119
- tabId: z.coerce.number().describe("Tab ID")
2246
+ tabId: z.coerce.number().describe("Tab ID"),
2247
+ include_other_pages: z.boolean().optional().describe("Include list of other registered pages on the same domain (default: false)")
2120
2248
  })
2121
2249
  };
2122
2250
  var smInvokeTool = {
@@ -2209,7 +2337,17 @@ var smAddActionTool = {
2209
2337
  ).optional().describe("Parameter definitions"),
2210
2338
  steps: z.array(
2211
2339
  z.object({
2212
- type: z.enum(["click", "type", "select", "wait", "scroll", "key", "navigate", "file_upload", "script"]).describe("Step type"),
2340
+ type: z.enum([
2341
+ "click",
2342
+ "type",
2343
+ "select",
2344
+ "wait",
2345
+ "scroll",
2346
+ "key",
2347
+ "navigate",
2348
+ "file_upload",
2349
+ "script"
2350
+ ]).describe("Step type"),
2213
2351
  target_id: z.string().optional().describe("Target to act on"),
2214
2352
  params: z.object({
2215
2353
  text: z.string().optional(),
@@ -2277,7 +2415,8 @@ var smStatusTool = {
2277
2415
  tabId: z.coerce.number().optional().describe("Tab ID (required for test_resolve)"),
2278
2416
  page_id: z.string().optional().describe("Filter by page ID"),
2279
2417
  domain: z.string().optional().describe("Filter results to pages matching this domain"),
2280
- test_resolve: z.boolean().optional().describe("Test-resolve all targets")
2418
+ test_resolve: z.boolean().optional().describe("Test-resolve all targets"),
2419
+ summary_only: z.boolean().optional().describe("Return only summary counts and page list, omitting detail arrays (default: true)")
2281
2420
  })
2282
2421
  };
2283
2422
  var smTestTargetTool = {
@@ -2301,7 +2440,9 @@ var smExportTool = {
2301
2440
  name: "sm_export",
2302
2441
  description: SM_EXPORT_DESCRIPTION,
2303
2442
  inputSchema: z.object({
2304
- domain: z.string().optional().describe("Export only pages matching this domain (subdomain-aware). Omit for all.")
2443
+ tabId: z.coerce.number().describe("Tab ID to identify the current page"),
2444
+ scope: z.enum(["page", "domain", "all"]).optional().describe("Export scope (default: page)"),
2445
+ file_path: z.string().optional().describe("Save to this path and return metadata only (keeps data out of context)")
2305
2446
  })
2306
2447
  };
2307
2448
  var smImportTool = {
@@ -2432,7 +2573,8 @@ var smScenarioRunTool = {
2432
2573
  tabId: z.coerce.number().describe("Tab ID for execution"),
2433
2574
  scenario_id: z.string().describe("Scenario to execute"),
2434
2575
  params: z.record(z.unknown()).optional().describe("Parameter values"),
2435
- wait_for_completion: z.boolean().optional().describe("Wait for completion (default: false, async)")
2576
+ wait_for_completion: z.boolean().optional().describe("Wait for completion (default: false, async)"),
2577
+ max_duration_ms: z.coerce.number().optional().describe("Override scenario timeout in ms (default: scenario value, typically 1800000)")
2436
2578
  })
2437
2579
  };
2438
2580
  var smReportListTool = {
@@ -2462,7 +2604,8 @@ var smReportExportTool = {
2462
2604
  description: SM_REPORT_EXPORT_DESCRIPTION,
2463
2605
  inputSchema: z.object({
2464
2606
  report_id: z.string().describe("Report ID to export"),
2465
- format: z.enum(["json", "csv"]).optional().describe("Export format (default: json)")
2607
+ format: z.enum(["json", "csv"]).optional().describe("Export format (default: json)"),
2608
+ file_path: z.string().optional().describe("Save to this path and return metadata only (keeps data out of context)")
2466
2609
  })
2467
2610
  };
2468
2611
  var jobEntrySchema = z.object({
@@ -2509,8 +2652,7 @@ var smJobRunTool = {
2509
2652
  inputSchema: z.object({
2510
2653
  tabId: z.coerce.number().describe("Tab ID to execute scenarios on"),
2511
2654
  job_id: z.string().describe("Job ID to execute"),
2512
- params: z.record(z.record(z.unknown())).optional().describe("Per-scenario param overrides keyed by scenario_id"),
2513
- wait_for_completion: z.boolean().optional().describe("Wait for all scenarios to complete (default: false)")
2655
+ params: z.record(z.record(z.unknown())).optional().describe("Per-scenario param overrides keyed by scenario_id")
2514
2656
  })
2515
2657
  };
2516
2658
  var smJobCancelTool = {
@@ -2520,6 +2662,17 @@ var smJobCancelTool = {
2520
2662
  tabId: z.coerce.number().describe("Tab ID where the job is running")
2521
2663
  })
2522
2664
  };
2665
+ var smExecutionResumeTool = {
2666
+ name: "sm_execution_resume",
2667
+ description: SM_EXECUTION_RESUME_DESCRIPTION,
2668
+ inputSchema: z.object({
2669
+ tabId: z.coerce.number().describe("Tab ID where execution is paused"),
2670
+ action: z.enum(["continue", "retry", "skip_iteration", "skip_scenario", "fail"]).describe("How to proceed after the dialog"),
2671
+ accept: z.boolean().optional().describe("For retry: accept (true) or dismiss (false) the dialog"),
2672
+ prompt_text: z.string().optional().describe("For retry on prompt dialogs: text to enter"),
2673
+ wait_for_completion: z.boolean().optional().default(true).describe("Wait for execution to finish or hit another pause")
2674
+ })
2675
+ };
2523
2676
  var smJobReportGetTool = {
2524
2677
  name: "sm_job_report_get",
2525
2678
  description: SM_JOB_REPORT_GET_DESCRIPTION,
@@ -2532,7 +2685,8 @@ var smJobReportExportTool = {
2532
2685
  description: SM_JOB_REPORT_EXPORT_DESCRIPTION,
2533
2686
  inputSchema: z.object({
2534
2687
  job_report_id: z.string().describe("Job report ID to export"),
2535
- format: z.enum(["json", "csv"]).optional().describe("Export format (default: json)")
2688
+ format: z.enum(["json", "csv"]).optional().describe("Export format (default: json)"),
2689
+ file_path: z.string().optional().describe("Save to this path and return metadata only (keeps data out of context)")
2536
2690
  })
2537
2691
  };
2538
2692
  var smJobHistoryTool = {
@@ -2557,7 +2711,15 @@ var smJobSnapshotGetTool = {
2557
2711
  name: "sm_job_snapshot_get",
2558
2712
  description: SM_JOB_SNAPSHOT_GET_DESCRIPTION,
2559
2713
  inputSchema: z.object({
2560
- snapshot_id: z.string().describe("Snapshot ID to retrieve (SNP_xxxx)")
2714
+ snapshot_id: z.string().describe("Snapshot ID to retrieve (SNP_xxxx)"),
2715
+ summary_only: z.boolean().optional().describe("If true, omit data_groups from scenario snapshots to reduce payload")
2716
+ })
2717
+ };
2718
+ var smExecutionStatusTool = {
2719
+ name: "sm_execution_status",
2720
+ description: SM_EXECUTION_STATUS_DESCRIPTION,
2721
+ inputSchema: z.object({
2722
+ tabId: z.coerce.number().describe("Tab ID to check execution status")
2561
2723
  })
2562
2724
  };
2563
2725
  var dataBindingSchema = z.object({
@@ -2724,12 +2886,15 @@ var allTools = [
2724
2886
  smJobListTool,
2725
2887
  smJobRunTool,
2726
2888
  smJobCancelTool,
2889
+ smExecutionResumeTool,
2727
2890
  smJobReportGetTool,
2728
2891
  smJobReportExportTool,
2729
2892
  // Job History (3)
2730
2893
  smJobHistoryTool,
2731
2894
  smJobCompareTool,
2732
2895
  smJobSnapshotGetTool,
2896
+ // Execution Status (1)
2897
+ smExecutionStatusTool,
2733
2898
  // Custom View (5)
2734
2899
  smCustomViewCreateTool,
2735
2900
  smCustomViewUpdateTool,
@@ -2743,6 +2908,25 @@ var allTools = [
2743
2908
  sheetsNavigateTool
2744
2909
  ];
2745
2910
 
2911
+ // src/tools/timeout.ts
2912
+ var FILE_EXPORT_TOOLS = /* @__PURE__ */ new Set(["sm_report_export", "sm_job_report_export", "sm_export"]);
2913
+ function computeToolTimeout(tool, input) {
2914
+ if (tool === "wait_for" && typeof input.timeout === "number") {
2915
+ return input.timeout + 5e3;
2916
+ }
2917
+ if (FILE_EXPORT_TOOLS.has(tool)) {
2918
+ return 3e5;
2919
+ }
2920
+ if (tool === "sm_scenario_run" && input.wait_for_completion === true) {
2921
+ const maxDuration = typeof input.max_duration_ms === "number" ? input.max_duration_ms : TIMEOUTS.SCENARIO_MAX_DURATION;
2922
+ return maxDuration + TIMEOUTS.MCP_SCENARIO_BUFFER;
2923
+ }
2924
+ if (tool === "sm_execution_resume" && input.wait_for_completion !== false) {
2925
+ return TIMEOUTS.MCP_JOB_WAIT;
2926
+ }
2927
+ return TIMEOUTS.MCP_TOOL;
2928
+ }
2929
+
2746
2930
  // src/server.ts
2747
2931
  var pendingRequests = /* @__PURE__ */ new Map();
2748
2932
  var extensionSocket = null;
@@ -2766,6 +2950,31 @@ function coerceShape(shape) {
2766
2950
  }
2767
2951
  return result;
2768
2952
  }
2953
+ function handleFileExport(filePath, result) {
2954
+ mkdirSync(dirname(filePath), { recursive: true });
2955
+ let content;
2956
+ let metadata;
2957
+ if (typeof result.data === "string") {
2958
+ const { data, ...rest } = result;
2959
+ content = data;
2960
+ metadata = { ...rest, file_path: filePath };
2961
+ if (result.format === "csv") {
2962
+ const lines = content.split("\n").filter((l) => l.trim());
2963
+ const headerLine = lines[0]?.replace(/^\uFEFF/, "") ?? "";
2964
+ const columns = headerLine.split(",").map((h) => h.replace(/^"|"$/g, "").trim());
2965
+ metadata.row_count = lines.length - 1;
2966
+ metadata.column_count = columns.length;
2967
+ metadata.columns = columns;
2968
+ }
2969
+ } else {
2970
+ content = JSON.stringify(result, null, 2);
2971
+ const pages = Array.isArray(result.pages) ? result.pages : [];
2972
+ metadata = { ok: true, file_path: filePath, page_count: pages.length };
2973
+ }
2974
+ writeFileSync(filePath, content, "utf-8");
2975
+ metadata.file_size = Buffer.byteLength(content, "utf-8");
2976
+ return metadata;
2977
+ }
2769
2978
  function createConfiguredMcpServer() {
2770
2979
  const server = new McpServer({
2771
2980
  name: MCP_SERVER.NAME,
@@ -3318,6 +3527,29 @@ function handleExtensionMessage(message) {
3318
3527
  });
3319
3528
  }
3320
3529
  }
3530
+ function formatAutoHandledDialogs(result) {
3531
+ const dialogs = result._auto_handled_dialogs;
3532
+ if (!dialogs?.length) return null;
3533
+ const lines = dialogs.map((d) => {
3534
+ const action = d.action === "accept" ? "accepted" : "dismissed";
3535
+ return `- [${d.type}] ${action}: "${d.message}"`;
3536
+ });
3537
+ return [
3538
+ "[Auto-handled dialog] A browser dialog appeared during this operation and was automatically handled:",
3539
+ ...lines,
3540
+ "Use handle_dialog with nextPolicy to change how future dialogs are handled."
3541
+ ].join("\n");
3542
+ }
3543
+ function formatDialogPaused(result) {
3544
+ if (result.status !== "dialog_paused") return null;
3545
+ const dp = result.dialog_pause;
3546
+ if (!dp) return null;
3547
+ return [
3548
+ `[Execution Paused] A ${dp.dialog_type} dialog appeared during "${dp.step_context}":`,
3549
+ `"${dp.dialog_message}" (auto-${dp.auto_action}ed)`,
3550
+ "Use sm_execution_resume to decide: continue, retry, skip_iteration, skip_scenario, or fail."
3551
+ ].join("\n");
3552
+ }
3321
3553
  async function callExtensionTool(tool, input) {
3322
3554
  if (tool === "browser_health") {
3323
3555
  return {
@@ -3383,10 +3615,7 @@ async function callExtensionTool(tool, input) {
3383
3615
  const agentId = getDefaultAgentId();
3384
3616
  touchSession(agentId);
3385
3617
  const sock = extensionSocket;
3386
- let toolTimeout = TIMEOUTS.MCP_TOOL;
3387
- if (tool === "wait_for" && typeof input.timeout === "number") {
3388
- toolTimeout = input.timeout + 5e3;
3389
- }
3618
+ const toolTimeout = computeToolTimeout(tool, input);
3390
3619
  return new Promise((resolve2) => {
3391
3620
  const onError = () => {
3392
3621
  const pending = pendingRequests.get(requestId);
@@ -3428,22 +3657,53 @@ async function callExtensionTool(tool, input) {
3428
3657
  ]
3429
3658
  });
3430
3659
  }, toolTimeout);
3660
+ const { chromeProfile, file_path: filePath, ...cleanInput } = input;
3431
3661
  pendingRequests.set(requestId, {
3432
3662
  resolve: (result) => {
3433
3663
  removeErrorListener();
3664
+ if (FILE_EXPORT_TOOLS.has(tool) && typeof filePath === "string" && ("data" in result || "pages" in result)) {
3665
+ try {
3666
+ const metadata = handleFileExport(filePath, result);
3667
+ resolve2({
3668
+ content: [{ type: "text", text: JSON.stringify(metadata) }]
3669
+ });
3670
+ } catch (e) {
3671
+ const msg = e instanceof Error ? e.message : String(e);
3672
+ resolve2({
3673
+ content: [
3674
+ {
3675
+ type: "text",
3676
+ text: JSON.stringify({
3677
+ error: {
3678
+ code: "FILE_WRITE_ERROR",
3679
+ message: `Failed to write ${filePath}: ${msg}`
3680
+ }
3681
+ })
3682
+ }
3683
+ ]
3684
+ });
3685
+ }
3686
+ return;
3687
+ }
3688
+ const dialogNote = formatAutoHandledDialogs(result);
3689
+ const pauseNote = formatDialogPaused(result);
3434
3690
  if (tool === "screenshot" && typeof result.data === "string") {
3435
3691
  const { data, ...metadata } = result;
3436
3692
  const mimeType = metadata.format === "png" ? "image/png" : "image/jpeg";
3437
- resolve2({
3438
- content: [
3439
- { type: "image", data, mimeType },
3440
- { type: "text", text: JSON.stringify(metadata) }
3441
- ]
3442
- });
3693
+ const content = [
3694
+ { type: "image", data, mimeType },
3695
+ { type: "text", text: JSON.stringify(metadata) }
3696
+ ];
3697
+ if (dialogNote) content.push({ type: "text", text: dialogNote });
3698
+ if (pauseNote) content.push({ type: "text", text: pauseNote });
3699
+ resolve2({ content });
3443
3700
  } else {
3444
- resolve2({
3445
- content: [{ type: "text", text: JSON.stringify(result) }]
3446
- });
3701
+ const content = [
3702
+ { type: "text", text: JSON.stringify(result) }
3703
+ ];
3704
+ if (dialogNote) content.push({ type: "text", text: dialogNote });
3705
+ if (pauseNote) content.push({ type: "text", text: pauseNote });
3706
+ resolve2({ content });
3447
3707
  }
3448
3708
  },
3449
3709
  reject: (error) => {
@@ -3461,7 +3721,6 @@ async function callExtensionTool(tool, input) {
3461
3721
  },
3462
3722
  timer
3463
3723
  });
3464
- const { chromeProfile, ...cleanInput } = input;
3465
3724
  const request = {
3466
3725
  id: requestId,
3467
3726
  type: "tool_call",
@@ -3540,9 +3799,9 @@ async function handleSwitchBrowser() {
3540
3799
 
3541
3800
  // src/setup.ts
3542
3801
  import { execSync } from "child_process";
3543
- import { chmodSync, existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
3802
+ import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "fs";
3544
3803
  import { homedir, platform } from "os";
3545
- import { dirname, resolve } from "path";
3804
+ import { dirname as dirname2, resolve } from "path";
3546
3805
  function runSetup(options = {}) {
3547
3806
  const os = platform();
3548
3807
  const binaryPath = getBinaryPath();
@@ -3569,11 +3828,11 @@ function runSetup(options = {}) {
3569
3828
  allowed_origins: allowedOrigins
3570
3829
  };
3571
3830
  const manifestPath = getManifestPath(os);
3572
- const manifestDir = dirname(manifestPath);
3831
+ const manifestDir = dirname2(manifestPath);
3573
3832
  console.log(`Wrapper: ${wrapperPath}`);
3574
3833
  console.log(`Manifest path: ${manifestPath}`);
3575
- mkdirSync(manifestDir, { recursive: true });
3576
- writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
3834
+ mkdirSync2(manifestDir, { recursive: true });
3835
+ writeFileSync2(manifestPath, JSON.stringify(manifest, null, 2));
3577
3836
  chmodSync(manifestPath, 420);
3578
3837
  console.log("\nNative Messaging Host registered successfully!");
3579
3838
  console.log("\nNext steps:");
@@ -3595,18 +3854,18 @@ function getBinaryPath() {
3595
3854
  return resolve(currentScript);
3596
3855
  }
3597
3856
  function createNativeHostWrapper(os, binaryPath) {
3598
- const manifestDir = dirname(getManifestPath(os));
3599
- mkdirSync(manifestDir, { recursive: true });
3857
+ const manifestDir = dirname2(getManifestPath(os));
3858
+ mkdirSync2(manifestDir, { recursive: true });
3600
3859
  const nodePath = getNodePath();
3601
3860
  if (os === "win32") {
3602
3861
  const wrapperPath2 = resolve(manifestDir, `${NATIVE_HOST_NAME}.bat`);
3603
- writeFileSync(wrapperPath2, `@echo off\r
3862
+ writeFileSync2(wrapperPath2, `@echo off\r
3604
3863
  "${nodePath}" "${binaryPath}" --native-host\r
3605
3864
  `);
3606
3865
  return wrapperPath2;
3607
3866
  }
3608
3867
  const wrapperPath = resolve(manifestDir, `${NATIVE_HOST_NAME}.sh`);
3609
- writeFileSync(wrapperPath, `#!/bin/bash
3868
+ writeFileSync2(wrapperPath, `#!/bin/bash
3610
3869
  exec "${nodePath}" "${binaryPath}" --native-host
3611
3870
  `);
3612
3871
  chmodSync(wrapperPath, 493);
@@ -3664,7 +3923,7 @@ function setupClaudeDesktopConfig() {
3664
3923
  if (!config.mcpServers || typeof config.mcpServers !== "object") {
3665
3924
  config.mcpServers = {};
3666
3925
  }
3667
- const nodeBinDir = dirname(getNodePath());
3926
+ const nodeBinDir = dirname2(getNodePath());
3668
3927
  const defaultPath = "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
3669
3928
  const envPath = nodeBinDir ? `${nodeBinDir}:${defaultPath}` : defaultPath;
3670
3929
  config.mcpServers["viyv-browser"] = {
@@ -3672,9 +3931,9 @@ function setupClaudeDesktopConfig() {
3672
3931
  args: ["-y", "viyv-browser-mcp@latest"],
3673
3932
  env: { PATH: envPath }
3674
3933
  };
3675
- const configDir = dirname(configPath);
3676
- mkdirSync(configDir, { recursive: true });
3677
- writeFileSync(configPath, JSON.stringify(config, null, 2));
3934
+ const configDir = dirname2(configPath);
3935
+ mkdirSync2(configDir, { recursive: true });
3936
+ writeFileSync2(configPath, JSON.stringify(config, null, 2));
3678
3937
  console.log(`npx path: ${npxPath}`);
3679
3938
  console.log("\nClaude Desktop config updated successfully!");
3680
3939
  console.log("Please restart Claude Desktop for changes to take effect.");