@houwert/conductor 0.2.0 → 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 (55) hide show
  1. package/.claude-plugin/plugin.json +4 -1
  2. package/README.md +121 -21
  3. package/dist/commands/assert-not-visible.js +5 -0
  4. package/dist/commands/assert-visible.js +15 -0
  5. package/dist/commands/back.js +6 -1
  6. package/dist/commands/cheat-sheet.js +4 -0
  7. package/dist/commands/clear-state.js +27 -0
  8. package/dist/commands/copy-app.js +55 -0
  9. package/dist/commands/daemon.js +4 -0
  10. package/dist/commands/device-pool.js +4 -0
  11. package/dist/commands/erase-text.js +2 -0
  12. package/dist/commands/focused.js +233 -0
  13. package/dist/commands/foreground-app.js +2 -0
  14. package/dist/commands/hide-keyboard.js +2 -0
  15. package/dist/commands/inspect.js +23 -1
  16. package/dist/commands/install.js +69 -4
  17. package/dist/commands/launch-app.js +6 -0
  18. package/dist/commands/list-apps.js +3 -1
  19. package/dist/commands/list-devices.js +70 -7
  20. package/dist/commands/open-link.js +2 -0
  21. package/dist/commands/press-key.js +73 -7
  22. package/dist/commands/run-flow-inline.js +3 -0
  23. package/dist/commands/run-flow.js +4 -0
  24. package/dist/commands/run-parallel.js +2 -0
  25. package/dist/commands/screenshot.js +2 -0
  26. package/dist/commands/scroll-until-visible.js +6 -0
  27. package/dist/commands/scroll.js +6 -0
  28. package/dist/commands/session.js +2 -0
  29. package/dist/commands/set-location.js +2 -0
  30. package/dist/commands/set-orientation.js +2 -0
  31. package/dist/commands/start-device.js +311 -9
  32. package/dist/commands/stop-app.js +2 -0
  33. package/dist/commands/swipe.js +10 -0
  34. package/dist/commands/tap.js +20 -0
  35. package/dist/commands/type.js +2 -0
  36. package/dist/commands/uninstall-app.js +31 -0
  37. package/dist/daemon/server.js +64 -22
  38. package/dist/device-picker.js +65 -0
  39. package/dist/drivers/android.js +18 -13
  40. package/dist/drivers/bootstrap.js +179 -6
  41. package/dist/drivers/element-resolver.js +10 -0
  42. package/dist/drivers/flow-runner.js +21 -5
  43. package/dist/drivers/ios.js +7 -1
  44. package/dist/index.js +134 -97
  45. package/dist/postinstall.js +2 -2
  46. package/dist/runner.js +40 -6
  47. package/drivers/ios/conductor-driver-ios.zip +0 -0
  48. package/drivers/ios/conductor-driver-iosUITests-Runner.zip +0 -0
  49. package/drivers/tvos/conductor-driver-tvos-config.xctestrun +121 -0
  50. package/drivers/tvos/conductor-driver-tvos.zip +0 -0
  51. package/drivers/tvos/conductor-driver-tvosUITests-Runner.zip +0 -0
  52. package/package.json +1 -1
  53. package/skills/conductor/SKILL.md +66 -6
  54. package/skills/conductor/references/flow-syntax.md +3 -0
  55. package/skills/skills.yaml +8 -0
package/dist/index.js CHANGED
@@ -9,6 +9,8 @@ const verbose_js_1 = require("./verbose.js");
9
9
  const list_devices_js_1 = require("./commands/list-devices.js");
10
10
  const launch_app_js_1 = require("./commands/launch-app.js");
11
11
  const stop_app_js_1 = require("./commands/stop-app.js");
12
+ const clear_state_js_1 = require("./commands/clear-state.js");
13
+ const uninstall_app_js_1 = require("./commands/uninstall-app.js");
12
14
  const tap_js_1 = require("./commands/tap.js");
13
15
  const type_js_1 = require("./commands/type.js");
14
16
  const back_js_1 = require("./commands/back.js");
