mobile-debug-mcp 0.12.5 → 0.12.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ios/manage.js +95 -5
- package/package.json +1 -1
- package/src/ios/manage.ts +87 -6
package/dist/ios/manage.js
CHANGED
|
@@ -303,15 +303,105 @@ export class iOSManage {
|
|
|
303
303
|
console.error('MCP-STARTAPP-EXEC', JSON.stringify(instrumentation));
|
|
304
304
|
}
|
|
305
305
|
catch (e) { }
|
|
306
|
-
|
|
306
|
+
}
|
|
307
|
+
catch { }
|
|
308
|
+
// Attempt to launch
|
|
309
|
+
let launchResult = null;
|
|
310
|
+
try {
|
|
311
|
+
launchResult = await execCommand(['simctl', 'launch', deviceId, bundleId], deviceId);
|
|
312
|
+
}
|
|
313
|
+
catch (launchErr) {
|
|
314
|
+
// Collect diagnostics when simctl launch fails
|
|
315
|
+
const launchDiag = execCommandWithDiagnostics(['simctl', 'launch', deviceId, bundleId], deviceId);
|
|
307
316
|
const device = await getIOSDeviceMetadata(deviceId);
|
|
308
|
-
|
|
317
|
+
const post = await this.collectPostLaunchDiagnostics(bundleId, deviceId);
|
|
318
|
+
return { device, appStarted: false, launchTimeMs: 0, error: launchErr instanceof Error ? launchErr.message : String(launchErr), diagnostics: { launchDiag, post }, instrumentation };
|
|
319
|
+
}
|
|
320
|
+
// Basic success — but verify RunningBoard/installcoordination didn't mark it as placeholder
|
|
321
|
+
const device = await getIOSDeviceMetadata(deviceId);
|
|
322
|
+
// short wait to let system settle
|
|
323
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
324
|
+
let appinfo = '';
|
|
325
|
+
try {
|
|
326
|
+
const ai = await execCommand(['simctl', 'appinfo', deviceId, bundleId], deviceId);
|
|
327
|
+
appinfo = ai.output || '';
|
|
328
|
+
}
|
|
329
|
+
catch { }
|
|
330
|
+
// capture recent runningboard/installcoordination logs
|
|
331
|
+
const logDiag = execCommandWithDiagnostics(['simctl', 'spawn', deviceId, 'log', 'show', '--style', 'syslog', '--predicate', `(process == "${bundleId}" ) OR eventMessage CONTAINS "installcoordinationd" OR eventMessage CONTAINS "runningboard"`, '--last', '1m'], deviceId);
|
|
332
|
+
const placeholderDetected = (appinfo && /isPlaceholder[:=]?\s*Y/i.test(appinfo)) || (logDiag && ((logDiag.runResult && ((logDiag.runResult.stdout || '').includes('isPlaceholder')) || (logDiag.runResult.stderr || '').includes('isPlaceholder'))));
|
|
333
|
+
if (placeholderDetected) {
|
|
334
|
+
const post = await this.collectPostLaunchDiagnostics(bundleId, deviceId, appinfo);
|
|
335
|
+
return { device, appStarted: false, launchTimeMs: 0, diagnostics: { appinfo, logDiag, post }, instrumentation };
|
|
336
|
+
}
|
|
337
|
+
return { device, appStarted: !!(launchResult && launchResult.output), launchTimeMs: 1000, instrumentation };
|
|
338
|
+
}
|
|
339
|
+
appExecutableName(bundleId) {
|
|
340
|
+
// Best-effort executable name: prefer last component of bundleId
|
|
341
|
+
try {
|
|
342
|
+
const candidate = bundleId.split('.').pop();
|
|
343
|
+
return candidate || bundleId;
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
return bundleId;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// Collect bundle- and system-level diagnostics after a failed or placeholder launch
|
|
350
|
+
async collectPostLaunchDiagnostics(bundleId, deviceId = "booted", appinfo) {
|
|
351
|
+
const diagnostics = { ts: new Date().toISOString(), bundleId, deviceId };
|
|
352
|
+
// gather simctl appinfo (if not provided)
|
|
353
|
+
try {
|
|
354
|
+
diagnostics.appinfo = appinfo || ((await execCommand(['simctl', 'appinfo', deviceId, bundleId], deviceId)).output || '');
|
|
309
355
|
}
|
|
310
356
|
catch (e) {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
357
|
+
diagnostics.appinfoError = String(e);
|
|
358
|
+
}
|
|
359
|
+
// attempt to discover bundle path from appinfo
|
|
360
|
+
let bundlePath = null;
|
|
361
|
+
if (diagnostics.appinfo) {
|
|
362
|
+
const m = diagnostics.appinfo.match(/Path\s*=\s*"?([\S]+)"?/) || diagnostics.appinfo.match(/Container: (\/\S+)/);
|
|
363
|
+
if (m)
|
|
364
|
+
bundlePath = m[1];
|
|
365
|
+
}
|
|
366
|
+
// lipo / file / otool / codesign / xattr
|
|
367
|
+
if (bundlePath) {
|
|
368
|
+
diagnostics.bundlePath = bundlePath;
|
|
369
|
+
const execs = [
|
|
370
|
+
{ name: 'file', cmd: ['file', bundlePath + '/' + this.appExecutableName(bundleId)] },
|
|
371
|
+
{ name: 'lipo', cmd: ['lipo', '-info', bundlePath + '/' + this.appExecutableName(bundleId)] },
|
|
372
|
+
{ name: 'otool-L', cmd: ['otool', '-L', bundlePath + '/' + this.appExecutableName(bundleId)] },
|
|
373
|
+
{ name: 'otool-load', cmd: ['otool', '-l', bundlePath + '/' + this.appExecutableName(bundleId)] },
|
|
374
|
+
{ name: 'plutil', cmd: ['plutil', '-p', bundlePath + '/Info.plist'] },
|
|
375
|
+
{ name: 'codesign', cmd: ['codesign', '-dvvv', bundlePath] },
|
|
376
|
+
{ name: 'xattr', cmd: ['xattr', '-l', bundlePath] },
|
|
377
|
+
{ name: 'ls', cmd: ['ls', '-la', bundlePath] },
|
|
378
|
+
];
|
|
379
|
+
diagnostics.bundle = {};
|
|
380
|
+
for (const e of execs) {
|
|
381
|
+
try {
|
|
382
|
+
const r = execCommandWithDiagnostics(e.cmd, deviceId);
|
|
383
|
+
diagnostics.bundle[e.name] = r && r.runResult ? { stdout: r.runResult.stdout, stderr: r.runResult.stderr, code: r.runResult.exitCode } : { error: 'no-result' };
|
|
384
|
+
}
|
|
385
|
+
catch (err) {
|
|
386
|
+
diagnostics.bundle[e.name] = { error: String(err) };
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
// collect recent system logs and a screenshot
|
|
391
|
+
try {
|
|
392
|
+
diagnostics.recentLogs = execCommandWithDiagnostics(['simctl', 'spawn', deviceId, 'log', 'show', '--style', 'syslog', '--predicate', `eventMessage CONTAINS "installcoordinationd" OR eventMessage CONTAINS "runningboard"`, '--last', '5m'], deviceId);
|
|
393
|
+
}
|
|
394
|
+
catch (e) {
|
|
395
|
+
diagnostics.recentLogsError = String(e);
|
|
396
|
+
}
|
|
397
|
+
try {
|
|
398
|
+
const shot = await execCommandWithDiagnostics(['simctl', 'io', deviceId, 'screenshot', '--type', 'png', '/tmp/mcp_post_launch_screenshot.png'], deviceId);
|
|
399
|
+
diagnostics.screenshot = { created: true, path: '/tmp/mcp_post_launch_screenshot.png', result: shot && shot.runResult };
|
|
400
|
+
}
|
|
401
|
+
catch (e) {
|
|
402
|
+
diagnostics.screenshotError = String(e);
|
|
314
403
|
}
|
|
404
|
+
return diagnostics;
|
|
315
405
|
}
|
|
316
406
|
async terminateApp(bundleId, deviceId = "booted") {
|
|
317
407
|
validateBundleId(bundleId);
|
package/package.json
CHANGED
package/src/ios/manage.ts
CHANGED
|
@@ -300,19 +300,100 @@ export class iOSManage {
|
|
|
300
300
|
validateBundleId(bundleId)
|
|
301
301
|
// Prepare instrumentation object upfront so it can be returned to callers
|
|
302
302
|
const instrumentation = { ts: new Date().toISOString(), action: 'startApp', cmd: 'xcrun', args: ['simctl','launch', deviceId, bundleId], cwd: process.cwd(), env: { PATH: process.env.PATH, XCRUN_PATH: process.env.XCRUN_PATH } }
|
|
303
|
+
|
|
303
304
|
try {
|
|
304
305
|
// Instrumentation: persist and emit to stderr for server logs
|
|
305
306
|
try { await fs.appendFile('/tmp/mcp_startapp_instrument.log', JSON.stringify(instrumentation) + '\n') } catch (e) {}
|
|
306
307
|
try { console.error('MCP-STARTAPP-EXEC', JSON.stringify(instrumentation)) } catch (e) {}
|
|
308
|
+
} catch {}
|
|
307
309
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
310
|
+
// Attempt to launch
|
|
311
|
+
let launchResult: any = null
|
|
312
|
+
try {
|
|
313
|
+
launchResult = await execCommand(['simctl', 'launch', deviceId, bundleId], deviceId)
|
|
314
|
+
} catch (launchErr:any) {
|
|
315
|
+
// Collect diagnostics when simctl launch fails
|
|
316
|
+
const launchDiag = execCommandWithDiagnostics(['simctl', 'launch', deviceId, bundleId], deviceId)
|
|
313
317
|
const device = await getIOSDeviceMetadata(deviceId)
|
|
314
|
-
|
|
318
|
+
const post = await this.collectPostLaunchDiagnostics(bundleId, deviceId)
|
|
319
|
+
return { device, appStarted: false, launchTimeMs: 0, error: launchErr instanceof Error ? launchErr.message : String(launchErr), diagnostics: { launchDiag, post }, instrumentation } as any
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Basic success — but verify RunningBoard/installcoordination didn't mark it as placeholder
|
|
323
|
+
const device = await getIOSDeviceMetadata(deviceId)
|
|
324
|
+
// short wait to let system settle
|
|
325
|
+
await new Promise(r => setTimeout(r, 1000))
|
|
326
|
+
|
|
327
|
+
let appinfo = ''
|
|
328
|
+
try {
|
|
329
|
+
const ai = await execCommand(['simctl', 'appinfo', deviceId, bundleId], deviceId)
|
|
330
|
+
appinfo = ai.output || ''
|
|
331
|
+
} catch {}
|
|
332
|
+
|
|
333
|
+
// capture recent runningboard/installcoordination logs
|
|
334
|
+
const logDiag = execCommandWithDiagnostics(['simctl','spawn',deviceId,'log','show','--style','syslog','--predicate',`(process == "${bundleId}" ) OR eventMessage CONTAINS "installcoordinationd" OR eventMessage CONTAINS "runningboard"`, '--last', '1m'], deviceId)
|
|
335
|
+
|
|
336
|
+
const placeholderDetected = (appinfo && /isPlaceholder[:=]?\s*Y/i.test(appinfo)) || (logDiag && ((logDiag.runResult && ((logDiag.runResult.stdout || '').includes('isPlaceholder')) || (logDiag.runResult.stderr || '').includes('isPlaceholder'))))
|
|
337
|
+
|
|
338
|
+
if (placeholderDetected) {
|
|
339
|
+
const post = await this.collectPostLaunchDiagnostics(bundleId, deviceId, appinfo)
|
|
340
|
+
return { device, appStarted: false, launchTimeMs: 0, diagnostics: { appinfo, logDiag, post }, instrumentation } as any
|
|
315
341
|
}
|
|
342
|
+
|
|
343
|
+
return { device, appStarted: !!(launchResult && launchResult.output), launchTimeMs: 1000, instrumentation }
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
appExecutableName(bundleId: string) {
|
|
347
|
+
// Best-effort executable name: prefer last component of bundleId
|
|
348
|
+
try { const candidate = bundleId.split('.').pop(); return candidate || bundleId }
|
|
349
|
+
catch { return bundleId }
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Collect bundle- and system-level diagnostics after a failed or placeholder launch
|
|
353
|
+
async collectPostLaunchDiagnostics(bundleId: string, deviceId: string = "booted", appinfo?: string) {
|
|
354
|
+
const diagnostics: any = { ts: new Date().toISOString(), bundleId, deviceId }
|
|
355
|
+
|
|
356
|
+
// gather simctl appinfo (if not provided)
|
|
357
|
+
try { diagnostics.appinfo = appinfo || ((await execCommand(['simctl','appinfo', deviceId, bundleId], deviceId)).output || '') } catch (e) { diagnostics.appinfoError = String(e) }
|
|
358
|
+
|
|
359
|
+
// attempt to discover bundle path from appinfo
|
|
360
|
+
let bundlePath: string | null = null
|
|
361
|
+
if (diagnostics.appinfo) {
|
|
362
|
+
const m = diagnostics.appinfo.match(/Path\s*=\s*"?([\S]+)"?/) || diagnostics.appinfo.match(/Container: (\/\S+)/)
|
|
363
|
+
if (m) bundlePath = m[1]
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// lipo / file / otool / codesign / xattr
|
|
367
|
+
if (bundlePath) {
|
|
368
|
+
diagnostics.bundlePath = bundlePath
|
|
369
|
+
const execs = [
|
|
370
|
+
{ name: 'file', cmd: ['file', bundlePath + '/' + this.appExecutableName(bundleId)] },
|
|
371
|
+
{ name: 'lipo', cmd: ['lipo', '-info', bundlePath + '/' + this.appExecutableName(bundleId)] },
|
|
372
|
+
{ name: 'otool-L', cmd: ['otool', '-L', bundlePath + '/' + this.appExecutableName(bundleId)] },
|
|
373
|
+
{ name: 'otool-load', cmd: ['otool', '-l', bundlePath + '/' + this.appExecutableName(bundleId)] },
|
|
374
|
+
{ name: 'plutil', cmd: ['plutil', '-p', bundlePath + '/Info.plist'] },
|
|
375
|
+
{ name: 'codesign', cmd: ['codesign', '-dvvv', bundlePath] },
|
|
376
|
+
{ name: 'xattr', cmd: ['xattr', '-l', bundlePath] },
|
|
377
|
+
{ name: 'ls', cmd: ['ls', '-la', bundlePath] },
|
|
378
|
+
]
|
|
379
|
+
|
|
380
|
+
diagnostics.bundle = {}
|
|
381
|
+
for (const e of execs) {
|
|
382
|
+
try {
|
|
383
|
+
const r = execCommandWithDiagnostics(e.cmd, deviceId)
|
|
384
|
+
diagnostics.bundle[e.name] = r && r.runResult ? { stdout: r.runResult.stdout, stderr: r.runResult.stderr, code: r.runResult.exitCode } : { error: 'no-result' }
|
|
385
|
+
} catch (err) { diagnostics.bundle[e.name] = { error: String(err) } }
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// collect recent system logs and a screenshot
|
|
390
|
+
try { diagnostics.recentLogs = execCommandWithDiagnostics(['simctl','spawn',deviceId,'log','show','--style','syslog','--predicate',`eventMessage CONTAINS "installcoordinationd" OR eventMessage CONTAINS "runningboard"`, '--last', '5m'], deviceId) } catch (e) { diagnostics.recentLogsError = String(e) }
|
|
391
|
+
try {
|
|
392
|
+
const shot = await execCommandWithDiagnostics(['simctl','io', deviceId, 'screenshot', '--type', 'png', '/tmp/mcp_post_launch_screenshot.png'], deviceId)
|
|
393
|
+
diagnostics.screenshot = { created: true, path: '/tmp/mcp_post_launch_screenshot.png', result: shot && shot.runResult }
|
|
394
|
+
} catch (e) { diagnostics.screenshotError = String(e) }
|
|
395
|
+
|
|
396
|
+
return diagnostics
|
|
316
397
|
}
|
|
317
398
|
|
|
318
399
|
async terminateApp(bundleId: string, deviceId: string = "booted"): Promise<TerminateAppResponse> {
|