@browserstack/mcp-server 1.0.14 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/config.js +6 -2
  2. package/dist/index.js +34 -31
  3. package/dist/lib/api.js +9 -3
  4. package/dist/lib/constants.js +6 -3
  5. package/dist/lib/device-cache.js +21 -14
  6. package/dist/lib/error.js +6 -3
  7. package/dist/lib/fuzzy.js +4 -1
  8. package/dist/lib/inmemory-store.js +4 -1
  9. package/dist/lib/instrumentation.js +18 -12
  10. package/dist/lib/local.js +35 -27
  11. package/dist/lib/utils.js +15 -6
  12. package/dist/logger.js +6 -4
  13. package/dist/tools/accessibility.js +54 -18
  14. package/dist/tools/accessiblity-utils/report-fetcher.js +35 -0
  15. package/dist/tools/accessiblity-utils/report-parser.js +57 -0
  16. package/dist/tools/accessiblity-utils/scanner.js +87 -0
  17. package/dist/tools/appautomate-utils/appautomate.js +27 -17
  18. package/dist/tools/appautomate.js +35 -29
  19. package/dist/tools/applive-utils/fuzzy-search.js +6 -3
  20. package/dist/tools/applive-utils/start-session.js +20 -14
  21. package/dist/tools/applive-utils/upload-app.js +51 -12
  22. package/dist/tools/applive.js +25 -18
  23. package/dist/tools/automate-utils/fetch-screenshots.js +14 -8
  24. package/dist/tools/automate.js +21 -14
  25. package/dist/tools/bstack-sdk.js +18 -14
  26. package/dist/tools/failurelogs-utils/app-automate.js +26 -15
  27. package/dist/tools/failurelogs-utils/automate.js +23 -13
  28. package/dist/tools/getFailureLogs.js +49 -42
  29. package/dist/tools/live-utils/desktop-filter.js +11 -8
  30. package/dist/tools/live-utils/mobile-filter.js +10 -7
  31. package/dist/tools/live-utils/start-session.js +23 -17
  32. package/dist/tools/live-utils/types.js +5 -2
  33. package/dist/tools/live-utils/version-resolver.js +4 -1
  34. package/dist/tools/live.js +29 -23
  35. package/dist/tools/observability.js +19 -12
  36. package/dist/tools/sdk-utils/constants.js +9 -3
  37. package/dist/tools/sdk-utils/instructions.js +9 -4
  38. package/dist/tools/sdk-utils/types.js +2 -1
  39. package/dist/tools/testmanagement-utils/TCG-utils/api.js +38 -26
  40. package/dist/tools/testmanagement-utils/TCG-utils/config.js +10 -5
  41. package/dist/tools/testmanagement-utils/TCG-utils/helpers.js +8 -3
  42. package/dist/tools/testmanagement-utils/TCG-utils/types.js +8 -5
  43. package/dist/tools/testmanagement-utils/add-test-result.js +24 -17
  44. package/dist/tools/testmanagement-utils/create-project-folder.js +28 -21
  45. package/dist/tools/testmanagement-utils/create-testcase.js +38 -30
  46. package/dist/tools/testmanagement-utils/create-testrun.js +30 -23
  47. package/dist/tools/testmanagement-utils/list-testcases.js +22 -15
  48. package/dist/tools/testmanagement-utils/list-testruns.js +19 -12
  49. package/dist/tools/testmanagement-utils/testcase-from-file.js +22 -16
  50. package/dist/tools/testmanagement-utils/update-testrun.js +22 -15
  51. package/dist/tools/testmanagement-utils/upload-file.js +29 -22
  52. package/dist/tools/testmanagement.js +76 -61
  53. package/package.json +4 -2
  54. package/dist/tools/accessiblity-utils/accessibility.js +0 -74
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.parseAccessibilityReportFromCSV = parseAccessibilityReportFromCSV;
7
+ const node_fetch_1 = __importDefault(require("node-fetch"));
8
+ const sync_1 = require("csv-parse/sync");
9
+ async function parseAccessibilityReportFromCSV(reportLink, { maxCharacterLength = 10_000, nextPage = 0 } = {}) {
10
+ // 1) Download & parse
11
+ const res = await (0, node_fetch_1.default)(reportLink);
12
+ if (!res.ok)
13
+ throw new Error(`Failed to download report: ${res.statusText}`);
14
+ const text = await res.text();
15
+ const all = (0, sync_1.parse)(text, {
16
+ columns: true,
17
+ skip_empty_lines: true,
18
+ }).map((row) => ({
19
+ issue_type: row["Issue type"],
20
+ component: row["Component"],
21
+ issue_description: row["Issue description"],
22
+ HTML_snippet: row["HTML snippet"],
23
+ how_to_fix: row["How to fix this issue"],
24
+ severity: (row["Severity"] || "unknown").trim(),
25
+ }));
26
+ // 2) Sort by severity
27
+ const order = {
28
+ critical: 0,
29
+ serious: 1,
30
+ moderate: 2,
31
+ minor: 3,
32
+ };
33
+ all.sort((a, b) => (order[a.severity] ?? 99) - (order[b.severity] ?? 99));
34
+ // 3) Walk to the starting offset
35
+ let charCursor = 0;
36
+ let idx = 0;
37
+ for (; idx < all.length; idx++) {
38
+ const len = JSON.stringify(all[idx]).length;
39
+ if (charCursor + len > nextPage)
40
+ break;
41
+ charCursor += len;
42
+ }
43
+ // 4) Collect up to maxCharacterLength
44
+ const page = [];
45
+ for (let i = idx; i < all.length; i++) {
46
+ const recStr = JSON.stringify(all[i]);
47
+ if (charCursor - nextPage + recStr.length > maxCharacterLength)
48
+ break;
49
+ page.push(all[i]);
50
+ charCursor += recStr.length;
51
+ }
52
+ const hasMore = idx + page.length < all.length;
53
+ return {
54
+ records: page,
55
+ next_page: hasMore ? charCursor : null,
56
+ };
57
+ }
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.AccessibilityScanner = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const config_js_1 = __importDefault(require("../../config.js"));
9
+ class AccessibilityScanner {
10
+ auth = {
11
+ username: config_js_1.default.browserstackUsername,
12
+ password: config_js_1.default.browserstackAccessKey,
13
+ };
14
+ async startScan(name, urlList) {
15
+ try {
16
+ const { data } = await axios_1.default.post("https://api-accessibility.browserstack.com/api/website-scanner/v1/scans", { name, urlList, recurring: false }, { auth: this.auth });
17
+ if (!data.success)
18
+ throw new Error(`Unable to start scan: ${data.errors?.join(", ")}`);
19
+ return data;
20
+ }
21
+ catch (err) {
22
+ if (axios_1.default.isAxiosError(err) && err.response?.data) {
23
+ const msg = err.response.data.error ||
24
+ err.response.data.message ||
25
+ err.message;
26
+ throw new Error(`Failed to start scan: ${msg}`);
27
+ }
28
+ throw err;
29
+ }
30
+ }
31
+ async pollStatus(scanId, scanRunId) {
32
+ try {
33
+ const { data } = await axios_1.default.get(`https://api-accessibility.browserstack.com/api/website-scanner/v1/scans/${scanId}/scan_runs/${scanRunId}/status`, { auth: this.auth });
34
+ if (!data.success)
35
+ throw new Error(`Failed to get status: ${data.errors?.join(", ")}`);
36
+ return data;
37
+ }
38
+ catch (err) {
39
+ if (axios_1.default.isAxiosError(err) && err.response?.data) {
40
+ const msg = err.response.data.message || err.message;
41
+ throw new Error(`Failed to get scan status: ${msg}`);
42
+ }
43
+ throw err;
44
+ }
45
+ }
46
+ async waitUntilComplete(scanId, scanRunId, context) {
47
+ return new Promise((resolve, reject) => {
48
+ let timepercent = 0;
49
+ let dotCount = 1;
50
+ const interval = setInterval(async () => {
51
+ try {
52
+ const statusResp = await this.pollStatus(scanId, scanRunId);
53
+ const status = statusResp.data.status;
54
+ timepercent += 1.67;
55
+ const progress = status === "completed" ? 100 : timepercent;
56
+ const dots = ".".repeat(dotCount);
57
+ dotCount = (dotCount % 4) + 1;
58
+ const message = status === "completed" || status === "failed"
59
+ ? `Scan completed with status: ${status}`
60
+ : `Scan in progress${dots}`;
61
+ await context.sendNotification({
62
+ method: "notifications/progress",
63
+ params: {
64
+ progressToken: context._meta?.progressToken ?? "NOT_FOUND",
65
+ message: message,
66
+ progress: progress,
67
+ total: 100,
68
+ },
69
+ });
70
+ if (status === "completed" || status === "failed") {
71
+ clearInterval(interval);
72
+ resolve(status);
73
+ }
74
+ }
75
+ catch (e) {
76
+ clearInterval(interval);
77
+ reject(e);
78
+ }
79
+ }, 5000);
80
+ setTimeout(() => {
81
+ clearInterval(interval);
82
+ reject(new Error("Scan timed out after 5 minutes"));
83
+ }, 5 * 60 * 1000);
84
+ });
85
+ }
86
+ }
87
+ exports.AccessibilityScanner = AccessibilityScanner;
@@ -1,14 +1,24 @@
1
- import fs from "fs";
2
- import axios from "axios";
3
- import config from "../../config.js";
4
- import FormData from "form-data";
5
- import { customFuzzySearch } from "../../lib/fuzzy.js";
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.findMatchingDevice = findMatchingDevice;
7
+ exports.getDeviceVersions = getDeviceVersions;
8
+ exports.resolveVersion = resolveVersion;
9
+ exports.validateArgs = validateArgs;
10
+ exports.uploadApp = uploadApp;
11
+ const fs_1 = __importDefault(require("fs"));
12
+ const axios_1 = __importDefault(require("axios"));
13
+ const config_js_1 = __importDefault(require("../../config.js"));
14
+ const form_data_1 = __importDefault(require("form-data"));
15
+ const fuzzy_js_1 = require("../../lib/fuzzy.js");
6
16
  /**
7
17
  * Finds devices that exactly match the provided display name.
8
18
  * Uses fuzzy search first, and then filters for exact case-insensitive match.
9
19
  */
