@ebowwa/ios-devices 1.0.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/dist/device-ctl.d.ts +128 -0
- package/dist/device-ctl.d.ts.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5365 -0
- package/dist/index.js.map +22 -0
- package/dist/lib-imobiledevice.d.ts +162 -0
- package/dist/lib-imobiledevice.d.ts.map +1 -0
- package/dist/sim-ctl.d.ts +216 -0
- package/dist/sim-ctl.d.ts.map +1 -0
- package/dist/types.d.ts +487 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/unified-api.d.ts +122 -0
- package/dist/unified-api.d.ts.map +1 -0
- package/dist/utils.d.ts +47 -0
- package/dist/utils.d.ts.map +1 -0
- package/package.json +49 -0
- package/src/device-ctl.ts +547 -0
- package/src/index.ts +8 -0
- package/src/lib-imobiledevice.ts +404 -0
- package/src/shell-quote.d.ts +5 -0
- package/src/sim-ctl.ts +502 -0
- package/src/types.ts +315 -0
- package/src/unified-api.ts +578 -0
- package/src/utils.ts +174 -0
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified API - Single interface for iOS device control
|
|
3
|
+
* Automatically selects best available tool (devicectl > libimobiledevice)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { DeviceCtl } from './device-ctl.js';
|
|
7
|
+
import { LibIMobileDevice } from './lib-imobiledevice.js';
|
|
8
|
+
import { SimCtl } from './sim-ctl.js';
|
|
9
|
+
import { checkAvailableTools, type ExecOptions } from './utils.js';
|
|
10
|
+
import {
|
|
11
|
+
type IOSDevice,
|
|
12
|
+
type DeviceDetails,
|
|
13
|
+
type ProcessInfo,
|
|
14
|
+
type InstalledApp,
|
|
15
|
+
type FileInfo,
|
|
16
|
+
type LogEntry,
|
|
17
|
+
type LogFilter,
|
|
18
|
+
type CommandResult,
|
|
19
|
+
type SimulatorDevice,
|
|
20
|
+
type Location,
|
|
21
|
+
} from './types.js';
|
|
22
|
+
|
|
23
|
+
export interface IOSDevicesOptions {
|
|
24
|
+
timeout?: number;
|
|
25
|
+
preferDeviceCtl?: boolean; // Default true for iOS 17+
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class IOSDevices {
|
|
29
|
+
private deviceCtl: DeviceCtl | null = null;
|
|
30
|
+
private libIMobile: LibIMobileDevice | null = null;
|
|
31
|
+
private simCtl: SimCtl;
|
|
32
|
+
private availableTools: {
|
|
33
|
+
devicectl: boolean;
|
|
34
|
+
simctl: boolean;
|
|
35
|
+
libimobiledevice: boolean;
|
|
36
|
+
} | null = null;
|
|
37
|
+
|
|
38
|
+
constructor(private options: IOSDevicesOptions = {}) {
|
|
39
|
+
this.simCtl = new SimCtl(options);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Initialize and check available tools
|
|
44
|
+
*/
|
|
45
|
+
async initialize(): Promise<void> {
|
|
46
|
+
this.availableTools = await checkAvailableTools();
|
|
47
|
+
|
|
48
|
+
if (this.availableTools.devicectl && this.options.preferDeviceCtl !== false) {
|
|
49
|
+
this.deviceCtl = new DeviceCtl(this.options);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (this.availableTools.libimobiledevice) {
|
|
53
|
+
this.libIMobile = new LibIMobileDevice(this.options);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get available tools status
|
|
59
|
+
*/
|
|
60
|
+
getToolsStatus(): { devicectl: boolean; simctl: boolean; libimobiledevice: boolean } | null {
|
|
61
|
+
return this.availableTools;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ==========================================================================
|
|
65
|
+
// Device Management (Physical Devices)
|
|
66
|
+
// ==========================================================================
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* List all connected devices
|
|
70
|
+
*/
|
|
71
|
+
async listDevices(): Promise<IOSDevice[]> {
|
|
72
|
+
if (!this.availableTools) await this.initialize();
|
|
73
|
+
|
|
74
|
+
if (this.deviceCtl) {
|
|
75
|
+
// DeviceCtl.listDevices() returns IOSDevice[] directly
|
|
76
|
+
return await this.deviceCtl.listDevices();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (this.libIMobile) {
|
|
80
|
+
const result = await this.libIMobile.listDevices();
|
|
81
|
+
if (result.success) {
|
|
82
|
+
return this.parseDeviceListLegacy(result.stdout);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get device details
|
|
91
|
+
*/
|
|
92
|
+
async getDeviceDetails(deviceId: string): Promise<DeviceDetails | null> {
|
|
93
|
+
if (!this.availableTools) await this.initialize();
|
|
94
|
+
|
|
95
|
+
if (this.deviceCtl) {
|
|
96
|
+
const details = await this.deviceCtl.getDeviceInfo(deviceId);
|
|
97
|
+
if (details) {
|
|
98
|
+
return details;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (this.libIMobile) {
|
|
103
|
+
const libIMobile = new LibIMobileDevice({ udid: deviceId, ...this.options });
|
|
104
|
+
const result = await libIMobile.getDeviceInfo();
|
|
105
|
+
if (result.success) {
|
|
106
|
+
return this.parseDeviceDetailsLegacy(result.stdout);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Reboot device
|
|
115
|
+
*/
|
|
116
|
+
async rebootDevice(deviceId: string): Promise<CommandResult> {
|
|
117
|
+
if (!this.availableTools) await this.initialize();
|
|
118
|
+
|
|
119
|
+
if (this.deviceCtl) {
|
|
120
|
+
return this.deviceCtl.reboot(deviceId);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (this.libIMobile) {
|
|
124
|
+
const libIMobile = new LibIMobileDevice({ udid: deviceId });
|
|
125
|
+
return libIMobile.restart();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { success: false, stdout: '', stderr: 'No device control tools available', exitCode: 1 };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ==========================================================================
|
|
132
|
+
// Process Management
|
|
133
|
+
// ==========================================================================
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* List processes on device
|
|
137
|
+
*/
|
|
138
|
+
async listProcesses(deviceId: string): Promise<ProcessInfo[]> {
|
|
139
|
+
if (!this.availableTools) await this.initialize();
|
|
140
|
+
|
|
141
|
+
if (this.deviceCtl) {
|
|
142
|
+
return this.deviceCtl.listProcesses(deviceId);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Launch app
|
|
150
|
+
*/
|
|
151
|
+
async launchApp(deviceId: string, bundleId: string): Promise<CommandResult> {
|
|
152
|
+
if (!this.availableTools) await this.initialize();
|
|
153
|
+
|
|
154
|
+
if (this.deviceCtl) {
|
|
155
|
+
return this.deviceCtl.launchApp(deviceId, bundleId);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return { success: false, stdout: '', stderr: 'devicectl required for launch', exitCode: 1 };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Terminate app/process
|
|
163
|
+
*/
|
|
164
|
+
async terminateProcess(deviceId: string, pidOrName: number | string): Promise<CommandResult> {
|
|
165
|
+
if (!this.availableTools) await this.initialize();
|
|
166
|
+
|
|
167
|
+
if (this.deviceCtl) {
|
|
168
|
+
if (typeof pidOrName === 'number') {
|
|
169
|
+
return this.deviceCtl.terminateProcess(deviceId, pidOrName);
|
|
170
|
+
} else {
|
|
171
|
+
// Find PID first
|
|
172
|
+
const processes = await this.listProcesses(deviceId);
|
|
173
|
+
const proc = processes.find((p) => p.name === pidOrName || p.bundleIdentifier === pidOrName);
|
|
174
|
+
if (proc) {
|
|
175
|
+
return this.deviceCtl.terminateProcess(deviceId, proc.pid);
|
|
176
|
+
}
|
|
177
|
+
return { success: false, stdout: '', stderr: 'Process not found', exitCode: 1 };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return { success: false, stdout: '', stderr: 'devicectl required', exitCode: 1 };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ==========================================================================
|
|
185
|
+
// App Management
|
|
186
|
+
// ==========================================================================
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* List installed apps
|
|
190
|
+
*/
|
|
191
|
+
async listApps(deviceId: string): Promise<InstalledApp[]> {
|
|
192
|
+
if (!this.availableTools) await this.initialize();
|
|
193
|
+
|
|
194
|
+
if (this.deviceCtl) {
|
|
195
|
+
return this.deviceCtl.listApps(deviceId);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Install app
|
|
203
|
+
*/
|
|
204
|
+
async installApp(deviceId: string, appPath: string): Promise<CommandResult> {
|
|
205
|
+
if (!this.availableTools) await this.initialize();
|
|
206
|
+
|
|
207
|
+
if (this.deviceCtl) {
|
|
208
|
+
return this.deviceCtl.installApp(deviceId, appPath);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return { success: false, stdout: '', stderr: 'devicectl required', exitCode: 1 };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Uninstall app
|
|
216
|
+
*/
|
|
217
|
+
async uninstallApp(deviceId: string, bundleId: string): Promise<CommandResult> {
|
|
218
|
+
if (!this.availableTools) await this.initialize();
|
|
219
|
+
|
|
220
|
+
if (this.deviceCtl) {
|
|
221
|
+
return this.deviceCtl.uninstallApp(deviceId, bundleId);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return { success: false, stdout: '', stderr: 'devicectl required', exitCode: 1 };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ==========================================================================
|
|
228
|
+
// Logging
|
|
229
|
+
// ==========================================================================
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Stream syslog
|
|
233
|
+
*/
|
|
234
|
+
async streamSyslog(deviceId: string, filter?: LogFilter): Promise<CommandResult> {
|
|
235
|
+
if (!this.availableTools) await this.initialize();
|
|
236
|
+
|
|
237
|
+
if (this.libIMobile) {
|
|
238
|
+
const libIMobile = new LibIMobileDevice({ udid: deviceId });
|
|
239
|
+
return libIMobile.streamSyslog(filter);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return { success: false, stdout: '', stderr: 'libimobiledevice required for syslog', exitCode: 1 };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get syslog archive
|
|
247
|
+
*/
|
|
248
|
+
async getSyslogArchive(deviceId: string, outputPath: string): Promise<CommandResult> {
|
|
249
|
+
if (!this.availableTools) await this.initialize();
|
|
250
|
+
|
|
251
|
+
if (this.libIMobile) {
|
|
252
|
+
const libIMobile = new LibIMobileDevice({ udid: deviceId });
|
|
253
|
+
return libIMobile.getSyslogArchive(outputPath);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return { success: false, stdout: '', stderr: 'libimobiledevice required', exitCode: 1 };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ==========================================================================
|
|
260
|
+
// Screenshot
|
|
261
|
+
// ==========================================================================
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Take screenshot
|
|
265
|
+
*/
|
|
266
|
+
async takeScreenshot(deviceId: string, outputPath?: string): Promise<CommandResult> {
|
|
267
|
+
if (!this.availableTools) await this.initialize();
|
|
268
|
+
|
|
269
|
+
if (this.libIMobile) {
|
|
270
|
+
const libIMobile = new LibIMobileDevice({ udid: deviceId });
|
|
271
|
+
return libIMobile.takeScreenshot(outputPath);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return { success: false, stdout: '', stderr: 'libimobiledevice required', exitCode: 1 };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ==========================================================================
|
|
278
|
+
// Location
|
|
279
|
+
// ==========================================================================
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Set simulated location
|
|
283
|
+
*/
|
|
284
|
+
async setLocation(deviceId: string, latitude: number, longitude: number): Promise<CommandResult> {
|
|
285
|
+
if (!this.availableTools) await this.initialize();
|
|
286
|
+
|
|
287
|
+
if (this.libIMobile) {
|
|
288
|
+
const libIMobile = new LibIMobileDevice({ udid: deviceId });
|
|
289
|
+
return libIMobile.setLocation(latitude, longitude);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return { success: false, stdout: '', stderr: 'libimobiledevice required', exitCode: 1 };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Reset location
|
|
297
|
+
*/
|
|
298
|
+
async resetLocation(deviceId: string): Promise<CommandResult> {
|
|
299
|
+
if (!this.availableTools) await this.initialize();
|
|
300
|
+
|
|
301
|
+
if (this.libIMobile) {
|
|
302
|
+
const libIMobile = new LibIMobileDevice({ udid: deviceId });
|
|
303
|
+
return libIMobile.resetLocation();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return { success: false, stdout: '', stderr: 'libimobiledevice required', exitCode: 1 };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ==========================================================================
|
|
310
|
+
// Backup
|
|
311
|
+
// ==========================================================================
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Create backup
|
|
315
|
+
*/
|
|
316
|
+
async createBackup(deviceId: string, directory: string, encrypt?: boolean, password?: string): Promise<CommandResult> {
|
|
317
|
+
if (!this.availableTools) await this.initialize();
|
|
318
|
+
|
|
319
|
+
if (this.libIMobile) {
|
|
320
|
+
const libIMobile = new LibIMobileDevice({ udid: deviceId });
|
|
321
|
+
return libIMobile.createBackup(directory, encrypt, password);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return { success: false, stdout: '', stderr: 'libimobiledevice required', exitCode: 1 };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Restore backup
|
|
329
|
+
*/
|
|
330
|
+
async restoreBackup(deviceId: string, directory: string, password?: string): Promise<CommandResult> {
|
|
331
|
+
if (!this.availableTools) await this.initialize();
|
|
332
|
+
|
|
333
|
+
if (this.libIMobile) {
|
|
334
|
+
const libIMobile = new LibIMobileDevice({ udid: deviceId });
|
|
335
|
+
return libIMobile.restoreBackup(directory, password);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return { success: false, stdout: '', stderr: 'libimobiledevice required', exitCode: 1 };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ==========================================================================
|
|
342
|
+
// Simulator (direct access)
|
|
343
|
+
// ==========================================================================
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Get SimCtl instance for simulator control
|
|
347
|
+
*/
|
|
348
|
+
getSimulator(): SimCtl {
|
|
349
|
+
return this.simCtl;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* List simulators
|
|
354
|
+
*/
|
|
355
|
+
async listSimulators(): Promise<SimulatorDevice[]> {
|
|
356
|
+
const result = await this.simCtl.list({ devices: true, available: true });
|
|
357
|
+
if (result.success) {
|
|
358
|
+
return this.parseSimulatorList(result.stdout);
|
|
359
|
+
}
|
|
360
|
+
return [];
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Boot simulator
|
|
365
|
+
*/
|
|
366
|
+
async bootSimulator(udid: string): Promise<CommandResult> {
|
|
367
|
+
return this.simCtl.boot(udid);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Shutdown simulator
|
|
372
|
+
*/
|
|
373
|
+
async shutdownSimulator(udid: string): Promise<CommandResult> {
|
|
374
|
+
return this.simCtl.shutdown(udid);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// ==========================================================================
|
|
378
|
+
// Parsing Helpers
|
|
379
|
+
// ==========================================================================
|
|
380
|
+
|
|
381
|
+
private parseDeviceList(json: unknown): IOSDevice[] {
|
|
382
|
+
// Parse devicectl JSON output
|
|
383
|
+
const devices: IOSDevice[] = [];
|
|
384
|
+
try {
|
|
385
|
+
const data = json as { devices?: Array<{
|
|
386
|
+
identifier?: string;
|
|
387
|
+
name?: string;
|
|
388
|
+
modelCode?: string;
|
|
389
|
+
productType?: string;
|
|
390
|
+
osVersion?: string;
|
|
391
|
+
osBuildVersion?: string;
|
|
392
|
+
architecture?: string;
|
|
393
|
+
platform?: string;
|
|
394
|
+
connectionProperties?: { connectionType?: string };
|
|
395
|
+
}> };
|
|
396
|
+
|
|
397
|
+
if (data?.devices) {
|
|
398
|
+
for (const d of data.devices) {
|
|
399
|
+
devices.push({
|
|
400
|
+
identifier: d.identifier || '',
|
|
401
|
+
name: d.name || '',
|
|
402
|
+
model: d.modelCode || '',
|
|
403
|
+
modelCode: d.modelCode || '',
|
|
404
|
+
productType: d.productType || '',
|
|
405
|
+
osVersion: d.osVersion || '',
|
|
406
|
+
osBuildVersion: d.osBuildVersion || '',
|
|
407
|
+
architecture: d.architecture || '',
|
|
408
|
+
platform: (d.platform as IOSDevice['platform']) || 'iOS',
|
|
409
|
+
connectionType: d.connectionProperties?.connectionType === 'Wired' ? 'USB' : 'Network',
|
|
410
|
+
isTrusted: true,
|
|
411
|
+
isAvailable: true,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
} catch {
|
|
416
|
+
// Parse error
|
|
417
|
+
}
|
|
418
|
+
return devices;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
private parseDeviceListLegacy(text: string): IOSDevice[] {
|
|
422
|
+
// Parse idevice_id -l output
|
|
423
|
+
const devices: IOSDevice[] = [];
|
|
424
|
+
const lines = text.split('\n');
|
|
425
|
+
for (const line of lines) {
|
|
426
|
+
const match = line.match(/^([A-F0-9-]+)\s+(.+)$/);
|
|
427
|
+
if (match) {
|
|
428
|
+
devices.push({
|
|
429
|
+
identifier: match[1],
|
|
430
|
+
name: match[2].trim(),
|
|
431
|
+
model: '',
|
|
432
|
+
modelCode: '',
|
|
433
|
+
productType: '',
|
|
434
|
+
osVersion: '',
|
|
435
|
+
osBuildVersion: '',
|
|
436
|
+
architecture: '',
|
|
437
|
+
platform: 'iOS',
|
|
438
|
+
connectionType: 'USB',
|
|
439
|
+
isTrusted: true,
|
|
440
|
+
isAvailable: true,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return devices;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
private parseDeviceDetails(json: unknown): DeviceDetails | null {
|
|
448
|
+
try {
|
|
449
|
+
const d = json as {
|
|
450
|
+
identifier?: string;
|
|
451
|
+
name?: string;
|
|
452
|
+
modelCode?: string;
|
|
453
|
+
productType?: string;
|
|
454
|
+
productVersion?: string;
|
|
455
|
+
buildVersion?: string;
|
|
456
|
+
serialNumber?: string;
|
|
457
|
+
architecture?: string;
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
udid: d.identifier || '',
|
|
462
|
+
name: d.name || '',
|
|
463
|
+
model: d.modelCode || '',
|
|
464
|
+
productType: d.productType || '',
|
|
465
|
+
productVersion: d.productVersion || '',
|
|
466
|
+
buildVersion: d.buildVersion || '',
|
|
467
|
+
serialNumber: d.serialNumber,
|
|
468
|
+
cpuArchitecture: d.architecture || '',
|
|
469
|
+
deviceName: d.name || '',
|
|
470
|
+
};
|
|
471
|
+
} catch {
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
private parseDeviceDetailsLegacy(text: string): DeviceDetails | null {
|
|
477
|
+
// Parse ideviceinfo output
|
|
478
|
+
const details: Partial<DeviceDetails> = {};
|
|
479
|
+
const lines = text.split('\n');
|
|
480
|
+
for (const line of lines) {
|
|
481
|
+
const match = line.match(/^(.+?):\s*(.+)$/);
|
|
482
|
+
if (match) {
|
|
483
|
+
const key = match[1].trim().toLowerCase().replace(/\s+/g, '_');
|
|
484
|
+
const value = match[2].trim();
|
|
485
|
+
if (key === 'unique_device_id') details.udid = value;
|
|
486
|
+
if (key === 'device_name') details.name = value;
|
|
487
|
+
if (key === 'model_name') details.model = value;
|
|
488
|
+
if (key === 'product_type') details.productType = value;
|
|
489
|
+
if (key === 'product_version') details.productVersion = value;
|
|
490
|
+
if (key === 'build_version') details.buildVersion = value;
|
|
491
|
+
if (key === 'serial_number') details.serialNumber = value;
|
|
492
|
+
if (key === 'cpu_architecture') details.cpuArchitecture = value;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return details as DeviceDetails;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
private parseProcessList(json: unknown): ProcessInfo[] {
|
|
499
|
+
const processes: ProcessInfo[] = [];
|
|
500
|
+
try {
|
|
501
|
+
const data = json as { processes?: Array<{
|
|
502
|
+
pid?: number;
|
|
503
|
+
name?: string;
|
|
504
|
+
user?: string;
|
|
505
|
+
bundleIdentifier?: string;
|
|
506
|
+
}> };
|
|
507
|
+
|
|
508
|
+
if (data?.processes) {
|
|
509
|
+
for (const p of data.processes) {
|
|
510
|
+
processes.push({
|
|
511
|
+
pid: p.pid || 0,
|
|
512
|
+
name: p.name || '',
|
|
513
|
+
user: p.user,
|
|
514
|
+
bundleIdentifier: p.bundleIdentifier,
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
} catch {
|
|
519
|
+
// Parse error
|
|
520
|
+
}
|
|
521
|
+
return processes;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
private parseAppList(json: unknown): InstalledApp[] {
|
|
525
|
+
const apps: InstalledApp[] = [];
|
|
526
|
+
try {
|
|
527
|
+
const data = json as { apps?: Array<{
|
|
528
|
+
bundleIdentifier?: string;
|
|
529
|
+
bundleName?: string;
|
|
530
|
+
bundleVersion?: string;
|
|
531
|
+
installPath?: string;
|
|
532
|
+
platform?: string;
|
|
533
|
+
type?: string;
|
|
534
|
+
}> };
|
|
535
|
+
|
|
536
|
+
if (data?.apps) {
|
|
537
|
+
for (const a of data.apps) {
|
|
538
|
+
apps.push({
|
|
539
|
+
bundleIdentifier: a.bundleIdentifier || '',
|
|
540
|
+
bundleName: a.bundleName || '',
|
|
541
|
+
bundleVersion: a.bundleVersion || '',
|
|
542
|
+
installPath: a.installPath || '',
|
|
543
|
+
platform: (a.platform as InstalledApp['platform']) || 'iOS',
|
|
544
|
+
type: (a.type as InstalledApp['type']) || 'User',
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
} catch {
|
|
549
|
+
// Parse error
|
|
550
|
+
}
|
|
551
|
+
return apps;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
private parseSimulatorList(text: string): SimulatorDevice[] {
|
|
555
|
+
const simulators: SimulatorDevice[] = [];
|
|
556
|
+
const lines = text.split('\n');
|
|
557
|
+
for (const line of lines) {
|
|
558
|
+
const match = line.match(/([A-F0-9-]+)\s+\((.+?)\)\s+\((.+?)\)/);
|
|
559
|
+
if (match) {
|
|
560
|
+
simulators.push({
|
|
561
|
+
udid: match[1],
|
|
562
|
+
name: match[2],
|
|
563
|
+
state: line.includes('Booted') ? 'Booted' : 'Shutdown',
|
|
564
|
+
runtime: match[3],
|
|
565
|
+
deviceType: '',
|
|
566
|
+
availability: 'available',
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return simulators;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Re-export classes for direct access
|
|
575
|
+
export { DeviceCtl } from './device-ctl.js';
|
|
576
|
+
export { LibIMobileDevice } from './lib-imobiledevice.js';
|
|
577
|
+
export { SimCtl } from './sim-ctl.js';
|
|
578
|
+
export { checkAvailableTools } from './utils.js';
|