mobile-debug-mcp 0.9.0 → 0.10.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.
- package/.eslintignore +5 -0
- package/.eslintrc.cjs +18 -0
- package/.github/workflows/.gitkeep +0 -0
- package/.github/workflows/ci.yml +63 -0
- package/README.md +4 -17
- package/dist/android/interact.js +26 -4
- package/dist/android/observe.js +3 -3
- package/dist/android/utils.js +59 -104
- package/dist/ios/interact.js +3 -3
- package/dist/ios/observe.js +4 -4
- package/dist/ios/utils.js +8 -8
- package/dist/server.js +34 -42
- package/dist/tools/install.js +1 -1
- package/dist/tools/interact.js +89 -0
- package/dist/tools/logs.js +2 -2
- package/dist/tools/observe.js +126 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/java.js +76 -0
- package/docs/CHANGELOG.md +21 -6
- package/eslint.config.cjs +36 -0
- package/eslint.config.js +60 -0
- package/package.json +8 -2
- package/src/android/interact.ts +24 -5
- package/src/android/observe.ts +3 -3
- package/src/android/utils.ts +65 -93
- package/src/ios/interact.ts +3 -4
- package/src/ios/observe.ts +4 -4
- package/src/ios/utils.ts +8 -8
- package/src/server.ts +37 -58
- package/src/tools/interact.ts +84 -0
- package/src/tools/observe.ts +132 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/java.ts +69 -0
- package/test/integration/install.integration.ts +3 -3
- package/test/integration/run-install-android.ts +1 -1
- package/test/integration/run-install-ios.ts +1 -1
- package/test/integration/smoke-test.ts +1 -1
- package/test/integration/test-dist.ts +1 -1
- package/test/integration/test-ui-tree.ts +1 -1
- package/test/integration/wait_for_element_real.ts +1 -1
- package/test/unit/detect-java.test.ts +22 -0
- package/test/unit/install.test.ts +0 -6
- package/src/tools/app.ts +0 -46
- package/src/tools/devices.ts +0 -6
- package/src/tools/install.ts +0 -43
- package/src/tools/logs.ts +0 -62
- package/src/tools/screenshot.ts +0 -18
- package/src/tools/ui.ts +0 -62
package/.eslintignore
ADDED
package/.eslintrc.cjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
root: true,
|
|
3
|
+
parser: '@typescript-eslint/parser',
|
|
4
|
+
parserOptions: {
|
|
5
|
+
ecmaVersion: 2020,
|
|
6
|
+
sourceType: 'module',
|
|
7
|
+
project: './tsconfig.json'
|
|
8
|
+
},
|
|
9
|
+
plugins: ['@typescript-eslint', 'unused-imports'],
|
|
10
|
+
rules: {
|
|
11
|
+
// Use plugin to error on unused imports and provide autofix where possible
|
|
12
|
+
'unused-imports/no-unused-imports': 'error',
|
|
13
|
+
'unused-imports/no-unused-vars': ['error', { vars: 'all', args: 'after-used', ignoreRestSiblings: true }],
|
|
14
|
+
// Disable the default TS rule to avoid duplicate warnings
|
|
15
|
+
'@typescript-eslint/no-unused-vars': 'off'
|
|
16
|
+
},
|
|
17
|
+
ignorePatterns: ['dist/', 'node_modules/', '.git/']
|
|
18
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
unit:
|
|
10
|
+
name: Unit tests
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- name: Use Node.js
|
|
15
|
+
uses: actions/setup-node@v4
|
|
16
|
+
with:
|
|
17
|
+
node-version: '18'
|
|
18
|
+
- name: Install dependencies
|
|
19
|
+
run: npm ci
|
|
20
|
+
- name: Lint
|
|
21
|
+
run: npm run lint
|
|
22
|
+
- name: Build
|
|
23
|
+
run: npm run build
|
|
24
|
+
- name: Run unit tests
|
|
25
|
+
run: npm test
|
|
26
|
+
|
|
27
|
+
android-integration:
|
|
28
|
+
name: Android emulator integration (manual)
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
needs: unit
|
|
31
|
+
# only run integration when manually triggered to avoid long runs on every PR
|
|
32
|
+
if: github.event_name == 'workflow_dispatch'
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
|
|
36
|
+
- name: Set up JDK 17
|
|
37
|
+
uses: actions/setup-java@v4
|
|
38
|
+
with:
|
|
39
|
+
distribution: 'temurin'
|
|
40
|
+
java-version: '17'
|
|
41
|
+
|
|
42
|
+
- name: Set up Node.js
|
|
43
|
+
uses: actions/setup-node@v4
|
|
44
|
+
with:
|
|
45
|
+
node-version: '18'
|
|
46
|
+
|
|
47
|
+
- name: Install dependencies
|
|
48
|
+
run: npm ci
|
|
49
|
+
|
|
50
|
+
- name: Start Android emulator
|
|
51
|
+
uses: reactivecircus/android-emulator-runner@v2
|
|
52
|
+
with:
|
|
53
|
+
api-level: 30
|
|
54
|
+
target: google_apis
|
|
55
|
+
arch: x86_64
|
|
56
|
+
force-avd-creation: true
|
|
57
|
+
|
|
58
|
+
- name: Build and run Android integration tests
|
|
59
|
+
env:
|
|
60
|
+
ADB_TIMEOUT: 120000
|
|
61
|
+
run: |
|
|
62
|
+
npm run build
|
|
63
|
+
node test/integration/run-install-android.js || true
|
package/README.md
CHANGED
|
@@ -2,18 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
A minimal, secure MCP server for AI-assisted mobile development. Build, install, and inspect Android/iOS apps from an MCP-compatible client.
|
|
4
4
|
|
|
5
|
-
This README was shortened to keep high-level info only. Detailed tool definitions moved to docs/TOOLS.md.
|
|
6
|
-
|
|
7
|
-
## Quick start
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
git clone https://github.com/YOUR_USERNAME/mobile-debug-mcp.git
|
|
11
|
-
cd mobile-debug-mcp
|
|
12
|
-
npm install
|
|
13
|
-
npm run build
|
|
14
|
-
npm start
|
|
15
|
-
```
|
|
16
|
-
|
|
17
5
|
## Requirements
|
|
18
6
|
|
|
19
7
|
- Node.js >= 18
|
|
@@ -34,14 +22,13 @@ npm start
|
|
|
34
22
|
}
|
|
35
23
|
}
|
|
36
24
|
```
|
|
37
|
-
|
|
38
|
-
|
|
25
|
+
## Usage
|
|
26
|
+
//TODO add examples
|
|
39
27
|
|
|
40
28
|
## Docs
|
|
41
29
|
|
|
42
|
-
- Tools: docs/TOOLS.md
|
|
43
|
-
- Changelog: docs/CHANGELOG.md
|
|
44
|
-
- Tests: test/
|
|
30
|
+
- Tools: [Tools](docs/TOOLS.md) — full input/response examples
|
|
31
|
+
- Changelog: [Changelog](docs/CHANGELOG.md)
|
|
45
32
|
|
|
46
33
|
## License
|
|
47
34
|
|
package/dist/android/interact.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { execAdb, getAndroidDeviceMetadata, getDeviceInfo,
|
|
1
|
+
import { execAdb, getAndroidDeviceMetadata, getDeviceInfo, spawnAdb } from "./utils.js";
|
|
2
|
+
import { detectJavaHome } from "../utils/java.js";
|
|
2
3
|
import { AndroidObserve } from "./observe.js";
|
|
3
4
|
import { promises as fs } from "fs";
|
|
4
5
|
import { spawn } from "child_process";
|
|
@@ -124,7 +125,7 @@ export class AndroidInteract {
|
|
|
124
125
|
// Remove obvious shell profile hints; avoid touching SDKMAN symlinks or on-disk state.
|
|
125
126
|
delete env.SHELL;
|
|
126
127
|
}
|
|
127
|
-
catch
|
|
128
|
+
catch { }
|
|
128
129
|
// If we detected a compatible JDK, instruct Gradle to use it and avoid daemon reuse
|
|
129
130
|
// Prepare gradle invocation
|
|
130
131
|
const gradleArgs = ['assembleDebug'];
|
|
@@ -164,8 +165,29 @@ export class AndroidInteract {
|
|
|
164
165
|
throw new Error('Could not locate built APK after running Gradle');
|
|
165
166
|
apkToInstall = built;
|
|
166
167
|
}
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
// Try normal adb install with streaming attempt
|
|
169
|
+
try {
|
|
170
|
+
const res = await spawnAdb(['install', '-r', apkToInstall], deviceId);
|
|
171
|
+
if (res.code === 0) {
|
|
172
|
+
return { device: deviceInfo, installed: true, output: res.stdout };
|
|
173
|
+
}
|
|
174
|
+
// fallthrough to fallback
|
|
175
|
+
}
|
|
176
|
+
catch (e) {
|
|
177
|
+
// Log and continue to fallback
|
|
178
|
+
console.debug('[android] adb install failed, attempting push+pm fallback:', e instanceof Error ? e.message : String(e));
|
|
179
|
+
}
|
|
180
|
+
// Fallback: push APK to device and use pm install -r
|
|
181
|
+
const basename = path.basename(apkToInstall);
|
|
182
|
+
const remotePath = `/data/local/tmp/${basename}`;
|
|
183
|
+
await execAdb(['push', apkToInstall, remotePath], deviceId);
|
|
184
|
+
const pmOut = await execAdb(['shell', 'pm', 'install', '-r', remotePath], deviceId);
|
|
185
|
+
// cleanup remote file
|
|
186
|
+
try {
|
|
187
|
+
await execAdb(['shell', 'rm', remotePath], deviceId);
|
|
188
|
+
}
|
|
189
|
+
catch { }
|
|
190
|
+
return { device: deviceInfo, installed: true, output: pmOut };
|
|
169
191
|
}
|
|
170
192
|
catch (e) {
|
|
171
193
|
return { device: deviceInfo, installed: false, error: e instanceof Error ? e.message : String(e) };
|
package/dist/android/observe.js
CHANGED
|
@@ -22,7 +22,7 @@ async function getScreenResolution(deviceId) {
|
|
|
22
22
|
return { width: parseInt(match[1]), height: parseInt(match[2]) };
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
-
catch
|
|
25
|
+
catch {
|
|
26
26
|
// ignore
|
|
27
27
|
}
|
|
28
28
|
return { width: 0, height: 0 };
|
|
@@ -117,8 +117,8 @@ export class AndroidObserve {
|
|
|
117
117
|
break; // Success
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
|
-
catch (
|
|
121
|
-
console.error(`Attempt ${attempts} failed: ${
|
|
120
|
+
catch (e) {
|
|
121
|
+
console.error(`Attempt ${attempts} failed: ${e}`);
|
|
122
122
|
}
|
|
123
123
|
if (attempts === maxAttempts) {
|
|
124
124
|
throw new Error(`Failed to retrieve valid UI dump after ${maxAttempts} attempts.`);
|
package/dist/android/utils.js
CHANGED
|
@@ -1,80 +1,7 @@
|
|
|
1
|
-
import { spawn
|
|
2
|
-
import {
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { createWriteStream, promises as fsPromises } from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
export const ADB = process.env.ADB_PATH || 'adb';
|
|
5
|
-
export async function detectJavaHome() {
|
|
6
|
-
try {
|
|
7
|
-
// If JAVA_HOME is set, validate it's Java 17
|
|
8
|
-
if (process.env.JAVA_HOME) {
|
|
9
|
-
try {
|
|
10
|
-
const javaBin = path.join(process.env.JAVA_HOME, 'bin', 'java');
|
|
11
|
-
const v = execSync(`"${javaBin}" -version`, { stdio: ['ignore', 'pipe', 'pipe'] }).toString();
|
|
12
|
-
if (/\b17\b/.test(v) || /17\./.test(v))
|
|
13
|
-
return process.env.JAVA_HOME;
|
|
14
|
-
console.debug('[android] Existing JAVA_HOME does not appear to be Java 17, will search for JDK17');
|
|
15
|
-
}
|
|
16
|
-
catch (e) {
|
|
17
|
-
console.debug('[android] Failed to validate existing JAVA_HOME, searching for JDK17');
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
// macOS explicit path
|
|
21
|
-
const explicit = '/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home';
|
|
22
|
-
if (existsSync(explicit))
|
|
23
|
-
return explicit;
|
|
24
|
-
// Android Studio JBR candidates
|
|
25
|
-
const jbrCandidates = [
|
|
26
|
-
'/Applications/Android Studio.app/Contents/jbr',
|
|
27
|
-
'/Applications/Android Studio Preview.app/Contents/jbr',
|
|
28
|
-
'/Applications/Android Studio Preview 2022.3.app/Contents/jbr',
|
|
29
|
-
'/Applications/Android Studio Preview 2023.1.app/Contents/jbr'
|
|
30
|
-
];
|
|
31
|
-
for (const p of jbrCandidates) {
|
|
32
|
-
const javaBin = path.join(p, 'bin', 'java');
|
|
33
|
-
if (existsSync(javaBin)) {
|
|
34
|
-
try {
|
|
35
|
-
const v = execSync(`"${javaBin}" -version`, { stdio: ['ignore', 'pipe', 'pipe'] }).toString();
|
|
36
|
-
if (/\b17\b/.test(v) || /17\./.test(v))
|
|
37
|
-
return p;
|
|
38
|
-
}
|
|
39
|
-
catch { }
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
// macOS /usr/libexec/java_home
|
|
43
|
-
try {
|
|
44
|
-
const out = execSync('/usr/libexec/java_home -v 17', { stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim();
|
|
45
|
-
if (out)
|
|
46
|
-
return out;
|
|
47
|
-
}
|
|
48
|
-
catch { }
|
|
49
|
-
// macOS common JDK locations
|
|
50
|
-
try {
|
|
51
|
-
const homes = execSync('ls -1 /Library/Java/JavaVirtualMachines || true', { stdio: ['ignore', 'pipe', 'inherit'] }).toString().split(/\r?\n/).filter(Boolean);
|
|
52
|
-
for (const h of homes) {
|
|
53
|
-
if (h.toLowerCase().includes('17') || h.toLowerCase().includes('jdk-17')) {
|
|
54
|
-
const candidate = `/Library/Java/JavaVirtualMachines/${h}/Contents/Home`;
|
|
55
|
-
return candidate;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
catch { }
|
|
60
|
-
// Linux locations
|
|
61
|
-
const linuxCandidates = [
|
|
62
|
-
'/usr/lib/jvm/java-17-openjdk-amd64',
|
|
63
|
-
'/usr/lib/jvm/java-17-openjdk',
|
|
64
|
-
'/usr/lib/jvm/zulu17',
|
|
65
|
-
'/usr/lib/jvm/temurin-17-jdk'
|
|
66
|
-
];
|
|
67
|
-
for (const p of linuxCandidates) {
|
|
68
|
-
try {
|
|
69
|
-
if (existsSync(p))
|
|
70
|
-
return p;
|
|
71
|
-
}
|
|
72
|
-
catch { }
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
catch (e) { }
|
|
76
|
-
return undefined;
|
|
77
|
-
}
|
|
78
5
|
// Helper to construct ADB args with optional device ID
|
|
79
6
|
function getAdbArgs(args, deviceId) {
|
|
80
7
|
if (deviceId) {
|
|
@@ -82,6 +9,24 @@ function getAdbArgs(args, deviceId) {
|
|
|
82
9
|
}
|
|
83
10
|
return args;
|
|
84
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Determine an effective ADB timeout (ms) prioritizing:
|
|
14
|
+
* 1. provided customTimeout
|
|
15
|
+
* 2. MCP_ADB_TIMEOUT or ADB_TIMEOUT env vars
|
|
16
|
+
* 3. sensible per-command defaults
|
|
17
|
+
*/
|
|
18
|
+
function getAdbTimeout(args, customTimeout) {
|
|
19
|
+
if (typeof customTimeout === 'number' && !isNaN(customTimeout))
|
|
20
|
+
return customTimeout;
|
|
21
|
+
const envTimeout = parseInt(process.env.MCP_ADB_TIMEOUT || process.env.ADB_TIMEOUT || '', 10);
|
|
22
|
+
if (!isNaN(envTimeout) && envTimeout > 0)
|
|
23
|
+
return envTimeout;
|
|
24
|
+
if (args.includes('logcat'))
|
|
25
|
+
return 10000;
|
|
26
|
+
if (args.includes('uiautomator') && args.includes('dump'))
|
|
27
|
+
return 20000;
|
|
28
|
+
return 120000;
|
|
29
|
+
}
|
|
85
30
|
export function execAdb(args, deviceId, options = {}) {
|
|
86
31
|
const adbArgs = getAdbArgs(args, deviceId);
|
|
87
32
|
return new Promise((resolve, reject) => {
|
|
@@ -101,27 +46,7 @@ export function execAdb(args, deviceId, options = {}) {
|
|
|
101
46
|
stderr += data.toString();
|
|
102
47
|
});
|
|
103
48
|
}
|
|
104
|
-
|
|
105
|
-
if (typeof customTimeout === 'number' && !isNaN(customTimeout)) {
|
|
106
|
-
timeoutMs = customTimeout;
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
const envTimeout = parseInt(process.env.MCP_ADB_TIMEOUT || process.env.ADB_TIMEOUT || '', 10);
|
|
110
|
-
if (!isNaN(envTimeout) && envTimeout > 0) {
|
|
111
|
-
timeoutMs = envTimeout;
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
if (args.includes('logcat')) {
|
|
115
|
-
timeoutMs = 10000;
|
|
116
|
-
}
|
|
117
|
-
else if (args.includes('uiautomator') && args.includes('dump')) {
|
|
118
|
-
timeoutMs = 20000; // UI dump can be slow
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
timeoutMs = 120000; // default 2 minutes for installs and slow commands
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
49
|
+
const timeoutMs = getAdbTimeout(args, customTimeout);
|
|
125
50
|
const timeout = setTimeout(() => {
|
|
126
51
|
child.kill();
|
|
127
52
|
reject(new Error(`ADB command timed out after ${timeoutMs}ms: ${args.join(' ')}`));
|
|
@@ -143,6 +68,36 @@ export function execAdb(args, deviceId, options = {}) {
|
|
|
143
68
|
});
|
|
144
69
|
});
|
|
145
70
|
}
|
|
71
|
+
// Spawn adb but return full streams and exit code so callers can implement fallbacks or stream output
|
|
72
|
+
export function spawnAdb(args, deviceId, options = {}) {
|
|
73
|
+
const adbArgs = getAdbArgs(args, deviceId);
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const { timeout: customTimeout, ...spawnOptions } = options;
|
|
76
|
+
const child = spawn(ADB, adbArgs, spawnOptions);
|
|
77
|
+
let stdout = '';
|
|
78
|
+
let stderr = '';
|
|
79
|
+
if (child.stdout)
|
|
80
|
+
child.stdout.on('data', d => { stdout += d.toString(); });
|
|
81
|
+
if (child.stderr)
|
|
82
|
+
child.stderr.on('data', d => { stderr += d.toString(); });
|
|
83
|
+
const timeoutMs = getAdbTimeout(args, customTimeout);
|
|
84
|
+
const timeout = setTimeout(() => {
|
|
85
|
+
try {
|
|
86
|
+
child.kill();
|
|
87
|
+
}
|
|
88
|
+
catch { }
|
|
89
|
+
reject(new Error(`ADB command timed out after ${timeoutMs}ms: ${args.join(' ')}`));
|
|
90
|
+
}, timeoutMs);
|
|
91
|
+
child.on('close', (code) => {
|
|
92
|
+
clearTimeout(timeout);
|
|
93
|
+
resolve({ stdout: stdout.trim(), stderr: stderr.trim(), code });
|
|
94
|
+
});
|
|
95
|
+
child.on('error', (err) => {
|
|
96
|
+
clearTimeout(timeout);
|
|
97
|
+
reject(err);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
146
101
|
export function getDeviceInfo(deviceId, metadata = {}) {
|
|
147
102
|
return {
|
|
148
103
|
platform: 'android',
|
|
@@ -169,7 +124,7 @@ export async function getAndroidDeviceMetadata(appId, deviceId) {
|
|
|
169
124
|
resolvedDeviceId = deviceLines[0];
|
|
170
125
|
}
|
|
171
126
|
}
|
|
172
|
-
catch
|
|
127
|
+
catch {
|
|
173
128
|
// ignore and continue without resolvedDeviceId
|
|
174
129
|
}
|
|
175
130
|
}
|
|
@@ -182,7 +137,7 @@ export async function getAndroidDeviceMetadata(appId, deviceId) {
|
|
|
182
137
|
const simulator = simOutput === '1';
|
|
183
138
|
return { platform: 'android', id: resolvedDeviceId || 'default', osVersion, model, simulator };
|
|
184
139
|
}
|
|
185
|
-
catch
|
|
140
|
+
catch {
|
|
186
141
|
return { platform: 'android', id: deviceId || 'default', osVersion: '', model: '', simulator: false };
|
|
187
142
|
}
|
|
188
143
|
}
|
|
@@ -219,7 +174,7 @@ export async function listAndroidDevices(appId) {
|
|
|
219
174
|
}));
|
|
220
175
|
return infos;
|
|
221
176
|
}
|
|
222
|
-
catch
|
|
177
|
+
catch {
|
|
223
178
|
return [];
|
|
224
179
|
}
|
|
225
180
|
}
|
|
@@ -354,7 +309,7 @@ export async function startAndroidLogStream(packageName, level = 'error', device
|
|
|
354
309
|
try {
|
|
355
310
|
activeLogStreams.get(sessionId).proc.kill();
|
|
356
311
|
}
|
|
357
|
-
catch
|
|
312
|
+
catch { }
|
|
358
313
|
activeLogStreams.delete(sessionId);
|
|
359
314
|
}
|
|
360
315
|
// Start logcat process
|
|
@@ -381,14 +336,14 @@ export async function startAndroidLogStream(packageName, level = 'error', device
|
|
|
381
336
|
stream.write(JSON.stringify(entry) + '\n');
|
|
382
337
|
}
|
|
383
338
|
});
|
|
384
|
-
proc.on('close', (
|
|
339
|
+
proc.on('close', () => {
|
|
385
340
|
stream.end();
|
|
386
341
|
activeLogStreams.delete(sessionId);
|
|
387
342
|
});
|
|
388
343
|
activeLogStreams.set(sessionId, { proc, file });
|
|
389
344
|
return { success: true, stream_started: true };
|
|
390
345
|
}
|
|
391
|
-
catch
|
|
346
|
+
catch {
|
|
392
347
|
return { success: false, error: 'log_stream_start_failed' };
|
|
393
348
|
}
|
|
394
349
|
}
|
|
@@ -399,7 +354,7 @@ export async function stopAndroidLogStream(sessionId = 'default') {
|
|
|
399
354
|
try {
|
|
400
355
|
entry.proc.kill();
|
|
401
356
|
}
|
|
402
|
-
catch
|
|
357
|
+
catch { }
|
|
403
358
|
activeLogStreams.delete(sessionId);
|
|
404
359
|
return { success: true };
|
|
405
360
|
}
|
|
@@ -468,7 +423,7 @@ export async function readLogStreamLines(sessionId = 'default', limit = 100, sin
|
|
|
468
423
|
const crash_summary = crashEntry ? { crash_detected: true, exception: crashEntry.exception, sample: crashEntry.message } : { crash_detected: false };
|
|
469
424
|
return { entries, crash_summary };
|
|
470
425
|
}
|
|
471
|
-
catch
|
|
426
|
+
catch {
|
|
472
427
|
return { entries: [], crash_summary: { crash_detected: false } };
|
|
473
428
|
}
|
|
474
429
|
}
|
package/dist/ios/interact.js
CHANGED
|
@@ -166,7 +166,7 @@ export class iOSInteract {
|
|
|
166
166
|
return { device, installed: true };
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
|
-
catch
|
|
169
|
+
catch {
|
|
170
170
|
// fallthrough
|
|
171
171
|
}
|
|
172
172
|
return { device, installed: false, error: e instanceof Error ? e.message : String(e) };
|
|
@@ -233,8 +233,8 @@ export class iOSInteract {
|
|
|
233
233
|
dataCleared: true
|
|
234
234
|
};
|
|
235
235
|
}
|
|
236
|
-
catch (
|
|
237
|
-
throw new Error(`Failed to clear data for ${bundleId}: ${
|
|
236
|
+
catch (e) {
|
|
237
|
+
throw new Error(`Failed to clear data for ${bundleId}: ${e instanceof Error ? e.message : String(e)}`);
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
}
|
package/dist/ios/observe.js
CHANGED
|
@@ -119,10 +119,10 @@ export class iOSObserve {
|
|
|
119
119
|
resolution: { width: 0, height: 0 },
|
|
120
120
|
};
|
|
121
121
|
}
|
|
122
|
-
catch (
|
|
122
|
+
catch (e) {
|
|
123
123
|
// Ensure cleanup happens even on error
|
|
124
124
|
await fs.rm(tmpFile).catch(() => { });
|
|
125
|
-
throw new Error(`Failed to capture screenshot: ${
|
|
125
|
+
throw new Error(`Failed to capture screenshot: ${e instanceof Error ? e.message : String(e)}`);
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
async getUITree(deviceId = "booted") {
|
|
@@ -174,8 +174,8 @@ export class iOSObserve {
|
|
|
174
174
|
break; // Success
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
|
-
catch (
|
|
178
|
-
console.error(`Attempt ${attempts} failed: ${
|
|
177
|
+
catch (e) {
|
|
178
|
+
console.error(`Attempt ${attempts} failed: ${e}`);
|
|
179
179
|
}
|
|
180
180
|
if (attempts === maxAttempts) {
|
|
181
181
|
return {
|
package/dist/ios/utils.js
CHANGED
|
@@ -106,7 +106,7 @@ export async function getIOSDeviceMetadata(deviceId = "booted") {
|
|
|
106
106
|
}
|
|
107
107
|
resolve(fallback);
|
|
108
108
|
}
|
|
109
|
-
catch
|
|
109
|
+
catch {
|
|
110
110
|
resolve(fallback);
|
|
111
111
|
}
|
|
112
112
|
});
|
|
@@ -149,7 +149,7 @@ export async function listIOSDevices(appId) {
|
|
|
149
149
|
}
|
|
150
150
|
Promise.all(checks).then(() => resolve(out)).catch(() => resolve(out));
|
|
151
151
|
}
|
|
152
|
-
catch
|
|
152
|
+
catch {
|
|
153
153
|
resolve([]);
|
|
154
154
|
}
|
|
155
155
|
});
|
|
@@ -167,7 +167,7 @@ export function _setIOSActiveLogStream(sessionId, file) {
|
|
|
167
167
|
export function _clearIOSActiveLogStream(sessionId) {
|
|
168
168
|
iosActiveLogStreams.delete(sessionId);
|
|
169
169
|
}
|
|
170
|
-
export async function startIOSLogStream(bundleId,
|
|
170
|
+
export async function startIOSLogStream(bundleId, deviceId = 'booted', sessionId = 'default') {
|
|
171
171
|
try {
|
|
172
172
|
// Build predicate to filter by process or subsystem
|
|
173
173
|
const predicate = `process == "${bundleId}" or subsystem contains "${bundleId}"`;
|
|
@@ -176,7 +176,7 @@ export async function startIOSLogStream(bundleId, level = 'error', deviceId = 'b
|
|
|
176
176
|
try {
|
|
177
177
|
iosActiveLogStreams.get(sessionId).proc.kill();
|
|
178
178
|
}
|
|
179
|
-
catch
|
|
179
|
+
catch { }
|
|
180
180
|
iosActiveLogStreams.delete(sessionId);
|
|
181
181
|
}
|
|
182
182
|
// Start simctl log stream: xcrun simctl spawn <device> log stream --style syslog --predicate '<predicate>'
|
|
@@ -203,14 +203,14 @@ export async function startIOSLogStream(bundleId, level = 'error', deviceId = 'b
|
|
|
203
203
|
stream.write(JSON.stringify(entry) + '\n');
|
|
204
204
|
}
|
|
205
205
|
});
|
|
206
|
-
proc.on('close', (
|
|
206
|
+
proc.on('close', () => {
|
|
207
207
|
stream.end();
|
|
208
208
|
iosActiveLogStreams.delete(sessionId);
|
|
209
209
|
});
|
|
210
210
|
iosActiveLogStreams.set(sessionId, { proc, file });
|
|
211
211
|
return { success: true, stream_started: true };
|
|
212
212
|
}
|
|
213
|
-
catch
|
|
213
|
+
catch {
|
|
214
214
|
return { success: false, error: 'log_stream_start_failed' };
|
|
215
215
|
}
|
|
216
216
|
}
|
|
@@ -221,7 +221,7 @@ export async function stopIOSLogStream(sessionId = 'default') {
|
|
|
221
221
|
try {
|
|
222
222
|
entry.proc.kill();
|
|
223
223
|
}
|
|
224
|
-
catch
|
|
224
|
+
catch { }
|
|
225
225
|
iosActiveLogStreams.delete(sessionId);
|
|
226
226
|
return { success: true };
|
|
227
227
|
}
|
|
@@ -262,7 +262,7 @@ export async function readIOSLogStreamLines(sessionId = 'default', limit = 100,
|
|
|
262
262
|
const crash_summary = crashEntry ? { crash_detected: true, exception: crashEntry.exception, sample: crashEntry.message } : { crash_detected: false };
|
|
263
263
|
return { entries, crash_summary };
|
|
264
264
|
}
|
|
265
|
-
catch
|
|
265
|
+
catch {
|
|
266
266
|
return { entries: [], crash_summary: { crash_detected: false } };
|
|
267
267
|
}
|
|
268
268
|
}
|