10
- export function findMatchingDevice(devices, deviceName) {
11
- const matches = customFuzzySearch(devices, ["display_name"], deviceName, 5);
20
+ function findMatchingDevice(devices, deviceName) {
21
+ const matches = (0, fuzzy_js_1.customFuzzySearch)(devices, ["display_name"], deviceName, 5);
12
22
  if (matches.length === 0) {
13
23
  const availableDevices = [
14
24
  ...new Set(devices.map((d) => d.display_name)),
@@ -25,14 +35,14 @@ export function findMatchingDevice(devices, deviceName) {
25
35
  /**
26
36
  * Extracts all unique OS versions from a device list and sorts them.
27
37
  */
28
- export function getDeviceVersions(devices) {
38
+ function getDeviceVersions(devices) {
29
39
  return [...new Set(devices.map((d) => d.os_version))].sort();
30
40
  }
31
41
  /**
32
42
  * Resolves the requested platform version against available versions.
33
43
  * Supports 'latest' and 'oldest' as dynamic selectors.
34
44
  */
35
- export function resolveVersion(versions, requestedVersion) {
45
+ function resolveVersion(versions, requestedVersion) {
36
46
  if (requestedVersion === "latest") {
37
47
  return versions[versions.length - 1];
38
48
  }
@@ -49,7 +59,7 @@ export function resolveVersion(versions, requestedVersion) {
49
59
  * Validates the input arguments for taking app screenshots.
50
60
  * Checks for presence and correctness of platform, device, and file types.
51
61
  */
52
- export function validateArgs(args) {
62
+ function validateArgs(args) {
53
63
  const { desiredPlatform, desiredPlatformVersion, appPath, desiredPhone } = args;
54
64
  if (!desiredPlatform || !desiredPhone) {
55
65
  throw new Error("Missing required arguments: desiredPlatform and desiredPhone are required");
@@ -70,20 +80,20 @@ export function validateArgs(args) {
70
80
  /**
71
81
  * Uploads an application file to AppAutomate and returns the app URL
72
82
  */
73
- export async function uploadApp(appPath) {
83
+ async function uploadApp(appPath) {
74
84
  const filePath = appPath;
75
- if (!fs.existsSync(filePath)) {
85
+ if (!fs_1.default.existsSync(filePath)) {
76
86
  throw new Error(`File not found at path: ${filePath}`);
77
87
  }
78
- const formData = new FormData();
79
- formData.append("file", fs.createReadStream(filePath));
80
- const response = await axios.post("https://api-cloud.browserstack.com/app-automate/upload", formData, {
88
+ const formData = new form_data_1.default();
89
+ formData.append("file", fs_1.default.createReadStream(filePath));
90
+ const response = await axios_1.default.post("https://api-cloud.browserstack.com/app-automate/upload", formData, {
81
91
  headers: {
82
92
  ...formData.getHeaders(),
83
93
  },
84
94
  auth: {
85
- username: config.browserstackUsername,
86
- password: config.browserstackAccessKey,
95
+ username: config_js_1.default.browserstackUsername,
96
+ password: config_js_1.default.browserstackAccessKey,
87
97
  },
88
98
  });
89
99
  if (response.data.app_url) {
@@ -1,11 +1,17 @@
1
- import { z } from "zod";
2
- import logger from "../logger.js";
3
- import config from "../config.js";
4
- import { trackMCP } from "../lib/instrumentation.js";
5
- import { maybeCompressBase64 } from "../lib/utils.js";
6
- import { remote } from "webdriverio";
7
- import { getDevicesAndBrowsers, BrowserStackProducts, } from "../lib/device-cache.js";
8
- import { findMatchingDevice, getDeviceVersions, resolveVersion, validateArgs, uploadApp, } from "./appautomate-utils/appautomate.js";
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.default = addAppAutomationTools;
7
+ const zod_1 = require("zod");
8
+ const logger_js_1 = __importDefault(require("../logger.js"));
9
+ const config_js_1 = __importDefault(require("../config.js"));
10
+ const instrumentation_js_1 = require("../lib/instrumentation.js");
11
+ const utils_js_1 = require("../lib/utils.js");
12
+ const webdriverio_1 = require("webdriverio");
13
+ const device_cache_js_1 = require("../lib/device-cache.js");
14
+ const appautomate_js_1 = require("./appautomate-utils/appautomate.js");
9
15
  var Platform;
10
16
  (function (Platform) {
11
17
  Platform["ANDROID"] = "android";
@@ -17,23 +23,23 @@ var Platform;
17
23
  async function takeAppScreenshot(args) {
18
24
  let driver;
19
25
  try {
20
- validateArgs(args);
26
+ (0, appautomate_js_1.validateArgs)(args);
21
27
  const { desiredPlatform, desiredPhone, appPath } = args;
22
28
  let { desiredPlatformVersion } = args;
23
- const platforms = (await getDevicesAndBrowsers(BrowserStackProducts.APP_AUTOMATE)).mobile;
29
+ const platforms = (await (0, device_cache_js_1.getDevicesAndBrowsers)(device_cache_js_1.BrowserStackProducts.APP_AUTOMATE)).mobile;
24
30
  const platformData = platforms.find((p) => p.os === desiredPlatform.toLowerCase());
25
31
  if (!platformData) {
26
32
  throw new Error(`Platform ${desiredPlatform} not found in device cache.`);
27
33
  }
28
- const matchingDevices = findMatchingDevice(platformData.devices, desiredPhone);
29
- const availableVersions = getDeviceVersions(matchingDevices);
30
- desiredPlatformVersion = resolveVersion(availableVersions, desiredPlatformVersion);
34
+ const matchingDevices = (0, appautomate_js_1.findMatchingDevice)(platformData.devices, desiredPhone);
35
+ const availableVersions = (0, appautomate_js_1.getDeviceVersions)(matchingDevices);
36
+ desiredPlatformVersion = (0, appautomate_js_1.resolveVersion)(availableVersions, desiredPlatformVersion);
31
37
  const selectedDevice = matchingDevices.find((d) => d.os_version === desiredPlatformVersion);
32
38
  if (!selectedDevice) {
33
39
  throw new Error(`Device "${desiredPhone}" with version ${desiredPlatformVersion} not found.`);
34
40
  }
35
- const app_url = await uploadApp(appPath);
36
- logger.info(`App uploaded. URL: ${app_url}`);
41
+ const app_url = await (0, appautomate_js_1.uploadApp)(appPath);
42
+ logger_js_1.default.info(`App uploaded. URL: ${app_url}`);
37
43
  const capabilities = {
38
44
  platformName: desiredPlatform,
39
45
  "appium:platformVersion": selectedDevice.os_version,
@@ -41,13 +47,13 @@ async function takeAppScreenshot(args) {
41
47
  "appium:app": app_url,
42
48
  "appium:autoGrantPermissions": true,
43
49
  "bstack:options": {
44
- userName: config.browserstackUsername,
45
- accessKey: config.browserstackAccessKey,
50
+ userName: config_js_1.default.browserstackUsername,
51
+ accessKey: config_js_1.default.browserstackAccessKey,
46
52
  appiumVersion: "2.0.1",
47
53
  },
48
54
  };
49
- logger.info("Starting WebDriver session on BrowserStack...");
50
- driver = await remote({
55
+ logger_js_1.default.info("Starting WebDriver session on BrowserStack...");
56
+ driver = await (0, webdriverio_1.remote)({
51
57
  protocol: "https",
52
58
  hostname: "hub.browserstack.com",
53
59
  port: 443,
@@ -55,7 +61,7 @@ async function takeAppScreenshot(args) {
55
61
  capabilities,
56
62
  });
57
63
  const screenshotBase64 = await driver.takeScreenshot();
58
- const compressed = await maybeCompressBase64(screenshotBase64);
64
+ const compressed = await (0, utils_js_1.maybeCompressBase64)(screenshotBase64);
59
65
  return {
60
66
  content: [
61
67
  {
@@ -68,12 +74,12 @@ async function takeAppScreenshot(args) {
68
74
  };
69
75
  }
70
76
  catch (error) {
71
- logger.error("Error during app automation or screenshot capture", error);
77
+ logger_js_1.default.error("Error during app automation or screenshot capture", error);
72
78
  throw error;
73
79
  }
74
80
  finally {
75
81
  if (driver) {
76
- logger.info("Cleaning up WebDriver session...");
82
+ logger_js_1.default.info("Cleaning up WebDriver session...");
77
83
  await driver.deleteSession();
78
84
  }
79
85
  }
@@ -81,27 +87,27 @@ async function takeAppScreenshot(args) {
81
87
  /**
82
88
  * Registers the `takeAppScreenshot` tool with the MCP server.
83
89
  */
84
- export default function addAppAutomationTools(server) {
90
+ function addAppAutomationTools(server) {
85
91
  server.tool("takeAppScreenshot", "Use this tool to take a screenshot of an app running on a BrowserStack device. This is useful for visual testing and debugging.", {
86
- desiredPhone: z
92
+ desiredPhone: zod_1.z
87
93
  .string()
88
94
  .describe("The full name of the device to run the app on. Example: 'iPhone 12 Pro' or 'Samsung Galaxy S20'. Always ask the user for the device they want to use."),
89
- desiredPlatformVersion: z
95
+ desiredPlatformVersion: zod_1.z
90
96
  .string()
91
97
  .describe("The platform version to run the app on. Use 'latest' or 'oldest' for dynamic resolution."),
92
- desiredPlatform: z
98
+ desiredPlatform: zod_1.z
93
99
  .enum([Platform.ANDROID, Platform.IOS])
94
100
  .describe("Platform to run the app on. Either 'android' or 'ios'."),
95
- appPath: z
101
+ appPath: zod_1.z
96
102
  .string()
97
103
  .describe("The path to the .apk or .ipa file. Required for app installation."),
98
104
  }, async (args) => {
99
105
  try {
100
- trackMCP("takeAppScreenshot", server.server.getClientVersion());
106
+ (0, instrumentation_js_1.trackMCP)("takeAppScreenshot", server.server.getClientVersion());
101
107
  return await takeAppScreenshot(args);
102
108
  }
103
109
  catch (error) {
104
- trackMCP("takeAppScreenshot", server.server.getClientVersion(), error);
110
+ (0, instrumentation_js_1.trackMCP)("takeAppScreenshot", server.server.getClientVersion(), error);
105
111
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
106
112
  return {
107
113
  content: [
@@ -1,8 +1,11 @@
1
- import { customFuzzySearch } from "../../lib/fuzzy.js";
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fuzzySearchDevices = fuzzySearchDevices;
4
+ const fuzzy_js_1 = require("../../lib/fuzzy.js");
2
5
  /**
3
6
  * Fuzzy searches App Live device entries by name.
4
7
  */
5
- export async function fuzzySearchDevices(devices, query, limit = 5) {
6
- const top_match = customFuzzySearch(devices, ["device", "display_name"], query, limit);
8
+ async function fuzzySearchDevices(devices, query, limit = 5) {
9
+ const top_match = (0, fuzzy_js_1.customFuzzySearch)(devices, ["device", "display_name"], query, limit);
7
10
  return top_match;
8
11
  }
@@ -1,25 +1,31 @@
1
- import childProcess from "child_process";
2
- import logger from "../../logger.js";
3
- import { BrowserStackProducts, getDevicesAndBrowsers, } from "../../lib/device-cache.js";
4
- import { fuzzySearchDevices } from "./fuzzy-search.js";
5
- import { sanitizeUrlParam } from "../../lib/utils.js";
6
- import { uploadApp } from "./upload-app.js";
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.startSession = startSession;
7
+ const child_process_1 = __importDefault(require("child_process"));
8
+ const logger_js_1 = __importDefault(require("../../logger.js"));
9
+ const device_cache_js_1 = require("../../lib/device-cache.js");
10
+ const fuzzy_search_js_1 = require("./fuzzy-search.js");
11
+ const utils_js_1 = require("../../lib/utils.js");
12
+ const upload_app_js_1 = require("./upload-app.js");
7
13
  /**
8
14
  * Starts an App Live session after filtering, fuzzy matching, and launching.
9
15
  * @param args - The arguments for starting the session.
10
16
  * @returns The launch URL for the session.
11
17
  * @throws Will throw an error if no devices are found or if the app URL is invalid.
12
18
  */
13
- export async function startSession(args) {
19
+ async function startSession(args) {
14
20
  const { appPath, desiredPlatform, desiredPhone } = args;
15
21
  let { desiredPlatformVersion } = args;
16
- const data = await getDevicesAndBrowsers(BrowserStackProducts.APP_LIVE);
22
+ const data = await (0, device_cache_js_1.getDevicesAndBrowsers)(device_cache_js_1.BrowserStackProducts.APP_LIVE);
17
23
  const allDevices = data.mobile.flatMap((group) => group.devices.map((dev) => ({ ...dev, os: group.os })));
18
24
  desiredPlatformVersion = resolvePlatformVersion(allDevices, desiredPlatform, desiredPlatformVersion);
19
25
  const filteredDevices = filterDevicesByPlatformAndVersion(allDevices, desiredPlatform, desiredPlatformVersion);
20
- const matches = await fuzzySearchDevices(filteredDevices, desiredPhone);
26
+ const matches = await (0, fuzzy_search_js_1.fuzzySearchDevices)(filteredDevices, desiredPhone);
21
27
  const selectedDevice = validateAndSelectDevice(matches, desiredPhone, desiredPlatform, desiredPlatformVersion);
22
- const { app_url } = await uploadApp(appPath);
28
+ const { app_url } = await (0, upload_app_js_1.uploadApp)(appPath);
23
29
  validateAppUrl(app_url);
24
30
  const launchUrl = constructLaunchUrl(app_url, selectedDevice, desiredPlatform, desiredPlatformVersion);
25
31
  openBrowser(launchUrl);
@@ -114,7 +120,7 @@ function validateAppUrl(appUrl) {
114
120
  * @returns The constructed launch URL.
115
121
  */
116
122
  function constructLaunchUrl(appUrl, device, desiredPlatform, desiredPlatformVersion) {
117
- const deviceParam = sanitizeUrlParam(device.display_name.replace(/\s+/g, "+"));
123
+ const deviceParam = (0, utils_js_1.sanitizeUrlParam)(device.display_name.replace(/\s+/g, "+"));
118
124
  const params = new URLSearchParams({
119
125
  os: desiredPlatform,
120
126
  os_version: desiredPlatformVersion,
@@ -138,16 +144,16 @@ function openBrowser(launchUrl) {
138
144
  ? ["cmd", "/c", "start", launchUrl]
139
145
  : ["xdg-open", launchUrl];
140
146
  // nosemgrep:javascript.lang.security.detect-child-process.detect-child-process
141
- const child = childProcess.spawn(command[0], command.slice(1), {
147
+ const child = child_process_1.default.spawn(command[0], command.slice(1), {
142
148
  stdio: "ignore",
143
149
  detached: true,
144
150
  });
145
151
  child.on("error", (error) => {
146
- logger.error(`Failed to open browser automatically: ${error}. Please open this URL manually: ${launchUrl}`);
152
+ logger_js_1.default.error(`Failed to open browser automatically: ${error}. Please open this URL manually: ${launchUrl}`);
147
153
  });
148
154
  child.unref();
149
155
  }
150
156
  catch (error) {
151
- logger.error(`Failed to open browser automatically: ${error}. Please open this URL manually: ${launchUrl}`);
157
+ logger_js_1.default.error(`Failed to open browser automatically: ${error}. Please open this URL manually: ${launchUrl}`);
152
158
  }
153
159
  }
@@ -1,27 +1,66 @@
1
- import axios, { AxiosError } from "axios";
2
- import FormData from "form-data";
3
- import fs from "fs";
4
- import config from "../../config.js";
5
- export async function uploadApp(filePath) {
6
- if (!fs.existsSync(filePath)) {
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.uploadApp = uploadApp;
40
+ const axios_1 = __importStar(require("axios"));
41
+ const form_data_1 = __importDefault(require("form-data"));
42
+ const fs_1 = __importDefault(require("fs"));
43
+ const config_js_1 = __importDefault(require("../../config.js"));
44
+ async function uploadApp(filePath) {
45
+ if (!fs_1.default.existsSync(filePath)) {
7
46
  throw new Error(`File not found at path: ${filePath}`);
8
47
  }
9
- const formData = new FormData();
10
- formData.append("file", fs.createReadStream(filePath));
48
+ const formData = new form_data_1.default();
49
+ formData.append("file", fs_1.default.createReadStream(filePath));
11
50
  try {
12
- const response = await axios.post("https://api-cloud.browserstack.com/app-live/upload", formData, {
51
+ const response = await axios_1.default.post("https://api-cloud.browserstack.com/app-live/upload", formData, {
13
52
  headers: {
14
53
  ...formData.getHeaders(),
15
54
  },
16
55
  auth: {
17
- username: config.browserstackUsername,
18
- password: config.browserstackAccessKey,
56
+ username: config_js_1.default.browserstackUsername,
57
+ password: config_js_1.default.browserstackAccessKey,
19
58
  },
20
59
  });
21
60
  return response.data;
22
61
  }
23
62
  catch (error) {
24
- if (error instanceof AxiosError) {
63
+ if (error instanceof axios_1.AxiosError) {
25
64
  throw new Error(`Failed to upload app: ${error.response?.data?.message || error.message}`);
26
65
  }
27
66
  throw error;
@@ -1,12 +1,19 @@
1
- import { z } from "zod";
2
- import fs from "fs";
3
- import { startSession } from "./applive-utils/start-session.js";
4
- import logger from "../logger.js";
5
- import { trackMCP } from "../lib/instrumentation.js";
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.startAppLiveSession = startAppLiveSession;
7
+ exports.default = addAppLiveTools;
8
+ const zod_1 = require("zod");
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const start_session_js_1 = require("./applive-utils/start-session.js");
11
+ const logger_js_1 = __importDefault(require("../logger.js"));
12
+ const instrumentation_js_1 = require("../lib/instrumentation.js");
6
13
  /**
7
14
  * Launches an App Live Session on BrowserStack.
8
15
  */
9
- export async function startAppLiveSession(args) {
16
+ async function startAppLiveSession(args) {
10
17
  if (!args.desiredPlatform) {
11
18
  throw new Error("You must provide a desiredPlatform.");
12
19
  }
@@ -24,16 +31,16 @@ export async function startAppLiveSession(args) {
24
31
  }
25
32
  // check if the app path exists && is readable
26
33
  try {
27
- if (!fs.existsSync(args.appPath)) {
34
+ if (!fs_1.default.existsSync(args.appPath)) {
28
35
  throw new Error("The app path does not exist.");
29
36
  }
30
- fs.accessSync(args.appPath, fs.constants.R_OK);
37
+ fs_1.default.accessSync(args.appPath, fs_1.default.constants.R_OK);
31
38
  }
32
39
  catch (error) {
33
- logger.error("The app path does not exist or is not readable: %s", error);
40
+ logger_js_1.default.error("The app path does not exist or is not readable: %s", error);
34
41
  throw new Error("The app path does not exist or is not readable.");
35
42
  }
36
- const launchUrl = await startSession({
43
+ const launchUrl = await (0, start_session_js_1.startSession)({
37
44
  appPath: args.appPath,
38
45
  desiredPlatform: args.desiredPlatform,
39
46
  desiredPhone: args.desiredPhone,
@@ -48,28 +55,28 @@ export async function startAppLiveSession(args) {
48
55
  ],
49
56
  };
50
57
  }
51
- export default function addAppLiveTools(server) {
58
+ function addAppLiveTools(server) {
52
59
  server.tool("runAppLiveSession", "Use this tool when user wants to manually check their app on a particular mobile device using BrowserStack's cloud infrastructure. Can be used to debug crashes, slow performance, etc.", {
53
- desiredPhone: z
60
+ desiredPhone: zod_1.z
54
61
  .string()
55
62
  .describe("The full name of the device to run the app on. Example: 'iPhone 12 Pro' or 'Samsung Galaxy S20' or 'Google Pixel 6'. Always ask the user for the device they want to use, do not assume it. "),
56
- desiredPlatformVersion: z
63
+ desiredPlatformVersion: zod_1.z
57
64
  .string()
58
65
  .describe("Specifies the platform version to run the app on. For example, use '12.0' for Android or '16.0' for iOS. If the user says 'latest', 'newest', or similar, normalize it to 'latest'. Likewise, convert terms like 'earliest' or 'oldest' to 'oldest'."),
59
- desiredPlatform: z
66
+ desiredPlatform: zod_1.z
60
67
  .enum(["android", "ios"])
61
68
  .describe("Which platform to run on, examples: 'android', 'ios'. Set this based on the app path provided."),
62
- appPath: z
69
+ appPath: zod_1.z
63
70
  .string()
64
71
  .describe("The path to the .ipa or .apk file to install on the device. Always ask the user for the app path, do not assume it."),
65
72
  }, async (args) => {
66
73
  try {
67
- trackMCP("runAppLiveSession", server.server.getClientVersion());
74
+ (0, instrumentation_js_1.trackMCP)("runAppLiveSession", server.server.getClientVersion());
68
75
  return await startAppLiveSession(args);
69
76
  }
70
77
  catch (error) {
71
- logger.error("App live session failed: %s", error);
72
- trackMCP("runAppLiveSession", server.server.getClientVersion(), error);
78
+ logger_js_1.default.error("App live session failed: %s", error);
79
+ (0, instrumentation_js_1.trackMCP)("runAppLiveSession", server.server.getClientVersion(), error);
73
80
  return {
74
81
  content: [
75
82
  {