@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":"idb-backend.mjs","sourceRoot":"","sources":["../../src/backends/idb-backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile as execFileCb, spawn } from 'node:child_process';\nimport type { ChildProcess } from 'node:child_process';\nimport { readFile } from 'node:fs/promises';\n\nimport type {\n DeviceBackend,\n DeviceButton,\n DeviceInfo,\n SnapshotResult,\n ScreenshotResult,\n ScreenshotFileResult,\n ScreenshotOptions,\n LogsResult,\n TapResult,\n AppStateResult,\n ElementQuery,\n UIElement,\n WindowSize,\n} from './types.js';\nimport { ACCEPT_LABELS, DENY_LABELS } from '../utils/alert-labels.js';\nimport {\n findElement,\n describeElement,\n computeSwipeEnd,\n} from '../utils/element.js';\nimport { execStrict, exec } from '../utils/exec.js';\nimport {\n hardenArtifactFile,\n resolveArtifactPath,\n} from '../utils/output-path.js';\nimport { resolveIdbPath } from '../utils/platform.js';\n\nexport class IdbBackend implements DeviceBackend {\n readonly kind = 'idb' as const;\n\n readonly platform = 'ios' as const;\n\n #connected = false;\n\n readonly #udid: string;\n\n #idbPath = 'idb';\n\n #recordingProcess: ChildProcess | null = null;\n\n #recordingPath: string | null = null;\n\n constructor(udid: string) {\n this.#udid = udid;\n }\n\n async ensureConnected(): Promise<void> {\n if (this.#connected) {\n return;\n }\n\n this.#idbPath = await resolveIdbPath();\n await exec(this.#idbPath, ['connect', this.#udid]);\n await this.#enableSimulatorAccessibility();\n this.#connected = true;\n }\n\n /**\n * Enable the simulator's software accessibility bridge, which `idb ui\n * describe-all` reads. React Native publishes its UI to this bridge; when\n * `ApplicationAccessibilityEnabled` is off, idb sees only native UIKit views\n * and an RN screen collapses to the app root node (a bottom sheet over it\n * appears as ~2 nodes). XCUITest backends avoid this because attaching a\n * test runner enables the bridge as a side effect; idb does not, so we set\n * it explicitly. The value is read when an app initializes accessibility, so\n * callers should launch/relaunch the app after connecting.\n */\n async #enableSimulatorAccessibility(): Promise<void> {\n try {\n await exec('xcrun', [\n 'simctl',\n 'spawn',\n this.#udid,\n 'defaults',\n 'write',\n 'com.apple.Accessibility',\n 'ApplicationAccessibilityEnabled',\n '-bool',\n 'true',\n ]);\n } catch {\n // Best-effort: on hosts where the write is unavailable, keep the\n // connection usable for native views and coordinate taps.\n }\n }\n\n async getDeviceInfo(): Promise<DeviceInfo> {\n await this.ensureConnected();\n const raw = await execStrict(this.#idbPath, [\n 'describe',\n '--udid',\n this.#udid,\n '--json',\n ]);\n let info: Record<string, unknown>;\n try {\n info = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n throw new Error(\n `idb describe returned invalid JSON for device ${this.#udid}: ${raw.slice(0, 200)}`,\n );\n }\n return {\n platform: 'ios',\n deviceId: this.#udid,\n name: (info.name as string) ?? 'Unknown',\n osVersion: (info.os_version as string) ?? 'Unknown',\n state: (info.state as string) ?? 'Unknown',\n };\n }\n\n async snapshot(): Promise<SnapshotResult> {\n await this.ensureConnected();\n const raw = await execStrict(this.#idbPath, [\n 'ui',\n 'describe-all',\n '--udid',\n this.#udid,\n ]);\n const hierarchy = parseIdbHierarchy(raw);\n return {\n platform: 'ios',\n hierarchy,\n raw,\n timestamp: Date.now(),\n };\n }\n\n async tapElement(query: ElementQuery): Promise<TapResult> {\n await this.ensureConnected();\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const x = Math.round(element.frame.x + element.frame.width / 2);\n const y = Math.round(element.frame.y + element.frame.height / 2);\n await this.tapCoordinates(x, y);\n\n return {\n success: true,\n x,\n y,\n targetDescription: describeElement(element),\n };\n }\n\n async getElementText(query: ElementQuery): Promise<string> {\n await this.ensureConnected();\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return element.label ?? element.value ?? '';\n }\n\n async tapCoordinates(x: number, y: number): Promise<void> {\n await this.ensureConnected();\n await execStrict(this.#idbPath, [\n 'ui',\n 'tap',\n String(x),\n String(y),\n '--udid',\n this.#udid,\n ]);\n }\n\n async typeText(text: string): Promise<void> {\n await this.ensureConnected();\n await exec(this.#idbPath, [\n 'ui',\n 'key-sequence',\n '--',\n 'cmd+a',\n '--udid',\n this.#udid,\n ]);\n await exec(this.#idbPath, ['ui', 'key', 'DELETE', '--udid', this.#udid]);\n await execStrict(this.#idbPath, ['ui', 'text', text, '--udid', this.#udid]);\n }\n\n async swipe(\n direction: 'up' | 'down' | 'left' | 'right',\n startX?: number,\n startY?: number,\n distance?: number,\n ): Promise<void> {\n await this.ensureConnected();\n const d = distance ?? 500;\n const sx = startX ?? 200;\n const sy = startY ?? 400;\n const [endX, endY] = computeSwipeEnd(sx, sy, direction, d);\n\n await execStrict(this.#idbPath, [\n 'ui',\n 'swipe',\n String(sx),\n String(sy),\n String(endX),\n String(endY),\n '--udid',\n this.#udid,\n ]);\n }\n\n async waitForElement(\n query: ElementQuery,\n timeoutMs = 10_000,\n intervalMs = 500,\n ): Promise<UIElement> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (element) {\n return element;\n }\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n throw new Error(\n `Timed out waiting for element: ${JSON.stringify(query)} (${timeoutMs}ms)`,\n );\n }\n\n async getAppState(bundleId: string): Promise<AppStateResult> {\n await this.ensureConnected();\n try {\n const raw = await execStrict(this.#idbPath, [\n 'list-apps',\n '--udid',\n this.#udid,\n ]);\n\n for (const line of raw.trim().split('\\n')) {\n if (line.includes(bundleId)) {\n const parts = line.split('|').map((s) => s.trim());\n return {\n bundleId,\n state: parts[2] ?? 'Unknown',\n pid: parts[3] ? parseInt(parts[3], 10) : undefined,\n };\n }\n }\n\n return { bundleId, state: 'Not Installed' };\n } catch {\n return this.#getAppStateViaSimctl(bundleId);\n }\n }\n\n async #getAppStateViaSimctl(bundleId: string): Promise<AppStateResult> {\n const result = await exec('xcrun', ['simctl', 'listapps', this.#udid]);\n if (result.exitCode === 0 && result.stdout.includes(bundleId)) {\n return { bundleId, state: 'Running' };\n }\n return { bundleId, state: 'Not Installed' };\n }\n\n screenshot(\n outputPath?: string,\n options?: { encode?: true },\n ): Promise<ScreenshotResult>;\n\n screenshot(\n outputPath: string | undefined,\n options: { encode: false },\n ): Promise<ScreenshotFileResult>;\n\n screenshot(\n outputPath?: string,\n options?: ScreenshotOptions,\n ): Promise<ScreenshotResult | ScreenshotFileResult>;\n\n async screenshot(\n outputPath?: string,\n options?: ScreenshotOptions,\n ): Promise<ScreenshotResult | ScreenshotFileResult> {\n await this.ensureConnected();\n const path = resolveArtifactPath(outputPath, 'screenshot');\n await execStrict(this.#idbPath, ['screenshot', path, '--udid', this.#udid]);\n hardenArtifactFile(path);\n if (options?.encode === false) {\n return { data: undefined, format: 'png', path };\n }\n const data = await readFile(path, 'base64');\n return { data, format: 'png', path };\n }\n\n async openApp(bundleId: string): Promise<void> {\n await this.ensureConnected();\n try {\n await execStrict(this.#idbPath, [\n 'launch',\n bundleId,\n '--udid',\n this.#udid,\n ]);\n } catch {\n await execStrict('xcrun', ['simctl', 'launch', this.#udid, bundleId]);\n }\n }\n\n async closeApp(bundleId: string): Promise<void> {\n await this.ensureConnected();\n try {\n await execStrict(this.#idbPath, [\n 'terminate',\n bundleId,\n '--udid',\n this.#udid,\n ]);\n } catch {\n await execStrict('xcrun', ['simctl', 'terminate', this.#udid, bundleId]);\n }\n }\n\n async pressButton(button: DeviceButton): Promise<void> {\n await this.ensureConnected();\n const keyMap: Record<DeviceButton, string> = {\n home: 'HOME',\n lock: 'LOCK',\n back: 'HOME',\n enter: 'RETURN',\n };\n await execStrict(this.#idbPath, [\n 'ui',\n 'key',\n keyMap[button],\n '--udid',\n this.#udid,\n ]);\n }\n\n async dismissKeyboard(): Promise<void> {\n await this.ensureConnected();\n await execStrict(this.#idbPath, [\n 'ui',\n 'key',\n 'RETURN',\n '--udid',\n this.#udid,\n ]);\n }\n\n async dismissAlert(accept: boolean): Promise<void> {\n await this.ensureConnected();\n const snap = await this.snapshot();\n const candidates = accept ? ACCEPT_LABELS : DENY_LABELS;\n\n for (const label of candidates) {\n const element = findElement(snap.hierarchy, { label });\n if (element) {\n const cx = Math.round(element.frame.x + element.frame.width / 2);\n const cy = Math.round(element.frame.y + element.frame.height / 2);\n await this.tapCoordinates(cx, cy);\n return;\n }\n }\n\n throw new Error(`No alert button found. Tried: ${candidates.join(', ')}`);\n }\n\n async getLogs(durationSeconds = 30, filter?: string): Promise<LogsResult> {\n await this.ensureConnected();\n const args = [\n 'log',\n '--udid',\n this.#udid,\n '--',\n '--last',\n `${durationSeconds}s`,\n ];\n if (filter) {\n args.push('--predicate', `eventMessage CONTAINS \"${filter}\"`);\n }\n const raw = await execStrict(this.#idbPath, args);\n const entries = raw\n .trim()\n .split('\\n')\n .filter(Boolean)\n .map((line) => ({\n timestamp: line.substring(0, 31).trim(),\n level: 'info',\n message: line.substring(31).trim(),\n }));\n return { entries, source: 'idb log' };\n }\n\n async longPress(query: ElementQuery, durationMs = 1000): Promise<TapResult> {\n await this.ensureConnected();\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const cx = Math.round(element.frame.x + element.frame.width / 2);\n const cy = Math.round(element.frame.y + element.frame.height / 2);\n await execStrict(this.#idbPath, [\n 'ui',\n 'tap',\n String(cx),\n String(cy),\n '--duration',\n String(durationMs / 1000),\n '--udid',\n this.#udid,\n ]);\n\n return {\n success: true,\n x: cx,\n y: cy,\n targetDescription: describeElement(element),\n };\n }\n\n async scrollToElement(\n query: ElementQuery,\n direction: 'up' | 'down' = 'down',\n maxAttempts = 10,\n ): Promise<UIElement> {\n await this.ensureConnected();\n let previousRaw = '';\n\n for (let i = 0; i < maxAttempts; i++) {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (element) {\n return element;\n }\n if (snap.raw === previousRaw) {\n break;\n }\n previousRaw = snap.raw;\n await this.swipe(direction);\n }\n throw new Error(\n `Element not found after scrolling: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n async getAlertText(): Promise<string> {\n await this.ensureConnected();\n const snap = await this.snapshot();\n const texts = collectAlertTexts(snap.hierarchy);\n if (texts.length === 0) {\n throw new Error(\n 'No alert is currently displayed.\\n' +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return texts.join('\\n');\n }\n\n async getWindowSize(): Promise<WindowSize> {\n await this.ensureConnected();\n const raw = await execStrict(this.#idbPath, [\n 'describe',\n '--udid',\n this.#udid,\n '--json',\n ]);\n try {\n const info = JSON.parse(raw) as Record<string, unknown>;\n const dims = info.screen_dimensions as\n | { width: number; height: number }\n | undefined;\n if (dims?.width && dims?.height) {\n return { width: dims.width, height: dims.height };\n }\n } catch {\n // fall through to snapshot-based approach\n }\n const snap = await this.snapshot();\n if (snap.hierarchy.length > 0) {\n const root = snap.hierarchy[0];\n if (root.frame.width > 0 && root.frame.height > 0) {\n return { width: root.frame.width, height: root.frame.height };\n }\n }\n throw new Error('Unable to determine window size from device');\n }\n\n async getContexts(): Promise<string[]> {\n return ['NATIVE_APP'];\n }\n\n async setContext(context: string): Promise<void> {\n if (context === 'NATIVE_APP') {\n return;\n }\n throw new Error(\n 'Context switching requires Appium backend. IDB only supports NATIVE_APP.',\n );\n }\n\n async getClipboard(): Promise<string> {\n await this.ensureConnected();\n return execStrict('xcrun', ['simctl', 'pbpaste', this.#udid]);\n }\n\n async setClipboard(text: string): Promise<void> {\n await this.ensureConnected();\n await new Promise<void>((resolve, reject) => {\n const proc = execFileCb(\n 'xcrun',\n ['simctl', 'pbcopy', this.#udid],\n { timeout: 30_000 },\n (error) => (error ? reject(new Error(error.message)) : resolve()),\n );\n proc.stdin?.end(text);\n });\n }\n\n async startScreenRecording(outputPath?: string): Promise<void> {\n await this.ensureConnected();\n if (this.#recordingProcess) {\n throw new Error('Screen recording is already in progress');\n }\n const path = resolveArtifactPath(outputPath, 'recording');\n this.#recordingPath = path;\n this.#recordingProcess = spawn(this.#idbPath, [\n 'record-video',\n path,\n '--udid',\n this.#udid,\n ]);\n this.#recordingProcess.on('error', () => {\n this.#recordingProcess = null;\n this.#recordingPath = null;\n });\n }\n\n async stopScreenRecording(): Promise<string> {\n if (!this.#recordingProcess || !this.#recordingPath) {\n throw new Error('No screen recording in progress');\n }\n const path = this.#recordingPath;\n this.#recordingProcess.kill('SIGINT');\n await new Promise((resolve) => setTimeout(resolve, 1000));\n this.#recordingProcess = null;\n this.#recordingPath = null;\n hardenArtifactFile(path);\n return path;\n }\n}\n\nfunction collectAlertTexts(elements: UIElement[]): string[] {\n const texts: string[] = [];\n for (const el of elements) {\n const isAlertContainer =\n el.type === 'Alert' || el.type === 'Sheet' || el.type.includes('Alert');\n if (isAlertContainer) {\n collectTextsFromChildren(el.children ?? [], texts);\n return texts;\n }\n if (el.children) {\n const found = collectAlertTexts(el.children);\n if (found.length > 0) {\n return found;\n }\n }\n }\n return texts;\n}\n\nfunction collectTextsFromChildren(\n elements: UIElement[],\n texts: string[],\n): void {\n for (const el of elements) {\n if (el.type === 'StaticText' || el.type === 'TextView') {\n const text = el.value ?? el.label;\n if (text) {\n texts.push(text);\n }\n }\n if (el.children) {\n collectTextsFromChildren(el.children, texts);\n }\n }\n}\n\nexport function parseIdbHierarchy(raw: string): UIElement[] {\n try {\n const parsed = JSON.parse(raw);\n if (Array.isArray(parsed)) {\n return parsed.map(mapIdbElement);\n }\n if (parsed.children) {\n return parsed.children.map(mapIdbElement);\n }\n return [mapIdbElement(parsed)];\n } catch {\n return [\n {\n type: 'raw',\n label: raw.slice(0, 200),\n frame: { x: 0, y: 0, width: 0, height: 0 },\n enabled: true,\n },\n ];\n }\n}\n\nexport function mapIdbElement(node: Record<string, unknown>): UIElement {\n const frame = node.frame as\n | { x: number; y: number; width: number; height: number }\n | undefined;\n\n return {\n type: (node.type as string) ?? (node.AXType as string) ?? 'Unknown',\n label: (node.AXLabel as string) ?? (node.label as string) ?? undefined,\n value: (node.AXValue as string) ?? (node.value as string) ?? undefined,\n identifier:\n (node.AXUniqueId as string) ?? (node.identifier as string) ?? undefined,\n frame: frame ?? { x: 0, y: 0, width: 0, height: 0 },\n enabled: (node.enabled as boolean) ?? true,\n children: Array.isArray(node.children)\n ? node.children.map(mapIdbElement)\n : undefined,\n };\n}\n"]}
1
+ {"version":3,"file":"idb-backend.mjs","sourceRoot":"","sources":["../../src/backends/idb-backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile as execFileCb, spawn } from 'node:child_process';\nimport type { ChildProcess } from 'node:child_process';\nimport { readFile } from 'node:fs/promises';\n\nimport type {\n DeviceBackend,\n DeviceButton,\n DeviceInfo,\n SnapshotResult,\n ScreenshotResult,\n ScreenshotFileResult,\n ScreenshotOptions,\n LogsResult,\n TapResult,\n AppStateResult,\n ElementQuery,\n UIElement,\n WindowSize,\n} from './types.js';\nimport { ACCEPT_LABELS, DENY_LABELS } from '../utils/alert-labels.js';\nimport {\n findElement,\n describeElement,\n computeSwipeEnd,\n} from '../utils/element.js';\nimport { execStrict, exec } from '../utils/exec.js';\nimport {\n hardenArtifactFile,\n resolveArtifactPath,\n} from '../utils/output-path.js';\nimport {\n chooseAxApi,\n idbSupportsApiSelection,\n resolveIdbPath,\n} from '../utils/platform.js';\n\nexport class IdbBackend implements DeviceBackend {\n readonly kind = 'idb' as const;\n\n readonly platform = 'ios' as const;\n\n #connected = false;\n\n readonly #udid: string;\n\n #idbPath = 'idb';\n\n #idbSupportsApi = false;\n\n #recordingProcess: ChildProcess | null = null;\n\n #recordingPath: string | null = null;\n\n constructor(udid: string) {\n this.#udid = udid;\n }\n\n async ensureConnected(): Promise<void> {\n if (this.#connected) {\n return;\n }\n\n this.#idbPath = await resolveIdbPath();\n this.#idbSupportsApi = await idbSupportsApiSelection(this.#idbPath);\n await exec(this.#idbPath, ['connect', this.#udid]);\n await this.#enableSimulatorAccessibility();\n this.#connected = true;\n }\n\n /**\n * Enable the simulator's software accessibility bridge, which `idb ui\n * describe-all` reads. React Native publishes its UI to this bridge; when\n * `ApplicationAccessibilityEnabled` is off, idb sees only native UIKit views\n * and an RN screen collapses to the app root node (a bottom sheet over it\n * appears as ~2 nodes). XCUITest backends avoid this because attaching a\n * test runner enables the bridge as a side effect; idb does not, so we set\n * it explicitly. The value is read when an app initializes accessibility, so\n * callers should launch/relaunch the app after connecting.\n */\n async #enableSimulatorAccessibility(): Promise<void> {\n try {\n await exec('xcrun', [\n 'simctl',\n 'spawn',\n this.#udid,\n 'defaults',\n 'write',\n 'com.apple.Accessibility',\n 'ApplicationAccessibilityEnabled',\n '-bool',\n 'true',\n ]);\n } catch {\n // Best-effort: on hosts where the write is unavailable, keep the\n // connection usable for native views and coordinate taps.\n }\n }\n\n async getDeviceInfo(): Promise<DeviceInfo> {\n await this.ensureConnected();\n const raw = await execStrict(this.#idbPath, [\n 'describe',\n '--udid',\n this.#udid,\n '--json',\n ]);\n let info: Record<string, unknown>;\n try {\n info = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n throw new Error(\n `idb describe returned invalid JSON for device ${this.#udid}: ${raw.slice(0, 200)}`,\n );\n }\n return {\n platform: 'ios',\n deviceId: this.#udid,\n name: (info.name as string) ?? 'Unknown',\n osVersion: (info.os_version as string) ?? 'Unknown',\n state: (info.state as string) ?? 'Unknown',\n };\n }\n\n async snapshot(): Promise<SnapshotResult> {\n await this.ensureConnected();\n const raw = await this.#describeAll();\n const hierarchy = parseIdbHierarchy(raw);\n return {\n platform: 'ios',\n hierarchy,\n raw,\n timestamp: Date.now(),\n };\n }\n\n /**\n * Run `idb ui describe-all`, trying the version-appropriate accessibility\n * backend first and falling back to the others if it errors. This keeps\n * snapshots working across iOS 17-26 regardless of the user's idb client.\n *\n * @returns The raw JSON hierarchy from the first backend that succeeds.\n * @throws When every attempted backend fails.\n */\n async #describeAll(): Promise<string> {\n const apis = await this.#describeAllApiOrder();\n let lastError: unknown;\n for (const api of apis) {\n const args = ['ui', 'describe-all', '--udid', this.#udid];\n if (api !== null) {\n args.push('--api', api);\n }\n try {\n return await execStrict(this.#idbPath, args);\n } catch (error) {\n lastError = error;\n }\n }\n throw lastError instanceof Error\n ? lastError\n : new Error('idb ui describe-all failed for all accessibility backends');\n }\n\n /**\n * Ordered `--api` values to attempt: the version preference, then the\n * complement; or a single `null` (omit `--api`) when the CLI lacks support.\n *\n * @returns The ordered `--api` attempts.\n */\n async #describeAllApiOrder(): Promise<('ax' | 'axbridge' | null)[]> {\n if (!this.#idbSupportsApi) {\n return [null];\n }\n const { osVersion } = await this.getDeviceInfo();\n const preferred = chooseAxApi(osVersion, true);\n const complement = preferred === 'axbridge' ? 'ax' : 'axbridge';\n return [preferred, complement];\n }\n\n async tapElement(query: ElementQuery): Promise<TapResult> {\n await this.ensureConnected();\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const { x, y } = await this.#visibleTapPoint(element);\n await this.tapCoordinates(x, y);\n\n return {\n success: true,\n x,\n y,\n targetDescription: describeElement(element),\n };\n }\n\n /**\n * Returns the center of the part of the element's frame that lies inside\n * the device viewport, in logical points. Tapping there is safer than\n * tapping the raw frame center, which can fall outside the screen for\n * off-screen or partially scrolled elements and either hit nothing or an\n * unrelated on-screen control.\n *\n * @param element - The element to tap.\n * @returns The tap point in logical points.\n * @throws When the element's frame does not intersect the viewport at all.\n */\n async #visibleTapPoint(\n element: UIElement,\n ): Promise<{ x: number; y: number }> {\n const { width, height } = await this.getWindowSize();\n const x0 = Math.max(element.frame.x, 0);\n const y0 = Math.max(element.frame.y, 0);\n const x1 = Math.min(element.frame.x + element.frame.width, width);\n const y1 = Math.min(element.frame.y + element.frame.height, height);\n if (x1 <= x0 || y1 <= y0) {\n throw new Error(\n `Element is outside the ${width}x${height} viewport: ` +\n `${describeElement(element)} at (${element.frame.x}, ${element.frame.y} ` +\n `${element.frame.width}x${element.frame.height}). ` +\n 'Scroll it into view first (device_swipe or scroll_to_element).',\n );\n }\n return {\n x: Math.round((x0 + x1) / 2),\n y: Math.round((y0 + y1) / 2),\n };\n }\n\n async getElementText(query: ElementQuery): Promise<string> {\n await this.ensureConnected();\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return element.label ?? element.value ?? '';\n }\n\n async tapCoordinates(x: number, y: number): Promise<void> {\n await this.ensureConnected();\n await execStrict(this.#idbPath, [\n 'ui',\n 'tap',\n String(x),\n String(y),\n '--udid',\n this.#udid,\n ]);\n }\n\n async typeText(text: string): Promise<void> {\n await this.ensureConnected();\n await exec(this.#idbPath, [\n 'ui',\n 'key-sequence',\n '--',\n 'cmd+a',\n '--udid',\n this.#udid,\n ]);\n await exec(this.#idbPath, ['ui', 'key', 'DELETE', '--udid', this.#udid]);\n await execStrict(this.#idbPath, ['ui', 'text', text, '--udid', this.#udid]);\n }\n\n async swipe(\n direction: 'up' | 'down' | 'left' | 'right',\n startX?: number,\n startY?: number,\n distance?: number,\n ): Promise<void> {\n await this.ensureConnected();\n const windowSize = await this.getWindowSize();\n await this.#swipe(direction, windowSize, startX, startY, distance);\n }\n\n async #swipe(\n direction: 'up' | 'down' | 'left' | 'right',\n { width, height }: WindowSize,\n startX?: number,\n startY?: number,\n distance?: number,\n ): Promise<void> {\n const sx = startX ?? Math.round(width / 2);\n const sy = startY ?? Math.round(height / 2);\n // Without an explicit duration the idb client sends duration=0 and the\n // companion delivers the interpolated touch sequence as a zero-delay\n // burst, which iOS does not process as a scroll (it reads as a tap at the\n // start point). 0.3s mirrors the Android backend's `input swipe` duration.\n const durationSeconds = 0.3;\n // Default to 40% of the swipe-axis dimension so the default endpoint\n // stays clear of the screen edges on any supported device size.\n const d =\n distance ??\n Math.max(\n 1,\n Math.round(\n (direction === 'up' || direction === 'down' ? height : width) * 0.4,\n ),\n );\n let [endX, endY] = computeSwipeEnd(sx, sy, direction, d);\n if (distance === undefined) {\n // The caller did not pin the endpoint; keep the computed one inside the\n // viewport even when explicit start coordinates leave little room.\n endX = Math.min(Math.max(endX, 1), width - 2);\n endY = Math.min(Math.max(endY, 1), height - 2);\n }\n\n await execStrict(this.#idbPath, [\n 'ui',\n 'swipe',\n String(sx),\n String(sy),\n String(endX),\n String(endY),\n '--duration',\n String(durationSeconds),\n '--udid',\n this.#udid,\n ]);\n }\n\n async waitForElement(\n query: ElementQuery,\n timeoutMs = 10_000,\n intervalMs = 500,\n ): Promise<UIElement> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (element) {\n return element;\n }\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n throw new Error(\n `Timed out waiting for element: ${JSON.stringify(query)} (${timeoutMs}ms)`,\n );\n }\n\n async getAppState(bundleId: string): Promise<AppStateResult> {\n await this.ensureConnected();\n try {\n const raw = await execStrict(this.#idbPath, [\n 'list-apps',\n '--udid',\n this.#udid,\n ]);\n\n for (const line of raw.trim().split('\\n')) {\n if (line.includes(bundleId)) {\n // `idb list-apps` rows are:\n // bundle_id | name | install_type | architectures |\n // process_state | debuggable | pid=<pid>\n // (the pid column is `pid=<n>` only when the app is running,\n // `pid=None` otherwise).\n const parts = line.split('|').map((s) => s.trim());\n const pidField = parts[6] ?? '';\n const pid = pidField.startsWith('pid=')\n ? Number.parseInt(pidField.slice(4), 10)\n : Number.NaN;\n return {\n bundleId,\n state: parts[4] ?? 'Unknown',\n pid: Number.isNaN(pid) ? undefined : pid,\n };\n }\n }\n\n return { bundleId, state: 'Not Installed' };\n } catch {\n return this.#getAppStateViaSimctl(bundleId);\n }\n }\n\n async #getAppStateViaSimctl(bundleId: string): Promise<AppStateResult> {\n const result = await exec('xcrun', ['simctl', 'listapps', this.#udid]);\n if (result.exitCode === 0 && result.stdout.includes(bundleId)) {\n return { bundleId, state: 'Running' };\n }\n return { bundleId, state: 'Not Installed' };\n }\n\n screenshot(\n outputPath?: string,\n options?: { encode?: true },\n ): Promise<ScreenshotResult>;\n\n screenshot(\n outputPath: string | undefined,\n options: { encode: false },\n ): Promise<ScreenshotFileResult>;\n\n screenshot(\n outputPath?: string,\n options?: ScreenshotOptions,\n ): Promise<ScreenshotResult | ScreenshotFileResult>;\n\n async screenshot(\n outputPath?: string,\n options?: ScreenshotOptions,\n ): Promise<ScreenshotResult | ScreenshotFileResult> {\n await this.ensureConnected();\n const path = resolveArtifactPath(outputPath, 'screenshot');\n await execStrict(this.#idbPath, ['screenshot', path, '--udid', this.#udid]);\n hardenArtifactFile(path);\n if (options?.encode === false) {\n return { data: undefined, format: 'png', path };\n }\n const data = await readFile(path, 'base64');\n return { data, format: 'png', path };\n }\n\n async openApp(bundleId: string): Promise<void> {\n await this.ensureConnected();\n try {\n await execStrict(this.#idbPath, [\n 'launch',\n bundleId,\n '--udid',\n this.#udid,\n ]);\n } catch {\n await execStrict('xcrun', ['simctl', 'launch', this.#udid, bundleId]);\n }\n }\n\n async closeApp(bundleId: string): Promise<void> {\n await this.ensureConnected();\n try {\n await execStrict(this.#idbPath, [\n 'terminate',\n bundleId,\n '--udid',\n this.#udid,\n ]);\n } catch {\n await execStrict('xcrun', ['simctl', 'terminate', this.#udid, bundleId]);\n }\n }\n\n async pressButton(button: DeviceButton): Promise<void> {\n await this.ensureConnected();\n const keyMap: Record<DeviceButton, string> = {\n home: 'HOME',\n lock: 'LOCK',\n back: 'HOME',\n enter: 'RETURN',\n };\n await execStrict(this.#idbPath, [\n 'ui',\n 'key',\n keyMap[button],\n '--udid',\n this.#udid,\n ]);\n }\n\n async dismissKeyboard(): Promise<void> {\n await this.ensureConnected();\n await execStrict(this.#idbPath, [\n 'ui',\n 'key',\n 'RETURN',\n '--udid',\n this.#udid,\n ]);\n }\n\n async dismissAlert(accept: boolean): Promise<void> {\n await this.ensureConnected();\n const snap = await this.snapshot();\n const candidates = accept ? ACCEPT_LABELS : DENY_LABELS;\n\n for (const label of candidates) {\n const element = findElement(snap.hierarchy, { label });\n if (element) {\n const cx = Math.round(element.frame.x + element.frame.width / 2);\n const cy = Math.round(element.frame.y + element.frame.height / 2);\n await this.tapCoordinates(cx, cy);\n return;\n }\n }\n\n throw new Error(`No alert button found. Tried: ${candidates.join(', ')}`);\n }\n\n async getLogs(durationSeconds = 30, filter?: string): Promise<LogsResult> {\n await this.ensureConnected();\n const args = [\n 'log',\n '--udid',\n this.#udid,\n '--',\n '--last',\n `${durationSeconds}s`,\n ];\n if (filter) {\n args.push('--predicate', `eventMessage CONTAINS \"${filter}\"`);\n }\n const raw = await execStrict(this.#idbPath, args);\n const entries = raw\n .trim()\n .split('\\n')\n .filter(Boolean)\n .map((line) => ({\n timestamp: line.substring(0, 31).trim(),\n level: 'info',\n message: line.substring(31).trim(),\n }));\n return { entries, source: 'idb log' };\n }\n\n async longPress(query: ElementQuery, durationMs = 1000): Promise<TapResult> {\n await this.ensureConnected();\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const { x: cx, y: cy } = await this.#visibleTapPoint(element);\n await execStrict(this.#idbPath, [\n 'ui',\n 'tap',\n String(cx),\n String(cy),\n '--duration',\n String(durationMs / 1000),\n '--udid',\n this.#udid,\n ]);\n\n return {\n success: true,\n x: cx,\n y: cy,\n targetDescription: describeElement(element),\n };\n }\n\n async scrollToElement(\n query: ElementQuery,\n direction: 'up' | 'down' = 'down',\n maxAttempts = 10,\n ): Promise<UIElement> {\n await this.ensureConnected();\n const windowSize = await this.getWindowSize();\n let previousRaw = '';\n\n for (let i = 0; i < maxAttempts; i++) {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (element) {\n return element;\n }\n if (snap.raw === previousRaw) {\n break;\n }\n previousRaw = snap.raw;\n await this.#swipe(direction, windowSize);\n }\n throw new Error(\n `Element not found after scrolling: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n async getAlertText(): Promise<string> {\n await this.ensureConnected();\n const snap = await this.snapshot();\n const texts = collectAlertTexts(snap.hierarchy);\n if (texts.length === 0) {\n throw new Error(\n 'No alert is currently displayed.\\n' +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return texts.join('\\n');\n }\n\n async getWindowSize(): Promise<WindowSize> {\n await this.ensureConnected();\n const raw = await execStrict(this.#idbPath, [\n 'describe',\n '--udid',\n this.#udid,\n '--json',\n ]);\n try {\n const info = JSON.parse(raw) as Record<string, unknown>;\n const dims = info.screen_dimensions as\n | {\n width?: number;\n height?: number;\n // eslint-disable-next-line @typescript-eslint/naming-convention -- idb wire field\n width_points?: number;\n // eslint-disable-next-line @typescript-eslint/naming-convention -- idb wire field\n height_points?: number;\n }\n | undefined;\n // Gestures and accessibility frames are in logical points, while\n // `width`/`height` are hardware pixels; prefer the point dimensions.\n if (dims?.width_points && dims?.height_points) {\n return { width: dims.width_points, height: dims.height_points };\n }\n } catch {\n // fall through to snapshot-based approach\n }\n const snap = await this.snapshot();\n if (snap.hierarchy.length > 0) {\n const root = snap.hierarchy[0];\n if (root.frame.width > 0 && root.frame.height > 0) {\n return { width: root.frame.width, height: root.frame.height };\n }\n }\n throw new Error('Unable to determine window size from device');\n }\n\n async getContexts(): Promise<string[]> {\n return ['NATIVE_APP'];\n }\n\n async setContext(context: string): Promise<void> {\n if (context === 'NATIVE_APP') {\n return;\n }\n throw new Error(\n 'Context switching requires Appium backend. IDB only supports NATIVE_APP.',\n );\n }\n\n async getClipboard(): Promise<string> {\n await this.ensureConnected();\n return execStrict('xcrun', ['simctl', 'pbpaste', this.#udid]);\n }\n\n async setClipboard(text: string): Promise<void> {\n await this.ensureConnected();\n await new Promise<void>((resolve, reject) => {\n const proc = execFileCb(\n 'xcrun',\n ['simctl', 'pbcopy', this.#udid],\n { timeout: 30_000 },\n (error) => (error ? reject(new Error(error.message)) : resolve()),\n );\n proc.stdin?.end(text);\n });\n }\n\n async startScreenRecording(outputPath?: string): Promise<void> {\n await this.ensureConnected();\n if (this.#recordingProcess) {\n throw new Error('Screen recording is already in progress');\n }\n const path = resolveArtifactPath(outputPath, 'recording');\n this.#recordingPath = path;\n this.#recordingProcess = spawn(this.#idbPath, [\n 'record-video',\n path,\n '--udid',\n this.#udid,\n ]);\n this.#recordingProcess.on('error', () => {\n this.#recordingProcess = null;\n this.#recordingPath = null;\n });\n }\n\n async stopScreenRecording(): Promise<string> {\n if (!this.#recordingProcess || !this.#recordingPath) {\n throw new Error('No screen recording in progress');\n }\n const path = this.#recordingPath;\n this.#recordingProcess.kill('SIGINT');\n await new Promise((resolve) => setTimeout(resolve, 1000));\n this.#recordingProcess = null;\n this.#recordingPath = null;\n hardenArtifactFile(path);\n return path;\n }\n}\n\nfunction collectAlertTexts(elements: UIElement[]): string[] {\n const texts: string[] = [];\n for (const el of elements) {\n const isAlertContainer =\n el.type === 'Alert' || el.type === 'Sheet' || el.type.includes('Alert');\n if (isAlertContainer) {\n collectTextsFromChildren(el.children ?? [], texts);\n return texts;\n }\n if (el.children) {\n const found = collectAlertTexts(el.children);\n if (found.length > 0) {\n return found;\n }\n }\n }\n return texts;\n}\n\nfunction collectTextsFromChildren(\n elements: UIElement[],\n texts: string[],\n): void {\n for (const el of elements) {\n if (el.type === 'StaticText' || el.type === 'TextView') {\n const text = el.value ?? el.label;\n if (text) {\n texts.push(text);\n }\n }\n if (el.children) {\n collectTextsFromChildren(el.children, texts);\n }\n }\n}\n\nexport function parseIdbHierarchy(raw: string): UIElement[] {\n try {\n const parsed = JSON.parse(raw);\n if (Array.isArray(parsed)) {\n return parsed.map(mapIdbElement);\n }\n if (parsed.children) {\n return parsed.children.map(mapIdbElement);\n }\n return [mapIdbElement(parsed)];\n } catch {\n return [\n {\n type: 'raw',\n label: raw.slice(0, 200),\n frame: { x: 0, y: 0, width: 0, height: 0 },\n enabled: true,\n },\n ];\n }\n}\n\nexport function mapIdbElement(node: Record<string, unknown>): UIElement {\n const frame = node.frame as\n | { x: number; y: number; width: number; height: number }\n | undefined;\n\n return {\n type: (node.type as string) ?? (node.AXType as string) ?? 'Unknown',\n label: (node.AXLabel as string) ?? (node.label as string) ?? undefined,\n value: (node.AXValue as string) ?? (node.value as string) ?? undefined,\n identifier:\n (node.AXUniqueId as string) ?? (node.identifier as string) ?? undefined,\n frame: frame ?? { x: 0, y: 0, width: 0, height: 0 },\n enabled: (node.enabled as boolean) ?? true,\n children: Array.isArray(node.children)\n ? node.children.map(mapIdbElement)\n : undefined,\n };\n}\n"]}
@@ -7,7 +7,9 @@ function registerSwipeTool(server, backend) {
7
7
  server.registerTool('device_swipe', {
8
8
  title: 'Swipe',
9
9
  description: 'Swipe on the device screen. Use natural scrolling: swipe "up" to scroll down. ' +
10
- 'Optionally specify start coordinates and distance.',
10
+ 'Optionally specify start coordinates and distance. Coordinates are logical points, ' +
11
+ 'not screenshot pixels. The gesture runs with a fixed 0.3s duration; command ' +
12
+ 'completion does not verify that content moved — confirm with a screenshot or snapshot.',
11
13
  inputSchema: {
12
14
  direction: zod_1.z
13
15
  .enum(['up', 'down', 'left', 'right'])
@@ -1 +1 @@
1
- {"version":3,"file":"swipe.cjs","sourceRoot":"","sources":["../../src/tools/swipe.ts"],"names":[],"mappings":";;AAMA,8CAuCC;AA5CD,6BAAwB;AAExB,4CAA0C;AAG1C,SAAgB,iBAAiB,CAC/B,MAAiB,EACjB,OAAsB;IAEtB,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;QACE,KAAK,EAAE,OAAO;QACd,WAAW,EACT,gFAAgF;YAChF,oDAAoD;QACtD,WAAW,EAAE;YACX,SAAS,EAAE,OAAC;iBACT,IAAI,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;iBACrC,QAAQ,CAAC,iBAAiB,CAAC;YAC9B,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAC;YAC5D,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAC;YAC5D,QAAQ,EAAE,OAAC;iBACR,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,yCAAyC,CAAC;SACvD;KACF,EACD,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE;QAChD,IAAI,CAAC;YACH,MAAM,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;YACzD,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,UAAU,SAAS,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,MAAM,KAAK,MAAM,GAAG,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,QAAQ,IAAI,EAAE;qBACnJ;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,IAAA,uBAAW,EAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { errorResult } from './shared.js';\nimport type { DeviceBackend } from '../backends/types.js';\n\nexport function registerSwipeTool(\n server: McpServer,\n backend: DeviceBackend,\n): void {\n server.registerTool(\n 'device_swipe',\n {\n title: 'Swipe',\n description:\n 'Swipe on the device screen. Use natural scrolling: swipe \"up\" to scroll down. ' +\n 'Optionally specify start coordinates and distance.',\n inputSchema: {\n direction: z\n .enum(['up', 'down', 'left', 'right'])\n .describe('Swipe direction'),\n startX: z.number().optional().describe('Start X coordinate'),\n startY: z.number().optional().describe('Start Y coordinate'),\n distance: z\n .number()\n .optional()\n .describe('Swipe distance in pixels (default: 500)'),\n },\n },\n async ({ direction, startX, startY, distance }) => {\n try {\n await backend.swipe(direction, startX, startY, distance);\n return {\n content: [\n {\n type: 'text' as const,\n text: `Swiped ${direction}${startX === undefined ? '' : ` from (${startX}, ${startY})`}${distance === undefined ? '' : ` distance=${distance}px`}`,\n },\n ],\n };\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n}\n"]}
1
+ {"version":3,"file":"swipe.cjs","sourceRoot":"","sources":["../../src/tools/swipe.ts"],"names":[],"mappings":";;AAMA,8CAyCC;AA9CD,6BAAwB;AAExB,4CAA0C;AAG1C,SAAgB,iBAAiB,CAC/B,MAAiB,EACjB,OAAsB;IAEtB,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;QACE,KAAK,EAAE,OAAO;QACd,WAAW,EACT,gFAAgF;YAChF,qFAAqF;YACrF,8EAA8E;YAC9E,wFAAwF;QAC1F,WAAW,EAAE;YACX,SAAS,EAAE,OAAC;iBACT,IAAI,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;iBACrC,QAAQ,CAAC,iBAAiB,CAAC;YAC9B,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAC;YAC5D,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAC;YAC5D,QAAQ,EAAE,OAAC;iBACR,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,yCAAyC,CAAC;SACvD;KACF,EACD,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE;QAChD,IAAI,CAAC;YACH,MAAM,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;YACzD,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,UAAU,SAAS,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,MAAM,KAAK,MAAM,GAAG,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,QAAQ,IAAI,EAAE;qBACnJ;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,IAAA,uBAAW,EAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { errorResult } from './shared.js';\nimport type { DeviceBackend } from '../backends/types.js';\n\nexport function registerSwipeTool(\n server: McpServer,\n backend: DeviceBackend,\n): void {\n server.registerTool(\n 'device_swipe',\n {\n title: 'Swipe',\n description:\n 'Swipe on the device screen. Use natural scrolling: swipe \"up\" to scroll down. ' +\n 'Optionally specify start coordinates and distance. Coordinates are logical points, ' +\n 'not screenshot pixels. The gesture runs with a fixed 0.3s duration; command ' +\n 'completion does not verify that content moved — confirm with a screenshot or snapshot.',\n inputSchema: {\n direction: z\n .enum(['up', 'down', 'left', 'right'])\n .describe('Swipe direction'),\n startX: z.number().optional().describe('Start X coordinate'),\n startY: z.number().optional().describe('Start Y coordinate'),\n distance: z\n .number()\n .optional()\n .describe('Swipe distance in pixels (default: 500)'),\n },\n },\n async ({ direction, startX, startY, distance }) => {\n try {\n await backend.swipe(direction, startX, startY, distance);\n return {\n content: [\n {\n type: 'text' as const,\n text: `Swiped ${direction}${startX === undefined ? '' : ` from (${startX}, ${startY})`}${distance === undefined ? '' : ` distance=${distance}px`}`,\n },\n ],\n };\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"swipe.d.cts","sourceRoot":"","sources":["../../src/tools/swipe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gDAAgD;AAIzE,OAAO,KAAK,EAAE,aAAa,EAAE,8BAA6B;AAE1D,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,aAAa,GACrB,IAAI,CAoCN"}
1
+ {"version":3,"file":"swipe.d.cts","sourceRoot":"","sources":["../../src/tools/swipe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gDAAgD;AAIzE,OAAO,KAAK,EAAE,aAAa,EAAE,8BAA6B;AAE1D,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,aAAa,GACrB,IAAI,CAsCN"}
@@ -1 +1 @@
1
- {"version":3,"file":"swipe.d.mts","sourceRoot":"","sources":["../../src/tools/swipe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gDAAgD;AAIzE,OAAO,KAAK,EAAE,aAAa,EAAE,8BAA6B;AAE1D,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,aAAa,GACrB,IAAI,CAoCN"}
1
+ {"version":3,"file":"swipe.d.mts","sourceRoot":"","sources":["../../src/tools/swipe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gDAAgD;AAIzE,OAAO,KAAK,EAAE,aAAa,EAAE,8BAA6B;AAE1D,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,aAAa,GACrB,IAAI,CAsCN"}
@@ -4,7 +4,9 @@ export function registerSwipeTool(server, backend) {
4
4
  server.registerTool('device_swipe', {
5
5
  title: 'Swipe',
6
6
  description: 'Swipe on the device screen. Use natural scrolling: swipe "up" to scroll down. ' +
7
- 'Optionally specify start coordinates and distance.',
7
+ 'Optionally specify start coordinates and distance. Coordinates are logical points, ' +
8
+ 'not screenshot pixels. The gesture runs with a fixed 0.3s duration; command ' +
9
+ 'completion does not verify that content moved — confirm with a screenshot or snapshot.',
8
10
  inputSchema: {
9
11
  direction: z
10
12
  .enum(['up', 'down', 'left', 'right'])
@@ -1 +1 @@
1
- {"version":3,"file":"swipe.mjs","sourceRoot":"","sources":["../../src/tools/swipe.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,YAAY;AAExB,OAAO,EAAE,WAAW,EAAE,qBAAoB;AAG1C,MAAM,UAAU,iBAAiB,CAC/B,MAAiB,EACjB,OAAsB;IAEtB,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;QACE,KAAK,EAAE,OAAO;QACd,WAAW,EACT,gFAAgF;YAChF,oDAAoD;QACtD,WAAW,EAAE;YACX,SAAS,EAAE,CAAC;iBACT,IAAI,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;iBACrC,QAAQ,CAAC,iBAAiB,CAAC;YAC9B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAC;YAC5D,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAC;YAC5D,QAAQ,EAAE,CAAC;iBACR,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,yCAAyC,CAAC;SACvD;KACF,EACD,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE;QAChD,IAAI,CAAC;YACH,MAAM,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;YACzD,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,UAAU,SAAS,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,MAAM,KAAK,MAAM,GAAG,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,QAAQ,IAAI,EAAE;qBACnJ;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { errorResult } from './shared.js';\nimport type { DeviceBackend } from '../backends/types.js';\n\nexport function registerSwipeTool(\n server: McpServer,\n backend: DeviceBackend,\n): void {\n server.registerTool(\n 'device_swipe',\n {\n title: 'Swipe',\n description:\n 'Swipe on the device screen. Use natural scrolling: swipe \"up\" to scroll down. ' +\n 'Optionally specify start coordinates and distance.',\n inputSchema: {\n direction: z\n .enum(['up', 'down', 'left', 'right'])\n .describe('Swipe direction'),\n startX: z.number().optional().describe('Start X coordinate'),\n startY: z.number().optional().describe('Start Y coordinate'),\n distance: z\n .number()\n .optional()\n .describe('Swipe distance in pixels (default: 500)'),\n },\n },\n async ({ direction, startX, startY, distance }) => {\n try {\n await backend.swipe(direction, startX, startY, distance);\n return {\n content: [\n {\n type: 'text' as const,\n text: `Swiped ${direction}${startX === undefined ? '' : ` from (${startX}, ${startY})`}${distance === undefined ? '' : ` distance=${distance}px`}`,\n },\n ],\n };\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n}\n"]}
1
+ {"version":3,"file":"swipe.mjs","sourceRoot":"","sources":["../../src/tools/swipe.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,YAAY;AAExB,OAAO,EAAE,WAAW,EAAE,qBAAoB;AAG1C,MAAM,UAAU,iBAAiB,CAC/B,MAAiB,EACjB,OAAsB;IAEtB,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;QACE,KAAK,EAAE,OAAO;QACd,WAAW,EACT,gFAAgF;YAChF,qFAAqF;YACrF,8EAA8E;YAC9E,wFAAwF;QAC1F,WAAW,EAAE;YACX,SAAS,EAAE,CAAC;iBACT,IAAI,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;iBACrC,QAAQ,CAAC,iBAAiB,CAAC;YAC9B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAC;YAC5D,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAC;YAC5D,QAAQ,EAAE,CAAC;iBACR,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,yCAAyC,CAAC;SACvD;KACF,EACD,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE;QAChD,IAAI,CAAC;YACH,MAAM,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;YACzD,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,UAAU,SAAS,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,MAAM,KAAK,MAAM,GAAG,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,QAAQ,IAAI,EAAE;qBACnJ;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { errorResult } from './shared.js';\nimport type { DeviceBackend } from '../backends/types.js';\n\nexport function registerSwipeTool(\n server: McpServer,\n backend: DeviceBackend,\n): void {\n server.registerTool(\n 'device_swipe',\n {\n title: 'Swipe',\n description:\n 'Swipe on the device screen. Use natural scrolling: swipe \"up\" to scroll down. ' +\n 'Optionally specify start coordinates and distance. Coordinates are logical points, ' +\n 'not screenshot pixels. The gesture runs with a fixed 0.3s duration; command ' +\n 'completion does not verify that content moved — confirm with a screenshot or snapshot.',\n inputSchema: {\n direction: z\n .enum(['up', 'down', 'left', 'right'])\n .describe('Swipe direction'),\n startX: z.number().optional().describe('Start X coordinate'),\n startY: z.number().optional().describe('Start Y coordinate'),\n distance: z\n .number()\n .optional()\n .describe('Swipe distance in pixels (default: 500)'),\n },\n },\n async ({ direction, startX, startY, distance }) => {\n try {\n await backend.swipe(direction, startX, startY, distance);\n return {\n content: [\n {\n type: 'text' as const,\n text: `Swiped ${direction}${startX === undefined ? '' : ` from (${startX}, ${startY})`}${distance === undefined ? '' : ` distance=${distance}px`}`,\n },\n ],\n };\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createPrivateTempDir = createPrivateTempDir;
3
4
  exports.resolveArtifactPath = resolveArtifactPath;
4
5
  exports.writeArtifactFile = writeArtifactFile;
5
6
  exports.hardenArtifactFile = hardenArtifactFile;
@@ -43,6 +44,18 @@ function getSessionTempDir() {
43
44
  }
44
45
  return sessionTempDir;
45
46
  }
47
+ /**
48
+ * Create a fresh private temporary directory (`0700`, unpredictable name) and
49
+ * return its absolute path. Unlike {@link getSessionTempDir} this is NOT
50
+ * memoized: each call yields a new directory, so callers can pull and inspect
51
+ * device files in isolation and remove the directory when done.
52
+ *
53
+ * @param prefix - A short label folded into the directory name.
54
+ * @returns The absolute path to the newly created directory.
55
+ */
56
+ function createPrivateTempDir(prefix) {
57
+ return (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), `device-mcp-${prefix}-`));
58
+ }
46
59
  /**
47
60
  * Resolve the optional output-directory allowlist from the environment.
48
61
  *
@@ -1 +1 @@
1
- {"version":3,"file":"output-path.cjs","sourceRoot":"","sources":["../../src/utils/output-path.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { chmodSync, constants as fsConstants, mkdtempSync } from 'node:fs';\nimport { writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { isAbsolute, join, relative, resolve } from 'node:path';\n\n/** Kinds of on-disk device artifact and their file extension. */\nconst ARTIFACT_EXTENSION = {\n screenshot: 'png',\n recording: 'mp4',\n} as const;\n\nexport type ArtifactKind = keyof typeof ARTIFACT_EXTENSION;\n\n/** Owner read/write only — device artifacts may contain sensitive UI (seed phrases, keys, balances). */\nconst ARTIFACT_MODE = 0o600;\n\n/**\n * Open flags for writing an artifact: create/truncate, write-only, and\n * `O_NOFOLLOW` so a pre-planted symlink at the destination is never followed\n * (defeats `/tmp` symlink / TOCTOU attacks). `O_NOFOLLOW` is a no-op on\n * platforms that do not define it.\n */\n/* eslint-disable no-bitwise -- POSIX open(2) flags are combined with bitwise OR */\nconst ARTIFACT_WRITE_FLAGS =\n fsConstants.O_WRONLY |\n fsConstants.O_CREAT |\n fsConstants.O_TRUNC |\n (fsConstants.O_NOFOLLOW ?? 0);\n/* eslint-enable no-bitwise */\n\nlet sessionTempDir: string | null = null;\n\n/**\n * Lazily create (once per process) a private temporary directory with\n * owner-only permissions (`0700`) to hold default artifacts. Because the\n * directory itself is unreadable to other users, files placed inside it are\n * protected even when written by an external tool (idb/adb) that we cannot\n * pass secure open flags to.\n *\n * @returns The absolute path to the per-process private temp directory.\n */\nfunction getSessionTempDir(): string {\n if (!sessionTempDir) {\n sessionTempDir = mkdtempSync(join(tmpdir(), 'device-mcp-'));\n }\n return sessionTempDir;\n}\n\n/**\n * Resolve the optional output-directory allowlist from the environment.\n *\n * @returns The absolute allowed base directory, or `null` when unrestricted.\n */\nfunction allowedBaseDir(): string | null {\n const configured = process.env.DEVICE_MCP_OUTPUT_DIR?.trim();\n return configured ? resolve(configured) : null;\n}\n\n/**\n * Reject a caller-supplied path that escapes `DEVICE_MCP_OUTPUT_DIR` when that\n * sandbox is configured. No-op when the env var is unset.\n *\n * @param target - The already-resolved absolute destination path.\n */\nfunction assertWithinAllowedBase(target: string): void {\n const base = allowedBaseDir();\n if (!base) {\n return;\n }\n const rel = relative(base, target);\n if (rel.length === 0 || rel.startsWith('..') || isAbsolute(rel)) {\n throw new Error(\n `Refusing to write outside DEVICE_MCP_OUTPUT_DIR (${base}): ${target}`,\n );\n }\n}\n\n/**\n * Compute a safe absolute path for a device artifact.\n *\n * - When no `outputPath` is given, returns an unpredictable file name inside a\n * private per-process temp directory. This replaces the previous predictable\n * `/tmp/device-mcp-*-${Date.now()}` names, which were vulnerable to symlink\n * and information-disclosure attacks in the shared temp directory.\n * - When an `outputPath` is given, it is rejected if it contains a NUL byte,\n * resolved to an absolute path, and — when `DEVICE_MCP_OUTPUT_DIR` is set —\n * confined to that directory.\n *\n * @param outputPath - Optional caller-supplied destination path.\n * @param kind - The artifact kind, used to pick the default file extension.\n * @returns The resolved, validated absolute path to write to.\n */\nexport function resolveArtifactPath(\n outputPath: string | undefined,\n kind: ArtifactKind,\n): string {\n if (outputPath === undefined) {\n const token = randomBytes(8).toString('hex');\n return join(\n getSessionTempDir(),\n `${kind}-${token}.${ARTIFACT_EXTENSION[kind]}`,\n );\n }\n\n if (outputPath.includes('\\0')) {\n throw new Error('Invalid output path: contains a NUL byte');\n }\n\n const target = resolve(outputPath);\n assertWithinAllowedBase(target);\n return target;\n}\n\n/**\n * Write bytes to an artifact path with hardened permissions and open flags:\n * owner-only (`0600`) and `O_NOFOLLOW`. Use for artifacts we write directly\n * in-process (e.g. the Appium backend decoding a base64 screenshot).\n *\n * @param path - The destination path (should come from `resolveArtifactPath`).\n * @param data - The bytes to write.\n */\nexport async function writeArtifactFile(\n path: string,\n data: Buffer,\n): Promise<void> {\n await writeFile(path, data, {\n mode: ARTIFACT_MODE,\n flag: ARTIFACT_WRITE_FLAGS,\n });\n}\n\n/**\n * Best-effort tightening of permissions on a file written by an external tool\n * (idb/adb create the file themselves, so we cannot pass secure open flags).\n * Silently ignored when unsupported or when the file is absent.\n *\n * @param path - The path of the file to restrict to owner-only access.\n */\nexport function hardenArtifactFile(path: string): void {\n try {\n chmodSync(path, ARTIFACT_MODE);\n } catch {\n // Best-effort: the filesystem may not support chmod (e.g. some mounts on\n // Windows) or the external tool may have failed to create the file.\n }\n}\n"]}
1
+ {"version":3,"file":"output-path.cjs","sourceRoot":"","sources":["../../src/utils/output-path.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { chmodSync, constants as fsConstants, mkdtempSync } from 'node:fs';\nimport { writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { isAbsolute, join, relative, resolve } from 'node:path';\n\n/** Kinds of on-disk device artifact and their file extension. */\nconst ARTIFACT_EXTENSION = {\n screenshot: 'png',\n recording: 'mp4',\n} as const;\n\nexport type ArtifactKind = keyof typeof ARTIFACT_EXTENSION;\n\n/** Owner read/write only — device artifacts may contain sensitive UI (seed phrases, keys, balances). */\nconst ARTIFACT_MODE = 0o600;\n\n/**\n * Open flags for writing an artifact: create/truncate, write-only, and\n * `O_NOFOLLOW` so a pre-planted symlink at the destination is never followed\n * (defeats `/tmp` symlink / TOCTOU attacks). `O_NOFOLLOW` is a no-op on\n * platforms that do not define it.\n */\n/* eslint-disable no-bitwise -- POSIX open(2) flags are combined with bitwise OR */\nconst ARTIFACT_WRITE_FLAGS =\n fsConstants.O_WRONLY |\n fsConstants.O_CREAT |\n fsConstants.O_TRUNC |\n (fsConstants.O_NOFOLLOW ?? 0);\n/* eslint-enable no-bitwise */\n\nlet sessionTempDir: string | null = null;\n\n/**\n * Lazily create (once per process) a private temporary directory with\n * owner-only permissions (`0700`) to hold default artifacts. Because the\n * directory itself is unreadable to other users, files placed inside it are\n * protected even when written by an external tool (idb/adb) that we cannot\n * pass secure open flags to.\n *\n * @returns The absolute path to the per-process private temp directory.\n */\nfunction getSessionTempDir(): string {\n if (!sessionTempDir) {\n sessionTempDir = mkdtempSync(join(tmpdir(), 'device-mcp-'));\n }\n return sessionTempDir;\n}\n\n/**\n * Create a fresh private temporary directory (`0700`, unpredictable name) and\n * return its absolute path. Unlike {@link getSessionTempDir} this is NOT\n * memoized: each call yields a new directory, so callers can pull and inspect\n * device files in isolation and remove the directory when done.\n *\n * @param prefix - A short label folded into the directory name.\n * @returns The absolute path to the newly created directory.\n */\nexport function createPrivateTempDir(prefix: string): string {\n return mkdtempSync(join(tmpdir(), `device-mcp-${prefix}-`));\n}\n\n/**\n * Resolve the optional output-directory allowlist from the environment.\n *\n * @returns The absolute allowed base directory, or `null` when unrestricted.\n */\nfunction allowedBaseDir(): string | null {\n const configured = process.env.DEVICE_MCP_OUTPUT_DIR?.trim();\n return configured ? resolve(configured) : null;\n}\n\n/**\n * Reject a caller-supplied path that escapes `DEVICE_MCP_OUTPUT_DIR` when that\n * sandbox is configured. No-op when the env var is unset.\n *\n * @param target - The already-resolved absolute destination path.\n */\nfunction assertWithinAllowedBase(target: string): void {\n const base = allowedBaseDir();\n if (!base) {\n return;\n }\n const rel = relative(base, target);\n if (rel.length === 0 || rel.startsWith('..') || isAbsolute(rel)) {\n throw new Error(\n `Refusing to write outside DEVICE_MCP_OUTPUT_DIR (${base}): ${target}`,\n );\n }\n}\n\n/**\n * Compute a safe absolute path for a device artifact.\n *\n * - When no `outputPath` is given, returns an unpredictable file name inside a\n * private per-process temp directory. This replaces the previous predictable\n * `/tmp/device-mcp-*-${Date.now()}` names, which were vulnerable to symlink\n * and information-disclosure attacks in the shared temp directory.\n * - When an `outputPath` is given, it is rejected if it contains a NUL byte,\n * resolved to an absolute path, and — when `DEVICE_MCP_OUTPUT_DIR` is set —\n * confined to that directory.\n *\n * @param outputPath - Optional caller-supplied destination path.\n * @param kind - The artifact kind, used to pick the default file extension.\n * @returns The resolved, validated absolute path to write to.\n */\nexport function resolveArtifactPath(\n outputPath: string | undefined,\n kind: ArtifactKind,\n): string {\n if (outputPath === undefined) {\n const token = randomBytes(8).toString('hex');\n return join(\n getSessionTempDir(),\n `${kind}-${token}.${ARTIFACT_EXTENSION[kind]}`,\n );\n }\n\n if (outputPath.includes('\\0')) {\n throw new Error('Invalid output path: contains a NUL byte');\n }\n\n const target = resolve(outputPath);\n assertWithinAllowedBase(target);\n return target;\n}\n\n/**\n * Write bytes to an artifact path with hardened permissions and open flags:\n * owner-only (`0600`) and `O_NOFOLLOW`. Use for artifacts we write directly\n * in-process (e.g. the Appium backend decoding a base64 screenshot).\n *\n * @param path - The destination path (should come from `resolveArtifactPath`).\n * @param data - The bytes to write.\n */\nexport async function writeArtifactFile(\n path: string,\n data: Buffer,\n): Promise<void> {\n await writeFile(path, data, {\n mode: ARTIFACT_MODE,\n flag: ARTIFACT_WRITE_FLAGS,\n });\n}\n\n/**\n * Best-effort tightening of permissions on a file written by an external tool\n * (idb/adb create the file themselves, so we cannot pass secure open flags).\n * Silently ignored when unsupported or when the file is absent.\n *\n * @param path - The path of the file to restrict to owner-only access.\n */\nexport function hardenArtifactFile(path: string): void {\n try {\n chmodSync(path, ARTIFACT_MODE);\n } catch {\n // Best-effort: the filesystem may not support chmod (e.g. some mounts on\n // Windows) or the external tool may have failed to create the file.\n }\n}\n"]}
@@ -4,6 +4,16 @@ declare const ARTIFACT_EXTENSION: {
4
4
  readonly recording: "mp4";
5
5
  };
6
6
  export type ArtifactKind = keyof typeof ARTIFACT_EXTENSION;
7
+ /**
8
+ * Create a fresh private temporary directory (`0700`, unpredictable name) and
9
+ * return its absolute path. Unlike {@link getSessionTempDir} this is NOT
10
+ * memoized: each call yields a new directory, so callers can pull and inspect
11
+ * device files in isolation and remove the directory when done.
12
+ *
13
+ * @param prefix - A short label folded into the directory name.
14
+ * @returns The absolute path to the newly created directory.
15
+ */
16
+ export declare function createPrivateTempDir(prefix: string): string;
7
17
  /**
8
18
  * Compute a safe absolute path for a device artifact.
9
19
  *
@@ -1 +1 @@
1
- {"version":3,"file":"output-path.d.cts","sourceRoot":"","sources":["../../src/utils/output-path.ts"],"names":[],"mappings":"AAMA,iEAAiE;AACjE,QAAA,MAAM,kBAAkB;;;CAGd,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,kBAAkB,CAAC;AAkE3D;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,IAAI,EAAE,YAAY,GACjB,MAAM,CAgBR;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAKf;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAOrD"}
1
+ {"version":3,"file":"output-path.d.cts","sourceRoot":"","sources":["../../src/utils/output-path.ts"],"names":[],"mappings":"AAMA,iEAAiE;AACjE,QAAA,MAAM,kBAAkB;;;CAGd,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,kBAAkB,CAAC;AAqC3D;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE3D;AA+BD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,IAAI,EAAE,YAAY,GACjB,MAAM,CAgBR;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAKf;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAOrD"}
@@ -4,6 +4,16 @@ declare const ARTIFACT_EXTENSION: {
4
4
  readonly recording: "mp4";
5
5
  };
6
6
  export type ArtifactKind = keyof typeof ARTIFACT_EXTENSION;
7
+ /**
8
+ * Create a fresh private temporary directory (`0700`, unpredictable name) and
9
+ * return its absolute path. Unlike {@link getSessionTempDir} this is NOT
10
+ * memoized: each call yields a new directory, so callers can pull and inspect
11
+ * device files in isolation and remove the directory when done.
12
+ *
13
+ * @param prefix - A short label folded into the directory name.
14
+ * @returns The absolute path to the newly created directory.
15
+ */
16
+ export declare function createPrivateTempDir(prefix: string): string;
7
17
  /**
8
18
  * Compute a safe absolute path for a device artifact.
9
19
  *
@@ -1 +1 @@
1
- {"version":3,"file":"output-path.d.mts","sourceRoot":"","sources":["../../src/utils/output-path.ts"],"names":[],"mappings":"AAMA,iEAAiE;AACjE,QAAA,MAAM,kBAAkB;;;CAGd,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,kBAAkB,CAAC;AAkE3D;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,IAAI,EAAE,YAAY,GACjB,MAAM,CAgBR;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAKf;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAOrD"}
1
+ {"version":3,"file":"output-path.d.mts","sourceRoot":"","sources":["../../src/utils/output-path.ts"],"names":[],"mappings":"AAMA,iEAAiE;AACjE,QAAA,MAAM,kBAAkB;;;CAGd,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,kBAAkB,CAAC;AAqC3D;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE3D;AA+BD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,IAAI,EAAE,YAAY,GACjB,MAAM,CAgBR;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAKf;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAOrD"}
@@ -38,6 +38,18 @@ function getSessionTempDir() {
38
38
  }
39
39
  return sessionTempDir;
40
40
  }
41
+ /**
42
+ * Create a fresh private temporary directory (`0700`, unpredictable name) and
43
+ * return its absolute path. Unlike {@link getSessionTempDir} this is NOT
44
+ * memoized: each call yields a new directory, so callers can pull and inspect
45
+ * device files in isolation and remove the directory when done.
46
+ *
47
+ * @param prefix - A short label folded into the directory name.
48
+ * @returns The absolute path to the newly created directory.
49
+ */
50
+ export function createPrivateTempDir(prefix) {
51
+ return mkdtempSync(join(tmpdir(), `device-mcp-${prefix}-`));
52
+ }
41
53
  /**
42
54
  * Resolve the optional output-directory allowlist from the environment.
43
55
  *
@@ -1 +1 @@
1
- {"version":3,"file":"output-path.mjs","sourceRoot":"","sources":["../../src/utils/output-path.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { chmodSync, constants as fsConstants, mkdtempSync } from 'node:fs';\nimport { writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { isAbsolute, join, relative, resolve } from 'node:path';\n\n/** Kinds of on-disk device artifact and their file extension. */\nconst ARTIFACT_EXTENSION = {\n screenshot: 'png',\n recording: 'mp4',\n} as const;\n\nexport type ArtifactKind = keyof typeof ARTIFACT_EXTENSION;\n\n/** Owner read/write only — device artifacts may contain sensitive UI (seed phrases, keys, balances). */\nconst ARTIFACT_MODE = 0o600;\n\n/**\n * Open flags for writing an artifact: create/truncate, write-only, and\n * `O_NOFOLLOW` so a pre-planted symlink at the destination is never followed\n * (defeats `/tmp` symlink / TOCTOU attacks). `O_NOFOLLOW` is a no-op on\n * platforms that do not define it.\n */\n/* eslint-disable no-bitwise -- POSIX open(2) flags are combined with bitwise OR */\nconst ARTIFACT_WRITE_FLAGS =\n fsConstants.O_WRONLY |\n fsConstants.O_CREAT |\n fsConstants.O_TRUNC |\n (fsConstants.O_NOFOLLOW ?? 0);\n/* eslint-enable no-bitwise */\n\nlet sessionTempDir: string | null = null;\n\n/**\n * Lazily create (once per process) a private temporary directory with\n * owner-only permissions (`0700`) to hold default artifacts. Because the\n * directory itself is unreadable to other users, files placed inside it are\n * protected even when written by an external tool (idb/adb) that we cannot\n * pass secure open flags to.\n *\n * @returns The absolute path to the per-process private temp directory.\n */\nfunction getSessionTempDir(): string {\n if (!sessionTempDir) {\n sessionTempDir = mkdtempSync(join(tmpdir(), 'device-mcp-'));\n }\n return sessionTempDir;\n}\n\n/**\n * Resolve the optional output-directory allowlist from the environment.\n *\n * @returns The absolute allowed base directory, or `null` when unrestricted.\n */\nfunction allowedBaseDir(): string | null {\n const configured = process.env.DEVICE_MCP_OUTPUT_DIR?.trim();\n return configured ? resolve(configured) : null;\n}\n\n/**\n * Reject a caller-supplied path that escapes `DEVICE_MCP_OUTPUT_DIR` when that\n * sandbox is configured. No-op when the env var is unset.\n *\n * @param target - The already-resolved absolute destination path.\n */\nfunction assertWithinAllowedBase(target: string): void {\n const base = allowedBaseDir();\n if (!base) {\n return;\n }\n const rel = relative(base, target);\n if (rel.length === 0 || rel.startsWith('..') || isAbsolute(rel)) {\n throw new Error(\n `Refusing to write outside DEVICE_MCP_OUTPUT_DIR (${base}): ${target}`,\n );\n }\n}\n\n/**\n * Compute a safe absolute path for a device artifact.\n *\n * - When no `outputPath` is given, returns an unpredictable file name inside a\n * private per-process temp directory. This replaces the previous predictable\n * `/tmp/device-mcp-*-${Date.now()}` names, which were vulnerable to symlink\n * and information-disclosure attacks in the shared temp directory.\n * - When an `outputPath` is given, it is rejected if it contains a NUL byte,\n * resolved to an absolute path, and — when `DEVICE_MCP_OUTPUT_DIR` is set —\n * confined to that directory.\n *\n * @param outputPath - Optional caller-supplied destination path.\n * @param kind - The artifact kind, used to pick the default file extension.\n * @returns The resolved, validated absolute path to write to.\n */\nexport function resolveArtifactPath(\n outputPath: string | undefined,\n kind: ArtifactKind,\n): string {\n if (outputPath === undefined) {\n const token = randomBytes(8).toString('hex');\n return join(\n getSessionTempDir(),\n `${kind}-${token}.${ARTIFACT_EXTENSION[kind]}`,\n );\n }\n\n if (outputPath.includes('\\0')) {\n throw new Error('Invalid output path: contains a NUL byte');\n }\n\n const target = resolve(outputPath);\n assertWithinAllowedBase(target);\n return target;\n}\n\n/**\n * Write bytes to an artifact path with hardened permissions and open flags:\n * owner-only (`0600`) and `O_NOFOLLOW`. Use for artifacts we write directly\n * in-process (e.g. the Appium backend decoding a base64 screenshot).\n *\n * @param path - The destination path (should come from `resolveArtifactPath`).\n * @param data - The bytes to write.\n */\nexport async function writeArtifactFile(\n path: string,\n data: Buffer,\n): Promise<void> {\n await writeFile(path, data, {\n mode: ARTIFACT_MODE,\n flag: ARTIFACT_WRITE_FLAGS,\n });\n}\n\n/**\n * Best-effort tightening of permissions on a file written by an external tool\n * (idb/adb create the file themselves, so we cannot pass secure open flags).\n * Silently ignored when unsupported or when the file is absent.\n *\n * @param path - The path of the file to restrict to owner-only access.\n */\nexport function hardenArtifactFile(path: string): void {\n try {\n chmodSync(path, ARTIFACT_MODE);\n } catch {\n // Best-effort: the filesystem may not support chmod (e.g. some mounts on\n // Windows) or the external tool may have failed to create the file.\n }\n}\n"]}
1
+ {"version":3,"file":"output-path.mjs","sourceRoot":"","sources":["../../src/utils/output-path.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { chmodSync, constants as fsConstants, mkdtempSync } from 'node:fs';\nimport { writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { isAbsolute, join, relative, resolve } from 'node:path';\n\n/** Kinds of on-disk device artifact and their file extension. */\nconst ARTIFACT_EXTENSION = {\n screenshot: 'png',\n recording: 'mp4',\n} as const;\n\nexport type ArtifactKind = keyof typeof ARTIFACT_EXTENSION;\n\n/** Owner read/write only — device artifacts may contain sensitive UI (seed phrases, keys, balances). */\nconst ARTIFACT_MODE = 0o600;\n\n/**\n * Open flags for writing an artifact: create/truncate, write-only, and\n * `O_NOFOLLOW` so a pre-planted symlink at the destination is never followed\n * (defeats `/tmp` symlink / TOCTOU attacks). `O_NOFOLLOW` is a no-op on\n * platforms that do not define it.\n */\n/* eslint-disable no-bitwise -- POSIX open(2) flags are combined with bitwise OR */\nconst ARTIFACT_WRITE_FLAGS =\n fsConstants.O_WRONLY |\n fsConstants.O_CREAT |\n fsConstants.O_TRUNC |\n (fsConstants.O_NOFOLLOW ?? 0);\n/* eslint-enable no-bitwise */\n\nlet sessionTempDir: string | null = null;\n\n/**\n * Lazily create (once per process) a private temporary directory with\n * owner-only permissions (`0700`) to hold default artifacts. Because the\n * directory itself is unreadable to other users, files placed inside it are\n * protected even when written by an external tool (idb/adb) that we cannot\n * pass secure open flags to.\n *\n * @returns The absolute path to the per-process private temp directory.\n */\nfunction getSessionTempDir(): string {\n if (!sessionTempDir) {\n sessionTempDir = mkdtempSync(join(tmpdir(), 'device-mcp-'));\n }\n return sessionTempDir;\n}\n\n/**\n * Create a fresh private temporary directory (`0700`, unpredictable name) and\n * return its absolute path. Unlike {@link getSessionTempDir} this is NOT\n * memoized: each call yields a new directory, so callers can pull and inspect\n * device files in isolation and remove the directory when done.\n *\n * @param prefix - A short label folded into the directory name.\n * @returns The absolute path to the newly created directory.\n */\nexport function createPrivateTempDir(prefix: string): string {\n return mkdtempSync(join(tmpdir(), `device-mcp-${prefix}-`));\n}\n\n/**\n * Resolve the optional output-directory allowlist from the environment.\n *\n * @returns The absolute allowed base directory, or `null` when unrestricted.\n */\nfunction allowedBaseDir(): string | null {\n const configured = process.env.DEVICE_MCP_OUTPUT_DIR?.trim();\n return configured ? resolve(configured) : null;\n}\n\n/**\n * Reject a caller-supplied path that escapes `DEVICE_MCP_OUTPUT_DIR` when that\n * sandbox is configured. No-op when the env var is unset.\n *\n * @param target - The already-resolved absolute destination path.\n */\nfunction assertWithinAllowedBase(target: string): void {\n const base = allowedBaseDir();\n if (!base) {\n return;\n }\n const rel = relative(base, target);\n if (rel.length === 0 || rel.startsWith('..') || isAbsolute(rel)) {\n throw new Error(\n `Refusing to write outside DEVICE_MCP_OUTPUT_DIR (${base}): ${target}`,\n );\n }\n}\n\n/**\n * Compute a safe absolute path for a device artifact.\n *\n * - When no `outputPath` is given, returns an unpredictable file name inside a\n * private per-process temp directory. This replaces the previous predictable\n * `/tmp/device-mcp-*-${Date.now()}` names, which were vulnerable to symlink\n * and information-disclosure attacks in the shared temp directory.\n * - When an `outputPath` is given, it is rejected if it contains a NUL byte,\n * resolved to an absolute path, and — when `DEVICE_MCP_OUTPUT_DIR` is set —\n * confined to that directory.\n *\n * @param outputPath - Optional caller-supplied destination path.\n * @param kind - The artifact kind, used to pick the default file extension.\n * @returns The resolved, validated absolute path to write to.\n */\nexport function resolveArtifactPath(\n outputPath: string | undefined,\n kind: ArtifactKind,\n): string {\n if (outputPath === undefined) {\n const token = randomBytes(8).toString('hex');\n return join(\n getSessionTempDir(),\n `${kind}-${token}.${ARTIFACT_EXTENSION[kind]}`,\n );\n }\n\n if (outputPath.includes('\\0')) {\n throw new Error('Invalid output path: contains a NUL byte');\n }\n\n const target = resolve(outputPath);\n assertWithinAllowedBase(target);\n return target;\n}\n\n/**\n * Write bytes to an artifact path with hardened permissions and open flags:\n * owner-only (`0600`) and `O_NOFOLLOW`. Use for artifacts we write directly\n * in-process (e.g. the Appium backend decoding a base64 screenshot).\n *\n * @param path - The destination path (should come from `resolveArtifactPath`).\n * @param data - The bytes to write.\n */\nexport async function writeArtifactFile(\n path: string,\n data: Buffer,\n): Promise<void> {\n await writeFile(path, data, {\n mode: ARTIFACT_MODE,\n flag: ARTIFACT_WRITE_FLAGS,\n });\n}\n\n/**\n * Best-effort tightening of permissions on a file written by an external tool\n * (idb/adb create the file themselves, so we cannot pass secure open flags).\n * Silently ignored when unsupported or when the file is absent.\n *\n * @param path - The path of the file to restrict to owner-only access.\n */\nexport function hardenArtifactFile(path: string): void {\n try {\n chmodSync(path, ARTIFACT_MODE);\n } catch {\n // Best-effort: the filesystem may not support chmod (e.g. some mounts on\n // Windows) or the external tool may have failed to create the file.\n }\n}\n"]}
@@ -3,7 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MultipleDevicesError = void 0;
4
4
  exports.detectAllDevices = detectAllDevices;
5
5
  exports.detectPlatform = detectPlatform;
6
+ exports.idbSupportsApiSelection = idbSupportsApiSelection;
6
7
  exports.resolveIdbPath = resolveIdbPath;
8
+ exports.chooseAxApi = chooseAxApi;
7
9
  const node_fs_1 = require("node:fs");
8
10
  const exec_js_1 = require("./exec.cjs");
9
11
  class MultipleDevicesError extends Error {
@@ -116,26 +118,82 @@ function findPythonBinDirs(base) {
116
118
  return [];
117
119
  }
118
120
  }
119
- async function resolveIdbPath() {
120
- const result = await (0, exec_js_1.exec)('idb', ['--help']).catch(() => null);
121
- if (result?.exitCode === 0) {
122
- return 'idb';
121
+ /**
122
+ * Whether an `idb` binary supports the `--api` describe-all backend selector.
123
+ * Only the unified `idb` (>= ~1.5) exposes it; the legacy python `fb-idb`
124
+ * rejects `--api` as a usage error, so we probe the help text.
125
+ *
126
+ * @param idbPath - The `idb` executable to probe.
127
+ * @returns True when `--api` is accepted.
128
+ */
129
+ async function idbSupportsApiSelection(idbPath) {
130
+ const help = await (0, exec_js_1.exec)(idbPath, ['ui', 'describe-all', '--help']).catch(() => null);
131
+ if (help?.exitCode !== 0) {
132
+ return false;
123
133
  }
134
+ return `${help.stdout}\n${help.stderr}`.includes('--api');
135
+ }
136
+ /**
137
+ * Resolve the `idb` executable, preferring one that supports `--api`.
138
+ *
139
+ * A machine can have both the legacy python `fb-idb` (often first on `PATH`)
140
+ * and the unified `facebook/fb/idb`; only the latter supports `--api axbridge`,
141
+ * which iOS 17+ simulators require. We return the first `--api`-capable
142
+ * candidate, falling back to the first merely-working one.
143
+ *
144
+ * @returns The path to the best available `idb`.
145
+ * @throws When no `idb` executable is found.
146
+ */
147
+ async function resolveIdbPath() {
124
148
  const home = process.env.HOME ?? '';
125
149
  const candidates = [
126
- '/usr/local/bin/idb',
150
+ 'idb',
127
151
  '/opt/homebrew/bin/idb',
152
+ '/usr/local/bin/idb',
128
153
  ...findPythonBinDirs(`${home}/Library/Python`).map((d) => `${d}/idb`),
129
154
  ...findPythonBinDirs('/usr/local/lib').map((d) => `${d}/idb`),
130
155
  ];
156
+ let firstWorking = null;
131
157
  for (const candidate of candidates) {
132
158
  const probe = await (0, exec_js_1.exec)(candidate, ['--help']).catch(() => null);
133
- if (probe?.exitCode === 0) {
159
+ if (probe?.exitCode !== 0) {
160
+ continue;
161
+ }
162
+ if (firstWorking === null) {
163
+ firstWorking = candidate;
164
+ }
165
+ if (await idbSupportsApiSelection(candidate)) {
134
166
  return candidate;
135
167
  }
136
168
  }
169
+ if (firstWorking !== null) {
170
+ return firstWorking;
171
+ }
137
172
  throw new Error('idb not found.\n' +
138
- 'Install: brew tap facebook/fb && brew install idb-companion && pip3 install fb-idb');
173
+ 'Install the unified client: brew tap facebook/fb && brew install idb');
174
+ }
175
+ /**
176
+ * The `--api` accessibility backend for a given iOS runtime.
177
+ *
178
+ * The backends are version-inverted: `ax` works on iOS <= 17 but is broken on
179
+ * iOS 26; `axbridge` works on iOS >= 18. So we pick `axbridge` for >= 18 and
180
+ * `ax` otherwise, or `null` (omit `--api`) when the CLI cannot select. The
181
+ * caller still falls back if the chosen backend errors.
182
+ *
183
+ * @param osVersion - The device OS version (e.g. '17.4', '26.1').
184
+ * @param supportsApi - Whether the resolved CLI accepts `--api`.
185
+ * @returns The `--api` value, or `null` to omit it.
186
+ */
187
+ function chooseAxApi(osVersion, supportsApi) {
188
+ if (!supportsApi) {
189
+ return null;
190
+ }
191
+ const major = Number.parseInt(osVersion, 10);
192
+ if (Number.isNaN(major)) {
193
+ // Unknown version - prefer the modern reader and let the caller fall back
194
+ return 'axbridge';
195
+ }
196
+ return major >= 18 ? 'axbridge' : 'ax';
139
197
  }
140
198
  async function resolveAdbPath() {
141
199
  const result = await (0, exec_js_1.exec)('adb', ['version']).catch(() => null);