@houwert/conductor 0.2.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 (60) hide show
  1. package/.claude-plugin/plugin.json +6 -0
  2. package/README.md +39 -0
  3. package/dist/commands/assert-not-visible.js +47 -0
  4. package/dist/commands/assert-visible.js +58 -0
  5. package/dist/commands/back.js +25 -0
  6. package/dist/commands/cheat-sheet.js +100 -0
  7. package/dist/commands/daemon.js +61 -0
  8. package/dist/commands/device-pool.js +202 -0
  9. package/dist/commands/erase-text.js +26 -0
  10. package/dist/commands/foreground-app.js +50 -0
  11. package/dist/commands/hide-keyboard.js +27 -0
  12. package/dist/commands/inspect.js +37 -0
  13. package/dist/commands/install.js +64 -0
  14. package/dist/commands/launch-app.js +42 -0
  15. package/dist/commands/list-apps.js +60 -0
  16. package/dist/commands/list-devices.js +61 -0
  17. package/dist/commands/open-link.js +22 -0
  18. package/dist/commands/press-key.js +91 -0
  19. package/dist/commands/run-flow-inline.js +25 -0
  20. package/dist/commands/run-flow.js +29 -0
  21. package/dist/commands/run-parallel.js +143 -0
  22. package/dist/commands/screenshot.js +29 -0
  23. package/dist/commands/scroll-until-visible.js +69 -0
  24. package/dist/commands/scroll.js +36 -0
  25. package/dist/commands/session.js +49 -0
  26. package/dist/commands/set-location.js +18 -0
  27. package/dist/commands/set-orientation.js +23 -0
  28. package/dist/commands/start-device.js +178 -0
  29. package/dist/commands/stop-app.js +32 -0
  30. package/dist/commands/swipe.js +72 -0
  31. package/dist/commands/tap.js +69 -0
  32. package/dist/commands/type.js +22 -0
  33. package/dist/daemon/client.js +112 -0
  34. package/dist/daemon/protocol.js +25 -0
  35. package/dist/daemon/server.js +208 -0
  36. package/dist/drivers/android.js +343 -0
  37. package/dist/drivers/bootstrap.js +371 -0
  38. package/dist/drivers/element-resolver.js +371 -0
  39. package/dist/drivers/flow-runner.js +1309 -0
  40. package/dist/drivers/ios.js +328 -0
  41. package/dist/drivers/js-engine.js +150 -0
  42. package/dist/drivers/wait.js +211 -0
  43. package/dist/index.js +426 -0
  44. package/dist/output.js +36 -0
  45. package/dist/pkg-root.js +28 -0
  46. package/dist/postinstall.js +12 -0
  47. package/dist/runner.js +190 -0
  48. package/dist/session.js +66 -0
  49. package/dist/update-check.js +109 -0
  50. package/dist/utils.js +19 -0
  51. package/dist/verbose.js +17 -0
  52. package/drivers/android/conductor-app.apk +0 -0
  53. package/drivers/android/conductor-server.apk +0 -0
  54. package/drivers/ios/conductor-driver-ios-config.xctestrun +126 -0
  55. package/drivers/ios/conductor-driver-ios.zip +0 -0
  56. package/drivers/ios/conductor-driver-iosUITests-Runner.zip +0 -0
  57. package/package.json +52 -0
  58. package/proto/conductor_android.proto +116 -0
  59. package/skills/conductor/SKILL.md +677 -0
  60. package/skills/conductor/references/flow-syntax.md +179 -0
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.scrollUntilVisible = scrollUntilVisible;
4
+ const runner_js_1 = require("../runner.js");
5
+ const output_js_1 = require("../output.js");
6
+ const ios_js_1 = require("../drivers/ios.js");
7
+ const android_js_1 = require("../drivers/android.js");
8
+ const element_resolver_js_1 = require("../drivers/element-resolver.js");
9
+ const utils_js_1 = require("../utils.js");
10
+ async function scrollUntilVisible(element, opts = {}, sessionName = 'default', flags = {}) {
11
+ if (!element && !flags.id && !flags.text) {
12
+ (0, output_js_1.printError)('scroll-until-visible requires <element> or --id <id>', opts);
13
+ return 1;
14
+ }
15
+ const sel = {
16
+ ...(flags.text ? { text: flags.text } : flags.id ? { id: flags.id } : { query: element }),
17
+ ...(flags.index !== undefined && { index: flags.index }),
18
+ ...(flags.focused !== undefined && { focused: flags.focused }),
19
+ ...(flags.enabled !== undefined && { enabled: flags.enabled }),
20
+ ...(flags.checked !== undefined && { checked: flags.checked }),
21
+ ...(flags.selected !== undefined && { selected: flags.selected }),
22
+ };
23
+ const label = flags.text
24
+ ? `text="${flags.text}"`
25
+ : flags.id
26
+ ? `id="${flags.id}"`
27
+ : `"${element}"`;
28
+ const direction = flags.direction ?? 'down';
29
+ const timeoutMs = flags.timeout ?? 30000;
30
+ const coords = (0, utils_js_1.swipeCoords)(direction);
31
+ try {
32
+ const driver = await (0, runner_js_1.getDriver)(sessionName);
33
+ const deadline = Date.now() + timeoutMs;
34
+ while (Date.now() < deadline) {
35
+ try {
36
+ if (driver instanceof ios_js_1.IOSDriver) {
37
+ const root = await driver.viewHierarchy().then((h) => h.axElement);
38
+ if ((0, element_resolver_js_1.findIOSElement)(root, sel)) {
39
+ (0, output_js_1.printSuccess)(`scroll-until-visible ${label} — found`, opts);
40
+ return 0;
41
+ }
42
+ const info = await driver.deviceInfo();
43
+ const { widthPoints: w, heightPoints: h } = info;
44
+ await driver.swipe(coords.startX * w, coords.startY * h, coords.endX * w, coords.endY * h, 0.5);
45
+ }
46
+ else if (driver instanceof android_js_1.AndroidDriver) {
47
+ const xml = await driver.viewHierarchy();
48
+ if ((0, element_resolver_js_1.findAndroidElement)(xml, sel)) {
49
+ (0, output_js_1.printSuccess)(`scroll-until-visible ${label} — found`, opts);
50
+ return 0;
51
+ }
52
+ const info = await driver.deviceInfo();
53
+ const { widthPixels: w, heightPixels: h } = info;
54
+ await driver.swipe(coords.startX * w, coords.startY * h, coords.endX * w, coords.endY * h, 500);
55
+ }
56
+ }
57
+ catch {
58
+ // hierarchy fetch failed — keep trying
59
+ }
60
+ }
61
+ (0, output_js_1.printError)(`scroll-until-visible ${label} — element not found after ${timeoutMs}ms`, opts);
62
+ return 1;
63
+ }
64
+ catch (err) {
65
+ const msg = err instanceof Error ? err.message : String(err);
66
+ (0, output_js_1.printError)(`scroll-until-visible ${label} — ${msg}`, opts);
67
+ return 1;
68
+ }
69
+ }
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.scroll = scroll;
4
+ const runner_js_1 = require("../runner.js");
5
+ const output_js_1 = require("../output.js");
6
+ const ios_js_1 = require("../drivers/ios.js");
7
+ const android_js_1 = require("../drivers/android.js");
8
+ const utils_js_1 = require("../utils.js");
9
+ async function scroll(direction = 'down', opts = {}, sessionName = 'default') {
10
+ const valid = ['down', 'up', 'left', 'right'];
11
+ if (!valid.includes(direction)) {
12
+ (0, output_js_1.printError)(`scroll --direction must be one of: ${valid.join(', ')}`, opts);
13
+ return 1;
14
+ }
15
+ const result = await (0, runner_js_1.runDirect)(async (driver) => {
16
+ const coords = (0, utils_js_1.swipeCoords)(direction);
17
+ if (driver instanceof ios_js_1.IOSDriver) {
18
+ const info = await driver.deviceInfo();
19
+ const { widthPoints: w, heightPoints: h } = info;
20
+ await driver.swipe(coords.startX * w, coords.startY * h, coords.endX * w, coords.endY * h, 0.5);
21
+ }
22
+ else if (driver instanceof android_js_1.AndroidDriver) {
23
+ const info = await driver.deviceInfo();
24
+ const { widthPixels: w, heightPixels: h } = info;
25
+ await driver.swipe(coords.startX * w, coords.startY * h, coords.endX * w, coords.endY * h, 500);
26
+ }
27
+ }, sessionName);
28
+ if (result.success) {
29
+ (0, output_js_1.printSuccess)(`scroll ${direction} — done`, opts);
30
+ return 0;
31
+ }
32
+ else {
33
+ (0, output_js_1.printError)(`scroll ${direction} — failed\n${result.stderr}`, opts);
34
+ return 1;
35
+ }
36
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sessionCmd = sessionCmd;
4
+ const session_js_1 = require("../session.js");
5
+ const output_js_1 = require("../output.js");
6
+ async function sessionCmd(clear, list, opts = {}, sessionName = 'default') {
7
+ if (list) {
8
+ const sessions = await (0, session_js_1.listSessions)();
9
+ if (opts.json) {
10
+ (0, output_js_1.printData)({ status: 'ok', sessions }, opts);
11
+ }
12
+ else if (sessions.length === 0) {
13
+ console.log('No sessions found.');
14
+ }
15
+ else {
16
+ console.log('Sessions:');
17
+ for (const name of sessions) {
18
+ const s = await (0, session_js_1.getSession)(name);
19
+ const marker = name === sessionName ? ' (current)' : '';
20
+ console.log(` ${name}${marker} appId=${s.appId ?? '—'} deviceId=${s.deviceId ?? '—'}`);
21
+ }
22
+ }
23
+ return 0;
24
+ }
25
+ if (clear) {
26
+ await (0, session_js_1.clearSession)(sessionName);
27
+ (0, output_js_1.printSuccess)(`session "${sessionName}" cleared`, opts);
28
+ return 0;
29
+ }
30
+ const session = await (0, session_js_1.getSession)(sessionName);
31
+ const filePath = (0, session_js_1.sessionFilePath)(sessionName);
32
+ if (opts.json) {
33
+ (0, output_js_1.printData)({ status: 'ok', sessionName, session, file: filePath }, opts);
34
+ }
35
+ else {
36
+ console.log(`Session: ${sessionName}`);
37
+ console.log(`File: ${filePath}`);
38
+ if (Object.keys(session).length === 0) {
39
+ console.log('No active session.');
40
+ }
41
+ else {
42
+ if (session.appId)
43
+ console.log(` appId: ${session.appId}`);
44
+ if (session.deviceId)
45
+ console.log(` deviceId: ${session.deviceId}`);
46
+ }
47
+ }
48
+ return 0;
49
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setLocation = setLocation;
4
+ const runner_js_1 = require("../runner.js");
5
+ const output_js_1 = require("../output.js");
6
+ async function setLocation(latitude, longitude, opts = {}, sessionName = 'default') {
7
+ const result = await (0, runner_js_1.runDirect)(async (driver) => {
8
+ await driver.setLocation(latitude, longitude);
9
+ }, sessionName);
10
+ if (result.success) {
11
+ (0, output_js_1.printSuccess)(`set-location ${latitude},${longitude} — done`, opts);
12
+ return 0;
13
+ }
14
+ else {
15
+ (0, output_js_1.printError)(`set-location ${latitude},${longitude} — failed\n${result.stderr}`, opts);
16
+ return 1;
17
+ }
18
+ }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setOrientation = setOrientation;
4
+ const runner_js_1 = require("../runner.js");
5
+ const output_js_1 = require("../output.js");
6
+ const VALID = ['portrait', 'landscape'];
7
+ async function setOrientation(orientation, opts = {}, sessionName = 'default') {
8
+ if (!VALID.includes(orientation.toLowerCase())) {
9
+ (0, output_js_1.printError)(`set-orientation must be one of: ${VALID.join(', ')}`, opts);
10
+ return 1;
11
+ }
12
+ const result = await (0, runner_js_1.runDirect)(async (driver) => {
13
+ await driver.setOrientation(orientation.toLowerCase());
14
+ }, sessionName);
15
+ if (result.success) {
16
+ (0, output_js_1.printSuccess)(`set-orientation ${orientation} — done`, opts);
17
+ return 0;
18
+ }
19
+ else {
20
+ (0, output_js_1.printError)(`set-orientation ${orientation} — failed\n${result.stderr}`, opts);
21
+ return 1;
22
+ }
23
+ }
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startDevice = startDevice;
4
+ const child_process_1 = require("child_process");
5
+ const runner_js_1 = require("../runner.js");
6
+ const output_js_1 = require("../output.js");
7
+ const utils_js_1 = require("../utils.js");
8
+ const IOS_BOOT_TIMEOUT_MS = 120000;
9
+ const ANDROID_BOOT_TIMEOUT_MS = 120000;
10
+ const POLL_MS = 1000;
11
+ async function listIOSSimulators() {
12
+ const result = await (0, runner_js_1.spawnCommand)('xcrun', ['simctl', 'list', 'devices', '--json']);
13
+ if (!result.success)
14
+ throw new Error(`xcrun simctl list failed: ${result.stderr}`);
15
+ const parsed = JSON.parse(result.stdout);
16
+ return parsed.devices;
17
+ }
18
+ async function bootIOSSimulator(udid) {
19
+ const result = await (0, runner_js_1.spawnCommand)('xcrun', ['simctl', 'boot', udid]);
20
+ // exit 149 = already booted, that's fine
21
+ if (!result.success && !result.stderr.includes('already booted')) {
22
+ throw new Error(`xcrun simctl boot failed: ${result.stderr.trim()}`);
23
+ }
24
+ }
25
+ async function waitForIOSBoot(udid) {
26
+ const deadline = Date.now() + IOS_BOOT_TIMEOUT_MS;
27
+ while (Date.now() < deadline) {
28
+ const result = await (0, runner_js_1.spawnCommand)('xcrun', ['simctl', 'list', 'devices', 'booted', '--json']);
29
+ if (result.success) {
30
+ const parsed = JSON.parse(result.stdout);
31
+ const booted = Object.values(parsed.devices).flat().some((d) => d.udid === udid);
32
+ if (booted)
33
+ return;
34
+ }
35
+ await (0, utils_js_1.sleep)(POLL_MS);
36
+ }
37
+ throw new Error(`Simulator ${udid} did not boot within ${IOS_BOOT_TIMEOUT_MS / 1000}s`);
38
+ }
39
+ async function startIOS(osVersion, opts) {
40
+ let devices;
41
+ try {
42
+ devices = await listIOSSimulators();
43
+ }
44
+ catch (e) {
45
+ (0, output_js_1.printError)(`Failed to list simulators: ${e instanceof Error ? e.message : String(e)}`, opts);
46
+ return 1;
47
+ }
48
+ // Filter to available (installable) simulators, optionally by OS version
49
+ const candidates = [];
50
+ for (const [runtime, sims] of Object.entries(devices)) {
51
+ if (osVersion && !runtime.includes(osVersion))
52
+ continue;
53
+ for (const sim of sims) {
54
+ if (sim.isAvailable && sim.state !== 'Booted') {
55
+ candidates.push({ runtime, device: sim });
56
+ }
57
+ // If already booted, just report it
58
+ if (sim.isAvailable && sim.state === 'Booted') {
59
+ if (!osVersion || runtime.includes(osVersion)) {
60
+ (0, output_js_1.printSuccess)(`Simulator already booted: ${sim.name} (${sim.udid})`, opts);
61
+ return 0;
62
+ }
63
+ }
64
+ }
65
+ }
66
+ if (candidates.length === 0) {
67
+ const hint = osVersion ? ` for iOS ${osVersion}` : '';
68
+ (0, output_js_1.printError)(`No available iOS simulator found${hint}. Install one via Xcode → Settings → Platforms.`, opts);
69
+ return 1;
70
+ }
71
+ // Prefer iPhone models over iPad
72
+ const sorted = candidates.sort((a, b) => {
73
+ const ai = a.device.name.toLowerCase().includes('iphone') ? 0 : 1;
74
+ const bi = b.device.name.toLowerCase().includes('iphone') ? 0 : 1;
75
+ return ai - bi;
76
+ });
77
+ const { device } = sorted[0];
78
+ console.log(`Booting simulator: ${device.name} (${device.udid})...`);
79
+ try {
80
+ await bootIOSSimulator(device.udid);
81
+ await waitForIOSBoot(device.udid);
82
+ }
83
+ catch (e) {
84
+ (0, output_js_1.printError)(e instanceof Error ? e.message : String(e), opts);
85
+ return 1;
86
+ }
87
+ // Open the Simulator.app so the window appears
88
+ (0, child_process_1.spawn)('open', ['-a', 'Simulator'], { detached: true, stdio: 'ignore' }).unref();
89
+ (0, output_js_1.printSuccess)(`Booted: ${device.name} (${device.udid})`, opts);
90
+ return 0;
91
+ }
92
+ // ── Android ───────────────────────────────────────────────────────────────────
93
+ async function listAVDs() {
94
+ const result = await (0, runner_js_1.spawnCommand)('emulator', ['-list-avds']);
95
+ if (!result.success)
96
+ throw new Error(`emulator -list-avds failed: ${result.stderr}`);
97
+ return result.stdout.split('\n').map((l) => l.trim()).filter(Boolean);
98
+ }
99
+ async function waitForAndroidBoot(avdName) {
100
+ const deadline = Date.now() + ANDROID_BOOT_TIMEOUT_MS;
101
+ const connectedBefore = new Set();
102
+ // Snapshot currently connected devices so we can identify the new one
103
+ const before = await (0, runner_js_1.spawnCommand)('adb', ['devices']);
104
+ for (const line of before.stdout.split('\n').slice(1)) {
105
+ const id = line.trim().split(/\s+/)[0];
106
+ if (id)
107
+ connectedBefore.add(id);
108
+ }
109
+ while (Date.now() < deadline) {
110
+ await (0, utils_js_1.sleep)(POLL_MS);
111
+ const result = await (0, runner_js_1.spawnCommand)('adb', ['devices']);
112
+ if (!result.success)
113
+ continue;
114
+ for (const line of result.stdout.split('\n').slice(1)) {
115
+ const parts = line.trim().split(/\s+/);
116
+ const id = parts[0];
117
+ const status = parts[1];
118
+ if (id && status === 'device' && !connectedBefore.has(id)) {
119
+ // Check boot completed
120
+ const boot = await (0, runner_js_1.spawnCommand)('adb', ['-s', id, 'shell', 'getprop', 'sys.boot_completed']);
121
+ if (boot.stdout.trim() === '1')
122
+ return id;
123
+ }
124
+ }
125
+ }
126
+ throw new Error(`Android emulator (${avdName}) did not appear within ${ANDROID_BOOT_TIMEOUT_MS / 1000}s`);
127
+ }
128
+ async function startAndroid(avdName, opts) {
129
+ let avds;
130
+ try {
131
+ avds = await listAVDs();
132
+ }
133
+ catch (e) {
134
+ (0, output_js_1.printError)(`Failed to list AVDs: ${e instanceof Error ? e.message : String(e)}`, opts);
135
+ return 1;
136
+ }
137
+ if (avds.length === 0) {
138
+ (0, output_js_1.printError)('No Android AVDs found. Create one in Android Studio → Device Manager.', opts);
139
+ return 1;
140
+ }
141
+ const target = avdName ?? avds[0];
142
+ if (!avds.includes(target)) {
143
+ (0, output_js_1.printError)(`AVD "${target}" not found. Available: ${avds.join(', ')}`, opts);
144
+ return 1;
145
+ }
146
+ console.log(`Launching emulator: ${target}...`);
147
+ const proc = (0, child_process_1.spawn)('emulator', ['-avd', target, '-netdelay', 'none', '-netspeed', 'full'], {
148
+ detached: true,
149
+ stdio: 'ignore',
150
+ });
151
+ proc.unref();
152
+ let deviceId;
153
+ try {
154
+ deviceId = await waitForAndroidBoot(target);
155
+ }
156
+ catch (e) {
157
+ (0, output_js_1.printError)(e instanceof Error ? e.message : String(e), opts);
158
+ return 1;
159
+ }
160
+ (0, output_js_1.printSuccess)(`Emulator ready: ${target} (${deviceId})`, opts);
161
+ return 0;
162
+ }
163
+ // ── Entry point ───────────────────────────────────────────────────────────────
164
+ async function startDevice(platform, opts, flags) {
165
+ if (!platform) {
166
+ (0, output_js_1.printError)('start-device requires --platform ios|android', opts);
167
+ return 1;
168
+ }
169
+ switch (platform.toLowerCase()) {
170
+ case 'ios':
171
+ return startIOS(flags.osVersion, opts);
172
+ case 'android':
173
+ return startAndroid(flags.avd, opts);
174
+ default:
175
+ (0, output_js_1.printError)(`Unknown platform "${platform}". Use ios or android.`, opts);
176
+ return 1;
177
+ }
178
+ }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stopApp = stopApp;
4
+ const runner_js_1 = require("../runner.js");
5
+ const session_js_1 = require("../session.js");
6
+ const output_js_1 = require("../output.js");
7
+ const ios_js_1 = require("../drivers/ios.js");
8
+ const android_js_1 = require("../drivers/android.js");
9
+ async function stopApp(appId, opts = {}, sessionName = 'default') {
10
+ const session = await (0, session_js_1.getSession)(sessionName);
11
+ const resolvedAppId = appId ?? session.appId;
12
+ if (!resolvedAppId) {
13
+ (0, output_js_1.printError)('stop-app: no appId provided and no active session. Run launch-app first.', opts);
14
+ return 1;
15
+ }
16
+ const result = await (0, runner_js_1.runDirect)(async (driver) => {
17
+ if (driver instanceof ios_js_1.IOSDriver) {
18
+ await driver.terminateApp(resolvedAppId);
19
+ }
20
+ else if (driver instanceof android_js_1.AndroidDriver) {
21
+ await driver.stopApp(resolvedAppId);
22
+ }
23
+ }, sessionName);
24
+ if (result.success) {
25
+ (0, output_js_1.printSuccess)(`stop-app "${resolvedAppId}" — done`, opts);
26
+ return 0;
27
+ }
28
+ else {
29
+ (0, output_js_1.printError)(`stop-app "${resolvedAppId}" — failed\n${result.stderr}`, opts);
30
+ return 1;
31
+ }
32
+ }
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.swipe = swipe;
4
+ const runner_js_1 = require("../runner.js");
5
+ const output_js_1 = require("../output.js");
6
+ const ios_js_1 = require("../drivers/ios.js");
7
+ const android_js_1 = require("../drivers/android.js");
8
+ const utils_js_1 = require("../utils.js");
9
+ function parseCoordPair(s) {
10
+ const [xs, ys] = s.split(',').map((p) => p.trim());
11
+ return { x: parseFloat(xs), y: parseFloat(ys) };
12
+ }
13
+ async function swipe(direction, opts = {}, sessionName = 'default', flags = {}) {
14
+ if (!direction && !(flags.start && flags.end)) {
15
+ (0, output_js_1.printError)('swipe requires --direction <up|down|left|right> or --start <x,y> --end <x,y>', opts);
16
+ return 1;
17
+ }
18
+ const result = await (0, runner_js_1.runDirect)(async (driver) => {
19
+ let startX, startY, endX, endY;
20
+ if (driver instanceof ios_js_1.IOSDriver) {
21
+ const { widthPoints: w, heightPoints: h } = await driver.deviceInfo();
22
+ const durationSec = (flags.duration ?? 500) / 1000;
23
+ if (flags.start && flags.end) {
24
+ const s = parseCoordPair(flags.start);
25
+ const e = parseCoordPair(flags.end);
26
+ startX = s.x <= 1 ? s.x * w : s.x;
27
+ startY = s.y <= 1 ? s.y * h : s.y;
28
+ endX = e.x <= 1 ? e.x * w : e.x;
29
+ endY = e.y <= 1 ? e.y * h : e.y;
30
+ }
31
+ else {
32
+ const normalized = direction.toLowerCase();
33
+ const coords = (0, utils_js_1.swipeCoords)(normalized);
34
+ startX = coords.startX * w;
35
+ startY = coords.startY * h;
36
+ endX = coords.endX * w;
37
+ endY = coords.endY * h;
38
+ }
39
+ await driver.swipe(startX, startY, endX, endY, durationSec);
40
+ }
41
+ else if (driver instanceof android_js_1.AndroidDriver) {
42
+ const { widthPixels: w, heightPixels: h } = await driver.deviceInfo();
43
+ const durationMs = flags.duration ?? 500;
44
+ if (flags.start && flags.end) {
45
+ const s = parseCoordPair(flags.start);
46
+ const e = parseCoordPair(flags.end);
47
+ startX = s.x <= 1 ? s.x * w : s.x;
48
+ startY = s.y <= 1 ? s.y * h : s.y;
49
+ endX = e.x <= 1 ? e.x * w : e.x;
50
+ endY = e.y <= 1 ? e.y * h : e.y;
51
+ }
52
+ else {
53
+ const normalized = direction.toLowerCase();
54
+ const coords = (0, utils_js_1.swipeCoords)(normalized);
55
+ startX = coords.startX * w;
56
+ startY = coords.startY * h;
57
+ endX = coords.endX * w;
58
+ endY = coords.endY * h;
59
+ }
60
+ await driver.swipe(startX, startY, endX, endY, durationMs);
61
+ }
62
+ }, sessionName);
63
+ const label = flags.start && flags.end ? `from ${flags.start} to ${flags.end}` : direction.toLowerCase();
64
+ if (result.success) {
65
+ (0, output_js_1.printSuccess)(`swipe ${label} — done`, opts);
66
+ return 0;
67
+ }
68
+ else {
69
+ (0, output_js_1.printError)(`swipe ${label} — failed\n${result.stderr}`, opts);
70
+ return 1;
71
+ }
72
+ }
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.tap = tap;
4
+ const runner_js_1 = require("../runner.js");
5
+ const output_js_1 = require("../output.js");
6
+ const ios_js_1 = require("../drivers/ios.js");
7
+ const android_js_1 = require("../drivers/android.js");
8
+ const wait_js_1 = require("../drivers/wait.js");
9
+ const utils_js_1 = require("../utils.js");
10
+ async function tap(query, opts = {}, sessionName = 'default', flags = {}) {
11
+ if (!query && !flags.id && !flags.text) {
12
+ (0, output_js_1.printError)('tap requires <element> or --id <id>', opts);
13
+ return 1;
14
+ }
15
+ const sel = {
16
+ ...(flags.text ? { text: flags.text } : flags.id ? { id: flags.id } : { query }),
17
+ ...(flags.index !== undefined && { index: flags.index }),
18
+ ...(flags.focused !== undefined && { focused: flags.focused }),
19
+ ...(flags.enabled !== undefined && { enabled: flags.enabled }),
20
+ ...(flags.checked !== undefined && { checked: flags.checked }),
21
+ ...(flags.selected !== undefined && { selected: flags.selected }),
22
+ ...(flags.below && { below: { query: flags.below } }),
23
+ ...(flags.above && { above: { query: flags.above } }),
24
+ ...(flags.leftOf && { leftOf: { query: flags.leftOf } }),
25
+ ...(flags.rightOf && { rightOf: { query: flags.rightOf } }),
26
+ };
27
+ const label = flags.text ? `text="${flags.text}"` : flags.id ? `id="${flags.id}"` : `"${query}"`;
28
+ const result = await (0, runner_js_1.runDirect)(async (driver) => {
29
+ let el;
30
+ if (driver instanceof ios_js_1.IOSDriver) {
31
+ el = await (0, wait_js_1.waitForIOSElement)(() => driver.viewHierarchy().then((h) => h.axElement), sel);
32
+ }
33
+ else if (driver instanceof android_js_1.AndroidDriver) {
34
+ el = await (0, wait_js_1.waitForAndroidElement)(() => driver.viewHierarchy(), sel);
35
+ }
36
+ else {
37
+ return;
38
+ }
39
+ if (flags.longPress) {
40
+ if (driver instanceof ios_js_1.IOSDriver) {
41
+ await driver.tap(el.centerX, el.centerY, 1.5);
42
+ }
43
+ else {
44
+ await driver.swipe(el.centerX, el.centerY, el.centerX, el.centerY, 1500);
45
+ }
46
+ }
47
+ else if (flags.doubleTap) {
48
+ await driver.tap(el.centerX, el.centerY);
49
+ await (0, utils_js_1.sleep)(100);
50
+ await driver.tap(el.centerX, el.centerY);
51
+ }
52
+ else {
53
+ await driver.tap(el.centerX, el.centerY);
54
+ }
55
+ }, sessionName);
56
+ const verb = flags.longPress ? 'long-press' : flags.doubleTap ? 'double-tap' : 'tap';
57
+ if (result.success) {
58
+ (0, output_js_1.printSuccess)(`${verb} ${label} — done`, opts);
59
+ return 0;
60
+ }
61
+ else if (flags.optional) {
62
+ (0, output_js_1.printSuccess)(`${verb} ${label} — not found (optional)`, opts);
63
+ return 0;
64
+ }
65
+ else {
66
+ (0, output_js_1.printError)(`${verb} ${label} — failed\n${result.stderr}`, opts);
67
+ return 1;
68
+ }
69
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.typeText = typeText;
4
+ const runner_js_1 = require("../runner.js");
5
+ const output_js_1 = require("../output.js");
6
+ async function typeText(text, opts = {}, sessionName = 'default') {
7
+ if (text === undefined || text === '') {
8
+ (0, output_js_1.printError)('type requires <text>', opts);
9
+ return 1;
10
+ }
11
+ const result = await (0, runner_js_1.runDirect)(async (driver) => {
12
+ await driver.inputText(text);
13
+ }, sessionName);
14
+ if (result.success) {
15
+ (0, output_js_1.printSuccess)(`type "${text}" — done`, opts);
16
+ return 0;
17
+ }
18
+ else {
19
+ (0, output_js_1.printError)(`type "${text}" — failed\n${result.stderr}`, opts);
20
+ return 1;
21
+ }
22
+ }