@houwert/conductor 0.22.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 (50) 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/workspace.js +4 -1
  32. package/dist/daemon/log-collector.js +9 -1
  33. package/dist/daemon/server.js +70 -50
  34. package/dist/drivers/bootstrap.js +12 -0
  35. package/dist/drivers/flow-runner.js +38 -8
  36. package/dist/drivers/ios.js +5 -2
  37. package/dist/drivers/log-sources/metro-discovery.js +43 -0
  38. package/dist/drivers/log-sources/vega.js +77 -0
  39. package/dist/drivers/vega/automation-client.js +79 -0
  40. package/dist/drivers/vega/cli.js +199 -0
  41. package/dist/drivers/vega/connection.js +48 -0
  42. package/dist/drivers/vega/input.js +100 -0
  43. package/dist/drivers/vega/page-source-parser.js +229 -0
  44. package/dist/drivers/vega.js +165 -0
  45. package/dist/enum-options.js +15 -3
  46. package/dist/index.js +7 -2
  47. package/dist/runner.js +31 -0
  48. package/package.json +1 -1
  49. package/skills/conductor-device-interact/SKILL.md +4 -3
  50. package/skills/conductor-device-setup/SKILL.md +16 -2
@@ -11,6 +11,7 @@ const output_js_1 = require("../output.js");
11
11
  const ios_js_1 = require("../drivers/ios.js");
12
12
  const android_js_1 = require("../drivers/android.js");
13
13
  const web_js_1 = require("../drivers/web.js");
14
+ const vega_js_1 = require("../drivers/vega.js");
14
15
  const wait_js_1 = require("../drivers/wait.js");
