@metamask/device-mcp 0.3.3 → 0.4.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 (95) hide show
  1. package/CHANGELOG.md +11 -1
  2. package/README.md +110 -21
  3. package/dist/android/NOTICE.md +65 -0
  4. package/dist/android/device-mcp-android-snapshot-helper-0.4.0.apk +0 -0
  5. package/dist/android/device-mcp-android-snapshot-helper-0.4.0.apk.idsig +0 -0
  6. package/dist/android/device-mcp-android-snapshot-helper-0.4.0.manifest.json +10 -0
  7. package/dist/backends/adb-backend.cjs +325 -17
  8. package/dist/backends/adb-backend.cjs.map +1 -1
  9. package/dist/backends/adb-backend.d.cts.map +1 -1
  10. package/dist/backends/adb-backend.d.mts.map +1 -1
  11. package/dist/backends/adb-backend.mjs +326 -18
  12. package/dist/backends/adb-backend.mjs.map +1 -1
  13. package/dist/backends/android-instrumentation/adb.cjs +28 -0
  14. package/dist/backends/android-instrumentation/adb.cjs.map +1 -0
  15. package/dist/backends/android-instrumentation/adb.d.cts +24 -0
  16. package/dist/backends/android-instrumentation/adb.d.cts.map +1 -0
  17. package/dist/backends/android-instrumentation/adb.d.mts +24 -0
  18. package/dist/backends/android-instrumentation/adb.d.mts.map +1 -0
  19. package/dist/backends/android-instrumentation/adb.mjs +25 -0
  20. package/dist/backends/android-instrumentation/adb.mjs.map +1 -0
  21. package/dist/backends/android-instrumentation/artifact.cjs +139 -0
  22. package/dist/backends/android-instrumentation/artifact.cjs.map +1 -0
  23. package/dist/backends/android-instrumentation/artifact.d.cts +28 -0
  24. package/dist/backends/android-instrumentation/artifact.d.cts.map +1 -0
  25. package/dist/backends/android-instrumentation/artifact.d.mts +28 -0
  26. package/dist/backends/android-instrumentation/artifact.d.mts.map +1 -0
  27. package/dist/backends/android-instrumentation/artifact.mjs +154 -0
  28. package/dist/backends/android-instrumentation/artifact.mjs.map +1 -0
  29. package/dist/backends/android-instrumentation/errors.cjs +79 -0
  30. package/dist/backends/android-instrumentation/errors.cjs.map +1 -0
  31. package/dist/backends/android-instrumentation/errors.d.cts +68 -0
  32. package/dist/backends/android-instrumentation/errors.d.cts.map +1 -0
  33. package/dist/backends/android-instrumentation/errors.d.mts +68 -0
  34. package/dist/backends/android-instrumentation/errors.d.mts.map +1 -0
  35. package/dist/backends/android-instrumentation/errors.mjs +73 -0
  36. package/dist/backends/android-instrumentation/errors.mjs.map +1 -0
  37. package/dist/backends/android-instrumentation/installer.cjs +235 -0
  38. package/dist/backends/android-instrumentation/installer.cjs.map +1 -0
  39. package/dist/backends/android-instrumentation/installer.d.cts +123 -0
  40. package/dist/backends/android-instrumentation/installer.d.cts.map +1 -0
  41. package/dist/backends/android-instrumentation/installer.d.mts +123 -0
  42. package/dist/backends/android-instrumentation/installer.d.mts.map +1 -0
  43. package/dist/backends/android-instrumentation/installer.mjs +225 -0
  44. package/dist/backends/android-instrumentation/installer.mjs.map +1 -0
  45. package/dist/backends/android-instrumentation/signer.cjs +435 -0
  46. package/dist/backends/android-instrumentation/signer.cjs.map +1 -0
  47. package/dist/backends/android-instrumentation/signer.d.cts +40 -0
  48. package/dist/backends/android-instrumentation/signer.d.cts.map +1 -0
  49. package/dist/backends/android-instrumentation/signer.d.mts +40 -0
  50. package/dist/backends/android-instrumentation/signer.d.mts.map +1 -0
  51. package/dist/backends/android-instrumentation/signer.mjs +431 -0
  52. package/dist/backends/android-instrumentation/signer.mjs.map +1 -0
  53. package/dist/backends/android-instrumentation/snapshot.cjs +159 -0
  54. package/dist/backends/android-instrumentation/snapshot.cjs.map +1 -0
  55. package/dist/backends/android-instrumentation/snapshot.d.cts +67 -0
  56. package/dist/backends/android-instrumentation/snapshot.d.cts.map +1 -0
  57. package/dist/backends/android-instrumentation/snapshot.d.mts +67 -0
  58. package/dist/backends/android-instrumentation/snapshot.d.mts.map +1 -0
  59. package/dist/backends/android-instrumentation/snapshot.mjs +154 -0
  60. package/dist/backends/android-instrumentation/snapshot.mjs.map +1 -0
  61. package/dist/backends/appium-backend.cjs +18 -1
  62. package/dist/backends/appium-backend.cjs.map +1 -1
  63. package/dist/backends/appium-backend.d.cts.map +1 -1
  64. package/dist/backends/appium-backend.d.mts.map +1 -1
  65. package/dist/backends/appium-backend.mjs +18 -1
  66. package/dist/backends/appium-backend.mjs.map +1 -1
  67. package/dist/backends/idb-backend.cjs +127 -31
  68. package/dist/backends/idb-backend.cjs.map +1 -1
  69. package/dist/backends/idb-backend.d.cts.map +1 -1
  70. package/dist/backends/idb-backend.d.mts.map +1 -1
  71. package/dist/backends/idb-backend.mjs +128 -32
  72. package/dist/backends/idb-backend.mjs.map +1 -1
  73. package/dist/tools/swipe.cjs +3 -1
  74. package/dist/tools/swipe.cjs.map +1 -1
  75. package/dist/tools/swipe.d.cts.map +1 -1
  76. package/dist/tools/swipe.d.mts.map +1 -1
  77. package/dist/tools/swipe.mjs +3 -1
  78. package/dist/tools/swipe.mjs.map +1 -1
  79. package/dist/utils/output-path.cjs +13 -0
  80. package/dist/utils/output-path.cjs.map +1 -1
  81. package/dist/utils/output-path.d.cts +10 -0
  82. package/dist/utils/output-path.d.cts.map +1 -1
  83. package/dist/utils/output-path.d.mts +10 -0
  84. package/dist/utils/output-path.d.mts.map +1 -1
  85. package/dist/utils/output-path.mjs +12 -0
  86. package/dist/utils/output-path.mjs.map +1 -1
  87. package/dist/utils/platform.cjs +65 -7
  88. package/dist/utils/platform.cjs.map +1 -1
  89. package/dist/utils/platform.d.cts +33 -0
  90. package/dist/utils/platform.d.cts.map +1 -1
  91. package/dist/utils/platform.d.mts +33 -0
  92. package/dist/utils/platform.d.mts.map +1 -1
  93. package/dist/utils/platform.mjs +63 -7
  94. package/dist/utils/platform.mjs.map +1 -1
  95. package/package.json +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"platform.cjs","sourceRoot":"","sources":["../../src/utils/platform.ts"],"names":[],"mappings":"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","sourcesContent":["import { readdirSync } from 'node:fs';\n\nimport { exec, isCommandAvailable } from './exec.js';\nimport type { Platform } from '../backends/types.js';\n\nexport type DetectedDevice = {\n platform: Platform;\n deviceId: string;\n};\n\nexport class MultipleDevicesError extends Error {\n readonly devices: DetectedDevice[];\n\n constructor(devices: DetectedDevice[]) {\n const lines = devices.map((d) => ` ${d.platform}\\t${d.deviceId}`);\n super(\n `Multiple devices detected. Set DEVICE_ID to one of:\\n${lines.join('\\n')}`,\n );\n this.name = 'MultipleDevicesError';\n this.devices = devices;\n }\n}\n\n/**\n * Detect all connected devices across iOS and Android.\n *\n * @param platform - Optional filter to only return devices of a specific platform.\n * @returns Array of detected devices (may be empty).\n */\nexport async function detectAllDevices(\n platform?: Platform,\n): Promise<DetectedDevice[]> {\n const searches: Promise<DetectedDevice[]>[] = [];\n\n if (platform !== 'android') {\n searches.push(findAllBootedIOSSimulators().catch(() => []));\n }\n if (platform !== 'ios') {\n searches.push(findAllConnectedAndroidDevices().catch(() => []));\n }\n\n const results = await Promise.all(searches);\n return results.flat();\n}\n\nexport async function detectPlatform(\n explicitDeviceId?: string,\n explicitPlatform?: Platform,\n): Promise<DetectedDevice> {\n if (explicitDeviceId) {\n return detectFromExplicitId(explicitDeviceId);\n }\n\n const devices = await detectAllDevices(explicitPlatform);\n\n if (devices.length === 1) {\n return devices[0];\n }\n\n if (devices.length > 1) {\n throw new MultipleDevicesError(devices);\n }\n\n const hasAdb =\n explicitPlatform === 'ios' ? false : await isCommandAvailable('adb');\n\n const platformLabel = explicitPlatform ?? 'any';\n throw new Error(\n `No connected ${platformLabel} device found.\\n` +\n ` iOS (simctl): no booted simulator\\n` +\n ` Android (adb): ${hasAdb ? 'available, no connected device' : 'not installed'}\\n` +\n 'Boot a simulator/emulator or set DEVICE_ID explicitly.',\n );\n}\n\nasync function detectFromExplicitId(deviceId: string): Promise<DetectedDevice> {\n // UUIDs with dashes are typically iOS simulator UDIDs\n if (/^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$/iu.test(deviceId)) {\n return { platform: 'ios', deviceId };\n }\n\n // Android emulator or device serial patterns\n if (deviceId.startsWith('emulator-') || /^[A-Za-z0-9]+$/u.test(deviceId)) {\n return { platform: 'android', deviceId };\n }\n\n const [hasIdb, hasAdb] = await Promise.all([\n isCommandAvailable('idb'),\n isCommandAvailable('adb'),\n ]);\n\n if (hasIdb) {\n const result = await exec('idb', ['describe', '--udid', deviceId]);\n if (result.exitCode === 0) {\n return { platform: 'ios', deviceId };\n }\n }\n\n if (hasAdb) {\n const result = await exec('adb', ['-s', deviceId, 'get-state']);\n if (result.exitCode === 0) {\n return { platform: 'android', deviceId };\n }\n }\n\n throw new Error(\n `Cannot determine platform for device ID: ${deviceId}. ` +\n 'Ensure the device is connected and idb/adb is available.',\n );\n}\n\ntype SimctlDevice = {\n udid: string;\n name: string;\n state: string;\n isAvailable: boolean;\n};\n\nasync function findAllBootedIOSSimulators(): Promise<DetectedDevice[]> {\n const result = await exec('xcrun', [\n 'simctl',\n 'list',\n 'devices',\n 'booted',\n '--json',\n ]);\n if (result.exitCode !== 0) {\n return [];\n }\n\n try {\n const parsed = JSON.parse(result.stdout) as {\n devices: Record<string, SimctlDevice[]>;\n };\n const devices: DetectedDevice[] = [];\n for (const runtimeDevices of Object.values(parsed.devices)) {\n for (const device of runtimeDevices) {\n if (device.state === 'Booted' && device.isAvailable) {\n devices.push({ platform: 'ios', deviceId: device.udid });\n }\n }\n }\n return devices;\n } catch {\n return [];\n }\n}\n\nfunction findPythonBinDirs(base: string): string[] {\n try {\n return readdirSync(base, { withFileTypes: true })\n .filter(\n (e) =>\n (e.isDirectory() && e.name.startsWith('python')) ||\n /^\\d+\\.\\d+$/u.test(e.name),\n )\n .map((e) => `${base}/${e.name}/bin`);\n } catch {\n return [];\n }\n}\n\nexport async function resolveIdbPath(): Promise<string> {\n const result = await exec('idb', ['--help']).catch(() => null);\n if (result?.exitCode === 0) {\n return 'idb';\n }\n\n const home = process.env.HOME ?? '';\n const candidates = [\n '/usr/local/bin/idb',\n '/opt/homebrew/bin/idb',\n ...findPythonBinDirs(`${home}/Library/Python`).map((d) => `${d}/idb`),\n ...findPythonBinDirs('/usr/local/lib').map((d) => `${d}/idb`),\n ];\n\n for (const candidate of candidates) {\n const probe = await exec(candidate, ['--help']).catch(() => null);\n if (probe?.exitCode === 0) {\n return candidate;\n }\n }\n\n throw new Error(\n 'idb not found.\\n' +\n 'Install: brew tap facebook/fb && brew install idb-companion && pip3 install fb-idb',\n );\n}\n\nasync function resolveAdbPath(): Promise<string> {\n const result = await exec('adb', ['version']).catch(() => null);\n if (result?.exitCode === 0) {\n return 'adb';\n }\n\n const home = process.env.HOME ?? '';\n const candidates = [\n process.env.ANDROID_HOME &&\n `${process.env.ANDROID_HOME}/platform-tools/adb`,\n process.env.ANDROID_SDK_ROOT &&\n `${process.env.ANDROID_SDK_ROOT}/platform-tools/adb`,\n `${home}/Library/Android/sdk/platform-tools/adb`,\n `${home}/Android/Sdk/platform-tools/adb`,\n ].filter(Boolean) as string[];\n\n for (const candidate of candidates) {\n const probe = await exec(candidate, ['version']).catch(() => null);\n if (probe?.exitCode === 0) {\n return candidate;\n }\n }\n\n throw new Error('adb not found');\n}\n\nasync function findAllConnectedAndroidDevices(): Promise<DetectedDevice[]> {\n const adb = await resolveAdbPath();\n const result = await exec(adb, ['devices']);\n if (result.exitCode !== 0) {\n return [];\n }\n\n const devices: DetectedDevice[] = [];\n const lines = result.stdout.trim().split('\\n').slice(1);\n for (const line of lines) {\n const [serial, state] = line.trim().split(/\\s+/u);\n if (serial && state === 'device') {\n devices.push({ platform: 'android', deviceId: serial });\n }\n }\n return devices;\n}\n"]}
