appium-ios-remotexpc 0.37.1 → 0.37.2

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/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## [0.37.2](https://github.com/appium/appium-ios-remotexpc/compare/v0.37.1...v0.37.2) (2026-03-22)
2
+
3
+ ### Miscellaneous Chores
4
+
5
+ * Migrate scripts to ESM/.mjs ([#173](https://github.com/appium/appium-ios-remotexpc/issues/173)) ([8d6053b](https://github.com/appium/appium-ios-remotexpc/commit/8d6053b6bfde353296905daf345b8e15d4307873))
6
+
1
7
  ## [0.37.1](https://github.com/appium/appium-ios-remotexpc/compare/v0.37.0...v0.37.1) (2026-03-21)
2
8
 
3
9
  ## [0.37.0](https://github.com/appium/appium-ios-remotexpc/compare/v0.36.0...v0.37.0) (2026-03-21)
package/README.md CHANGED
@@ -196,10 +196,19 @@ All pull requests must pass these checks before merging. The workflows are defin
196
196
  - `npm run format` - Run prettier
197
197
  - `npm run lint:fix` - Run ESLint with auto-fix
198
198
  - `npm test` - Run tests (requires sudo privileges for tunneling)
199
- - `npm run test:tunnel-creation` - Create tunnels for testing (requires sudo)
199
+
200
+ CLI helpers under `scripts/` are ESM (`.mjs`) and load the library via the package entrypoint. Run `npm run build` before using them so `appium-ios-remotexpc` resolves to `build/`.
201
+
202
+ - `npm run tunnel-creation` / `npm run test:tunnel-creation` — Create USB tunnels and start the tunnel registry HTTP API (requires `sudo`)
203
+ - `npm run test:tunnel-creation:lsof` — Same as above with `--keep-open` (for inspecting open sockets)
204
+ - `npm run pair-appletv` — Pair an Apple TV over WiFi for Remote XPC (requires `sudo`)
205
+ - `npm run start-appletv-tunnel` — Start an Apple TV WiFi tunnel and tunnel registry (requires `sudo`)
206
+
207
+ Pass `--help` after `--` to any of these npm scripts to see CLI flags (for example: `npm run pair-appletv -- --help`).
200
208
 
201
209
  ## Project Structure
202
210
 
211
+ - `/scripts` - Optional CLI helpers (ESM `.mjs`) for tunnels and Apple TV pairing; use via `npm run` entries under [Scripts](#scripts)
203
212
  - `/src` - Source code
204
213
  - `/lib` - Core libraries
205
214
  - `/lockdown` - Device lockdown protocol
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appium-ios-remotexpc",
3
- "version": "0.37.1",
3
+ "version": "0.37.2",
4
4
  "main": "build/src/index.js",
5
5
  "types": "build/src/index.d.ts",
6
6
  "type": "module",
@@ -18,11 +18,11 @@
18
18
  "clean:build": "rimraf ./build",
19
19
  "build:es": "tsc",
20
20
  "build": "run-s clean:* build:*",
21
- "lint": "eslint src --ext .ts --quiet",
21
+ "lint": "eslint src scripts --quiet",
22
22
  "prepare": "husky && npm run build",
23
23
  "format": "prettier --write \"{src,test}/**/*.{ts,tsx}\"",
24
24
  "format:check": "prettier --check \"{src,test}/**/*.{ts,tsx}\"",
25
- "lint:fix": "eslint src --ext .ts --fix",
25
+ "lint:fix": "eslint src scripts --fix",
26
26
  "test": "mocha test/integration/**/*.ts",
27
27
  "test:all": "mocha -r tsx/cjs test/run-integration-tests.ts",
28
28
  "test:tunnel": "mocha test/integration/tunnel-test.ts --exit --timeout 1m",
@@ -33,9 +33,9 @@
33
33
  "test:mobile-config": "mocha test/integration/mobile-config-test.ts --exit --timeout 1m",
34
34
  "test:springboard": "mocha test/integration/springboard-service-test.ts --exit --timeout 1m",
35
35
  "test:unit": "NODE_ENV=test mocha 'test/unit/**/*.ts' --exit --timeout 2m",
36
- "tunnel-creation": "sudo tsx scripts/test-tunnel-creation.ts",
37
- "pair-appletv": "sudo tsx scripts/pair-appletv.ts",
38
- "start-appletv-tunnel": "sudo tsx scripts/start-appletv-tunnel.ts",
36
+ "tunnel-creation": "sudo node scripts/tunnel-creation.mjs",
37
+ "pair-appletv": "sudo node scripts/pair-appletv.mjs",
38
+ "start-appletv-tunnel": "sudo node scripts/start-appletv-tunnel.mjs",
39
39
  "test:webinspector": "mocha test/integration/webinspector-test.ts --exit --timeout 1m",
40
40
  "test:misagent": "mocha test/integration/misagent-service-test.ts --exit --timeout 1m",
41
41
  "test:afc": "mocha test/integration/afc-test.ts --exit --timeout 1m",
@@ -55,8 +55,8 @@
55
55
  "test:dvt:network-monitor": "mocha test/integration/dvt_instruments/network-monitor-test.ts --exit --timeout 1m",
56
56
  "test:dvt:process-control": "mocha test/integration/process-control-test.ts --exit --timeout 1m",
57
57
  "test:testmanagerd": "mocha test/integration/testmanagerd-test.ts --exit --timeout 2m",
58
- "test:tunnel-creation": "sudo tsx scripts/test-tunnel-creation.ts",
59
- "test:tunnel-creation:lsof": "sudo tsx scripts/test-tunnel-creation.ts --keep-open"
58
+ "test:tunnel-creation": "sudo node scripts/tunnel-creation.mjs",
59
+ "test:tunnel-creation:lsof": "sudo node scripts/tunnel-creation.mjs --keep-open"
60
60
  },
61
61
  "keywords": [],
62
62
  "author": "Appium Contributors",
@@ -102,6 +102,7 @@
102
102
  "@xmldom/xmldom": "^0.9.8",
103
103
  "appium-ios-tuntap": "^0.x",
104
104
  "axios": "^1.12.0",
105
+ "commander": "^14.0.1",
105
106
  "dnssd": "^0.x",
106
107
  "minimatch": "^10.1.1",
107
108
  "node-devicectl": "^1.2.0",
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Pair Apple TV / tvOS devices over WiFi for Remote XPC tunnels.
4
+ *
5
+ * Usage:
6
+ * npm run pair-appletv -- [options]
7
+ */
8
+
9
+ import { logger } from '@appium/support';
10
+ import { Command } from 'commander';
11
+ import { AppleTVPairingService, UserInputService } from 'appium-ios-remotexpc';
12
+
13
+ const log = logger.getLogger('AppleTVPairing');
14
+
15
+ async function main() {
16
+ const program = new Command();
17
+ program
18
+ .name('pair-appletv')
19
+ .description('Pair Apple TV / tvOS devices over WiFi for Remote XPC tunnels')
20
+ .option(
21
+ '-d, --device <selector>',
22
+ 'Device selector: name, identifier (e.g. AA:BB:CC:DD:EE:FF), or index (0, 1, …)',
23
+ );
24
+
25
+ program.parse(process.argv);
26
+ const options = program.opts();
27
+
28
+ const userInput = new UserInputService();
29
+ const pairingService = new AppleTVPairingService(userInput);
30
+ const result = await pairingService.discoverAndPair(options.device);
31
+
32
+ if (result.success) {
33
+ log.info(`Pairing successful! Record saved to: ${result.pairingFile}`);
34
+ } else {
35
+ throw result.error ?? new Error('Pairing failed');
36
+ }
37
+ }
38
+
39
+ await main();
@@ -1,43 +1,70 @@
1
- #!/usr/bin/env tsx
2
- import * as tls from 'node:tls';
3
-
4
- import { PacketStreamServer, TunnelManager } from '../src/index.js';
5
- import type { TunnelRegistry } from '../src/index.js';
6
- import { AppleTVTunnelService } from '../src/lib/apple-tv/tunnel/index.js';
7
- import type { AppleTVDevice } from '../src/lib/bonjour/bonjour-discovery.js';
8
- import { getLogger } from '../src/lib/logger.js';
9
- import type { TunnelConnection } from '../src/lib/tunnel/index.js';
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Start an Apple TV Remote XPC tunnel and expose the tunnel registry API.
4
+ */
5
+
6
+ import { logger } from '@appium/support';
7
+ import { Command } from 'commander';
10
8
  import {
11
- DEFAULT_TUNNEL_REGISTRY_PORT,
9
+ AppleTVTunnelService,
10
+ PacketStreamServer,
11
+ TunnelManager,
12
12
  startTunnelRegistryServer,
13
- } from '../src/lib/tunnel/tunnel-registry-server.js';
13
+ } from 'appium-ios-remotexpc';
14
+ import { DEFAULT_TUNNEL_REGISTRY_PORT } from '../build/src/lib/tunnel/tunnel-registry-server.js';
14
15
 
15
- const log = getLogger('WiFiTunnel');
16
+ const log = logger.getLogger('WiFiTunnel');
16
17
  const PACKET_STREAM_PORT = 50100;
17
18
 
18
- async function main(): Promise<void> {
19
- const args = process.argv.slice(2);
20
- const specificDeviceIdentifier = args.find((arg) => !arg.startsWith('-'));
19
+ function parsePort(value) {
20
+ const port = Number.parseInt(value, 10);
21
+ if (!Number.isFinite(port) || port <= 0 || port > 65535) {
22
+ throw new Error(
23
+ `Invalid port: ${value}. Expected an integer between 1 and 65535.`,
24
+ );
25
+ }
26
+ return port;
27
+ }
28
+
29
+ async function main() {
30
+ const program = new Command();
31
+ program
32
+ .name('start-appletv-tunnel')
33
+ .description('Start an Apple TV WiFi tunnel and tunnel registry HTTP API')
34
+ .argument(
35
+ '[deviceIdentifier]',
36
+ 'Optional Apple TV device identifier to target',
37
+ )
38
+ .option(
39
+ '--tunnel-registry-port <port>',
40
+ `Port for tunnel registry API (default: ${DEFAULT_TUNNEL_REGISTRY_PORT})`,
41
+ parsePort,
42
+ );
43
+
44
+ program.parse(process.argv);
45
+ const options = program.opts();
46
+ const deviceIdentifier = program.args[0];
47
+ const registryPort =
48
+ options.tunnelRegistryPort ?? DEFAULT_TUNNEL_REGISTRY_PORT;
21
49
 
22
- if (specificDeviceIdentifier) {
50
+ if (deviceIdentifier) {
23
51
  log.info(
24
- `Starting Apple TV tunnel for specific device identifier: ${specificDeviceIdentifier}`,
52
+ `Starting Apple TV tunnel for specific device identifier: ${deviceIdentifier}`,
25
53
  );
26
54
  } else {
27
55
  log.info('Starting Apple TV tunnel (will try all discovered devices)');
28
56
  }
29
57
 
30
58
  const tunnelService = new AppleTVTunnelService();
31
- let tunnel: TunnelConnection | null = null;
32
- let tlsSocket: tls.TLSSocket | null = null;
33
- let deviceInfo: AppleTVDevice | null = null;
34
- let packetStreamServer: PacketStreamServer | null = null;
59
+ let tunnel = null;
60
+ let tlsSocket = null;
61
+ let deviceInfo = null;
62
+ let packetStreamServer = null;
35
63
 
36
- const cleanup = async (signal: string): Promise<void> => {
64
+ const cleanup = async (signal) => {
37
65
  log.warn(`\nCleaning up (${signal})...`);
38
66
 
39
67
  try {
40
- // Close packet stream server first
41
68
  if (packetStreamServer) {
42
69
  log.info('Closing packet stream server...');
43
70
  await packetStreamServer.stop();
@@ -89,7 +116,7 @@ async function main(): Promise<void> {
89
116
  log.info('Starting Apple TV tunnel...');
90
117
  const result = await tunnelService.startTunnel(
91
118
  undefined,
92
- specificDeviceIdentifier,
119
+ deviceIdentifier,
93
120
  );
94
121
  tlsSocket = result.socket;
95
122
  deviceInfo = result.device;
@@ -101,13 +128,11 @@ async function main(): Promise<void> {
101
128
  log.info('Creating tunnel with TunnelManager...');
102
129
  tunnel = await TunnelManager.getTunnel(tlsSocket);
103
130
 
104
- // Start packet stream server (same as iPhone tunnel)
105
131
  let packetStreamPort = 0;
106
132
  try {
107
133
  packetStreamServer = new PacketStreamServer(PACKET_STREAM_PORT);
108
134
  await packetStreamServer.start();
109
135
 
110
- // Attach packet consumer to tunnel to receive packet data
111
136
  const consumer = packetStreamServer.getPacketConsumer();
112
137
  if (consumer && tunnel.addPacketConsumer) {
113
138
  tunnel.addPacketConsumer(consumer);
@@ -123,7 +148,7 @@ async function main(): Promise<void> {
123
148
  const now = Date.now();
124
149
  const nowISOString = new Date().toISOString();
125
150
 
126
- const registry: TunnelRegistry = {
151
+ const registry = {
127
152
  tunnels: {
128
153
  [deviceInfo.identifier]: {
129
154
  udid: deviceInfo.identifier,
@@ -144,7 +169,7 @@ async function main(): Promise<void> {
144
169
  },
145
170
  };
146
171
 
147
- await startTunnelRegistryServer(registry);
172
+ await startTunnelRegistryServer(registry, registryPort);
148
173
 
149
174
  log.info('=== TUNNEL ESTABLISHED ===');
150
175
  log.info(`Tunnel Address: ${tunnel.Address}`);
@@ -154,7 +179,7 @@ async function main(): Promise<void> {
154
179
 
155
180
  log.info('\n📁 Tunnel registry API:');
156
181
  log.info(
157
- ` http://localhost:${DEFAULT_TUNNEL_REGISTRY_PORT}/remotexpc/tunnels`,
182
+ ` http://localhost:${registryPort}/remotexpc/tunnels`,
158
183
  );
159
184
  log.info(' - GET /remotexpc/tunnels - List all tunnels');
160
185
  log.info(
@@ -169,9 +194,7 @@ async function main(): Promise<void> {
169
194
  } catch (error) {
170
195
  log.error('Tunnel failed:', error);
171
196
  throw error;
172
- } finally {
173
- await cleanup('Shutdown');
174
197
  }
175
198
  }
176
199
 
177
- main();
200
+ await main();
@@ -1,10 +1,10 @@
1
- #!/usr/bin/env tsx
1
+ #!/usr/bin/env node
2
2
  /**
3
- * Test script for creating lockdown service, starting CoreDeviceProxy, and creating tunnel
4
- * This script demonstrates the tunnel creation workflow for all connected devices
3
+ * Create lockdown + CoreDeviceProxy tunnels for connected USB devices and expose the tunnel registry API.
5
4
  */
5
+
6
6
  import { logger } from '@appium/support';
7
- import type { ConnectionOptions } from 'tls';
7
+ import { Command } from 'commander';
8
8
 
9
9
  import {
10
10
  PacketStreamServer,
@@ -12,26 +12,27 @@ import {
12
12
  createLockdownServiceByUDID,
13
13
  createUsbmux,
14
14
  startCoreDeviceProxy,
15
- } from '../src/index.js';
16
- import type { SocketInfo, TunnelRegistry } from '../src/index.js';
17
- import {
18
- DEFAULT_TUNNEL_REGISTRY_PORT,
19
15
  startTunnelRegistryServer,
20
- } from '../src/lib/tunnel/tunnel-registry-server.js';
21
- import type { Device } from '../src/lib/usbmux/index.js';
16
+ } from 'appium-ios-remotexpc';
17
+ import { DEFAULT_TUNNEL_REGISTRY_PORT } from '../build/src/lib/tunnel/tunnel-registry-server.js';
22
18
 
23
19
  const log = logger.getLogger('TunnelCreation');
24
- /**
25
- * Update tunnel registry with new tunnel information
26
- */
27
- async function updateTunnelRegistry(
28
- results: TunnelResult[],
29
- ): Promise<TunnelRegistry> {
20
+
21
+ function parsePort(value) {
22
+ const port = Number.parseInt(value, 10);
23
+ if (!Number.isFinite(port) || port <= 0 || port > 65535) {
24
+ throw new Error(
25
+ `Invalid port: ${value}. Expected an integer between 1 and 65535.`,
26
+ );
27
+ }
28
+ return port;
29
+ }
30
+
31
+ async function updateTunnelRegistry(results) {
30
32
  const now = Date.now();
31
33
  const nowISOString = new Date().toISOString();
32
34
 
33
- // Initialize registry if it doesn't exist
34
- const registry: TunnelRegistry = {
35
+ const registry = {
35
36
  tunnels: {},
36
37
  metadata: {
37
38
  lastUpdated: nowISOString,
@@ -40,7 +41,6 @@ async function updateTunnelRegistry(
40
41
  },
41
42
  };
42
43
 
43
- // Update tunnels
44
44
  for (const result of results) {
45
45
  if (result.success) {
46
46
  const udid = result.device.Properties.SerialNumber;
@@ -58,39 +58,21 @@ async function updateTunnelRegistry(
58
58
  }
59
59
  }
60
60
 
61
- // Update metadata
62
61
  registry.metadata = {
63
62
  lastUpdated: nowISOString,
64
63
  totalTunnels: Object.keys(registry.tunnels).length,
65
- activeTunnels: Object.keys(registry.tunnels).length, // Assuming all are active for now
64
+ activeTunnels: Object.keys(registry.tunnels).length,
66
65
  };
67
66
 
68
67
  return registry;
69
68
  }
70
69
 
71
- const activeServers: Array<{ server: any; port: number }> = [];
72
- const packetStreamServers: Map<string, PacketStreamServer> = new Map();
73
-
74
- const deviceInfoMap: Map<
75
- string,
76
- {
77
- udid: string;
78
- address: string;
79
- rsdPort?: number;
80
- connectionType: string;
81
- productId: number;
82
- }
83
- > = new Map();
70
+ const packetStreamServers = new Map();
84
71
 
85
- let PACKET_STREAM_BASE_PORT = 50000;
86
- /**
87
- * Setup cleanup handlers for graceful shutdown
88
- */
89
- function setupCleanupHandlers(): void {
90
- const cleanup = async (signal: string) => {
72
+ function setupCleanupHandlers() {
73
+ const cleanup = async (signal) => {
91
74
  log.warn(`\nCleaning up (${signal})...`);
92
75
 
93
- // Close all packet stream servers
94
76
  if (packetStreamServers.size > 0) {
95
77
  log.info(
96
78
  `Closing ${packetStreamServers.size} packet stream server(s)...`,
@@ -111,7 +93,6 @@ function setupCleanupHandlers(): void {
111
93
  log.info('Cleanup completed.');
112
94
  };
113
95
 
114
- // Handle various termination signals
115
96
  process.on('SIGINT', async () => {
116
97
  await cleanup('SIGINT (Ctrl+C)');
117
98
  process.exit(0);
@@ -125,7 +106,6 @@ function setupCleanupHandlers(): void {
125
106
  process.exit(0);
126
107
  });
127
108
 
128
- // Handle uncaught exceptions and unhandled rejections
129
109
  process.on('uncaughtException', async (error) => {
130
110
  log.error('Uncaught Exception:', error);
131
111
  await cleanup('Uncaught Exception');
@@ -137,24 +117,7 @@ function setupCleanupHandlers(): void {
137
117
  });
138
118
  }
139
119
 
140
- /**
141
- * Interface for tunnel result
142
- */
143
- interface TunnelResult {
144
- device: Device;
145
- tunnel: {
146
- Address: string;
147
- RsdPort?: number;
148
- };
149
- packetStreamPort?: number;
150
- success: boolean;
151
- error?: string;
152
- }
153
-
154
- async function createTunnelForDevice(
155
- device: Device,
156
- tlsOptions: Partial<ConnectionOptions>,
157
- ): Promise<TunnelResult & { socket?: any; socketInfo?: SocketInfo }> {
120
+ async function createTunnelForDevice(device, tlsOptions, packetStreamBaseRef) {
158
121
  const udid = device.Properties.SerialNumber;
159
122
 
160
123
  try {
@@ -185,9 +148,9 @@ async function createTunnelForDevice(
185
148
  `Tunnel created for address: ${tunnel.Address} with RsdPort: ${tunnel.RsdPort}`,
186
149
  );
187
150
 
188
- let packetStreamPort: number | undefined;
151
+ let packetStreamPort;
189
152
  try {
190
- packetStreamPort = PACKET_STREAM_BASE_PORT++;
153
+ packetStreamPort = packetStreamBaseRef.value++;
191
154
  const packetStreamServer = new PacketStreamServer(packetStreamPort);
192
155
  await packetStreamServer.start();
193
156
 
@@ -215,16 +178,6 @@ async function createTunnelForDevice(
215
178
  socket.setNoDelay(true);
216
179
  }
217
180
 
218
- const deviceInfo = {
219
- udid: device.Properties.SerialNumber,
220
- address: tunnel.Address,
221
- rsdPort: tunnel.RsdPort,
222
- connectionType: device.Properties.ConnectionType,
223
- productId: device.Properties.ProductID,
224
- };
225
-
226
- deviceInfoMap.set(device.Properties.SerialNumber, deviceInfo);
227
-
228
181
  return {
229
182
  device,
230
183
  tunnel: {
@@ -261,14 +214,32 @@ async function createTunnelForDevice(
261
214
  }
262
215
  }
263
216
 
264
- /**
265
- */
266
- async function main(): Promise<void> {
217
+ async function main() {
267
218
  setupCleanupHandlers();
268
219
 
269
- const args = process.argv.slice(2);
270
- const keepOpenFlag = args.includes('--keep-open') || args.includes('-k');
271
- const specificUdid = args.find((arg) => !arg.startsWith('-'));
220
+ const program = new Command();
221
+ program
222
+ .name('tunnel-creation')
223
+ .description(
224
+ 'Create tunnels for connected USB devices (lockdown + CoreDeviceProxy)',
225
+ )
226
+ .argument('[udid]', 'Optional device UDID (omit for all devices)')
227
+ .option('--udid <udid>', 'UDID of the device to create tunnel for')
228
+ .option('-k, --keep-open', 'Keep connections open for lsof inspection')
229
+ .option(
230
+ '--packet-stream-base-port <port>',
231
+ 'Base port for packet stream servers (1-65535)',
232
+ parsePort,
233
+ )
234
+ .option(
235
+ '--tunnel-registry-port <port>',
236
+ `Port for tunnel registry API (default: ${DEFAULT_TUNNEL_REGISTRY_PORT})`,
237
+ parsePort,
238
+ );
239
+
240
+ program.parse(process.argv);
241
+ const options = program.opts();
242
+ const specificUdid = options.udid ?? program.args[0] ?? undefined;
272
243
 
273
244
  if (specificUdid) {
274
245
  log.info(
@@ -278,16 +249,22 @@ async function main(): Promise<void> {
278
249
  log.info('Starting tunnel creation test for all connected devices');
279
250
  }
280
251
 
281
- if (keepOpenFlag) {
252
+ if (options.keepOpen) {
282
253
  log.info('Running in "keep connections open" mode for lsof inspection');
283
254
  }
284
255
 
285
- try {
286
- const tlsOptions: Partial<ConnectionOptions> = {
287
- rejectUnauthorized: false,
288
- minVersion: 'TLSv1.2',
289
- };
256
+ const tlsOptions = {
257
+ rejectUnauthorized: false,
258
+ minVersion: 'TLSv1.2',
259
+ };
260
+
261
+ const packetStreamBaseRef = {
262
+ value: options.packetStreamBasePort ?? 50000,
263
+ };
264
+ const registryPort =
265
+ options.tunnelRegistryPort ?? DEFAULT_TUNNEL_REGISTRY_PORT;
290
266
 
267
+ try {
291
268
  log.info('Connecting to usbmuxd...');
292
269
  const usbmux = await createUsbmux();
293
270
 
@@ -331,10 +308,14 @@ async function main(): Promise<void> {
331
308
 
332
309
  log.info(`\nProcessing ${devicesToProcess.length} device(s)...`);
333
310
 
334
- const results: TunnelResult[] = [];
311
+ const results = [];
335
312
 
336
313
  for (const device of devicesToProcess) {
337
- const result = await createTunnelForDevice(device, tlsOptions);
314
+ const result = await createTunnelForDevice(
315
+ device,
316
+ tlsOptions,
317
+ packetStreamBaseRef,
318
+ );
338
319
  results.push(result);
339
320
 
340
321
  if (devicesToProcess.length > 1) {
@@ -353,11 +334,11 @@ async function main(): Promise<void> {
353
334
  if (successful.length > 0) {
354
335
  log.info('\n✅ Successful tunnels:');
355
336
  const registry = await updateTunnelRegistry(results);
356
- await startTunnelRegistryServer(registry);
337
+ await startTunnelRegistryServer(registry, registryPort);
357
338
 
358
339
  log.info('\n📁 Tunnel registry API:');
359
340
  log.info(' The tunnel registry is now available through the API at:');
360
- log.info(' http://localhost:42314/remotexpc/tunnels');
341
+ log.info(` http://localhost:${registryPort}/remotexpc/tunnels`);
361
342
  log.info('\n Available endpoints:');
362
343
  log.info(' - GET /remotexpc/tunnels - List all tunnels');
363
344
  log.info(' - GET /remotexpc/tunnels/:udid - Get tunnel by UDID');
@@ -365,15 +346,15 @@ async function main(): Promise<void> {
365
346
 
366
347
  log.info('\n💡 Example usage:');
367
348
  log.info(
368
- ` curl http://localhost:${DEFAULT_TUNNEL_REGISTRY_PORT}/remotexpc/tunnels`,
349
+ ` curl http://localhost:${registryPort}/remotexpc/tunnels`,
369
350
  );
370
351
  log.info(
371
- ` curl http://localhost:${DEFAULT_TUNNEL_REGISTRY_PORT}/remotexpc/tunnels/metadata`,
352
+ ` curl http://localhost:${registryPort}/remotexpc/tunnels/metadata`,
372
353
  );
373
354
  if (successful.length > 0) {
374
355
  const firstUdid = successful[0].device.Properties.SerialNumber;
375
356
  log.info(
376
- ` curl http://localhost:${DEFAULT_TUNNEL_REGISTRY_PORT}/remotexpc/tunnels/${firstUdid}`,
357
+ ` curl http://localhost:${registryPort}/remotexpc/tunnels/${firstUdid}`,
377
358
  );
378
359
  }
379
360
  }
@@ -383,5 +364,4 @@ async function main(): Promise<void> {
383
364
  }
384
365
  }
385
366
 
386
- // Run the main function
387
- main();
367
+ await main();
@@ -1,78 +0,0 @@
1
- #!/usr/bin/env tsx
2
- import {
3
- AppleTVPairingService,
4
- UserInputService,
5
- } from '../src/lib/apple-tv/index.js';
6
- import { getLogger } from '../src/lib/logger.js';
7
-
8
- interface CLIArgs {
9
- device?: string;
10
- help?: boolean;
11
- }
12
-
13
- function parseArgs(): CLIArgs {
14
- const args: CLIArgs = {};
15
- const cliArgs = process.argv.slice(2);
16
-
17
- for (let i = 0; i < cliArgs.length; i++) {
18
- const arg = cliArgs[i];
19
- if (arg === '--device' || arg === '-d') {
20
- args.device = cliArgs[++i];
21
- } else if (arg === '--help' || arg === '-h') {
22
- args.help = true;
23
- }
24
- }
25
-
26
- return args;
27
- }
28
-
29
- function printHelp(): void {
30
- // eslint-disable-next-line no-console
31
- console.log(`
32
- Apple TV Pairing Script
33
-
34
- Usage: pair-appletv [options]
35
-
36
- Options:
37
- -d, --device <selector> Specify device to pair with. Can be:
38
- - Device name (e.g., "Living Room")
39
- - Device identifier (e.g., "AA:BB:CC:DD:EE:FF")
40
- - Device index (e.g., "0", "1", "2")
41
- If not specified and multiple devices are found,
42
- you will be prompted to choose one.
43
- -h, --help Show this help message
44
-
45
- Examples:
46
- pair-appletv # Discover and select device interactively
47
- pair-appletv --device "Living Room" # Pair with device named "Living Room"
48
- pair-appletv --device 0 # Pair with first discovered device
49
- pair-appletv -d AA:BB:CC:DD:EE:FF # Pair with device by identifier
50
- `);
51
- }
52
-
53
- // CLI interface
54
- async function main(): Promise<void> {
55
- const log = getLogger('AppleTVPairing');
56
- const args = parseArgs();
57
-
58
- if (args.help) {
59
- printHelp();
60
- return;
61
- }
62
-
63
- const userInput = new UserInputService();
64
- const pairingService = new AppleTVPairingService(userInput);
65
- const result = await pairingService.discoverAndPair(args.device);
66
-
67
- if (result.success) {
68
- log.info(`Pairing successful! Record saved to: ${result.pairingFile}`);
69
- } else {
70
- const error = result.error ?? new Error('Pairing failed');
71
- log.error(`Pairing failed: ${error.message}`);
72
- throw error;
73
- }
74
- }
75
-
76
- main().catch(() => {
77
- process.exit(1);
78
- });