15
16
  async function assertNotVisible(element, opts = {}, sessionName = 'default', flags = {}) {
16
17
  if (!element && !flags.id && !flags.text) {
@@ -42,7 +43,8 @@ async function assertNotVisible(element, opts = {}, sessionName = 'default', fla
42
43
  else if (driver instanceof web_js_1.WebDriver) {
43
44
  await (0, wait_js_1.waitUntilWebElementGone)(() => driver.viewHierarchy(), sel, flags.timeout);
44
45
  }
45
- else if (driver instanceof android_js_1.AndroidDriver) {
46
+ else if (driver instanceof android_js_1.AndroidDriver || driver instanceof vega_js_1.VegaDriver) {
47
+ // Vega emits uiautomator-style XML, so it reuses the Android resolver.
46
48
  await (0, wait_js_1.waitUntilAndroidElementGone)(() => driver.viewHierarchy(), sel, flags.timeout);
47
49
  }
48
50
  (0, output_js_1.printSuccess)(`assert-not-visible ${label} — element not found`, opts);
@@ -21,6 +21,7 @@ const output_js_1 = require("../output.js");
21
21
  const ios_js_1 = require("../drivers/ios.js");
22
22
  const android_js_1 = require("../drivers/android.js");
23
23
  const web_js_1 = require("../drivers/web.js");
24
+ const vega_js_1 = require("../drivers/vega.js");
24
25
  const wait_js_1 = require("../drivers/wait.js");
25
26
  const direct_ios_selector_js_1 = require("../drivers/direct-ios-selector.js");
26
27
  async function assertVisible(element, opts = {}, sessionName = 'default', flags = {}) {
@@ -55,7 +56,8 @@ async function assertVisible(element, opts = {}, sessionName = 'default', flags
55
56
  else if (driver instanceof web_js_1.WebDriver) {
56
57
  return (0, wait_js_1.waitForWebElement)(() => driver.viewHierarchy(), sel, timeoutMs);
57
58
  }
58
- else if (driver instanceof android_js_1.AndroidDriver) {
59
+ else if (driver instanceof android_js_1.AndroidDriver || driver instanceof vega_js_1.VegaDriver) {
60
+ // Vega emits uiautomator-style XML, so it reuses the Android resolver.
59
61
  return (0, wait_js_1.waitForAndroidElement)(() => driver.viewHierarchy(), sel, timeoutMs);
60
62
  }
61
63
  };
@@ -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 back(opts = {}, sessionName = 'default') {
12
13
  const result = await (0, runner_js_1.runDirect)(async (driver) => {
13
14
  if (driver instanceof ios_js_1.IOSDriver) {
@@ -19,6 +20,9 @@ async function back(opts = {}, sessionName = 'default') {
19
20
  else if (driver instanceof web_js_1.WebDriver) {
20
21
  await driver.goBack();
21
22
  }
23
+ else if (driver instanceof vega_js_1.VegaDriver) {
24
+ await driver.back();
25
+ }
22
26
  else if (driver instanceof android_js_1.AndroidDriver) {
23
27
  await driver.back(); // adb shell input keyevent 4
24
28
  }
@@ -13,6 +13,7 @@ const output_js_1 = require("../output.js");
13
13
  const ios_js_1 = require("../drivers/ios.js");
14
14
  const android_js_1 = require("../drivers/android.js");
15
15
  const web_js_1 = require("../drivers/web.js");
16
+ const vega_js_1 = require("../drivers/vega.js");
16
17
  const a11y_js_1 = require("../drivers/a11y.js");
17
18
  const snapshot_store_js_1 = require("../snapshot-store.js");
18
19
  async function captureUI(outputPath, opts = {}, sessionName = 'default') {
@@ -66,8 +67,9 @@ async function captureUI(outputPath, opts = {}, sessionName = 'default') {
66
67
  a11ySnapshot = built.a11ySnapshot;
67
68
  screenshotBuf = shot;
68
69
  }
69
- else if (driver instanceof android_js_1.AndroidDriver) {
70
- platform = 'android';
70
+ else if (driver instanceof android_js_1.AndroidDriver || driver instanceof vega_js_1.VegaDriver) {
71
+ // Vega emits uiautomator-style XML, so it reuses the Android a11y builder.
72
+ platform = driver instanceof vega_js_1.VegaDriver ? 'vega' : 'android';
71
73
  const [info, xml, shot] = await Promise.all([
72
74
  driver.deviceInfo(),
73
75
  driver.viewHierarchy(),
@@ -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 ANDROID_MSG = 'clipboard is iOS-only. On Android, use `conductor input-text` to type instead.';
16
17
  async function clipboardRead(opts = {}, sessionName = 'default') {
17
18
  const result = await (0, runner_js_1.runDirect)(async (driver) => {
@@ -21,6 +22,8 @@ async function clipboardRead(opts = {}, sessionName = 'default') {
21
22
  throw new Error(ANDROID_MSG);
22
23
  if (driver instanceof web_js_1.WebDriver)
23
24
  throw new Error('clipboard read is not supported on Web');
25
+ if (driver instanceof vega_js_1.VegaDriver)
26
+ throw new Error('clipboard read is not supported on vega');
24
27
  return '';
25
28
  }, sessionName);
26
29
  if (result.success) {
@@ -46,6 +49,9 @@ async function clipboardWrite(text, opts = {}, sessionName = 'default') {
46
49
  else if (driver instanceof web_js_1.WebDriver) {
47
50
  throw new Error('clipboard write is not supported on Web');
48
51
  }
52
+ else if (driver instanceof vega_js_1.VegaDriver) {
53
+ throw new Error('clipboard write is not supported on vega');
54
+ }
49
55
  }, sessionName);
50
56
  if (result.success) {
51
57
  (0, output_js_1.printSuccess)('clipboard write — done', opts);
@@ -72,6 +78,9 @@ async function paste(opts = {}, sessionName = 'default') {
72
78
  else if (driver instanceof web_js_1.WebDriver) {
73
79
  throw new Error('paste is not supported on Web');
74
80
  }
81
+ else if (driver instanceof vega_js_1.VegaDriver) {
82
+ throw new Error('paste is not supported on vega');
83
+ }
75
84
  }, sessionName);
76
85
  if (result.success) {
77
86
  (0, output_js_1.printSuccess)('paste — done', opts);
@@ -175,6 +175,11 @@ async function crashesList(opts, sessionName, listOpts) {
175
175
  if (platform === 'android' && sessionName !== 'default') {
176
176
  reports.push(...(await listAndroidReports(sessionName, { app: listOpts.app, sinceMs })));
177
177
  }
178
+ else if (platform === 'vega') {
179
+ // No crash-log retrieval channel on Vega in v1.
180
+ if (!opts.json)
181
+ console.error('note: crash reports are not supported on vega (Amazon Fire TV)');
182
+ }
178
183
  if (opts.json) {
179
184
  (0, output_js_1.printData)({ count: reports.length, reports }, opts);
180
185
  }
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.HELP = void 0;
7
7
  exports.deleteDevice = deleteDevice;
8
8
  exports.HELP = ` delete-device <name-or-id>
9
- --platform <ios|tvos|android|web> Scope to a single platform
9
+ --platform <ios|tvos|android|web> Scope to a single platform (Vega VVDs are managed by Amazon's tooling)
10
10
  --all Delete all shutdown simulators / non-running AVDs / web sessions`;
11
11
  const fs_1 = __importDefault(require("fs"));
12
12
  const runner_js_1 = require("../runner.js");
@@ -105,6 +105,11 @@ async function deleteDevice(nameOrId, opts, flags) {
105
105
  return 1;
106
106
  }
107
107
  const platform = flags.platform?.toLowerCase();
108
+ // Vega VVD lifecycle is owned by Amazon's tooling — we can't delete one.
109
+ if (platform === 'vega' || (nameOrId && nameOrId.startsWith('vega:'))) {
110
+ (0, output_js_1.printError)("delete-device is not supported on vega (Amazon Fire TV) — manage VVDs via Amazon's tooling.", opts);
111
+ return 1;
112
+ }
108
113
  const includeIOS = !platform || platform === 'ios';
109
114
  const includeTvOS = !platform || platform === 'tvos';
110
115
  const includeAndroid = !platform || platform === 'android';
@@ -33,6 +33,10 @@ async function downloadApp(appId, output, opts = {}, sessionName = 'default') {
33
33
  (0, output_js_1.printError)('download-app is not supported on web.', opts);
34
34
  return 1;
35
35
  }
36
+ if (platform === 'vega') {
37
+ (0, output_js_1.printError)('download-app is not supported on vega (Amazon Fire TV).', opts);
38
+ return 1;
39
+ }
36
40
  if (platform === 'ios' || platform === 'tvos') {
37
41
  // Get the .app bundle path from the simulator
38
42
  const getPath = await (0, runner_js_1.spawnCommand)('xcrun', [
@@ -8,9 +8,10 @@ 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 eraseText(characters, opts = {}, sessionName = 'default') {
12
13
  const result = await (0, runner_js_1.runDirect)(async (driver) => {
13
- if (driver instanceof android_js_1.AndroidDriver) {
14
+ if (driver instanceof android_js_1.AndroidDriver || driver instanceof vega_js_1.VegaDriver) {
14
15
  await driver.eraseAllText(characters);
15
16
  }
16
17
  else if (driver instanceof web_js_1.WebDriver) {
@@ -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
  const element_resolver_js_1 = require("../drivers/element-resolver.js");
13
14
  // XCUIElementType rawValue → human-readable name.
14
15
  // Source: XCUIElementType enum in XCTest framework.
@@ -206,7 +207,8 @@ async function queryFocused(driver) {
206
207
  const node = findFocusedWeb(hierarchy.elements);
207
208
  return node ? formatWebElement(node) : null;
208
209
  }
209
- else if (driver instanceof android_js_1.AndroidDriver) {
210
+ else if (driver instanceof android_js_1.AndroidDriver || driver instanceof vega_js_1.VegaDriver) {
211
+ // Vega emits uiautomator-style XML, so it reuses the Android parser.
210
212
  const xml = await driver.viewHierarchy();
211
213
  const nodes = (0, element_resolver_js_1.parseAndroidHierarchy)(xml);
212
214
  const node = nodes.find((n) => n.focused);
@@ -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 resolveDeviceId(sessionName) {
13
14
  if (sessionName !== 'default')
14
15
  return sessionName;
@@ -40,7 +41,7 @@ async function foregroundApp(opts = {}, sessionName = 'default') {
40
41
  else if (driver instanceof web_js_1.WebDriver) {
41
42
  appId = await driver.runningApp();
42
43
  }
43
- else if (driver instanceof android_js_1.AndroidDriver) {
44
+ else if (driver instanceof android_js_1.AndroidDriver || driver instanceof vega_js_1.VegaDriver) {
44
45
  appId = await driver.getForegroundApp();
45
46
  }
46
47
  else {
@@ -19,6 +19,7 @@ const runner_js_1 = require("../runner.js");
19
19
  const output_js_1 = require("../output.js");
20
20
  const ios_js_1 = require("../drivers/ios.js");
21
21
  const web_js_1 = require("../drivers/web.js");
22
+ const vega_js_1 = require("../drivers/vega.js");
22
23
  function parseCenter(s) {
23
24
  if (!s)
24
25
  return null;
@@ -108,6 +109,8 @@ async function pinch(opts, sessionName, pinchOpts) {
108
109
  const result = await (0, runner_js_1.runDirect)(async (driver) => {
109
110
  if (driver instanceof web_js_1.WebDriver)
110
111
  throw new Error('pinch is not supported on Web');
112
+ if (driver instanceof vega_js_1.VegaDriver)
113
+ throw new Error('pinch is not supported on vega (Amazon Fire TV)');
111
114
  const { width, height, cx: defCx, cy: defCy } = await screenCenter(driver);
112
115
  const center = parseCenter(pinchOpts.center) ?? { x: defCx, y: defCy };
113
116
  // Start span is half the shorter screen dimension; end is scaled.
@@ -130,6 +133,8 @@ async function rotateGesture(opts, sessionName, rotateOpts) {
130
133
  const result = await (0, runner_js_1.runDirect)(async (driver) => {
131
134
  if (driver instanceof web_js_1.WebDriver)
132
135
  throw new Error('rotate-gesture is not supported on Web');
136
+ if (driver instanceof vega_js_1.VegaDriver)
137
+ throw new Error('rotate-gesture is not supported on vega (Amazon Fire TV)');
133
138
  const { width, height, cx: defCx, cy: defCy } = await screenCenter(driver);
134
139
  const center = parseCenter(rotateOpts.center) ?? { x: defCx, y: defCy };
135
140
  const radius = Math.min(width, height) * 0.25;
@@ -185,6 +190,8 @@ async function gesture(rawJson, filePath, opts, sessionName) {
185
190
  const result = await (0, runner_js_1.runDirect)(async (driver) => {
186
191
  if (driver instanceof web_js_1.WebDriver)
187
192
  throw new Error('gesture is not supported on Web');
193
+ if (driver instanceof vega_js_1.VegaDriver)
194
+ throw new Error('gesture is not supported on vega (Amazon Fire TV)');
188
195
  await playPaths(driver, paths);
189
196
  }, sessionName);
190
197
  if (result.success) {
@@ -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 hideKeyboard(opts = {}, sessionName = 'default') {
12
13
  const result = await (0, runner_js_1.runDirect)(async (driver) => {
13
14
  if (driver instanceof ios_js_1.IOSDriver) {
@@ -18,6 +19,9 @@ async function hideKeyboard(opts = {}, sessionName = 'default') {
18
19
  else if (driver instanceof web_js_1.WebDriver) {
19
20
  // No virtual keyboard on web — no-op
20
21
  }
22
+ else if (driver instanceof vega_js_1.VegaDriver) {
23
+ // Vega has no keyboard-hide primitive — no-op
24
+ }
21
25
  else if (driver instanceof android_js_1.AndroidDriver) {
22
26
  await driver.pressKeyEvent(111); // KEYCODE_ESCAPE
23
27
  }
@@ -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
  const element_resolver_js_1 = require("../drivers/element-resolver.js");
13
14
  const a11y_js_1 = require("../drivers/a11y.js");
14
15
  async function inspect(opts = {}, sessionName = 'default', inspectOpts = {}) {
@@ -28,7 +29,7 @@ async function inspect(opts = {}, sessionName = 'default', inspectOpts = {}) {
28
29
  const hierarchy = await driver.viewHierarchy(false);
29
30
  hit = (0, element_resolver_js_1.findIOSViewAtPoint)(hierarchy.axElement, x, y, tappable);
30
31
  }
31
- else if (driver instanceof android_js_1.AndroidDriver) {
32
+ else if (driver instanceof android_js_1.AndroidDriver || driver instanceof vega_js_1.VegaDriver) {
32
33
  const xml = await driver.viewHierarchy();
33
34
  hit = (0, element_resolver_js_1.findAndroidViewAtPoint)(xml, x, y, tappable);
34
35
  }
@@ -67,7 +68,7 @@ async function inspect(opts = {}, sessionName = 'default', inspectOpts = {}) {
67
68
  const built = (0, a11y_js_1.buildWebA11y)(vh);
68
69
  raw = JSON.stringify({ ...vh, elements: built.hierarchy }, null, 2);
69
70
  }
70
- else if (driver instanceof android_js_1.AndroidDriver) {
71
+ else if (driver instanceof android_js_1.AndroidDriver || driver instanceof vega_js_1.VegaDriver) {
71
72
  const xml = await driver.viewHierarchy();
72
73
  const built = (0, a11y_js_1.buildAndroidA11y)(xml);
73
74
  raw = JSON.stringify(built.hierarchy, null, 2);
@@ -92,7 +93,7 @@ async function inspect(opts = {}, sessionName = 'default', inspectOpts = {}) {
92
93
  const hierarchy = await driver.viewHierarchy();
93
94
  text = (0, element_resolver_js_1.inspectWebToText)(hierarchy);
94
95
  }
95
- else if (driver instanceof android_js_1.AndroidDriver) {
96
+ else if (driver instanceof android_js_1.AndroidDriver || driver instanceof vega_js_1.VegaDriver) {
96
97
  const xml = await driver.viewHierarchy();
97
98
  text = (0, element_resolver_js_1.inspectAndroidToText)(xml);
98
99
  }
@@ -8,6 +8,7 @@ const sdk_js_1 = require("../android/sdk.js");
8
8
  const session_js_1 = require("../session.js");
9
9
  const output_js_1 = require("../output.js");
10
10
  const bootstrap_js_1 = require("../drivers/bootstrap.js");
11
+ const cli_js_1 = require("../drivers/vega/cli.js");
11
12
  async function resolveDeviceId(sessionName) {
12
13
  if (sessionName !== 'default')
13
14
  return sessionName;
@@ -29,6 +30,16 @@ async function installApp(appPath, opts = {}, sessionName = 'default') {
29
30
  (0, output_js_1.printError)('install-app is not supported on web. Use launch-app with a URL instead.', opts);
30
31
  return 1;
31
32
  }
33
+ else if (platform === 'vega') {
34
+ // Vega installs a `.vpkg` through Amazon's CLI; strip the `vega:` id prefix.
35
+ try {
36
+ await new cli_js_1.VegaCli(deviceId.replace(/^vega:/, '')).installApp(appPath);
37
+ }
38
+ catch (e) {
39
+ (0, output_js_1.printError)(`install-app failed: ${e instanceof Error ? e.message : String(e)}`, opts);
40
+ return 1;
41
+ }
42
+ }
32
43
  else if (platform === 'ios' || platform === 'tvos') {
33
44
  const result = await (0, runner_js_1.spawnCommand)('xcrun', ['simctl', 'install', deviceId, appPath]);
34
45
  if (!result.success) {
@@ -19,6 +19,7 @@ const output_js_1 = require("../output.js");
19
19
  const ios_js_1 = require("../drivers/ios.js");
20
20
  const android_js_1 = require("../drivers/android.js");
21
21
  const web_js_1 = require("../drivers/web.js");
22
+ const vega_js_1 = require("../drivers/vega.js");
22
23
  async function launchApp(appId, deviceId, opts = {}, sessionName = 'default', flags = {}) {
23
24
  if (!appId) {
24
25
  (0, output_js_1.printError)('launch-app requires <appId>', opts);
@@ -40,6 +41,8 @@ async function launchApp(appId, deviceId, opts = {}, sessionName = 'default', fl
40
41
  await driver.terminateApp(appId);
41
42
  else if (driver instanceof web_js_1.WebDriver)
42
43
  await driver.terminateApp();
44
+ else if (driver instanceof vega_js_1.VegaDriver)
45
+ await driver.terminateApp(appId);
43
46
  else if (driver instanceof android_js_1.AndroidDriver)
44
47
  await driver.stopApp(appId);
45
48
  }
@@ -49,6 +52,9 @@ async function launchApp(appId, deviceId, opts = {}, sessionName = 'default', fl
49
52
  else if (driver instanceof web_js_1.WebDriver) {
50
53
  await driver.launchApp(appId);
51
54
  }
55
+ else if (driver instanceof vega_js_1.VegaDriver) {
56
+ await driver.launchApp(appId, flags.launchArgs);
57
+ }
52
58
  else if (driver instanceof android_js_1.AndroidDriver) {
53
59
  await driver.launchApp(appId, flags.launchArgs);
54
60
  }
@@ -8,6 +8,7 @@ const sdk_js_1 = require("../android/sdk.js");
8
8
  const session_js_1 = require("../session.js");
9
9
  const output_js_1 = require("../output.js");
10
10
  const bootstrap_js_1 = require("../drivers/bootstrap.js");
11
+ const cli_js_1 = require("../drivers/vega/cli.js");
11
12
  async function resolveDeviceId(sessionName) {
12
13
  if (sessionName !== 'default')
13
14
  return sessionName;
@@ -26,6 +27,10 @@ async function listApps(opts = {}, sessionName = 'default') {
26
27
  (0, output_js_1.printError)('list-apps is not supported on web. Use foreground-app to get the current URL.', opts);
27
28
  return 1;
28
29
  }
30
+ else if (platform === 'vega') {
31
+ // Vega is driven through Amazon's CLI, not adb; strip the `vega:` id prefix.
32
+ appIds = (await new cli_js_1.VegaCli(deviceId.replace(/^vega:/, '')).listInstalledApps()).sort();
33
+ }
29
34
  else if (platform === 'ios' || platform === 'tvos') {
30
35
  const result = await (0, runner_js_1.spawnCommand)('bash', [
31
36
  '-c',
@@ -15,6 +15,7 @@ const output_js_1 = require("../output.js");
15
15
  const bootstrap_js_1 = require("../drivers/bootstrap.js");
16
16
  const client_js_1 = require("../daemon/client.js");
17
17
  const protocol_js_1 = require("../daemon/protocol.js");
18
+ const cli_js_1 = require("../drivers/vega/cli.js");
18
19
  // Captured during discoverAvailableDevices so listDevices() can report it.
19
20
  // Module-scoped because the discover function returns Device[]; bolting an
20
21
  // extra return field onto the public type would ripple beyond this fix.
@@ -88,6 +89,21 @@ async function discoverBootedDevices() {
88
89
  }
89
90
  }
90
91
  }
92
+ // Vega (Amazon Fire TV): booted devices reported by the vega CLI. Best-effort —
93
+ // the CLI is absent unless the Vega SDK is installed.
94
+ try {
95
+ for (const d of await new cli_js_1.VegaCli().listDevices()) {
96
+ devices.push({
97
+ id: `vega:${d.serial}`,
98
+ name: d.description,
99
+ platform: 'vega',
100
+ status: 'booted',
101
+ });
102
+ }
103
+ }
104
+ catch {
105
+ /* vega CLI not installed */
106
+ }
91
107
  return devices;
92
108
  }
93
109
  async function discoverAvailableDevices() {
@@ -1116,6 +1116,11 @@ async function memory(appIdArg, opts = {}, sessionName = 'default', memOpts = {}
1116
1116
  return 1;
1117
1117
  }
1118
1118
  const platform = await (0, bootstrap_js_1.detectPlatform)(deviceId);
1119
+ if (platform === 'vega') {
1120
+ // Memory relies on adb/simctl host tooling that has no Vega equivalent yet.
1121
+ (0, output_js_1.printError)('memory is not yet supported on vega (Amazon Fire TV)', opts);
1122
+ return 1;
1123
+ }
1119
1124
  let report;
1120
1125
  if (platform === 'web') {
1121
1126
  report = await collectWeb(deviceId, sessionName, memOpts);
@@ -2,12 +2,15 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VALID_KEYS = exports.HELP = void 0;
4
4
  exports.pressKey = pressKey;
5
- exports.HELP = ` press-key <key> Press a key (Enter, Backspace, Home, ...)`;
5
+ exports.HELP = ` press-key <key> Press a key (Enter, Backspace, Home, ...)
6
+ --long-press Hold the button ~1.5s (tvOS remote buttons)
7
+ --duration <seconds> Hold for a custom duration (tvOS remote buttons)`;
6
8
  const runner_js_1 = require("../runner.js");
7
9
  const output_js_1 = require("../output.js");
8
10
  const ios_js_1 = require("../drivers/ios.js");
9
11
  const android_js_1 = require("../drivers/android.js");
10
12
  const web_js_1 = require("../drivers/web.js");
13
+ const vega_js_1 = require("../drivers/vega.js");
11
14
  exports.VALID_KEYS = [
12
15
  'Enter',
13
16
  'Backspace',
@@ -67,6 +70,22 @@ const TVOS_REMOTE_BUTTONS = {
67
70
  'Remote Menu': 'menu',
68
71
  'Remote Media Play Pause': 'playPause',
69
72
  };
73
+ // vega (Amazon Fire TV) remote: map key names to VegaDriver button values.
74
+ const VEGA_REMOTE_BUTTONS = {
75
+ 'Remote Dpad Up': 'up',
76
+ 'Remote Dpad Down': 'down',
77
+ 'Remote Dpad Left': 'left',
78
+ 'Remote Dpad Right': 'right',
79
+ 'Remote Dpad Center': 'select',
80
+ 'Remote Menu': 'menu',
81
+ 'Remote Media Play Pause': 'playPause',
82
+ 'Remote Media Rewind': 'rewind',
83
+ 'Remote Media Fast Forward': 'fastForward',
84
+ Home: 'home',
85
+ Back: 'back',
86
+ VolumeUp: 'volumeUp',
87
+ VolumeDown: 'volumeDown',
88
+ };
70
89
  // Android keyevent codes
71
90
  const ANDROID_KEYCODE = {
72
91
  Home: 3,
@@ -105,7 +124,9 @@ const ANDROID_KEYCODE = {
105
124
  'TV Input HDMI 2': 244,
106
125
  'TV Input HDMI 3': 245,
107
126
  };
108
- async function pressKey(key, opts = {}, sessionName = 'default') {
127
+ async function pressKey(key, opts = {}, sessionName = 'default', flags = {}) {
128
+ // A held press is requested via --long-press (default 1.5s) or an explicit --duration.
129
+ const holdSeconds = flags.duration ?? (flags.longPress ? 1.5 : undefined);
109
130
  if (!key) {
110
131
  (0, output_js_1.printError)(`press-key requires <key>. Valid keys: ${exports.VALID_KEYS.join(', ')}`, opts);
111
132
  return 1;
@@ -121,10 +142,10 @@ async function pressKey(key, opts = {}, sessionName = 'default') {
121
142
  const tvosButton = TVOS_REMOTE_BUTTONS[matched];
122
143
  const iosButton = IOS_BUTTON_MAP[matched];
123
144
  if (tvosButton) {
124
- await driver.pressButton(tvosButton);
145
+ await driver.pressButton(tvosButton, holdSeconds);
125
146
  }
126
147
  else if (iosButton) {
127
- await driver.pressButton(iosButton);
148
+ await driver.pressButton(iosButton, holdSeconds);
128
149
  }
129
150
  // Keys not mapped on tvOS are silently ignored
130
151
  }
@@ -162,6 +183,13 @@ async function pressKey(key, opts = {}, sessionName = 'default') {
162
183
  await driver.pressKey(webKey);
163
184
  }
164
185
  }
186
+ else if (driver instanceof vega_js_1.VegaDriver) {
187
+ const vegaButton = VEGA_REMOTE_BUTTONS[matched];
188
+ if (vegaButton) {
189
+ await driver.pressButton(vegaButton);
190
+ }
191
+ // Keys not mapped on vega are silently ignored
192
+ }
165
193
  else if (driver instanceof android_js_1.AndroidDriver) {
166
194
  const code = ANDROID_KEYCODE[matched];
167
195
  if (code !== undefined) {
@@ -99,6 +99,11 @@ async function profileCpu(opts, sessionName, profileOpts) {
99
99
  else if (platform === 'android') {
100
100
  await recordAndroidCpu(sessionName, profileOpts.appId, profileOpts.durationSec, out);
101
101
  }
102
+ else if (platform === 'vega') {
103
+ // No native CPU-trace tooling (xctrace/simpleperf) on Vega yet.
104
+ (0, output_js_1.printError)('profile cpu is not yet supported on vega (Amazon Fire TV)', opts);
105
+ return 1;
106
+ }
102
107
  else {
103
108
  (0, output_js_1.printError)(`profile cpu is not supported on platform ${platform ?? '(unknown)'}`, opts);
104
109
  return 1;
@@ -27,6 +27,7 @@ const output_js_1 = require("../output.js");
27
27
  const ios_js_1 = require("../drivers/ios.js");
28
28
  const android_js_1 = require("../drivers/android.js");
29
29
  const web_js_1 = require("../drivers/web.js");
30
+ const vega_js_1 = require("../drivers/vega.js");
30
31
  const wait_js_1 = require("../drivers/wait.js");
31
32
  const direct_ios_selector_js_1 = require("../drivers/direct-ios-selector.js");
32
33
  const png_crop_js_1 = require("../png-crop.js");
@@ -84,7 +85,8 @@ async function screenshot(outputPath, opts = {}, sessionName = 'default', fullPa
84
85
  hierarchyH = info.heightPixels;
85
86
  el = await (0, wait_js_1.waitForWebElement)(() => driver.viewHierarchy(), sel);
86
87
  }
87
- else if (driver instanceof android_js_1.AndroidDriver) {
88
+ else if (driver instanceof android_js_1.AndroidDriver || driver instanceof vega_js_1.VegaDriver) {
89
+ // Vega emits uiautomator-style XML, so it reuses the Android resolver.
88
90
  const xml = await driver.viewHierarchy();
89
91
  // Android XML root bounds: derive from the first parseable <node bounds="[0,0][W,H]">
90
92
  const m = xml.match(/<node[^>]*bounds="\[0,0\]\[(\d+),(\d+)\]"/);
@@ -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 element_resolver_js_1 = require("../drivers/element-resolver.js");
16
17
  const direct_ios_selector_js_1 = require("../drivers/direct-ios-selector.js");
17
18
  const utils_js_1 = require("../utils.js");
@@ -77,7 +78,8 @@ async function scrollUntilVisible(element, opts = {}, sessionName = 'default', f
77
78
  const { widthPixels: w, heightPixels: h } = info;
78
79
  await driver.swipe(coords.startX * w, coords.startY * h, coords.endX * w, coords.endY * h, 500);
79
80
  }
80
- else if (driver instanceof android_js_1.AndroidDriver) {
81
+ else if (driver instanceof android_js_1.AndroidDriver || driver instanceof vega_js_1.VegaDriver) {
82
+ // Vega emits uiautomator-style XML, so it reuses the Android resolver.
81
83
  const xml = await driver.viewHierarchy();
82
84
  if ((0, element_resolver_js_1.findAndroidElement)(xml, sel)) {
83
85
  (0, output_js_1.printSuccess)(`scroll-until-visible ${label} — found`, opts);
@@ -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
  const utils_js_1 = require("../utils.js");
12
13
  async function scroll(direction = 'down', opts = {}, sessionName = 'default') {
13
14
  const valid = ['down', 'up', 'left', 'right'];
@@ -31,7 +32,7 @@ async function scroll(direction = 'down', opts = {}, sessionName = 'default') {
31
32
  const { widthPixels: w, heightPixels: h } = info;
32
33
  await driver.swipe(coords.startX * w, coords.startY * h, coords.endX * w, coords.endY * h, 500);
33
34
  }
34
- else if (driver instanceof android_js_1.AndroidDriver) {
35
+ else if (driver instanceof android_js_1.AndroidDriver || driver instanceof vega_js_1.VegaDriver) {
35
36
  const info = await driver.deviceInfo();
36
37
  const { widthPixels: w, heightPixels: h } = info;
37
38
  await driver.swipe(coords.startX * w, coords.startY * h, coords.endX * w, coords.endY * h, 500);