1
+ {"version":3,"file":"platform.cjs","sourceRoot":"","sources":["../../src/utils/platform.ts"],"names":[],"mappings":"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","sourcesContent":["import { readdirSync } from 'node:fs';\n\nimport { exec, isCommandAvailable } from './exec.js';\nimport type { Platform } from '../backends/types.js';\n\nexport type DetectedDevice = {\n platform: Platform;\n deviceId: string;\n};\n\nexport class MultipleDevicesError extends Error {\n readonly devices: DetectedDevice[];\n\n constructor(devices: DetectedDevice[]) {\n const lines = devices.map((d) => ` ${d.platform}\\t${d.deviceId}`);\n super(\n `Multiple devices detected. Set DEVICE_ID to one of:\\n${lines.join('\\n')}`,\n );\n this.name = 'MultipleDevicesError';\n this.devices = devices;\n }\n}\n\n/**\n * Detect all connected devices across iOS and Android.\n *\n * @param platform - Optional filter to only return devices of a specific platform.\n * @returns Array of detected devices (may be empty).\n */\nexport async function detectAllDevices(\n platform?: Platform,\n): Promise<DetectedDevice[]> {\n const searches: Promise<DetectedDevice[]>[] = [];\n\n if (platform !== 'android') {\n searches.push(findAllBootedIOSSimulators().catch(() => []));\n }\n if (platform !== 'ios') {\n searches.push(findAllConnectedAndroidDevices().catch(() => []));\n }\n\n const results = await Promise.all(searches);\n return results.flat();\n}\n\nexport async function detectPlatform(\n explicitDeviceId?: string,\n explicitPlatform?: Platform,\n): Promise<DetectedDevice> {\n if (explicitDeviceId) {\n return detectFromExplicitId(explicitDeviceId);\n }\n\n const devices = await detectAllDevices(explicitPlatform);\n\n if (devices.length === 1) {\n return devices[0];\n }\n\n if (devices.length > 1) {\n throw new MultipleDevicesError(devices);\n }\n\n const hasAdb =\n explicitPlatform === 'ios' ? false : await isCommandAvailable('adb');\n\n const platformLabel = explicitPlatform ?? 'any';\n throw new Error(\n `No connected ${platformLabel} device found.\\n` +\n ` iOS (simctl): no booted simulator\\n` +\n ` Android (adb): ${hasAdb ? 'available, no connected device' : 'not installed'}\\n` +\n 'Boot a simulator/emulator or set DEVICE_ID explicitly.',\n );\n}\n\nasync function detectFromExplicitId(deviceId: string): Promise<DetectedDevice> {\n // UUIDs with dashes are typically iOS simulator UDIDs\n if (/^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$/iu.test(deviceId)) {\n return { platform: 'ios', deviceId };\n }\n\n // Android emulator or device serial patterns\n if (deviceId.startsWith('emulator-') || /^[A-Za-z0-9]+$/u.test(deviceId)) {\n return { platform: 'android', deviceId };\n }\n\n const [hasIdb, hasAdb] = await Promise.all([\n isCommandAvailable('idb'),\n isCommandAvailable('adb'),\n ]);\n\n if (hasIdb) {\n const result = await exec('idb', ['describe', '--udid', deviceId]);\n if (result.exitCode === 0) {\n return { platform: 'ios', deviceId };\n }\n }\n\n if (hasAdb) {\n const result = await exec('adb', ['-s', deviceId, 'get-state']);\n if (result.exitCode === 0) {\n return { platform: 'android', deviceId };\n }\n }\n\n throw new Error(\n `Cannot determine platform for device ID: ${deviceId}. ` +\n 'Ensure the device is connected and idb/adb is available.',\n );\n}\n\ntype SimctlDevice = {\n udid: string;\n name: string;\n state: string;\n isAvailable: boolean;\n};\n\nasync function findAllBootedIOSSimulators(): Promise<DetectedDevice[]> {\n const result = await exec('xcrun', [\n 'simctl',\n 'list',\n 'devices',\n 'booted',\n '--json',\n ]);\n if (result.exitCode !== 0) {\n return [];\n }\n\n try {\n const parsed = JSON.parse(result.stdout) as {\n devices: Record<string, SimctlDevice[]>;\n };\n const devices: DetectedDevice[] = [];\n for (const runtimeDevices of Object.values(parsed.devices)) {\n for (const device of runtimeDevices) {\n if (device.state === 'Booted' && device.isAvailable) {\n devices.push({ platform: 'ios', deviceId: device.udid });\n }\n }\n }\n return devices;\n } catch {\n return [];\n }\n}\n\nfunction findPythonBinDirs(base: string): string[] {\n try {\n return readdirSync(base, { withFileTypes: true })\n .filter(\n (e) =>\n (e.isDirectory() && e.name.startsWith('python')) ||\n /^\\d+\\.\\d+$/u.test(e.name),\n )\n .map((e) => `${base}/${e.name}/bin`);\n } catch {\n return [];\n }\n}\n\n/**\n * Whether an `idb` binary supports the `--api` describe-all backend selector.\n * Only the unified `idb` (>= ~1.5) exposes it; the legacy python `fb-idb`\n * rejects `--api` as a usage error, so we probe the help text.\n *\n * @param idbPath - The `idb` executable to probe.\n * @returns True when `--api` is accepted.\n */\nexport async function idbSupportsApiSelection(\n idbPath: string,\n): Promise<boolean> {\n const help = await exec(idbPath, ['ui', 'describe-all', '--help']).catch(\n () => null,\n );\n if (help?.exitCode !== 0) {\n return false;\n }\n return `${help.stdout}\\n${help.stderr}`.includes('--api');\n}\n\n/**\n * Resolve the `idb` executable, preferring one that supports `--api`.\n *\n * A machine can have both the legacy python `fb-idb` (often first on `PATH`)\n * and the unified `facebook/fb/idb`; only the latter supports `--api axbridge`,\n * which iOS 17+ simulators require. We return the first `--api`-capable\n * candidate, falling back to the first merely-working one.\n *\n * @returns The path to the best available `idb`.\n * @throws When no `idb` executable is found.\n */\nexport async function resolveIdbPath(): Promise<string> {\n const home = process.env.HOME ?? '';\n const candidates = [\n 'idb',\n '/opt/homebrew/bin/idb',\n '/usr/local/bin/idb',\n ...findPythonBinDirs(`${home}/Library/Python`).map((d) => `${d}/idb`),\n ...findPythonBinDirs('/usr/local/lib').map((d) => `${d}/idb`),\n ];\n\n let firstWorking: string | null = null;\n for (const candidate of candidates) {\n const probe = await exec(candidate, ['--help']).catch(() => null);\n if (probe?.exitCode !== 0) {\n continue;\n }\n if (firstWorking === null) {\n firstWorking = candidate;\n }\n if (await idbSupportsApiSelection(candidate)) {\n return candidate;\n }\n }\n\n if (firstWorking !== null) {\n return firstWorking;\n }\n\n throw new Error(\n 'idb not found.\\n' +\n 'Install the unified client: brew tap facebook/fb && brew install idb',\n );\n}\n\n/**\n * The `--api` accessibility backend for a given iOS runtime.\n *\n * The backends are version-inverted: `ax` works on iOS <= 17 but is broken on\n * iOS 26; `axbridge` works on iOS >= 18. So we pick `axbridge` for >= 18 and\n * `ax` otherwise, or `null` (omit `--api`) when the CLI cannot select. The\n * caller still falls back if the chosen backend errors.\n *\n * @param osVersion - The device OS version (e.g. '17.4', '26.1').\n * @param supportsApi - Whether the resolved CLI accepts `--api`.\n * @returns The `--api` value, or `null` to omit it.\n */\nexport function chooseAxApi(\n osVersion: string,\n supportsApi: boolean,\n): 'ax' | 'axbridge' | null {\n if (!supportsApi) {\n return null;\n }\n const major = Number.parseInt(osVersion, 10);\n if (Number.isNaN(major)) {\n // Unknown version - prefer the modern reader and let the caller fall back\n return 'axbridge';\n }\n return major >= 18 ? 'axbridge' : 'ax';\n}\n\nasync function resolveAdbPath(): Promise<string> {\n const result = await exec('adb', ['version']).catch(() => null);\n if (result?.exitCode === 0) {\n return 'adb';\n }\n\n const home = process.env.HOME ?? '';\n const candidates = [\n process.env.ANDROID_HOME &&\n `${process.env.ANDROID_HOME}/platform-tools/adb`,\n process.env.ANDROID_SDK_ROOT &&\n `${process.env.ANDROID_SDK_ROOT}/platform-tools/adb`,\n `${home}/Library/Android/sdk/platform-tools/adb`,\n `${home}/Android/Sdk/platform-tools/adb`,\n ].filter(Boolean) as string[];\n\n for (const candidate of candidates) {\n const probe = await exec(candidate, ['version']).catch(() => null);\n if (probe?.exitCode === 0) {\n return candidate;\n }\n }\n\n throw new Error('adb not found');\n}\n\nasync function findAllConnectedAndroidDevices(): Promise<DetectedDevice[]> {\n const adb = await resolveAdbPath();\n const result = await exec(adb, ['devices']);\n if (result.exitCode !== 0) {\n return [];\n }\n\n const devices: DetectedDevice[] = [];\n const lines = result.stdout.trim().split('\\n').slice(1);\n for (const line of lines) {\n const [serial, state] = line.trim().split(/\\s+/u);\n if (serial && state === 'device') {\n devices.push({ platform: 'android', deviceId: serial });\n }\n }\n return devices;\n}\n"]}
@@ -15,5 +15,38 @@ export declare class MultipleDevicesError extends Error {
15
15
  */
16
16
  export declare function detectAllDevices(platform?: Platform): Promise<DetectedDevice[]>;
17
17
  export declare function detectPlatform(explicitDeviceId?: string, explicitPlatform?: Platform): Promise<DetectedDevice>;
18
+ /**
19
+ * Whether an `idb` binary supports the `--api` describe-all backend selector.
20
+ * Only the unified `idb` (>= ~1.5) exposes it; the legacy python `fb-idb`
21
+ * rejects `--api` as a usage error, so we probe the help text.
22
+ *
23
+ * @param idbPath - The `idb` executable to probe.
24
+ * @returns True when `--api` is accepted.
25
+ */
26
+ export declare function idbSupportsApiSelection(idbPath: string): Promise<boolean>;
27
+ /**
28
+ * Resolve the `idb` executable, preferring one that supports `--api`.
29
+ *
30
+ * A machine can have both the legacy python `fb-idb` (often first on `PATH`)
31
+ * and the unified `facebook/fb/idb`; only the latter supports `--api axbridge`,
32
+ * which iOS 17+ simulators require. We return the first `--api`-capable
33
+ * candidate, falling back to the first merely-working one.
34
+ *
35
+ * @returns The path to the best available `idb`.
36
+ * @throws When no `idb` executable is found.
37
+ */
18
38
  export declare function resolveIdbPath(): Promise<string>;
39
+ /**
40
+ * The `--api` accessibility backend for a given iOS runtime.
41
+ *
42
+ * The backends are version-inverted: `ax` works on iOS <= 17 but is broken on
43
+ * iOS 26; `axbridge` works on iOS >= 18. So we pick `axbridge` for >= 18 and
44
+ * `ax` otherwise, or `null` (omit `--api`) when the CLI cannot select. The
45
+ * caller still falls back if the chosen backend errors.
46
+ *
47
+ * @param osVersion - The device OS version (e.g. '17.4', '26.1').
48
+ * @param supportsApi - Whether the resolved CLI accepts `--api`.
49
+ * @returns The `--api` value, or `null` to omit it.
50
+ */
51
+ export declare function chooseAxApi(osVersion: string, supportsApi: boolean): 'ax' | 'axbridge' | null;
19
52
  //# sourceMappingURL=platform.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"platform.d.cts","sourceRoot":"","sources":["../../src/utils/platform.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,8BAA6B;AAErD,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,OAAO,EAAE,cAAc,EAAE,CAAC;gBAEvB,OAAO,EAAE,cAAc,EAAE;CAQtC;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,CAAC,EAAE,QAAQ,GAClB,OAAO,CAAC,cAAc,EAAE,CAAC,CAY3B;AAED,wBAAsB,cAAc,CAClC,gBAAgB,CAAC,EAAE,MAAM,EACzB,gBAAgB,CAAC,EAAE,QAAQ,GAC1B,OAAO,CAAC,cAAc,CAAC,CAyBzB;AAyFD,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CAyBtD"}
1
+ {"version":3,"file":"platform.d.cts","sourceRoot":"","sources":["../../src/utils/platform.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,8BAA6B;AAErD,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,OAAO,EAAE,cAAc,EAAE,CAAC;gBAEvB,OAAO,EAAE,cAAc,EAAE;CAQtC;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,CAAC,EAAE,QAAQ,GAClB,OAAO,CAAC,cAAc,EAAE,CAAC,CAY3B;AAED,wBAAsB,cAAc,CAClC,gBAAgB,CAAC,EAAE,MAAM,EACzB,gBAAgB,CAAC,EAAE,QAAQ,GAC1B,OAAO,CAAC,cAAc,CAAC,CAyBzB;AAyFD;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,OAAO,CAAC,CAQlB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CAgCtD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,OAAO,GACnB,IAAI,GAAG,UAAU,GAAG,IAAI,CAU1B"}
@@ -15,5 +15,38 @@ export declare class MultipleDevicesError extends Error {
15
15
  */
16
16
  export declare function detectAllDevices(platform?: Platform): Promise<DetectedDevice[]>;
17
17
  export declare function detectPlatform(explicitDeviceId?: string, explicitPlatform?: Platform): Promise<DetectedDevice>;
18
+ /**
19
+ * Whether an `idb` binary supports the `--api` describe-all backend selector.
20
+ * Only the unified `idb` (>= ~1.5) exposes it; the legacy python `fb-idb`
21
+ * rejects `--api` as a usage error, so we probe the help text.
22
+ *
23
+ * @param idbPath - The `idb` executable to probe.
24
+ * @returns True when `--api` is accepted.
25
+ */
26
+ export declare function idbSupportsApiSelection(idbPath: string): Promise<boolean>;
27
+ /**
28
+ * Resolve the `idb` executable, preferring one that supports `--api`.
29
+ *
30
+ * A machine can have both the legacy python `fb-idb` (often first on `PATH`)
31
+ * and the unified `facebook/fb/idb`; only the latter supports `--api axbridge`,
32
+ * which iOS 17+ simulators require. We return the first `--api`-capable
33
+ * candidate, falling back to the first merely-working one.
34
+ *
35
+ * @returns The path to the best available `idb`.
36
+ * @throws When no `idb` executable is found.
37
+ */
18
38
  export declare function resolveIdbPath(): Promise<string>;
39
+ /**
40
+ * The `--api` accessibility backend for a given iOS runtime.
41
+ *
42
+ * The backends are version-inverted: `ax` works on iOS <= 17 but is broken on
43
+ * iOS 26; `axbridge` works on iOS >= 18. So we pick `axbridge` for >= 18 and
44
+ * `ax` otherwise, or `null` (omit `--api`) when the CLI cannot select. The
45
+ * caller still falls back if the chosen backend errors.
46
+ *
47
+ * @param osVersion - The device OS version (e.g. '17.4', '26.1').
48
+ * @param supportsApi - Whether the resolved CLI accepts `--api`.
49
+ * @returns The `--api` value, or `null` to omit it.
50
+ */
51
+ export declare function chooseAxApi(osVersion: string, supportsApi: boolean): 'ax' | 'axbridge' | null;
19
52
  //# sourceMappingURL=platform.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"platform.d.mts","sourceRoot":"","sources":["../../src/utils/platform.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,8BAA6B;AAErD,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,OAAO,EAAE,cAAc,EAAE,CAAC;gBAEvB,OAAO,EAAE,cAAc,EAAE;CAQtC;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,CAAC,EAAE,QAAQ,GAClB,OAAO,CAAC,cAAc,EAAE,CAAC,CAY3B;AAED,wBAAsB,cAAc,CAClC,gBAAgB,CAAC,EAAE,MAAM,EACzB,gBAAgB,CAAC,EAAE,QAAQ,GAC1B,OAAO,CAAC,cAAc,CAAC,CAyBzB;AAyFD,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CAyBtD"}
1
+ {"version":3,"file":"platform.d.mts","sourceRoot":"","sources":["../../src/utils/platform.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,8BAA6B;AAErD,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,OAAO,EAAE,cAAc,EAAE,CAAC;gBAEvB,OAAO,EAAE,cAAc,EAAE;CAQtC;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,CAAC,EAAE,QAAQ,GAClB,OAAO,CAAC,cAAc,EAAE,CAAC,CAY3B;AAED,wBAAsB,cAAc,CAClC,gBAAgB,CAAC,EAAE,MAAM,EACzB,gBAAgB,CAAC,EAAE,QAAQ,GAC1B,OAAO,CAAC,cAAc,CAAC,CAyBzB;AAyFD;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,OAAO,CAAC,CAQlB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CAgCtD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,OAAO,GACnB,IAAI,GAAG,UAAU,GAAG,IAAI,CAU1B"}
@@ -109,26 +109,82 @@ function findPythonBinDirs(base) {
109
109
  return [];
110
110
  }
111
111
  }
112
- export async function resolveIdbPath() {
113
- const result = await exec('idb', ['--help']).catch(() => null);
114
- if (result?.exitCode === 0) {
115
- return 'idb';
112
+ /**
113
+ * Whether an `idb` binary supports the `--api` describe-all backend selector.
114
+ * Only the unified `idb` (>= ~1.5) exposes it; the legacy python `fb-idb`
115
+ * rejects `--api` as a usage error, so we probe the help text.
116
+ *
117
+ * @param idbPath - The `idb` executable to probe.
118
+ * @returns True when `--api` is accepted.
119
+ */
120
+ export async function idbSupportsApiSelection(idbPath) {
121
+ const help = await exec(idbPath, ['ui', 'describe-all', '--help']).catch(() => null);
122
+ if (help?.exitCode !== 0) {
123
+ return false;
116
124
  }
125
+ return `${help.stdout}\n${help.stderr}`.includes('--api');
126
+ }
127
+ /**
128
+ * Resolve the `idb` executable, preferring one that supports `--api`.
129
+ *
130
+ * A machine can have both the legacy python `fb-idb` (often first on `PATH`)
131
+ * and the unified `facebook/fb/idb`; only the latter supports `--api axbridge`,
132
+ * which iOS 17+ simulators require. We return the first `--api`-capable
133
+ * candidate, falling back to the first merely-working one.
134
+ *
135
+ * @returns The path to the best available `idb`.
136
+ * @throws When no `idb` executable is found.
137
+ */
138
+ export async function resolveIdbPath() {
117
139
  const home = process.env.HOME ?? '';
118
140
  const candidates = [
119
- '/usr/local/bin/idb',
141
+ 'idb',
120
142
  '/opt/homebrew/bin/idb',
143
+ '/usr/local/bin/idb',
121
144
  ...findPythonBinDirs(`${home}/Library/Python`).map((d) => `${d}/idb`),
122
145
  ...findPythonBinDirs('/usr/local/lib').map((d) => `${d}/idb`),
123
146
  ];
147
+ let firstWorking = null;
124
148
  for (const candidate of candidates) {
125
149
  const probe = await exec(candidate, ['--help']).catch(() => null);
126
- if (probe?.exitCode === 0) {
150
+ if (probe?.exitCode !== 0) {
151
+ continue;
152
+ }
153
+ if (firstWorking === null) {
154
+ firstWorking = candidate;
155
+ }
156
+ if (await idbSupportsApiSelection(candidate)) {
127
157
  return candidate;
128
158
  }
129
159
  }
160
+ if (firstWorking !== null) {
161
+ return firstWorking;
162
+ }
130
163
  throw new Error('idb not found.\n' +
131
- 'Install: brew tap facebook/fb && brew install idb-companion && pip3 install fb-idb');
164
+ 'Install the unified client: brew tap facebook/fb && brew install idb');
165
+ }
166
+ /**
167
+ * The `--api` accessibility backend for a given iOS runtime.
168
+ *
169
+ * The backends are version-inverted: `ax` works on iOS <= 17 but is broken on
170
+ * iOS 26; `axbridge` works on iOS >= 18. So we pick `axbridge` for >= 18 and
171
+ * `ax` otherwise, or `null` (omit `--api`) when the CLI cannot select. The
172
+ * caller still falls back if the chosen backend errors.
173
+ *
174
+ * @param osVersion - The device OS version (e.g. '17.4', '26.1').
175
+ * @param supportsApi - Whether the resolved CLI accepts `--api`.
176
+ * @returns The `--api` value, or `null` to omit it.
177
+ */
178
+ export function chooseAxApi(osVersion, supportsApi) {
179
+ if (!supportsApi) {
180
+ return null;
181
+ }
182
+ const major = Number.parseInt(osVersion, 10);
183
+ if (Number.isNaN(major)) {
184
+ // Unknown version - prefer the modern reader and let the caller fall back
185
+ return 'axbridge';
186
+ }
187
+ return major >= 18 ? 'axbridge' : 'ax';
132
188
  }
133
189
  async function resolveAdbPath() {
134
190
  const result = await exec('adb', ['version']).catch(() => null);
@@ -1 +1 @@
1
- {"version":3,"file":"platform.mjs","sourceRoot":"","sources":["../../src/utils/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,gBAAgB;AAEtC,OAAO,EAAE,IAAI,EAAE,kBAAkB,EAAE,mBAAkB;AAQrD,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAG7C,YAAY,OAAyB;QACnC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;QACnE,KAAK,CACH,wDAAwD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC3E,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;QACnC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,QAAmB;IAEnB,MAAM,QAAQ,GAAgC,EAAE,CAAC;IAEjD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,QAAQ,CAAC,IAAI,CAAC,0BAA0B,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9D,CAAC;IACD,IAAI,QAAQ,KAAK,KAAK,EAAE,CAAC;QACvB,QAAQ,CAAC,IAAI,CAAC,8BAA8B,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAClE,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC5C,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;AACxB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,gBAAyB,EACzB,gBAA2B;IAE3B,IAAI,gBAAgB,EAAE,CAAC;QACrB,OAAO,oBAAoB,CAAC,gBAAgB,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,gBAAgB,CAAC,gBAAgB,CAAC,CAAC;IAEzD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC1C,CAAC;IAED,MAAM,MAAM,GACV,gBAAgB,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAEvE,MAAM,aAAa,GAAG,gBAAgB,IAAI,KAAK,CAAC;IAChD,MAAM,IAAI,KAAK,CACb,gBAAgB,aAAa,kBAAkB;QAC7C,uCAAuC;QACvC,oBAAoB,MAAM,CAAC,CAAC,CAAC,gCAAgC,CAAC,CAAC,CAAC,eAAe,IAAI;QACnF,wDAAwD,CAC3D,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,oBAAoB,CAAC,QAAgB;IAClD,sDAAsD;IACtD,IAAI,+CAA+C,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;IACvC,CAAC;IAED,6CAA6C;IAC7C,IAAI,QAAQ,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;IAC3C,CAAC;IAED,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACzC,kBAAkB,CAAC,KAAK,CAAC;QACzB,kBAAkB,CAAC,KAAK,CAAC;KAC1B,CAAC,CAAC;IAEH,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;QACnE,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;QACvC,CAAC;IACH,CAAC;IAED,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC;QAChE,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;QAC3C,CAAC;IACH,CAAC;IAED,MAAM,IAAI,KAAK,CACb,4CAA4C,QAAQ,IAAI;QACtD,0DAA0D,CAC7D,CAAC;AACJ,CAAC;AASD,KAAK,UAAU,0BAA0B;IACvC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE;QACjC,QAAQ;QACR,MAAM;QACN,SAAS;QACT,QAAQ;QACR,QAAQ;KACT,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAEtC,CAAC;QACF,MAAM,OAAO,GAAqB,EAAE,CAAC;QACrC,KAAK,MAAM,cAAc,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3D,KAAK,MAAM,MAAM,IAAI,cAAc,EAAE,CAAC;gBACpC,IAAI,MAAM,CAAC,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;oBACpD,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;gBAC3D,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAY;IACrC,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;aAC9C,MAAM,CACL,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;YAChD,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAC7B;aACA,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAC/D,IAAI,MAAM,EAAE,QAAQ,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;IACpC,MAAM,UAAU,GAAG;QACjB,oBAAoB;QACpB,uBAAuB;QACvB,GAAG,iBAAiB,CAAC,GAAG,IAAI,iBAAiB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC;QACrE,GAAG,iBAAiB,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC;KAC9D,CAAC;IAEF,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QAClE,IAAI,KAAK,EAAE,QAAQ,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED,MAAM,IAAI,KAAK,CACb,kBAAkB;QAChB,oFAAoF,CACvF,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,cAAc;IAC3B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAChE,IAAI,MAAM,EAAE,QAAQ,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;IACpC,MAAM,UAAU,GAAG;QACjB,OAAO,CAAC,GAAG,CAAC,YAAY;YACtB,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,qBAAqB;QAClD,OAAO,CAAC,GAAG,CAAC,gBAAgB;YAC1B,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,qBAAqB;QACtD,GAAG,IAAI,yCAAyC;QAChD,GAAG,IAAI,iCAAiC;KACzC,CAAC,MAAM,CAAC,OAAO,CAAa,CAAC;IAE9B,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QACnE,IAAI,KAAK,EAAE,QAAQ,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC;AACnC,CAAC;AAED,KAAK,UAAU,8BAA8B;IAC3C,MAAM,GAAG,GAAG,MAAM,cAAc,EAAE,CAAC;IACnC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IAC5C,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,OAAO,GAAqB,EAAE,CAAC;IACrC,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACxD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAClD,IAAI,MAAM,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC;YACjC,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC","sourcesContent":["import { readdirSync } from 'node:fs';\n\nimport { exec, isCommandAvailable } from './exec.js';\nimport type { Platform } from '../backends/types.js';\n\nexport type DetectedDevice = {\n platform: Platform;\n deviceId: string;\n};\n\nexport class MultipleDevicesError extends Error {\n readonly devices: DetectedDevice[];\n\n constructor(devices: DetectedDevice[]) {\n const lines = devices.map((d) => ` ${d.platform}\\t${d.deviceId}`);\n super(\n `Multiple devices detected. Set DEVICE_ID to one of:\\n${lines.join('\\n')}`,\n );\n this.name = 'MultipleDevicesError';\n this.devices = devices;\n }\n}\n\n/**\n * Detect all connected devices across iOS and Android.\n *\n * @param platform - Optional filter to only return devices of a specific platform.\n * @returns Array of detected devices (may be empty).\n */\nexport async function detectAllDevices(\n platform?: Platform,\n): Promise<DetectedDevice[]> {\n const searches: Promise<DetectedDevice[]>[] = [];\n\n if (platform !== 'android') {\n searches.push(findAllBootedIOSSimulators().catch(() => []));\n }\n if (platform !== 'ios') {\n searches.push(findAllConnectedAndroidDevices().catch(() => []));\n }\n\n const results = await Promise.all(searches);\n return results.flat();\n}\n\nexport async function detectPlatform(\n explicitDeviceId?: string,\n explicitPlatform?: Platform,\n): Promise<DetectedDevice> {\n if (explicitDeviceId) {\n return detectFromExplicitId(explicitDeviceId);\n }\n\n const devices = await detectAllDevices(explicitPlatform);\n\n if (devices.length === 1) {\n return devices[0];\n }\n\n if (devices.length > 1) {\n throw new MultipleDevicesError(devices);\n }\n\n const hasAdb =\n explicitPlatform === 'ios' ? false : await isCommandAvailable('adb');\n\n const platformLabel = explicitPlatform ?? 'any';\n throw new Error(\n `No connected ${platformLabel} device found.\\n` +\n ` iOS (simctl): no booted simulator\\n` +\n ` Android (adb): ${hasAdb ? 'available, no connected device' : 'not installed'}\\n` +\n 'Boot a simulator/emulator or set DEVICE_ID explicitly.',\n );\n}\n\nasync function detectFromExplicitId(deviceId: string): Promise<DetectedDevice> {\n // UUIDs with dashes are typically iOS simulator UDIDs\n if (/^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$/iu.test(deviceId)) {\n return { platform: 'ios', deviceId };\n }\n\n // Android emulator or device serial patterns\n if (deviceId.startsWith('emulator-') || /^[A-Za-z0-9]+$/u.test(deviceId)) {\n return { platform: 'android', deviceId };\n }\n\n const [hasIdb, hasAdb] = await Promise.all([\n isCommandAvailable('idb'),\n isCommandAvailable('adb'),\n ]);\n\n if (hasIdb) {\n const result = await exec('idb', ['describe', '--udid', deviceId]);\n if (result.exitCode === 0) {\n return { platform: 'ios', deviceId };\n }\n }\n\n if (hasAdb) {\n const result = await exec('adb', ['-s', deviceId, 'get-state']);\n if (result.exitCode === 0) {\n return { platform: 'android', deviceId };\n }\n }\n\n throw new Error(\n `Cannot determine platform for device ID: ${deviceId}. ` +\n 'Ensure the device is connected and idb/adb is available.',\n );\n}\n\ntype SimctlDevice = {\n udid: string;\n name: string;\n state: string;\n isAvailable: boolean;\n};\n\nasync function findAllBootedIOSSimulators(): Promise<DetectedDevice[]> {\n const result = await exec('xcrun', [\n 'simctl',\n 'list',\n 'devices',\n 'booted',\n '--json',\n ]);\n if (result.exitCode !== 0) {\n return [];\n }\n\n try {\n const parsed = JSON.parse(result.stdout) as {\n devices: Record<string, SimctlDevice[]>;\n };\n const devices: DetectedDevice[] = [];\n for (const runtimeDevices of Object.values(parsed.devices)) {\n for (const device of runtimeDevices) {\n if (device.state === 'Booted' && device.isAvailable) {\n devices.push({ platform: 'ios', deviceId: device.udid });\n }\n }\n }\n return devices;\n } catch {\n return [];\n }\n}\n\nfunction findPythonBinDirs(base: string): string[] {\n try {\n return readdirSync(base, { withFileTypes: true })\n .filter(\n (e) =>\n (e.isDirectory() && e.name.startsWith('python')) ||\n /^\\d+\\.\\d+$/u.test(e.name),\n )\n .map((e) => `${base}/${e.name}/bin`);\n } catch {\n return [];\n }\n}\n\nexport async function resolveIdbPath(): Promise<string> {\n const result = await exec('idb', ['--help']).catch(() => null);\n if (result?.exitCode === 0) {\n return 'idb';\n }\n\n const home = process.env.HOME ?? '';\n const candidates = [\n '/usr/local/bin/idb',\n '/opt/homebrew/bin/idb',\n ...findPythonBinDirs(`${home}/Library/Python`).map((d) => `${d}/idb`),\n ...findPythonBinDirs('/usr/local/lib').map((d) => `${d}/idb`),\n ];\n\n for (const candidate of candidates) {\n const probe = await exec(candidate, ['--help']).catch(() => null);\n if (probe?.exitCode === 0) {\n return candidate;\n }\n }\n\n throw new Error(\n 'idb not found.\\n' +\n 'Install: brew tap facebook/fb && brew install idb-companion && pip3 install fb-idb',\n );\n}\n\nasync function resolveAdbPath(): Promise<string> {\n const result = await exec('adb', ['version']).catch(() => null);\n if (result?.exitCode === 0) {\n return 'adb';\n }\n\n const home = process.env.HOME ?? '';\n const candidates = [\n process.env.ANDROID_HOME &&\n `${process.env.ANDROID_HOME}/platform-tools/adb`,\n process.env.ANDROID_SDK_ROOT &&\n `${process.env.ANDROID_SDK_ROOT}/platform-tools/adb`,\n `${home}/Library/Android/sdk/platform-tools/adb`,\n `${home}/Android/Sdk/platform-tools/adb`,\n ].filter(Boolean) as string[];\n\n for (const candidate of candidates) {\n const probe = await exec(candidate, ['version']).catch(() => null);\n if (probe?.exitCode === 0) {\n return candidate;\n }\n }\n\n throw new Error('adb not found');\n}\n\nasync function findAllConnectedAndroidDevices(): Promise<DetectedDevice[]> {\n const adb = await resolveAdbPath();\n const result = await exec(adb, ['devices']);\n if (result.exitCode !== 0) {\n return [];\n }\n\n const devices: DetectedDevice[] = [];\n const lines = result.stdout.trim().split('\\n').slice(1);\n for (const line of lines) {\n const [serial, state] = line.trim().split(/\\s+/u);\n if (serial && state === 'device') {\n devices.push({ platform: 'android', deviceId: serial });\n }\n }\n return devices;\n}\n"]}
1
+ {"version":3,"file":"platform.mjs","sourceRoot":"","sources":["../../src/utils/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,gBAAgB;AAEtC,OAAO,EAAE,IAAI,EAAE,kBAAkB,EAAE,mBAAkB;AAQrD,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAG7C,YAAY,OAAyB;QACnC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;QACnE,KAAK,CACH,wDAAwD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC3E,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;QACnC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,QAAmB;IAEnB,MAAM,QAAQ,GAAgC,EAAE,CAAC;IAEjD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,QAAQ,CAAC,IAAI,CAAC,0BAA0B,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9D,CAAC;IACD,IAAI,QAAQ,KAAK,KAAK,EAAE,CAAC;QACvB,QAAQ,CAAC,IAAI,CAAC,8BAA8B,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAClE,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC5C,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;AACxB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,gBAAyB,EACzB,gBAA2B;IAE3B,IAAI,gBAAgB,EAAE,CAAC;QACrB,OAAO,oBAAoB,CAAC,gBAAgB,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,gBAAgB,CAAC,gBAAgB,CAAC,CAAC;IAEzD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC1C,CAAC;IAED,MAAM,MAAM,GACV,gBAAgB,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAEvE,MAAM,aAAa,GAAG,gBAAgB,IAAI,KAAK,CAAC;IAChD,MAAM,IAAI,KAAK,CACb,gBAAgB,aAAa,kBAAkB;QAC7C,uCAAuC;QACvC,oBAAoB,MAAM,CAAC,CAAC,CAAC,gCAAgC,CAAC,CAAC,CAAC,eAAe,IAAI;QACnF,wDAAwD,CAC3D,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,oBAAoB,CAAC,QAAgB;IAClD,sDAAsD;IACtD,IAAI,+CAA+C,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;IACvC,CAAC;IAED,6CAA6C;IAC7C,IAAI,QAAQ,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;IAC3C,CAAC;IAED,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACzC,kBAAkB,CAAC,KAAK,CAAC;QACzB,kBAAkB,CAAC,KAAK,CAAC;KAC1B,CAAC,CAAC;IAEH,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;QACnE,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;QACvC,CAAC;IACH,CAAC;IAED,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC;QAChE,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;QAC3C,CAAC;IACH,CAAC;IAED,MAAM,IAAI,KAAK,CACb,4CAA4C,QAAQ,IAAI;QACtD,0DAA0D,CAC7D,CAAC;AACJ,CAAC;AASD,KAAK,UAAU,0BAA0B;IACvC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE;QACjC,QAAQ;QACR,MAAM;QACN,SAAS;QACT,QAAQ;QACR,QAAQ;KACT,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAEtC,CAAC;QACF,MAAM,OAAO,GAAqB,EAAE,CAAC;QACrC,KAAK,MAAM,cAAc,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3D,KAAK,MAAM,MAAM,IAAI,cAAc,EAAE,CAAC;gBACpC,IAAI,MAAM,CAAC,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;oBACpD,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;gBAC3D,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAY;IACrC,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;aAC9C,MAAM,CACL,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;YAChD,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAC7B;aACA,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,OAAe;IAEf,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,cAAc,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,CACtE,GAAG,EAAE,CAAC,IAAI,CACX,CAAC;IACF,IAAI,IAAI,EAAE,QAAQ,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,GAAG,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AAC5D,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;IACpC,MAAM,UAAU,GAAG;QACjB,KAAK;QACL,uBAAuB;QACvB,oBAAoB;QACpB,GAAG,iBAAiB,CAAC,GAAG,IAAI,iBAAiB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC;QACrE,GAAG,iBAAiB,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC;KAC9D,CAAC;IAEF,IAAI,YAAY,GAAkB,IAAI,CAAC;IACvC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QAClE,IAAI,KAAK,EAAE,QAAQ,KAAK,CAAC,EAAE,CAAC;YAC1B,SAAS;QACX,CAAC;QACD,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;YAC1B,YAAY,GAAG,SAAS,CAAC;QAC3B,CAAC;QACD,IAAI,MAAM,uBAAuB,CAAC,SAAS,CAAC,EAAE,CAAC;YAC7C,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;QAC1B,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,MAAM,IAAI,KAAK,CACb,kBAAkB;QAChB,sEAAsE,CACzE,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,WAAW,CACzB,SAAiB,EACjB,WAAoB;IAEpB,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAC7C,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QACxB,0EAA0E;QAC1E,OAAO,UAAU,CAAC;IACpB,CAAC;IACD,OAAO,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;AACzC,CAAC;AAED,KAAK,UAAU,cAAc;IAC3B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAChE,IAAI,MAAM,EAAE,QAAQ,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;IACpC,MAAM,UAAU,GAAG;QACjB,OAAO,CAAC,GAAG,CAAC,YAAY;YACtB,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,qBAAqB;QAClD,OAAO,CAAC,GAAG,CAAC,gBAAgB;YAC1B,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,qBAAqB;QACtD,GAAG,IAAI,yCAAyC;QAChD,GAAG,IAAI,iCAAiC;KACzC,CAAC,MAAM,CAAC,OAAO,CAAa,CAAC;IAE9B,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QACnE,IAAI,KAAK,EAAE,QAAQ,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC;AACnC,CAAC;AAED,KAAK,UAAU,8BAA8B;IAC3C,MAAM,GAAG,GAAG,MAAM,cAAc,EAAE,CAAC;IACnC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IAC5C,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,OAAO,GAAqB,EAAE,CAAC;IACrC,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACxD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAClD,IAAI,MAAM,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC;YACjC,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC","sourcesContent":["import { readdirSync } from 'node:fs';\n\nimport { exec, isCommandAvailable } from './exec.js';\nimport type { Platform } from '../backends/types.js';\n\nexport type DetectedDevice = {\n platform: Platform;\n deviceId: string;\n};\n\nexport class MultipleDevicesError extends Error {\n readonly devices: DetectedDevice[];\n\n constructor(devices: DetectedDevice[]) {\n const lines = devices.map((d) => ` ${d.platform}\\t${d.deviceId}`);\n super(\n `Multiple devices detected. Set DEVICE_ID to one of:\\n${lines.join('\\n')}`,\n );\n this.name = 'MultipleDevicesError';\n this.devices = devices;\n }\n}\n\n/**\n * Detect all connected devices across iOS and Android.\n *\n * @param platform - Optional filter to only return devices of a specific platform.\n * @returns Array of detected devices (may be empty).\n */\nexport async function detectAllDevices(\n platform?: Platform,\n): Promise<DetectedDevice[]> {\n const searches: Promise<DetectedDevice[]>[] = [];\n\n if (platform !== 'android') {\n searches.push(findAllBootedIOSSimulators().catch(() => []));\n }\n if (platform !== 'ios') {\n searches.push(findAllConnectedAndroidDevices().catch(() => []));\n }\n\n const results = await Promise.all(searches);\n return results.flat();\n}\n\nexport async function detectPlatform(\n explicitDeviceId?: string,\n explicitPlatform?: Platform,\n): Promise<DetectedDevice> {\n if (explicitDeviceId) {\n return detectFromExplicitId(explicitDeviceId);\n }\n\n const devices = await detectAllDevices(explicitPlatform);\n\n if (devices.length === 1) {\n return devices[0];\n }\n\n if (devices.length > 1) {\n throw new MultipleDevicesError(devices);\n }\n\n const hasAdb =\n explicitPlatform === 'ios' ? false : await isCommandAvailable('adb');\n\n const platformLabel = explicitPlatform ?? 'any';\n throw new Error(\n `No connected ${platformLabel} device found.\\n` +\n ` iOS (simctl): no booted simulator\\n` +\n ` Android (adb): ${hasAdb ? 'available, no connected device' : 'not installed'}\\n` +\n 'Boot a simulator/emulator or set DEVICE_ID explicitly.',\n );\n}\n\nasync function detectFromExplicitId(deviceId: string): Promise<DetectedDevice> {\n // UUIDs with dashes are typically iOS simulator UDIDs\n if (/^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$/iu.test(deviceId)) {\n return { platform: 'ios', deviceId };\n }\n\n // Android emulator or device serial patterns\n if (deviceId.startsWith('emulator-') || /^[A-Za-z0-9]+$/u.test(deviceId)) {\n return { platform: 'android', deviceId };\n }\n\n const [hasIdb, hasAdb] = await Promise.all([\n isCommandAvailable('idb'),\n isCommandAvailable('adb'),\n ]);\n\n if (hasIdb) {\n const result = await exec('idb', ['describe', '--udid', deviceId]);\n if (result.exitCode === 0) {\n return { platform: 'ios', deviceId };\n }\n }\n\n if (hasAdb) {\n const result = await exec('adb', ['-s', deviceId, 'get-state']);\n if (result.exitCode === 0) {\n return { platform: 'android', deviceId };\n }\n }\n\n throw new Error(\n `Cannot determine platform for device ID: ${deviceId}. ` +\n 'Ensure the device is connected and idb/adb is available.',\n );\n}\n\ntype SimctlDevice = {\n udid: string;\n name: string;\n state: string;\n isAvailable: boolean;\n};\n\nasync function findAllBootedIOSSimulators(): Promise<DetectedDevice[]> {\n const result = await exec('xcrun', [\n 'simctl',\n 'list',\n 'devices',\n 'booted',\n '--json',\n ]);\n if (result.exitCode !== 0) {\n return [];\n }\n\n try {\n const parsed = JSON.parse(result.stdout) as {\n devices: Record<string, SimctlDevice[]>;\n };\n const devices: DetectedDevice[] = [];\n for (const runtimeDevices of Object.values(parsed.devices)) {\n for (const device of runtimeDevices) {\n if (device.state === 'Booted' && device.isAvailable) {\n devices.push({ platform: 'ios', deviceId: device.udid });\n }\n }\n }\n return devices;\n } catch {\n return [];\n }\n}\n\nfunction findPythonBinDirs(base: string): string[] {\n try {\n return readdirSync(base, { withFileTypes: true })\n .filter(\n (e) =>\n (e.isDirectory() && e.name.startsWith('python')) ||\n /^\\d+\\.\\d+$/u.test(e.name),\n )\n .map((e) => `${base}/${e.name}/bin`);\n } catch {\n return [];\n }\n}\n\n/**\n * Whether an `idb` binary supports the `--api` describe-all backend selector.\n * Only the unified `idb` (>= ~1.5) exposes it; the legacy python `fb-idb`\n * rejects `--api` as a usage error, so we probe the help text.\n *\n * @param idbPath - The `idb` executable to probe.\n * @returns True when `--api` is accepted.\n */\nexport async function idbSupportsApiSelection(\n idbPath: string,\n): Promise<boolean> {\n const help = await exec(idbPath, ['ui', 'describe-all', '--help']).catch(\n () => null,\n );\n if (help?.exitCode !== 0) {\n return false;\n }\n return `${help.stdout}\\n${help.stderr}`.includes('--api');\n}\n\n/**\n * Resolve the `idb` executable, preferring one that supports `--api`.\n *\n * A machine can have both the legacy python `fb-idb` (often first on `PATH`)\n * and the unified `facebook/fb/idb`; only the latter supports `--api axbridge`,\n * which iOS 17+ simulators require. We return the first `--api`-capable\n * candidate, falling back to the first merely-working one.\n *\n * @returns The path to the best available `idb`.\n * @throws When no `idb` executable is found.\n */\nexport async function resolveIdbPath(): Promise<string> {\n const home = process.env.HOME ?? '';\n const candidates = [\n 'idb',\n '/opt/homebrew/bin/idb',\n '/usr/local/bin/idb',\n ...findPythonBinDirs(`${home}/Library/Python`).map((d) => `${d}/idb`),\n ...findPythonBinDirs('/usr/local/lib').map((d) => `${d}/idb`),\n ];\n\n let firstWorking: string | null = null;\n for (const candidate of candidates) {\n const probe = await exec(candidate, ['--help']).catch(() => null);\n if (probe?.exitCode !== 0) {\n continue;\n }\n if (firstWorking === null) {\n firstWorking = candidate;\n }\n if (await idbSupportsApiSelection(candidate)) {\n return candidate;\n }\n }\n\n if (firstWorking !== null) {\n return firstWorking;\n }\n\n throw new Error(\n 'idb not found.\\n' +\n 'Install the unified client: brew tap facebook/fb && brew install idb',\n );\n}\n\n/**\n * The `--api` accessibility backend for a given iOS runtime.\n *\n * The backends are version-inverted: `ax` works on iOS <= 17 but is broken on\n * iOS 26; `axbridge` works on iOS >= 18. So we pick `axbridge` for >= 18 and\n * `ax` otherwise, or `null` (omit `--api`) when the CLI cannot select. The\n * caller still falls back if the chosen backend errors.\n *\n * @param osVersion - The device OS version (e.g. '17.4', '26.1').\n * @param supportsApi - Whether the resolved CLI accepts `--api`.\n * @returns The `--api` value, or `null` to omit it.\n */\nexport function chooseAxApi(\n osVersion: string,\n supportsApi: boolean,\n): 'ax' | 'axbridge' | null {\n if (!supportsApi) {\n return null;\n }\n const major = Number.parseInt(osVersion, 10);\n if (Number.isNaN(major)) {\n // Unknown version - prefer the modern reader and let the caller fall back\n return 'axbridge';\n }\n return major >= 18 ? 'axbridge' : 'ax';\n}\n\nasync function resolveAdbPath(): Promise<string> {\n const result = await exec('adb', ['version']).catch(() => null);\n if (result?.exitCode === 0) {\n return 'adb';\n }\n\n const home = process.env.HOME ?? '';\n const candidates = [\n process.env.ANDROID_HOME &&\n `${process.env.ANDROID_HOME}/platform-tools/adb`,\n process.env.ANDROID_SDK_ROOT &&\n `${process.env.ANDROID_SDK_ROOT}/platform-tools/adb`,\n `${home}/Library/Android/sdk/platform-tools/adb`,\n `${home}/Android/Sdk/platform-tools/adb`,\n ].filter(Boolean) as string[];\n\n for (const candidate of candidates) {\n const probe = await exec(candidate, ['version']).catch(() => null);\n if (probe?.exitCode === 0) {\n return candidate;\n }\n }\n\n throw new Error('adb not found');\n}\n\nasync function findAllConnectedAndroidDevices(): Promise<DetectedDevice[]> {\n const adb = await resolveAdbPath();\n const result = await exec(adb, ['devices']);\n if (result.exitCode !== 0) {\n return [];\n }\n\n const devices: DetectedDevice[] = [];\n const lines = result.stdout.trim().split('\\n').slice(1);\n for (const line of lines) {\n const [serial, state] = line.trim().split(/\\s+/u);\n if (serial && state === 'device') {\n devices.push({ platform: 'android', deviceId: serial });\n }\n }\n return devices;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask/device-mcp",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "MCP server for mobile device interaction — iOS (IDB), Android (ADB), and Appium/BrowserStack",
5
5
  "homepage": "https://github.com/MetaMask/device-mcp#readme",
6
6
  "bugs": {
@@ -34,6 +34,7 @@
34
34
  ],
35
35
  "scripts": {
36
36
  "build": "ts-bridge --project tsconfig.build.json --clean",
37
+ "build:android-helper": "./scripts/build-android-helper.sh \"$(node -p \"require('./package.json').version\")\" dist/android",
37
38
  "build:docs": "typedoc",
38
39
  "clean": "rm -rf dist",
39
40
  "dev": "tsc --watch",