@@ -17,6 +19,7 @@ const swipe_js_1 = require("./commands/swipe.js");
17
19
  const assert_visible_js_1 = require("./commands/assert-visible.js");
18
20
  const screenshot_js_1 = require("./commands/screenshot.js");
19
21
  const inspect_js_1 = require("./commands/inspect.js");
22
+ const focused_js_1 = require("./commands/focused.js");
20
23
  const run_flow_js_1 = require("./commands/run-flow.js");
21
24
  const run_flow_inline_js_1 = require("./commands/run-flow-inline.js");
22
25
  const press_key_js_1 = require("./commands/press-key.js");
@@ -28,6 +31,7 @@ const device_pool_js_1 = require("./commands/device-pool.js");
28
31
  const run_parallel_js_1 = require("./commands/run-parallel.js");
29
32
  const foreground_app_js_1 = require("./commands/foreground-app.js");
30
33
  const list_apps_js_1 = require("./commands/list-apps.js");
34
+ const copy_app_js_1 = require("./commands/copy-app.js");
31
35
  const erase_text_js_1 = require("./commands/erase-text.js");
32
36
  const assert_not_visible_js_1 = require("./commands/assert-not-visible.js");
33
37
  const open_link_js_1 = require("./commands/open-link.js");
@@ -36,100 +40,58 @@ const scroll_until_visible_js_1 = require("./commands/scroll-until-visible.js");
36
40
  const set_location_js_1 = require("./commands/set-location.js");
37
41
  const set_orientation_js_1 = require("./commands/set-orientation.js");
38
42
  const start_device_js_1 = require("./commands/start-device.js");
39
- const runner_js_1 = require("./runner.js");
43
+ const device_picker_js_1 = require("./device-picker.js");
40
44
  const update_check_js_1 = require("./update-check.js");
