@houwert/conductor 0.21.0 → 0.23.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 (52) hide show
  1. package/dist/commands/assert-not-visible.js +3 -1
  2. package/dist/commands/assert-visible.js +3 -1
  3. package/dist/commands/back.js +4 -0
  4. package/dist/commands/capture-ui.js +4 -2
  5. package/dist/commands/clipboard.js +9 -0
  6. package/dist/commands/crashes.js +5 -0
  7. package/dist/commands/delete-device.js +6 -1
  8. package/dist/commands/download-app.js +4 -0
  9. package/dist/commands/erase-text.js +2 -1
  10. package/dist/commands/focused.js +3 -1
  11. package/dist/commands/foreground-app.js +2 -1
  12. package/dist/commands/gestures.js +7 -0
  13. package/dist/commands/hide-keyboard.js +4 -0
  14. package/dist/commands/inspect.js +4 -3
  15. package/dist/commands/install-app.js +11 -0
  16. package/dist/commands/launch-app.js +6 -0
  17. package/dist/commands/list-apps.js +5 -0
  18. package/dist/commands/list-devices.js +16 -0
  19. package/dist/commands/memory.js +5 -0
  20. package/dist/commands/press-key.js +32 -4
  21. package/dist/commands/profile.js +5 -0
  22. package/dist/commands/screenshot.js +3 -1
  23. package/dist/commands/scroll-until-visible.js +3 -1
  24. package/dist/commands/scroll.js +2 -1
  25. package/dist/commands/start-device.js +60 -3
  26. package/dist/commands/stop-app.js +4 -0
  27. package/dist/commands/stop-device.js +8 -3
  28. package/dist/commands/swipe.js +2 -1
  29. package/dist/commands/tap.js +9 -6
  30. package/dist/commands/uninstall-app.js +4 -0
  31. package/dist/commands/web-targets.js +84 -0
  32. package/dist/commands/workspace.js +4 -1
  33. package/dist/daemon/log-collector.js +9 -1
  34. package/dist/daemon/server.js +70 -50
  35. package/dist/drivers/bootstrap.js +12 -0
  36. package/dist/drivers/flow-runner.js +38 -8
  37. package/dist/drivers/ios.js +5 -2
  38. package/dist/drivers/log-sources/metro-discovery.js +56 -2
  39. package/dist/drivers/log-sources/vega.js +77 -0
  40. package/dist/drivers/metro-cdp.js +11 -3
  41. package/dist/drivers/vega/automation-client.js +79 -0
  42. package/dist/drivers/vega/cli.js +199 -0
  43. package/dist/drivers/vega/connection.js +48 -0
  44. package/dist/drivers/vega/input.js +100 -0
  45. package/dist/drivers/vega/page-source-parser.js +229 -0
  46. package/dist/drivers/vega.js +165 -0
  47. package/dist/enum-options.js +15 -3
  48. package/dist/index.js +46 -2
  49. package/dist/runner.js +31 -0
  50. package/package.json +1 -1
  51. package/skills/conductor-device-interact/SKILL.md +4 -3
  52. package/skills/conductor-device-setup/SKILL.md +59 -28
@@ -10,7 +10,7 @@ exports.pickSystemImage = pickSystemImage;
10
10
  exports.buildAvdmanagerCreateArgs = buildAvdmanagerCreateArgs;
11
11
  exports.startDevice = startDevice;
12
12
  exports.HELP = ` start-device
13
- --platform <ios|android|tvos|web> Boot a simulator/emulator, or start the web driver (Playwright)
13
+ --platform <ios|android|tvos|web|vega> Boot a simulator/emulator, start the web driver (Playwright), or boot/attach a Vega VVD
14
14
  --os-version <n> iOS/tvOS version (e.g. 18) or Android API level (e.g. 33)
15
15
  --avd <name> Android AVD name (default: first available; created if missing + --device-type)
16
16
  --name <name> Set a custom name on the device after creation (iOS/tvOS/web)
@@ -28,6 +28,7 @@ const protocol_js_1 = require("../daemon/protocol.js");
28
28
  const bootstrap_js_1 = require("../drivers/bootstrap.js");
29
29
  const output_js_1 = require("../output.js");
30
30
  const utils_js_1 = require("../utils.js");
31
+ const cli_js_1 = require("../drivers/vega/cli.js");
31
32
  const IOS_BOOT_TIMEOUT_MS = 120000;
32
33
  const ANDROID_BOOT_TIMEOUT_MS = 120000;
33
34
  const POLL_MS = 1000;
@@ -694,10 +695,64 @@ async function startWebDriver(opts, browser, name) {
694
695
  (0, output_js_1.printSuccess)(`Web driver ready: ${displayName} (${session})`, opts);
695
696
  return 0;
696
697
  }
698
+ // ── Vega (Amazon Fire TV) ───────────────────────────────────────────────────
699
+ const VEGA_BOOT_TIMEOUT_MS = 180000;
700
+ /** Pick the target VVD from a device list: by name if given, else first virtual, else first. */
701
+ function pickVegaDevice(devices, deviceName) {
702
+ return ((deviceName ? devices.find((d) => d.serial === deviceName) : undefined) ??
703
+ devices.find((d) => d.isVirtual) ??
704
+ devices[0]);
705
+ }
706
+ /**
707
+ * Boot (or attach to) a Vega Virtual Device. If one is already running we attach;
708
+ * otherwise we launch it via `vega virtual-device start` and poll `vega device
709
+ * list` until it appears — mirroring how the Android emulator is booted. Then we
710
+ * prewarm the log daemon.
711
+ */
712
+ async function startVega(opts, deviceName) {
713
+ const cli = new cli_js_1.VegaCli();
714
+ let devices;
715
+ try {
716
+ devices = await cli.listDevices();
717
+ }
718
+ catch {
719
+ (0, output_js_1.printError)('The Vega SDK CLI (`vega`/`kepler`) was not found. Install it and ensure it is on ' +
720
+ 'PATH (or set CONDUCTOR_VEGA_CLI), then retry.', opts);
721
+ return 1;
722
+ }
723
+ let device = pickVegaDevice(devices, deviceName);
724
+ if (!device) {
725
+ console.log(`No running Vega device — booting a VVD${deviceName ? ` (${deviceName})` : ''} ` +
726
+ `via \`vega virtual-device start\`...`);
727
+ try {
728
+ cli.spawnVirtualDeviceStart(deviceName);
729
+ }
730
+ catch (e) {
731
+ (0, output_js_1.printError)(`Failed to start a Vega Virtual Device: ${e instanceof Error ? e.message : String(e)}`, opts);
732
+ return 1;
733
+ }
734
+ const deadline = Date.now() + VEGA_BOOT_TIMEOUT_MS;
735
+ while (Date.now() < deadline && !device) {
736
+ await (0, utils_js_1.sleep)(POLL_MS);
737
+ const current = await cli.listDevices().catch(() => []);
738
+ device = pickVegaDevice(current, deviceName);
739
+ }
740
+ if (!device) {
741
+ (0, output_js_1.printError)(`Vega Virtual Device did not become ready within ${VEGA_BOOT_TIMEOUT_MS / 1000}s. ` +
742
+ `Check \`vega virtual-device list\` — you may need to create one first, or pass ` +
743
+ `--name <vvd>.`, opts);
744
+ return 1;
745
+ }
746
+ }
747
+ // Prewarm the daemon so device/Metro log collection is already running.
748
+ await (0, runner_js_1.prewarmDriver)(`vega:${device.serial}`);
749
+ (0, output_js_1.printSuccess)(`Vega device ready: ${device.serial}`, opts);
750
+ return 0;
751
+ }
697
752
  // ── Entry point ───────────────────────────────────────────────────────────────