41
- const HELP = `
42
- Usage: conductor <command> [args] [options]
43
-
44
- Commands:
45
- start-device --platform <ios|android> Boot a simulator or emulator
46
- --os-version <n> iOS version (e.g. 18) or Android API level (e.g. 33)
47
- --avd <name> Android AVD name (default: first available)
48
- list-devices List connected devices/simulators
49
- foreground-app Print bundle ID / package of the foreground app
50
- list-apps List installed app IDs / package names
51
- launch-app <appId> Launch app (saves to session)
52
- --clear-state Clear app data/state before launching
53
- --clear-keychain Clear keychain before launching
54
- --no-stop-app Do not stop the app before launching (resume instead of restart)
55
- --argument key=value Set launch argument (repeatable)
56
- stop-app [<appId>] Stop app
57
- tap <element> Tap element by text or id
58
- --id <id> Match by accessibility id instead of text
59
- --text <text> Match by text only (not id)
60
- --index <n> Pick the nth match (0-based)
61
- --long-press Hold instead of tap
62
- --double-tap Double-tap the element
63
- --optional Do not fail if element is not found
64
- --focused Match only focused elements
65
- --enabled / --no-enabled Match by enabled state
66
- --checked / --no-checked Match by checked state
67
- --selected / --no-selected Match by selected state
68
- --below <text> Match element below the given reference
69
- --above <text> Match element above the given reference
70
- --left-of <text> Match element left of the given reference
71
- --right-of <text> Match element right of the given reference
72
- --verbose Log all candidates and chosen element
73
- type <text> Type text into focused field
74
- erase-text [n] Erase n characters (default: 50)
75
- back Press back button
76
- press-key <key> Press a key (Enter, Backspace, Home, ...)
77
- hide-keyboard Dismiss the on-screen keyboard
78
- scroll [--direction down|up|left|right]
79
- swipe --direction <UP|DOWN|LEFT|RIGHT>
80
- --start <x,y> Start coordinate (0–1 normalised or absolute px)
81
- --end <x,y> End coordinate (0–1 normalised or absolute px)
82
- --duration <ms> Swipe duration in milliseconds (default: 500)
83
- scroll-until-visible <element> Scroll until element is visible
84
- --id <id> Match by accessibility id instead of text
85
- --text <text> Match by text only (not id)
86
- --direction <down|up|left|right> Scroll direction (default: down)
87
- --timeout <ms> Max time in milliseconds (default: 30000)
88
- assert-visible <element> Assert element is visible
89
- --id <id> Match by accessibility id instead of text
90
- --text <text> Match by text only (not id)
91
- --index <n> Pick the nth match (0-based)
92
- --timeout <ms> Max wait time in milliseconds
93
- --optional Do not fail if element is not found
94
- --focused Match only focused elements
95
- --enabled / --no-enabled Match by enabled state
96
- --checked / --no-checked Match by checked state
97
- --selected / --no-selected Match by selected state
98
- --below <text> Match element below the given reference
99
- --above <text> Match element above the given reference
100
- --left-of <text> Match element left of the given reference
101
- --right-of <text> Match element right of the given reference
102
- assert-not-visible <element> Assert element is absent from screen
103
- --id <id> Match by accessibility id instead of text
104
- --text <text> Match by text only (not id)
105
- --timeout <ms> Max check time in milliseconds (default: 1000)
106
- open-link <url> Open a URL / deep link
107
- set-location --lat <n> --lng <n> Set GPS coordinates
108
- set-orientation <portrait|landscape> Set device orientation
109
- screenshot [--output <path>] Take screenshot
110
- inspect Print UI hierarchy
111
- run-flow <file> [--device <id>] Run a Maestro YAML flow file
112
- --env KEY=VALUE Inject env var (repeatable; overrides flow env block)
113
- --benchmark Print elapsed time for each command and total flow time
114
- run-flow-inline <yaml> Run inline YAML commands
115
- --benchmark Print elapsed time for each command and total flow time
116
- session [--clear] [--list] Show, clear, or list device sessions
117
- cheat-sheet Print command reference
118
- install Install/reinstall Claude Code plugin
119
- daemon-start Start background daemon (manages driver process)
120
- daemon-stop [--all] Stop background daemon (--all stops every session's daemon)
121
- daemon-status Show daemon status
122
- device-pool --list List all devices and pool status
123
- device-pool --acquire Claim a free device (prints device ID)
124
- device-pool --release <id> Release a device back to the pool
125
- run-parallel --flows-dir <path> Run flows in parallel across all devices
126
-
127
- Options:
45
+ const COMMAND_HELP = {
46
+ 'start-device': start_device_js_1.HELP,
47
+ 'list-devices': list_devices_js_1.HELP,
48
+ 'foreground-app': foreground_app_js_1.HELP,
49
+ 'list-apps': list_apps_js_1.HELP,
50
+ 'copy-app': copy_app_js_1.HELP,
51
+ 'launch-app': launch_app_js_1.HELP,
52
+ 'stop-app': stop_app_js_1.HELP,
53
+ 'clear-state': clear_state_js_1.HELP,
54
+ 'uninstall-app': uninstall_app_js_1.HELP,
55
+ tap: tap_js_1.HELP,
56
+ type: type_js_1.HELP,
57
+ 'erase-text': erase_text_js_1.HELP,
58
+ back: back_js_1.HELP,
59
+ 'press-key': press_key_js_1.HELP,
60
+ 'hide-keyboard': hide_keyboard_js_1.HELP,
61
+ scroll: scroll_js_1.HELP,
62
+ swipe: swipe_js_1.HELP,
63
+ 'scroll-until-visible': scroll_until_visible_js_1.HELP,
64
+ 'assert-visible': assert_visible_js_1.HELP,
65
+ 'assert-not-visible': assert_not_visible_js_1.HELP,
66
+ 'open-link': open_link_js_1.HELP,
67
+ 'set-location': set_location_js_1.HELP,
68
+ 'set-orientation': set_orientation_js_1.HELP,
69
+ screenshot: screenshot_js_1.HELP,
70
+ inspect: inspect_js_1.HELP,
71
+ focused: focused_js_1.HELP,
72
+ 'run-flow': run_flow_js_1.HELP,
73
+ 'run-flow-inline': run_flow_inline_js_1.HELP,
74
+ session: session_js_1.HELP,
75
+ 'cheat-sheet': cheat_sheet_js_1.HELP,
76
+ install: install_js_1.HELP,
77
+ 'daemon-start': daemon_js_1.HELP_DAEMON_START,
78
+ 'daemon-stop': daemon_js_1.HELP_DAEMON_STOP,
79
+ 'daemon-status': daemon_js_1.HELP_DAEMON_STATUS,
80
+ 'device-pool': device_pool_js_1.HELP,
81
+ 'run-parallel': run_parallel_js_1.HELP,
82
+ };
83
+ const OPTIONS_HELP = `Options:
128
84
  --device <id> Target device ID (also keys the session and daemon)