698
753
  async function startDevice(platform, opts, flags) {
699
754
  if (!platform) {
700
- (0, output_js_1.printError)('start-device requires --platform ios|android|tvos|web', opts);
755
+ (0, output_js_1.printError)('start-device requires --platform ios|android|tvos|web|vega', opts);
701
756
  return 1;
702
757
  }
703
758
  switch (platform.toLowerCase()) {
@@ -709,8 +764,10 @@ async function startDevice(platform, opts, flags) {
709
764
  return startAndroid(flags.avd, opts, flags.deviceType, flags.osVersion, flags.systemImage);
710
765
  case 'web':
711
766
  return startWebDriver(opts, flags.browser, flags.name);
767
+ case 'vega':
768
+ return startVega(opts, flags.name);
712
769
  default:
713
- (0, output_js_1.printError)(`Unknown platform "${platform}". Use ios, android, tvos, or web.`, opts);
770
+ (0, output_js_1.printError)(`Unknown platform "${platform}". Use ios, android, tvos, web, or vega.`, opts);
714
771
  return 1;
715
772
  }
716
773
  }
@@ -9,6 +9,7 @@ const output_js_1 = require("../output.js");
9
9
  const ios_js_1 = require("../drivers/ios.js");
10
10
  const android_js_1 = require("../drivers/android.js");
11
11
  const web_js_1 = require("../drivers/web.js");
12
+ const vega_js_1 = require("../drivers/vega.js");
12
13
  async function stopApp(appId, opts = {}, sessionName = 'default') {
13
14
  const session = await (0, session_js_1.getSession)(sessionName);
14
15
  const resolvedAppId = appId ?? session.appId;
@@ -23,6 +24,9 @@ async function stopApp(appId, opts = {}, sessionName = 'default') {
23
24
  else if (driver instanceof web_js_1.WebDriver) {
24
25
  await driver.terminateApp();
25
26
  }
27
+ else if (driver instanceof vega_js_1.VegaDriver) {
28
+ await driver.stopApp(resolvedAppId);
29
+ }
26
30
  else if (driver instanceof android_js_1.AndroidDriver) {
27
31
  await driver.stopApp(resolvedAppId);
28
32
  }
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.HELP = void 0;
4
4
  exports.stopDevice = stopDevice;
5
5
  exports.HELP = ` stop-device [<name-or-id>]
6
- --platform <ios|tvos|android|web> Scope to a single platform
6
+ --platform <ios|tvos|android|web|vega> Scope to a single platform
7
7
  --all Stop all booted simulators / running emulators / web sessions`;
8
8
  const runner_js_1 = require("../runner.js");
9
9
  const sdk_js_1 = require("../android/sdk.js");
@@ -35,6 +35,7 @@ async function stopDevice(nameOrId, opts, flags) {
35
35
  const includeTvOS = !platform || platform === 'tvos';
36
36
  const includeAndroid = !platform || platform === 'android';
37
37
  const includeWeb = !platform || platform === 'web';
38
+ const includeVega = !platform || platform === 'vega';
38
39
  const stopped = [];
39
40
  // ── --all mode ───────────────────────────────────────────────────────────
40
41
  if (flags.all) {
@@ -48,6 +49,8 @@ async function stopDevice(nameOrId, opts, flags) {
48
49
  continue;
49
50
  if (d.platform === 'web' && !includeWeb)
50
51
  continue;
52
+ if (d.platform === 'vega' && !includeVega)
53
+ continue;
51
54
  try {
52
55
  if (d.platform === 'ios' || d.platform === 'tvos') {
53
56
  await shutdownSimulator(d.id);
@@ -55,7 +58,8 @@ async function stopDevice(nameOrId, opts, flags) {
55
58
  else if (d.platform === 'android') {
56
59
  await killEmulator(d.id);
57
60
  }
58
- else if (d.platform === 'web') {
61
+ else if (d.platform === 'web' || d.platform === 'vega') {
62
+ // Vega VVD lifecycle is owned by Amazon's tooling — we only stop our log daemon.
59
63
  await (0, client_js_1.stopDaemon)(d.id);
60
64
  }
61
65
  stopped.push({ id: d.id, name: d.name, platform: d.platform });
@@ -96,7 +100,8 @@ async function stopDevice(nameOrId, opts, flags) {
96
100
  else if (match.platform === 'android') {
97
101
  await killEmulator(match.id);
98
102
  }
99
- else if (match.platform === 'web') {
103
+ else if (match.platform === 'web' || match.platform === 'vega') {
104
+ // Vega VVD lifecycle is owned by Amazon's tooling — we only stop our log daemon.
100
105
  await (0, client_js_1.stopDaemon)(match.id);
101
106
  }
102
107
  }
@@ -12,6 +12,7 @@ const output_js_1 = require("../output.js");
12
12
  const ios_js_1 = require("../drivers/ios.js");
13
13
  const android_js_1 = require("../drivers/android.js");
14
14
  const web_js_1 = require("../drivers/web.js");
15
+ const vega_js_1 = require("../drivers/vega.js");
15
16
  const utils_js_1 = require("../utils.js");
16
17
  function parseCoordPair(s) {
17
18
  const [xs, ys] = s.split(',').map((p) => p.trim());
@@ -70,7 +71,7 @@ async function swipe(direction, opts = {}, sessionName = 'default', flags = {})
70
71
  }
71
72
  await driver.swipe(startX, startY, endX, endY, durationMs);
72
73
  }
73
- else if (driver instanceof android_js_1.AndroidDriver) {
74
+ else if (driver instanceof android_js_1.AndroidDriver || driver instanceof vega_js_1.VegaDriver) {
74
75
  const { widthPixels: w, heightPixels: h } = await driver.deviceInfo();
75
76
  const durationMs = flags.duration ?? 500;
76
77
  if (flags.start && flags.end) {
@@ -22,6 +22,7 @@ const output_js_1 = require("../output.js");
22
22
  const ios_js_1 = require("../drivers/ios.js");
23
23
  const android_js_1 = require("../drivers/android.js");
24
24
  const web_js_1 = require("../drivers/web.js");
25
+ const vega_js_1 = require("../drivers/vega.js");
25
26
  const wait_js_1 = require("../drivers/wait.js");
26
27
  const direct_ios_selector_js_1 = require("../drivers/direct-ios-selector.js");
27
28
  const snapshot_store_js_1 = require("../snapshot-store.js");
@@ -68,6 +69,10 @@ async function tap(query, opts = {}, sessionName = 'default', flags = {}) {
68
69
  else if (driver instanceof web_js_1.WebDriver) {
69
70
  el = await (0, wait_js_1.waitForWebElement)(() => driver.viewHierarchy(), sel);
70
71
  }
72
+ else if (driver instanceof vega_js_1.VegaDriver) {
73
+ // Vega emits uiautomator-style XML, so it reuses the Android resolver.
74
+ el = await (0, wait_js_1.waitForAndroidElement)(() => driver.viewHierarchy(), sel);
75
+ }
71
76
  else if (driver instanceof android_js_1.AndroidDriver) {
72
77
  el = await (0, wait_js_1.waitForAndroidElement)(() => driver.viewHierarchy(), sel);
73
78
  }
@@ -75,14 +80,12 @@ async function tap(query, opts = {}, sessionName = 'default', flags = {}) {
75
80
  return;
76
81
  }
77
82
  if (flags.longPress) {
78
- if (driver instanceof ios_js_1.IOSDriver) {
79
- await driver.tap(el.centerX, el.centerY, 1.5);
80
- }
81
- else if (driver instanceof web_js_1.WebDriver) {
82
- await driver.tap(el.centerX, el.centerY, 1.5);
83
+ if (driver instanceof android_js_1.AndroidDriver) {
84
+ await driver.swipe(el.centerX, el.centerY, el.centerX, el.centerY, 1500);
83
85
  }
84
86
  else {
85
- await driver.swipe(el.centerX, el.centerY, el.centerX, el.centerY, 1500);
87
+ // iOS, web, and vega express a long press as a held tap.
88
+ await driver.tap(el.centerX, el.centerY, 1.5);
86
89
  }
87
90
  }
88
91
  else if (flags.doubleTap) {
@@ -8,6 +8,7 @@ const output_js_1 = require("../output.js");
8
8
  const ios_js_1 = require("../drivers/ios.js");
9
9
  const android_js_1 = require("../drivers/android.js");
10
10
  const web_js_1 = require("../drivers/web.js");
11
+ const vega_js_1 = require("../drivers/vega.js");
11
12
  async function uninstallApp(appId, opts = {}, sessionName = 'default') {
12
13
  if (!appId) {
13
14
  (0, output_js_1.printError)('uninstall-app requires <appId>', opts);
@@ -20,6 +21,9 @@ async function uninstallApp(appId, opts = {}, sessionName = 'default') {
20
21
  else if (driver instanceof web_js_1.WebDriver) {
21
22
  throw new Error('uninstall-app is not supported on web');
22
23
  }
24
+ else if (driver instanceof vega_js_1.VegaDriver) {
25
+ throw new Error('uninstall-app is not supported on vega (Amazon Fire TV)');
26
+ }
23
27
  else if (driver instanceof android_js_1.AndroidDriver) {
24
28
  await driver.uninstallApp(appId);
25
29
  }
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.HELP = void 0;
7
+ exports.webTargets = webTargets;
8
+ /**
9
+ * List the CDP page targets exposed by an external browser (e.g. an Electron app
10
+ * launched with `--remote-debugging-port`). Each target is a controllable page —
11
+ * for the Lightning emulator, one per tile plus its control/remote chrome.
12
+ *
13
+ * Reads the DevTools HTTP endpoint (`/json/list`) directly, so it needs no
14
+ * Playwright browser and works before any daemon session exists. Use the printed
15
+ * target IDs with `--cdp-url` / `--cdp-target` to bind a session to a tile.
16
+ */
17
+ const http_1 = __importDefault(require("http"));
18
+ const output_js_1 = require("../output.js");
19
+ /** Derive the `http://host:port` base from a CDP URL (which may be ws:// or include a path). */
20
+ function httpBase(cdpUrl) {
21
+ const u = new URL(cdpUrl);
22
+ const proto = u.protocol === 'https:' || u.protocol === 'wss:' ? 'https:' : 'http:';
23
+ return `${proto}//${u.host}`;
24
+ }
25
+ function fetchTargets(cdpUrl) {
26
+ const url = `${httpBase(cdpUrl)}/json/list`;
27
+ return new Promise((resolve, reject) => {
28
+ const req = http_1.default.get(url, (res) => {
29
+ const chunks = [];
30
+ res.on('data', (c) => chunks.push(c));
31
+ res.on('end', () => {
32
+ if ((res.statusCode ?? 0) >= 300) {
33
+ reject(new Error(`HTTP ${res.statusCode} from ${url}`));
34
+ return;
35
+ }
36
+ try {
37
+ resolve(JSON.parse(Buffer.concat(chunks).toString('utf-8')));
38
+ }
39
+ catch (err) {
40
+ reject(err);
41
+ }
42
+ });
43
+ });
44
+ req.setTimeout(5000, () => req.destroy(new Error(`Timed out fetching ${url}`)));
45
+ req.on('error', reject);
46
+ });
47
+ }
48
+ async function webTargets(cdpUrl, opts) {
49
+ if (!cdpUrl) {
50
+ console.error('web-targets requires --cdp-url <url> (e.g. --cdp-url http://127.0.0.1:9222).\n' +
51
+ 'Launch the browser/Electron app with --remote-debugging-port to expose it.');
52
+ return 1;
53
+ }
54
+ let targets;
55
+ try {
56
+ targets = await fetchTargets(cdpUrl);
57
+ }
58
+ catch (err) {
59
+ console.error(`Could not reach CDP endpoint at ${cdpUrl}: ${err instanceof Error ? err.message : String(err)}`);
60
+ return 1;
61
+ }
62
+ // Only type="page" targets are controllable as Playwright Pages.
63
+ const pages = targets.filter((t) => t.type === 'page');
64
+ if (opts.json) {
65
+ (0, output_js_1.printData)(pages.map((t) => ({ id: t.id, title: t.title, url: t.url })), opts);
66
+ return 0;
67
+ }
68
+ if (pages.length === 0) {
69
+ console.log('No page targets found. Is the app loaded and started with --remote-debugging-port?');
70
+ return 0;
71
+ }
72
+ console.log(`Found ${pages.length} page target(s) at ${cdpUrl}:\n`);
73
+ pages.forEach((t, i) => {
74
+ console.log(` [${i}] ${t.title || '(untitled)'}`);
75
+ console.log(` url: ${t.url}`);
76
+ console.log(` target: ${t.id}`);
77
+ console.log(` bind: conductor --device web:chromium:t${i} --cdp-url ${cdpUrl} --cdp-target ${t.id} inspect`);
78
+ console.log('');
79
+ });
80
+ console.log('Bind a session to a target once (any command), then drop the --cdp-* flags on later\n' +
81
+ 'commands for that --device — the attachment is remembered per session.');
82
+ return 0;
83
+ }
84
+ exports.HELP = ' web-targets --cdp-url <url> List controllable CDP page targets (one per Electron webview/tile)';
@@ -112,7 +112,10 @@ async function workspaceInfo(opts = {}) {
112
112
  const devices = await (0, list_devices_js_1.discoverBootedDevices)().catch(() => []);
113
113
  let metroPort = null;
114
114
  for (const d of devices) {
115
- if (d.platform !== 'ios' && d.platform !== 'tvos' && d.platform !== 'android')
115
+ if (d.platform !== 'ios' &&
116
+ d.platform !== 'tvos' &&
117
+ d.platform !== 'android' &&
118
+ d.platform !== 'vega')
116
119
  continue;
117
120
  const port = await (0, metro_discovery_js_1.discoverMetroPortForDevice)(d.platform, d.id).catch(() => null);
118
121
  if (port) {
@@ -32,6 +32,7 @@ const http_1 = __importDefault(require("http"));
32
32
  const types_js_1 = require("../drivers/log-sources/types.js");
33
33
  const ios_js_1 = require("../drivers/log-sources/ios.js");
34
34
  const android_js_1 = require("../drivers/log-sources/android.js");
35
+ const vega_js_1 = require("../drivers/log-sources/vega.js");
35
36
  const metro_js_1 = require("../drivers/log-sources/metro.js");
36
37
  const metro_discovery_js_1 = require("../drivers/log-sources/metro-discovery.js");
37
38
  const MAX_BUFFER = 5000;
@@ -75,7 +76,10 @@ class LogCollector {
75
76
  // Metro is always auto-discovered. Discovery retries forever with a
76
77
  // backoff ceiling — the app may be launched long after the daemon starts,
77
78
  // and the cost per attempt is tiny (a few spawns + one HTTP call).
78
- if (this.platform === 'ios' || this.platform === 'tvos' || this.platform === 'android') {
79
+ if (this.platform === 'ios' ||
80
+ this.platform === 'tvos' ||
81
+ this.platform === 'android' ||
82
+ this.platform === 'vega') {
79
83
  this.startMetroAutoDiscovery();
80
84
  }
81
85
  }
@@ -146,6 +150,10 @@ class LogCollector {
146
150
  else if (this.platform === 'android') {
147
151
  this.source = new android_js_1.AndroidLogSource(this.deviceId, this.appId);
148
152
  }
153
+ else if (this.platform === 'vega') {
154
+ // deviceId is `vega:<serial>`; VegaCli wants the bare serial.
155
+ this.source = new vega_js_1.VegaLogSource(this.deviceId.replace(/^vega:/, ''));
156
+ }
149
157
  else {
150
158
  return; // Unsupported platform for device log collection
151
159
  }
@@ -85,6 +85,9 @@ let _driverStartError = null;
85
85
  async function ensureDriverRunning() {
86
86
  if (_restartInProgress || !_driverStarted)
87
87
  return;
88
+ // Vega has no driver process/port to health-check — control is host-side via the CLI.
89
+ if (driverPlatform === 'vega')
90
+ return;
88
91
  let alive;
89
92
  if (driverPlatform === 'android') {
90
93
  const probe = new android_js_1.AndroidDriver(sessionName, driverPort);
@@ -197,6 +200,9 @@ async function main() {
197
200
  if (driverPlatform === 'tvos') {
198
201
  dlog('tvOS: leaving driver running to preserve app state');
199
202
  }
203
+ else if (driverPlatform === 'vega') {
204
+ dlog('vega: no driver process to stop (control is host-side via the CLI)');
205
+ }
200
206
  else if (driverPlatform === 'web') {
201
207
  dlog('Stopping web driver');
202
208
  try {
@@ -321,59 +327,14 @@ async function main() {
321
327
  driverPlatform = platform;
322
328
  driverPort = await (0, bootstrap_js_1.getDriverPort)(platform, sessionName);
323
329
  dlog(`Platform: ${platform}, port: ${driverPort}`);
324
- let driverAlive;
325
- if (platform === 'android') {
326
- const probe = new android_js_1.AndroidDriver(sessionName, driverPort);
327
- await probe.connect();
328
- driverAlive = await probe.isAlive().catch(() => false);
329
- probe.close();
330
- }
331
- else {
332
- // 'ios', 'tvos', and 'web' all use an HTTP server — port open = alive
333
- driverAlive = await (0, bootstrap_js_1.isPortOpen)(driverPort);
334
- }
335
- if (driverAlive) {
330
+ // Vega has no driver process — control is host-side via the vega CLI.
331
+ // The daemon exists only to collect device + Metro logs.
332
+ if (platform === 'vega') {
336
333
  _driverStarted = true;
337
- dlog(`Driver already running on port ${driverPort}`);
334
+ dlog('vega: no driver process to start; collecting logs only');
338
335
  }
339
336
  else {
340
- // Android: install APKs before starting the driver.
341
- // iOS/tvOS: xcodebuild installs silently via DependentProductPaths.
342
- // Web: ensure Playwright browser binary is installed.
343
- if (platform === 'android') {
344
- dlog(`Installing Android driver on ${sessionName}`);
345
- await (0, bootstrap_js_1.installDriver)(sessionName);
346
- dlog(`Driver installation complete`);
347
- }
348
- else if (platform === 'web' && !cdpUrl) {
349
- // Only install Playwright browser when launching standalone.
350
- // In CDP mode we attach to the host app's browser (e.g. Electron).
351
- const browser = (0, bootstrap_js_1.webBrowserName)(sessionName);
352
- await (0, bootstrap_js_1.ensurePlaywrightBrowser)(browser, dlog);
353
- }
354
- dlog(`Starting ${platform} driver on port ${driverPort}`);
355
- try {
356
- if (platform === 'ios') {
357
- await (0, bootstrap_js_1.startIOSDriver)(sessionName, driverPort);
358
- }
359
- else if (platform === 'tvos') {
360
- // First install — the runner takes foreground; ask it to hand
361
- // focus back to whatever app the user had open.
362
- await (0, bootstrap_js_1.startTvOSDriver)(sessionName, driverPort, /* restoreFocusAfterLaunch */ true);
363
- }
364
- else if (platform === 'web') {
365
- await (0, web_server_js_1.startWebServer)(driverPort, (0, bootstrap_js_1.webBrowserName)(sessionName), dlog, cdpUrl, cdpTargetId);
366
- }
367
- else {
368
- await (0, bootstrap_js_1.startAndroidDriver)(sessionName, driverPort);
369
- }
370
- _driverStarted = true;
371
- dlog(`Driver started successfully`);
372
- }
373
- catch (err) {
374
- _driverStartError = err instanceof Error ? err.message : String(err);
375
- dlog(`Driver startup error: ${_driverStartError}`);
376
- }
337
+ await startDriverForPlatform(platform);
377
338
  }
378
339
  // Start collecting logs once the driver is (or was already) running.
379
340
  if (_driverStarted) {
@@ -394,6 +355,65 @@ async function main() {
394
355
  }
395
356
  });
396
357
  }
358
+ /**
359
+ * Bring up the driver process for a non-vega platform. Sets `_driverStarted` /
360
+ * `_driverStartError`. Extracted so the vega path can skip it entirely.
361
+ */
362
+ async function startDriverForPlatform(platform) {
363
+ let driverAlive;
364
+ if (platform === 'android') {
365
+ const probe = new android_js_1.AndroidDriver(sessionName, driverPort);
366
+ await probe.connect();
367
+ driverAlive = await probe.isAlive().catch(() => false);
368
+ probe.close();
369
+ }
370
+ else {
371
+ // 'ios', 'tvos', and 'web' all use an HTTP server — port open = alive
372
+ driverAlive = await (0, bootstrap_js_1.isPortOpen)(driverPort);
373
+ }
374
+ if (driverAlive) {
375
+ _driverStarted = true;
376
+ dlog(`Driver already running on port ${driverPort}`);
377
+ return;
378
+ }
379
+ // Android: install APKs before starting the driver.
380
+ // iOS/tvOS: xcodebuild installs silently via DependentProductPaths.
381
+ // Web: ensure Playwright browser binary is installed.
382
+ if (platform === 'android') {
383
+ dlog(`Installing Android driver on ${sessionName}`);
384
+ await (0, bootstrap_js_1.installDriver)(sessionName);
385
+ dlog(`Driver installation complete`);
386
+ }
387
+ else if (platform === 'web' && !cdpUrl) {
388
+ // Only install Playwright browser when launching standalone.
389
+ // In CDP mode we attach to the host app's browser (e.g. Electron).
390
+ const browser = (0, bootstrap_js_1.webBrowserName)(sessionName);
391
+ await (0, bootstrap_js_1.ensurePlaywrightBrowser)(browser, dlog);
392
+ }
393
+ dlog(`Starting ${platform} driver on port ${driverPort}`);
394
+ try {
395
+ if (platform === 'ios') {
396
+ await (0, bootstrap_js_1.startIOSDriver)(sessionName, driverPort);
397
+ }
398
+ else if (platform === 'tvos') {
399
+ // First install — the runner takes foreground; ask it to hand
400
+ // focus back to whatever app the user had open.
401
+ await (0, bootstrap_js_1.startTvOSDriver)(sessionName, driverPort, /* restoreFocusAfterLaunch */ true);
402
+ }
403
+ else if (platform === 'web') {
404
+ await (0, web_server_js_1.startWebServer)(driverPort, (0, bootstrap_js_1.webBrowserName)(sessionName), dlog, cdpUrl, cdpTargetId);
405
+ }
406
+ else {
407
+ await (0, bootstrap_js_1.startAndroidDriver)(sessionName, driverPort);
408
+ }
409
+ _driverStarted = true;
410
+ dlog(`Driver started successfully`);
411
+ }
412
+ catch (err) {
413
+ _driverStartError = err instanceof Error ? err.message : String(err);
414
+ dlog(`Driver startup error: ${_driverStartError}`);
415
+ }
416
+ }
397
417
  main().catch((err) => {
398
418
  console.error('Daemon error:', err);
399
419
  process.exit(1);
@@ -53,6 +53,11 @@ async function detectPlatform(deviceId) {
53
53
  _platformCache.set(deviceId, 'web');
54
54
  return 'web';
55
55
  }
56
+ // Vega (Amazon Fire TV): "vega:<serial>" (e.g. "vega:VirtualDevice")
57
+ if (deviceId === 'vega' || deviceId.startsWith('vega:')) {
58
+ _platformCache.set(deviceId, 'vega');
59
+ return 'vega';
60
+ }
56
61
  // Check if it looks like an iOS/tvOS simulator UUID (8-4-4-4-12 hex chars)
57
62
  const iosUuidRe = /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i;
58
63
  if (iosUuidRe.test(deviceId)) {
@@ -83,6 +88,7 @@ const IOS_BASE_PORT = 1075;
83
88
  const TVOS_BASE_PORT = 2075;
84
89
  const ANDROID_BASE_PORT = 3763;
85
90
  const WEB_BASE_PORT = 4075;
91
+ const VEGA_BASE_PORT = 5075;
86
92
  const PORT_FILE = path_1.default.join(os_1.default.homedir(), '.conductor', 'ports.json');
87
93
  const PORT_LOCK = PORT_FILE + '.lock';
88
94
  const PORT_LOCK_TIMEOUT_MS = 5000;
@@ -97,6 +103,7 @@ function readPortState() {
97
103
  nextTvosPort: TVOS_BASE_PORT,
98
104
  nextAndroidPort: ANDROID_BASE_PORT,
99
105
  nextWebPort: WEB_BASE_PORT,
106
+ nextVegaPort: VEGA_BASE_PORT,
100
107
  };
101
108
  }
102
109
  }
@@ -145,6 +152,8 @@ async function getDriverPort(platform, deviceId) {
145
152
  state.nextTvosPort = TVOS_BASE_PORT;
146
153
  if (state.nextWebPort === undefined)
147
154
  state.nextWebPort = WEB_BASE_PORT;
155
+ if (state.nextVegaPort === undefined)
156
+ state.nextVegaPort = VEGA_BASE_PORT;
148
157
  let port;
149
158
  if (platform === 'ios') {
150
159
  port = state.nextIosPort++;
@@ -155,6 +164,9 @@ async function getDriverPort(platform, deviceId) {
155
164
  else if (platform === 'web') {
156
165
  port = state.nextWebPort++;
157
166
  }
167
+ else if (platform === 'vega') {
168
+ port = state.nextVegaPort++;
169
+ }
158
170
  else {
159
171
  port = state.nextAndroidPort++;
160
172
  }
@@ -17,6 +17,7 @@ const js_yaml_1 = __importDefault(require("js-yaml"));
17
17
  const ios_js_1 = require("./ios.js");
18
18
  const android_js_1 = require("./android.js");
19
19
  const web_js_1 = require("./web.js");
20
+ const vega_js_1 = require("./vega.js");
20
21
  const wait_js_1 = require("./wait.js");
21
22
  const direct_ios_selector_js_1 = require("./direct-ios-selector.js");
22
23
  const perf_hooks_1 = require("perf_hooks");
@@ -25,6 +26,20 @@ const utils_js_1 = require("../utils.js");
25
26
  function fmtMs(ms) {
26
27
  return ms < 1000 ? `${Math.round(ms)}ms` : `${(ms / 1000).toFixed(1)}s`;
27
28
  }
29
+ // vega (Amazon Fire TV) remote key names → VegaDriver button values, for flow pressKey.
30
+ const VEGA_FLOW_BUTTONS = {
31
+ BACK: 'back',
32
+ HOME: 'home',
33
+ 'REMOTE DPAD UP': 'up',
34
+ 'REMOTE DPAD DOWN': 'down',
35
+ 'REMOTE DPAD LEFT': 'left',
36
+ 'REMOTE DPAD RIGHT': 'right',
37
+ 'REMOTE DPAD CENTER': 'select',
38
+ ENTER: 'select',
39
+ RETURN: 'select',
40
+ VOLUME_UP: 'volumeUp',
41
+ VOLUME_DOWN: 'volumeDown',
42
+ };
28
43
  function toElementSelector(sel) {
29
44
  // A bare string matches text OR id (Maestro's query semantics).
30
45
  // Only object form with explicit `text:` or `id:` keys forces one field.
@@ -507,7 +522,13 @@ async function executeCommand(cmd, driver, opts) {
507
522
  }
508
523
  }
509
524
  function getConductorObj(driver, output) {
510
- const platform = driver instanceof ios_js_1.IOSDriver ? 'ios' : driver instanceof web_js_1.WebDriver ? 'web' : 'android';
525
+ const platform = driver instanceof ios_js_1.IOSDriver
526
+ ? 'ios'
527
+ : driver instanceof web_js_1.WebDriver
528
+ ? 'web'
529
+ : driver instanceof vega_js_1.VegaDriver
530
+ ? 'vega'
531
+ : 'android';
511
532
  return {
512
533
  platform,
513
534
  copiedText: output['__copiedText'] ?? '',
@@ -634,16 +655,14 @@ async function executeCommandBody(key, val, driver, opts) {
634
655
  const n = typeof val === 'number'
635
656
  ? val
636
657
  : (val?.charactersToErase ?? 50);
637
- if (driver instanceof android_js_1.AndroidDriver) {
638
- await driver.eraseAllText(n);
639
- }
640
- else if (driver instanceof web_js_1.WebDriver) {
641
- await driver.eraseAllText(n);
642
- }
643
- else {
658
+ if (driver instanceof ios_js_1.IOSDriver) {
644
659
  for (let i = 0; i < n; i++)
645
660
  await driver.pressKey('delete');
646
661
  }
662
+ else {
663
+ // Android, web, and vega all expose eraseAllText.
664
+ await driver.eraseAllText(n);
665
+ }
647
666
  break;
648
667
  }
649
668
  case 'inputRandomText': {
@@ -782,6 +801,8 @@ async function executeCommandBody(key, val, driver, opts) {
782
801
  await driver.back();
783
802
  else if (driver instanceof web_js_1.WebDriver)
784
803
  await driver.goBack();
804
+ else if (driver instanceof vega_js_1.VegaDriver)
805
+ await driver.back();
785
806
  // iOS has no hardware back button — noop
786
807
  break;
787
808
  }
@@ -1055,6 +1076,12 @@ async function executeCommandBody(key, val, driver, opts) {
1055
1076
  };
1056
1077
  await driver.pressKey(WEB_KEY_MAP[keyName] ?? keyName);
1057
1078
  }
1079
+ else if (driver instanceof vega_js_1.VegaDriver) {
1080
+ const button = VEGA_FLOW_BUTTONS[keyName];
1081
+ if (!button)
1082
+ throw new Error(`pressKey: key "${val}" is not supported on vega`);
1083
+ await driver.pressButton(button);
1084
+ }
1058
1085
  else {
1059
1086
  const keycode = ANDROID_KEYCODES[keyName];
1060
1087
  if (keycode === undefined)
@@ -1072,6 +1099,9 @@ async function executeCommandBody(key, val, driver, opts) {
1072
1099
  else if (driver instanceof web_js_1.WebDriver) {
1073
1100
  // No virtual keyboard on web — noop
1074
1101
  }
1102
+ else if (driver instanceof vega_js_1.VegaDriver) {
1103
+ // No reliable keyboard-hide primitive on vega — noop
1104
+ }
1075
1105
  else {
1076
1106
  await driver.pressKeyEvent(111); // KEYCODE_ESCAPE
1077
1107
  }