85
+ --device-name <n> Target a booted device by name (resolved to ID from booted devices)
129
86
  --json Output as machine-readable JSON
130
87
  --verbose, -v Log daemon calls, fallbacks, and raw output
131
- --help, -h Show this help
132
- `.trim();
88
+ --help, -h Show this help`;
89
+ const HELP = `Usage: conductor <command> [args] [options]
90
+
91
+ Commands:
92
+ ${Object.values(COMMAND_HELP).join('\n')}
93
+
94
+ ${OPTIONS_HELP}`;
133
95
  async function main() {
134
96
  (0, update_check_js_1.checkForUpdates)();
135
97
  const argv = (0, minimist_1.default)(process.argv.slice(2), {
@@ -140,6 +102,7 @@ async function main() {
140
102
  'list',
141
103
  'verbose',
142
104
  'skills',
105
+ 'check',
143
106
  'all',
144
107
  'acquire',
145
108
  'release',
@@ -150,6 +113,7 @@ async function main() {
150
113
  'double-tap',
151
114
  'optional',
152
115
  'benchmark',
116
+ 'dump',
153
117
  ],
154
118
  string: [
155
119
  'device',
@@ -171,6 +135,11 @@ async function main() {
171
135
  'platform',
172
136
  'os-version',
173
137
  'avd',
138
+ 'name',
139
+ 'device-name',
140
+ 'device-type',
141
+ 'from',
142
+ 'to',
174
143
  ],
175
144
  alias: { h: 'help', v: 'verbose' },
176
145
  });
@@ -178,20 +147,64 @@ async function main() {
178
147
  (0, verbose_js_1.setVerbose)(true);
179
148
  const [command, ...rest] = argv._;
180
149
  const opts = { json: argv['json'] };
181
- // The device ID is the natural key for both the session file and the daemon.
182
- // Use --device if given, otherwise detect the first booted device, otherwise 'default'.
183
- const explicitDevice = argv['device'];
184
- const sessionName = explicitDevice ?? (await (0, runner_js_1.detectFirstDevice)()) ?? 'default';
150
+ // Handle help and unknown commands before device resolution
151
+ // no point prompting for a device if we're just printing help or erroring out.
185
152
  if (!command || argv['help']) {
186
- console.log(HELP);
153
+ if (command && argv['help'] && COMMAND_HELP[command]) {
154
+ console.log(`Usage: conductor ${command} [options]\n\n${COMMAND_HELP[command]}\n\n${OPTIONS_HELP}`);
155
+ }
156
+ else {
157
+ console.log(HELP);
158
+ }
187
159
  process.exit(0);
188
160
  }
161
+ // Commands that don't need a device session
162
+ const NO_DEVICE_COMMANDS = new Set([
163
+ 'list-devices',
164
+ 'start-device',
165
+ 'cheat-sheet',
166
+ 'install',
167
+ 'copy-app',
168
+ 'device-pool',
169
+ 'run-parallel',
170
+ ]);
171
+ if (!NO_DEVICE_COMMANDS.has(command) && !COMMAND_HELP[command]) {
172
+ console.error(`Unknown command: ${command}`);
173
+ console.error('Run `conductor --help` for usage.');
174
+ process.exit(1);
175
+ }
176
+ // The device ID is the natural key for both the session file and the daemon.
177
+ // Use --device if given, otherwise detect the first booted device, otherwise 'default'.
178
+ // Only resolve for commands that actually need a device.
179
+ let sessionName = 'default';
180
+ if (!NO_DEVICE_COMMANDS.has(command)) {
181
+ const explicitDevice = argv['device'];
182
+ const deviceName = argv['device-name'];
183
+ if (explicitDevice && deviceName) {
184
+ console.error('Error: --device and --device-name are mutually exclusive.');
185
+ process.exit(1);
186
+ }
187
+ if (deviceName) {
188
+ const devices = await (0, list_devices_js_1.discoverBootedDevices)();
189
+ const match = devices.find((d) => d.name === deviceName);
190
+ if (!match) {
191
+ console.error(`Error: No booted device found with name "${deviceName}". Run \`conductor list-devices\` to see booted devices.`);
192
+ process.exit(1);
193
+ }
194
+ sessionName = match.id;
195
+ }
196
+ else {
197
+ sessionName = explicitDevice ?? (await (0, device_picker_js_1.pickDevice)()) ?? 'default';
198
+ }
199
+ }
189
200
  let exitCode = 0;
190
201
  switch (command) {
191
202
  case 'start-device':
192
203
  exitCode = await (0, start_device_js_1.startDevice)(argv['platform'], opts, {
193
204
  osVersion: argv['os-version'],
194
205
  avd: argv['avd'],
206
+ name: argv['name'],
207
+ deviceType: argv['device-type'],
195
208
  });
196
209
  break;
197
210
  case 'list-devices':
@@ -203,6 +216,13 @@ async function main() {
203
216
  case 'list-apps':
204
217
  exitCode = await (0, list_apps_js_1.listApps)(opts, sessionName);
205
218
  break;
219
+ case 'copy-app': {
220
+ const bundleId = rest[0] ?? '';
221
+ const from = argv['from'];
222
+ const to = argv['to'];
223
+ exitCode = await (0, copy_app_js_1.copyApp)(bundleId, from ?? '', to ?? '', opts);
224
+ break;
225
+ }
206
226
  case 'launch-app': {
207
227
  const appId = rest[0] ?? '';
208
228
  // --argument key=value (repeatable) → Record<string, string>
@@ -224,6 +244,16 @@ async function main() {
224
244
  exitCode = await (0, stop_app_js_1.stopApp)(appId, opts, sessionName);
225
245
  break;
226
246
  }
247
+ case 'clear-state': {
248
+ const appId = rest[0];
249
+ exitCode = await (0, clear_state_js_1.clearState)(appId, opts, sessionName);
250
+ break;
251
+ }
252
+ case 'uninstall-app': {
253
+ const appId = rest[0] ?? '';
254
+ exitCode = await (0, uninstall_app_js_1.uninstallApp)(appId, opts, sessionName);
255
+ break;
256
+ }
227
257
  case 'tap': {
228
258
  const element = rest.join(' ');
229
259
  exitCode = await (0, tap_js_1.tap)(element, opts, sessionName, {
@@ -366,7 +396,13 @@ async function main() {
366
396
  break;
367
397
  }
368
398
  case 'inspect':
369
- exitCode = await (0, inspect_js_1.inspect)(opts, sessionName);
399
+ exitCode = await (0, inspect_js_1.inspect)(opts, sessionName, { dump: argv['dump'] });
400
+ break;
401
+ case 'focused':
402
+ exitCode = await (0, focused_js_1.focused)(opts, sessionName, {
403
+ poll: argv['poll'],
404
+ interval: argv['interval'] !== undefined ? Number(argv['interval']) : undefined,
405
+ });
370
406
  break;
371
407
  case 'run-flow': {
372
408
  const file = rest[0] ?? '';
@@ -388,7 +424,7 @@ async function main() {
388
424
  exitCode = await (0, cheat_sheet_js_1.cheatSheet)();
389
425
  break;
390
426
  case 'install':
391
- exitCode = await (0, install_js_1.installSkills)(opts);
427
+ exitCode = await (0, install_js_1.installSkills)(opts, argv['skills'], argv['check']);
392
428
  break;
393
429
  case 'daemon-start':
394
430
  exitCode = await (0, daemon_js_1.daemonStart)(opts, sessionName);
@@ -413,6 +449,7 @@ async function main() {
413
449
  break;
414
450
  }
415
451
  default:
452
+ // Should be unreachable — unknown commands are caught before device resolution.
416
453
  console.error(`Unknown command: ${command}`);
417
454
  console.error('Run `conductor --help` for usage.');
418
455
  exitCode = 1;
@@ -4,8 +4,8 @@ const install_js_1 = require("./commands/install.js");
4
4
  // Runs as `postinstall` after `npm install -g @houwert/conductor`.
5
5
  // Silent on failure so it never breaks the npm install.
6
6
  try {
7
- (0, install_js_1.installPlugin)();
8
- process.stdout.write('✓ Conductor Claude Code plugin installed\n');
7
+ const version = (0, install_js_1.installPlugin)();
8
+ process.stdout.write(`✓ Conductor Claude Code plugin installed (v${version})\n`);
9
9
  }
10
10
  catch {
11
11
  // Silent on failure — never break npm install
package/dist/runner.js CHANGED
@@ -95,20 +95,54 @@ async function getDriver(sessionName = 'default') {
95
95
  const port = await (0, bootstrap_js_1.getDriverPort)(platform, deviceId);
96
96
  (0, verbose_js_1.log)(`getDriver: platform=${platform} deviceId=${deviceId} port=${port}`);
97
97
  let driver;
98
- if (!(await (0, bootstrap_js_1.isPortOpen)(port))) {
99
- (0, verbose_js_1.log)(`Driver not running — starting daemon for ${deviceId}...`);
100
- await (0, client_js_1.startDaemon)(deviceId);
101
- await waitForPort(port);
102
- }
103
98
  if (platform === 'ios') {
104
- const iosDriver = new ios_js_1.IOSDriver(port, '127.0.0.1', deviceId);
99
+ if (!(await (0, bootstrap_js_1.isPortOpen)(port))) {
100
+ (0, verbose_js_1.log)(`Driver not running — starting daemon for ${deviceId}...`);
101
+ await (0, client_js_1.startDaemon)(deviceId);
102
+ await waitForPort(port);
103
+ }
104
+ const iosDriver = new ios_js_1.IOSDriver(port, '127.0.0.1', deviceId, 'ios');
105
105
  if (!(await iosDriver.isAlive())) {
106
106
  throw new Error(`iOS XCTest driver on port ${port} is not responding.\n` +
107
107
  `Run: conductor daemon-start --device ${deviceId}`);
108
108
  }
109
109
  driver = iosDriver;
110
110
  }
111
+ else if (platform === 'tvos') {
112
+ if (!(await (0, bootstrap_js_1.isPortOpen)(port))) {
113
+ (0, verbose_js_1.log)(`tvOS driver not running — starting daemon for ${deviceId}...`);
114
+ await (0, client_js_1.startDaemon)(deviceId);
115
+ await waitForPort(port);
116
+ }
117
+ const tvosDriver = new ios_js_1.IOSDriver(port, '127.0.0.1', deviceId, 'tvos');
118
+ if (!(await tvosDriver.isAlive())) {
119
+ throw new Error(`tvOS XCTest driver on port ${port} is not responding.\n` +
120
+ `Run: conductor daemon-start --device ${deviceId}`);
121
+ }
122
+ driver = tvosDriver;
123
+ }
111
124
  else {
125
+ // Ensure the daemon is running — it handles APK install and driver startup.
126
+ await (0, client_js_1.startDaemon)(deviceId);
127
+ // Poll isAlive() directly instead of isPortOpen(): with ADB port forwarding the
128
+ // local port appears open as soon as `adb forward` runs, before the gRPC server
129
+ // on the device is ready, making isPortOpen() unreliable as a readiness signal.
130
+ const ANDROID_READY_TIMEOUT_MS = 180000;
131
+ const deadline = Date.now() + ANDROID_READY_TIMEOUT_MS;
132
+ let alive = false;
133
+ while (Date.now() < deadline) {
134
+ const probe = new android_js_1.AndroidDriver(deviceId, port);
135
+ await probe.connect();
136
+ alive = await probe.isAlive().catch(() => false);
137
+ probe.close();
138
+ if (alive)
139
+ break;
140
+ await new Promise((r) => setTimeout(r, 2000));
141
+ }
142
+ if (!alive) {
143
+ throw new Error(`Android gRPC driver on port ${port} is not responding.\n` +
144
+ `Make sure the Conductor driver APK is installed: conductor install --device ${deviceId}`);
145
+ }
112
146
  const androidDriver = new android_js_1.AndroidDriver(deviceId, port);
113
147
  await androidDriver.connect();
114
148
  driver = androidDriver;
@@ -0,0 +1,121 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>__xctestrun_metadata__</key>
6
+ <dict>
7
+ <key>ContainerInfo</key>
8
+ <dict>
9
+ <key>ContainerName</key>
10
+ <string>conductor-driver-ios</string>
11
+ <key>SchemeName</key>
12
+ <string>conductor-driver-tvos</string>
13
+ </dict>
14
+ <key>FormatVersion</key>
15
+ <integer>1</integer>
16
+ </dict>
17
+ <key>conductor-driver-tvosUITests</key>
18
+ <dict>
19
+ <key>BlueprintName</key>
20
+ <string>conductor-driver-tvosUITests</string>
21
+ <key>BlueprintProviderName</key>
22
+ <string>conductor-driver-ios</string>
23
+ <key>BlueprintProviderRelativePath</key>
24
+ <string>conductor-driver-ios.xcodeproj</string>
25
+ <key>BundleIdentifiersForCrashReportEmphasis</key>
26
+ <array>
27
+ <string>dev.houwert.ConductorDriverLib</string>
28
+ <string>dev.houwert.conductor-driver-tvos</string>
29
+ <string>dev.houwert.conductor-driver-tvosUITests</string>
30
+ </array>
31
+ <key>CommandLineArguments</key>
32
+ <array/>
33
+ <key>DefaultTestExecutionTimeAllowance</key>
34
+ <integer>600</integer>
35
+ <key>DependentProductPaths</key>
36
+ <array>
37
+ <string>__TESTROOT__/Debug-appletvsimulator/ConductorDriverLib.framework</string>
38
+ <string>__TESTROOT__/Debug-appletvsimulator/conductor-driver-tvos.app</string>
39
+ <string>__TESTROOT__/Debug-appletvsimulator/conductor-driver-tvosUITests-Runner.app</string>
40
+ <string>__TESTROOT__/Debug-appletvsimulator/conductor-driver-tvosUITests-Runner.app/PlugIns/conductor-driver-tvosUITests.xctest</string>
41
+ </array>
42
+ <key>DiagnosticCollectionPolicy</key>
43
+ <integer>1</integer>
44
+ <key>EnvironmentVariables</key>
45
+ <dict>
46
+ <key>APP_DISTRIBUTOR_ID_OVERRIDE</key>
47
+ <string>com.apple.AppStore</string>
48
+ <key>OS_ACTIVITY_DT_MODE</key>
49
+ <string>YES</string>
50
+ <key>SQLITE_ENABLE_THREAD_ASSERTIONS</key>
51
+ <string>1</string>
52
+ <key>TERM</key>
53
+ <string>dumb</string>
54
+ </dict>
55
+ <key>IsUITestBundle</key>
56
+ <true/>
57
+ <key>IsXCTRunnerHostedTestBundle</key>
58
+ <true/>
59
+ <key>PreferredScreenCaptureFormat</key>
60
+ <string>screenRecording</string>
61
+ <key>ProductModuleName</key>
62
+ <string>conductor_driver_tvosUITests</string>
63
+ <key>RunOrder</key>
64
+ <integer>0</integer>
65
+ <key>SystemAttachmentLifetime</key>
66
+ <string>deleteOnSuccess</string>
67
+ <key>TestBundlePath</key>
68
+ <string>__TESTHOST__/PlugIns/conductor-driver-tvosUITests.xctest</string>
69
+ <key>TestHostBundleIdentifier</key>
70
+ <string>dev.houwert.conductor-driver-tvosUITests.xctrunner</string>
71
+ <key>TestHostPath</key>
72
+ <string>__TESTROOT__/Debug-appletvsimulator/conductor-driver-tvosUITests-Runner.app</string>
73
+ <key>TestLanguage</key>
74
+ <string></string>
75
+ <key>TestRegion</key>
76
+ <string></string>
77
+ <key>TestTimeoutsEnabled</key>
78
+ <false/>
79
+ <key>TestingEnvironmentVariables</key>
80
+ <dict>
81
+ <key>DYLD_FRAMEWORK_PATH</key>
82
+ <string>__TESTROOT__/Debug-appletvsimulator:__TESTROOT__/Debug-appletvsimulator/PackageFrameworks:__PLATFORMS__/AppleTVSimulator.platform/Developer/Library/Frameworks</string>
83
+ <key>DYLD_LIBRARY_PATH</key>
84
+ <string>__TESTROOT__/Debug-appletvsimulator:__PLATFORMS__/AppleTVSimulator.platform/Developer/usr/lib</string>
85
+ <key>XCODE_SCHEME_NAME</key>
86
+ <string>conductor-driver-tvos</string>
87
+ <key>__XCODE_BUILT_PRODUCTS_DIR_PATHS</key>
88
+ <string>__TESTROOT__/Debug-appletvsimulator</string>
89
+ <key>__XPC_DYLD_FRAMEWORK_PATH</key>
90
+ <string>__TESTROOT__/Debug-appletvsimulator</string>
91
+ <key>__XPC_DYLD_LIBRARY_PATH</key>
92
+ <string>__TESTROOT__/Debug-appletvsimulator</string>
93
+ </dict>
94
+ <key>ToolchainsSettingValue</key>
95
+ <array/>
96
+ <key>UITargetAppCommandLineArguments</key>
97
+ <array/>
98
+ <key>UITargetAppEnvironmentVariables</key>
99
+ <dict>
100
+ <key>APP_DISTRIBUTOR_ID_OVERRIDE</key>
101
+ <string>com.apple.AppStore</string>
102
+ <key>DYLD_FRAMEWORK_PATH</key>
103
+ <string>__TESTROOT__/Debug-appletvsimulator:__TESTROOT__/Debug-appletvsimulator/PackageFrameworks</string>
104
+ <key>DYLD_LIBRARY_PATH</key>
105
+ <string>__TESTROOT__/Debug-appletvsimulator</string>
106
+ <key>XCODE_SCHEME_NAME</key>
107
+ <string>conductor-driver-tvos</string>
108
+ <key>__XCODE_BUILT_PRODUCTS_DIR_PATHS</key>
109
+ <string>__TESTROOT__/Debug-appletvsimulator</string>
110
+ <key>__XPC_DYLD_FRAMEWORK_PATH</key>
111
+ <string>__TESTROOT__/Debug-appletvsimulator</string>
112
+ <key>__XPC_DYLD_LIBRARY_PATH</key>
113
+ <string>__TESTROOT__/Debug-appletvsimulator</string>
114
+ </dict>
115
+ <key>UITargetAppPath</key>
116
+ <string>__TESTROOT__/Debug-appletvsimulator/conductor-driver-tvos.app</string>
117
+ <key>UserAttachmentLifetime</key>
118
+ <string>deleteOnSuccess</string>
119
+ </dict>
120
+ </dict>
121
+ </plist>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@houwert/conductor",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "CLI tool for mobile app interactions — optimized for AI agents",
5
5
  "license": "MIT",
6
6
  "repository": {