@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
package/dist/runner.js ADDED
@@ -0,0 +1,190 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectFirstDevice = detectFirstDevice;
4
+ exports.getDriver = getDriver;
5
+ exports.runDirect = runDirect;
6
+ exports.spawnCommand = spawnCommand;
7
+ exports.runInlineFlow = runInlineFlow;
8
+ const child_process_1 = require("child_process");
9
+ const session_js_1 = require("./session.js");
10
+ const flow_runner_js_1 = require("./drivers/flow-runner.js");
11
+ const verbose_js_1 = require("./verbose.js");
12
+ const ios_js_1 = require("./drivers/ios.js");
13
+ const android_js_1 = require("./drivers/android.js");
14
+ const bootstrap_js_1 = require("./drivers/bootstrap.js");
15
+ const client_js_1 = require("./daemon/client.js");
16
+ /**
17
+ * Detect the first booted device/emulator without requiring a session.
18
+ * Checks Android (adb) and iOS simulators (xcrun simctl).
19
+ * Result is cached for the process lifetime to avoid repeated subprocess calls.
20
+ */
21
+ let _cachedDeviceId; // undefined = not yet queried, null = none found
22
+ async function detectFirstDevice() {
23
+ if (_cachedDeviceId !== undefined)
24
+ return _cachedDeviceId ?? undefined;
25
+ // Android: adb devices
26
+ const adb = await spawnCommand('adb', ['devices', '-l']).catch(() => null);
27
+ if (adb) {
28
+ for (const line of adb.stdout.split('\n').slice(1)) {
29
+ const id = line.trim().split(/\s+/)[0];
30
+ if (id && !line.includes('offline')) {
31
+ (0, verbose_js_1.log)(`detectFirstDevice: found Android device "${id}"`);
32
+ _cachedDeviceId = id;
33
+ return id;
34
+ }
35
+ }
36
+ }
37
+ // iOS: xcrun simctl list booted
38
+ const xcrun = await spawnCommand('xcrun', [
39
+ 'simctl',
40
+ 'list',
41
+ 'devices',
42
+ 'booted',
43
+ '--json',
44
+ ]).catch(() => null);
45
+ if (xcrun?.success) {
46
+ try {
47
+ const parsed = JSON.parse(xcrun.stdout);
48
+ for (const sims of Object.values(parsed.devices)) {
49
+ for (const sim of sims) {
50
+ if (sim.state === 'Booted') {
51
+ (0, verbose_js_1.log)(`detectFirstDevice: found iOS simulator "${sim.udid}"`);
52
+ _cachedDeviceId = sim.udid;
53
+ return sim.udid;
54
+ }
55
+ }
56
+ }
57
+ }
58
+ catch {
59
+ /* ignore */
60
+ }
61
+ }
62
+ _cachedDeviceId = null;
63
+ return undefined;
64
+ }
65
+ /** Per-session driver cache (process lifetime). */
66
+ const _driverCache = new Map();
67
+ /**
68
+ * Resolve a session name to a device ID.
69
+ * If sessionName is not 'default', treat it as a device ID directly.
70
+ */
71
+ async function resolveDeviceId(sessionName) {
72
+ if (sessionName !== 'default')
73
+ return sessionName;
74
+ const session = await (0, session_js_1.getSession)(sessionName);
75
+ return session.deviceId ?? (await detectFirstDevice());
76
+ }
77
+ /**
78
+ * Get or create a driver for the given session.
79
+ * Auto-starts the driver process if it's not already running.
80
+ */
81
+ async function getDriver(sessionName = 'default') {
82
+ if (_driverCache.has(sessionName)) {
83
+ const cached = _driverCache.get(sessionName);
84
+ // Quick alive check — if still alive, reuse
85
+ const alive = await cached.isAlive().catch(() => false);
86
+ if (alive)
87
+ return cached;
88
+ _driverCache.delete(sessionName);
89
+ }
90
+ const deviceId = await resolveDeviceId(sessionName);
91
+ if (!deviceId) {
92
+ throw new Error('No device found. Connect a device or start a simulator, then run again.');
93
+ }
94
+ const platform = await (0, bootstrap_js_1.detectPlatform)(deviceId);
95
+ const port = await (0, bootstrap_js_1.getDriverPort)(platform, deviceId);
96
+ (0, verbose_js_1.log)(`getDriver: platform=${platform} deviceId=${deviceId} port=${port}`);
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
+ if (platform === 'ios') {
104
+ const iosDriver = new ios_js_1.IOSDriver(port, '127.0.0.1', deviceId);
105
+ if (!(await iosDriver.isAlive())) {
106
+ throw new Error(`iOS XCTest driver on port ${port} is not responding.\n` +
107
+ `Run: conductor daemon-start --device ${deviceId}`);
108
+ }
109
+ driver = iosDriver;
110
+ }
111
+ else {
112
+ const androidDriver = new android_js_1.AndroidDriver(deviceId, port);
113
+ await androidDriver.connect();
114
+ driver = androidDriver;
115
+ }
116
+ _driverCache.set(sessionName, driver);
117
+ return driver;
118
+ }
119
+ /**
120
+ * Execute a function with the driver for the given session.
121
+ * Returns a RunResult for consistent error handling across commands.
122
+ */
123
+ async function runDirect(fn, sessionName = 'default') {
124
+ try {
125
+ const driver = await getDriver(sessionName);
126
+ const output = await fn(driver);
127
+ return {
128
+ success: true,
129
+ stdout: output ?? '',
130
+ stderr: '',
131
+ exitCode: 0,
132
+ };
133
+ }
134
+ catch (err) {
135
+ const msg = err instanceof Error ? err.message : String(err);
136
+ return {
137
+ success: false,
138
+ stdout: '',
139
+ stderr: msg,
140
+ exitCode: 1,
141
+ };
142
+ }
143
+ }
144
+ // ── Spawn helpers ─────────────────────────────────────────────────────────────
145
+ async function spawnCommand(cmd, args) {
146
+ return new Promise((resolve) => {
147
+ const proc = (0, child_process_1.spawn)(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'] });
148
+ let stdout = '';
149
+ let stderr = '';
150
+ proc.stdout.on('data', (chunk) => {
151
+ stdout += chunk.toString();
152
+ });
153
+ proc.stderr.on('data', (chunk) => {
154
+ stderr += chunk.toString();
155
+ });
156
+ proc.on('close', (code) => {
157
+ const exitCode = code ?? 1;
158
+ resolve({ success: exitCode === 0, stdout, stderr, exitCode });
159
+ });
160
+ proc.on('error', (err) => {
161
+ resolve({ success: false, stdout: '', stderr: err.message, exitCode: 1 });
162
+ });
163
+ });
164
+ }
165
+ /** Poll until a TCP port is open, or throw after timeout. */
166
+ async function waitForPort(port, timeoutMs = 180000, pollMs = 500) {
167
+ const deadline = Date.now() + timeoutMs;
168
+ while (Date.now() < deadline) {
169
+ if (await (0, bootstrap_js_1.isPortOpen)(port))
170
+ return;
171
+ await new Promise((r) => setTimeout(r, pollMs));
172
+ }
173
+ throw new Error(`Driver port ${port} did not open within ${timeoutMs / 1000}s`);
174
+ }
175
+ async function runInlineFlow(commands, sessionName = 'default', benchmark = false) {
176
+ const session = await (0, session_js_1.getSession)(sessionName);
177
+ const appId = session.appId ?? 'com.placeholder';
178
+ const yamlContent = `appId: ${appId}\n---\n${commands}`;
179
+ (0, verbose_js_1.log)(`runInlineFlow: executing inline flow:\n${yamlContent}`);
180
+ try {
181
+ const driver = await getDriver(sessionName);
182
+ const flow = (0, flow_runner_js_1.parseFlowString)(yamlContent);
183
+ await (0, flow_runner_js_1.executeFlow)(flow, driver, { benchmark });
184
+ return { success: true, stdout: '', stderr: '', exitCode: 0 };
185
+ }
186
+ catch (err) {
187
+ const msg = err instanceof Error ? err.message : String(err);
188
+ return { success: false, stdout: '', stderr: msg, exitCode: 1 };
189
+ }
190
+ }
@@ -0,0 +1,66 @@
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.sessionFilePath = sessionFilePath;
7
+ exports.getSession = getSession;
8
+ exports.saveSession = saveSession;
9
+ exports.updateSession = updateSession;
10
+ exports.clearSession = clearSession;
11
+ exports.listSessions = listSessions;
12
+ const promises_1 = __importDefault(require("fs/promises"));
13
+ const os_1 = __importDefault(require("os"));
14
+ const path_1 = __importDefault(require("path"));
15
+ const CONDUCTOR_DIR = path_1.default.join(os_1.default.homedir(), '.conductor');
16
+ const SESSIONS_DIR = path_1.default.join(CONDUCTOR_DIR, 'sessions');
17
+ const LEGACY_SESSION_FILE = path_1.default.join(CONDUCTOR_DIR, 'session.json');
18
+ function sessionFilePath(sessionName = 'default') {
19
+ return path_1.default.join(SESSIONS_DIR, `${sessionName}.json`);
20
+ }
21
+ async function getSession(sessionName = 'default') {
22
+ try {
23
+ const data = await promises_1.default.readFile(sessionFilePath(sessionName), 'utf-8');
24
+ return JSON.parse(data);
25
+ }
26
+ catch {
27
+ // For the default session, fall back to the legacy session.json
28
+ if (sessionName === 'default') {
29
+ try {
30
+ const data = await promises_1.default.readFile(LEGACY_SESSION_FILE, 'utf-8');
31
+ return JSON.parse(data);
32
+ }
33
+ catch {
34
+ return {};
35
+ }
36
+ }
37
+ return {};
38
+ }
39
+ }
40
+ async function saveSession(session, sessionName = 'default') {
41
+ await promises_1.default.mkdir(SESSIONS_DIR, { recursive: true });
42
+ await promises_1.default.writeFile(sessionFilePath(sessionName), JSON.stringify(session, null, 2));
43
+ }
44
+ async function updateSession(updates, sessionName = 'default') {
45
+ const current = await getSession(sessionName);
46
+ const updated = { ...current, ...updates };
47
+ await saveSession(updated, sessionName);
48
+ return updated;
49
+ }
50
+ async function clearSession(sessionName = 'default') {
51
+ try {
52
+ await promises_1.default.unlink(sessionFilePath(sessionName));
53
+ }
54
+ catch {
55
+ // File doesn't exist — nothing to clear
56
+ }
57
+ }
58
+ async function listSessions() {
59
+ try {
60
+ const files = await promises_1.default.readdir(SESSIONS_DIR);
61
+ return files.filter((f) => f.endsWith('.json')).map((f) => f.replace(/\.json$/, ''));
62
+ }
63
+ catch {
64
+ return [];
65
+ }
66
+ }
@@ -0,0 +1,109 @@
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.checkForUpdates = checkForUpdates;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const https_1 = __importDefault(require("https"));
9
+ const os_1 = __importDefault(require("os"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const pkg_root_js_1 = require("./pkg-root.js");
12
+ const CACHE_FILE = path_1.default.join(os_1.default.homedir(), '.conductor', 'update-check.json');
13
+ const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
14
+ const REGISTRY_URL = 'https://registry.npmjs.org/@houwert/conductor/latest';
15
+ function readCache() {
16
+ try {
17
+ const raw = fs_1.default.readFileSync(CACHE_FILE, 'utf8');
18
+ return JSON.parse(raw);
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ function writeCache(data) {
25
+ try {
26
+ fs_1.default.mkdirSync(path_1.default.dirname(CACHE_FILE), { recursive: true });
27
+ fs_1.default.writeFileSync(CACHE_FILE, JSON.stringify(data));
28
+ }
29
+ catch {
30
+ // ignore write failures
31
+ }
32
+ }
33
+ function fetchLatestVersion() {
34
+ return new Promise((resolve, reject) => {
35
+ const req = https_1.default.get(REGISTRY_URL, { timeout: 3000 }, (res) => {
36
+ let body = '';
37
+ res.on('data', (chunk) => {
38
+ body += chunk.toString();
39
+ });
40
+ res.on('end', () => {
41
+ try {
42
+ const data = JSON.parse(body);
43
+ resolve(data.version);
44
+ }
45
+ catch {
46
+ reject(new Error('Failed to parse registry response'));
47
+ }
48
+ });
49
+ });
50
+ req.on('error', reject);
51
+ req.on('timeout', () => {
52
+ req.destroy();
53
+ reject(new Error('Registry request timed out'));
54
+ });
55
+ });
56
+ }
57
+ function parseVersion(v) {
58
+ return v.split('.').map(Number);
59
+ }
60
+ function isNewer(latest, current) {
61
+ const l = parseVersion(latest);
62
+ const c = parseVersion(current);
63
+ for (let i = 0; i < 3; i++) {
64
+ const li = l[i] ?? 0;
65
+ const ci = c[i] ?? 0;
66
+ if (li > ci)
67
+ return true;
68
+ if (li < ci)
69
+ return false;
70
+ }
71
+ return false;
72
+ }
73
+ function getOwnVersion() {
74
+ try {
75
+ const pkgPath = path_1.default.join((0, pkg_root_js_1.findPkgRoot)(__dirname), 'package.json');
76
+ const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf8'));
77
+ return pkg.version;
78
+ }
79
+ catch {
80
+ return '0.0.0';
81
+ }
82
+ }
83
+ function checkForUpdates() {
84
+ if (process.env['CONDUCTOR_NO_UPDATE_CHECK'] === '1')
85
+ return;
86
+ // Fire-and-forget: run async without blocking
87
+ void (async () => {
88
+ try {
89
+ const now = Date.now();
90
+ const cache = readCache();
91
+ let latestVersion;
92
+ if (cache && now - cache.checkedAt < CACHE_TTL_MS) {
93
+ latestVersion = cache.latestVersion;
94
+ }
95
+ else {
96
+ latestVersion = await fetchLatestVersion();
97
+ writeCache({ checkedAt: now, latestVersion });
98
+ }
99
+ const currentVersion = getOwnVersion();
100
+ if (isNewer(latestVersion, currentVersion)) {
101
+ process.stderr.write(`\nA new version of conductor is available: ${latestVersion} (you have ${currentVersion})\n` +
102
+ `Run: npm install -g @houwert/conductor\n`);
103
+ }
104
+ }
105
+ catch {
106
+ // Never surface update-check errors to the user
107
+ }
108
+ })();
109
+ }
package/dist/utils.js ADDED
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sleep = sleep;
4
+ exports.swipeCoords = swipeCoords;
5
+ function sleep(ms) {
6
+ return new Promise((resolve) => setTimeout(resolve, ms));
7
+ }
8
+ function swipeCoords(dir) {
9
+ switch (dir) {
10
+ case 'down':
11
+ return { startX: 0.5, startY: 0.7, endX: 0.5, endY: 0.3 };
12
+ case 'up':
13
+ return { startX: 0.5, startY: 0.3, endX: 0.5, endY: 0.7 };
14
+ case 'left':
15
+ return { startX: 0.8, startY: 0.5, endX: 0.2, endY: 0.5 };
16
+ case 'right':
17
+ return { startX: 0.2, startY: 0.5, endX: 0.8, endY: 0.5 };
18
+ }
19
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setVerbose = setVerbose;
4
+ exports.isVerbose = isVerbose;
5
+ exports.log = log;
6
+ let _verbose = false;
7
+ function setVerbose(v) {
8
+ _verbose = v;
9
+ }
10
+ function isVerbose() {
11
+ return _verbose;
12
+ }
13
+ function log(...args) {
14
+ if (_verbose) {
15
+ console.error('[verbose]', ...args);
16
+ }
17
+ }
@@ -0,0 +1,126 @@
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-ios</string>
13
+ </dict>
14
+ <key>FormatVersion</key>
15
+ <integer>1</integer>
16
+ </dict>
17
+ <key>conductor-driver-iosUITests</key>
18
+ <dict>
19
+ <key>BlueprintName</key>
20
+ <string>conductor-driver-iosUITests</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-ios</string>
29
+ <string>dev.houwert.conductor-driver-iosUITests</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-iphonesimulator/ConductorDriverLib.framework</string>
38
+ <string>__TESTROOT__/Debug-iphonesimulator/conductor-driver-ios.app</string>
39
+ <string>__TESTROOT__/Debug-iphonesimulator/conductor-driver-iosUITests-Runner.app</string>
40
+ <string>__TESTROOT__/Debug-iphonesimulator/conductor-driver-iosUITests-Runner.app/PlugIns/conductor-driver-iosUITests.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_iosUITests</string>
63
+ <key>RunOrder</key>
64
+ <integer>0</integer>
65
+ <key>SkipTestIdentifiers</key>
66
+ <array>
67
+ <string>ViewHierarchyHandlerTests</string>
68
+ <string>ViewHierarchyHandlerTests/testViewHierarchyHandlerReturnsNonEmptyHierarchy()</string>
69
+ </array>
70
+ <key>SystemAttachmentLifetime</key>
71
+ <string>deleteOnSuccess</string>
72
+ <key>TestBundlePath</key>
73
+ <string>__TESTHOST__/PlugIns/conductor-driver-iosUITests.xctest</string>
74
+ <key>TestHostBundleIdentifier</key>
75
+ <string>dev.houwert.conductor-driver-iosUITests.xctrunner</string>
76
+ <key>TestHostPath</key>
77
+ <string>__TESTROOT__/Debug-iphonesimulator/conductor-driver-iosUITests-Runner.app</string>
78
+ <key>TestLanguage</key>
79
+ <string></string>
80
+ <key>TestRegion</key>
81
+ <string></string>
82
+ <key>TestTimeoutsEnabled</key>
83
+ <false/>
84
+ <key>TestingEnvironmentVariables</key>
85
+ <dict>
86
+ <key>DYLD_FRAMEWORK_PATH</key>
87
+ <string>__TESTROOT__/Debug-iphonesimulator:__TESTROOT__/Debug-iphonesimulator/PackageFrameworks:__PLATFORMS__/iPhoneSimulator.platform/Developer/Library/Frameworks</string>
88
+ <key>DYLD_LIBRARY_PATH</key>
89
+ <string>__TESTROOT__/Debug-iphonesimulator:__PLATFORMS__/iPhoneSimulator.platform/Developer/usr/lib</string>
90
+ <key>XCODE_SCHEME_NAME</key>
91
+ <string>conductor-driver-ios</string>
92
+ <key>__XCODE_BUILT_PRODUCTS_DIR_PATHS</key>
93
+ <string>__TESTROOT__/Debug-iphonesimulator</string>
94
+ <key>__XPC_DYLD_FRAMEWORK_PATH</key>
95
+ <string>__TESTROOT__/Debug-iphonesimulator</string>
96
+ <key>__XPC_DYLD_LIBRARY_PATH</key>
97
+ <string>__TESTROOT__/Debug-iphonesimulator</string>
98
+ </dict>
99
+ <key>ToolchainsSettingValue</key>
100
+ <array/>
101
+ <key>UITargetAppCommandLineArguments</key>
102
+ <array/>
103
+ <key>UITargetAppEnvironmentVariables</key>
104
+ <dict>
105
+ <key>APP_DISTRIBUTOR_ID_OVERRIDE</key>
106
+ <string>com.apple.AppStore</string>
107
+ <key>DYLD_FRAMEWORK_PATH</key>
108
+ <string>__TESTROOT__/Debug-iphonesimulator:__TESTROOT__/Debug-iphonesimulator/PackageFrameworks</string>
109
+ <key>DYLD_LIBRARY_PATH</key>
110
+ <string>__TESTROOT__/Debug-iphonesimulator</string>
111
+ <key>XCODE_SCHEME_NAME</key>
112
+ <string>conductor-driver-ios</string>
113
+ <key>__XCODE_BUILT_PRODUCTS_DIR_PATHS</key>
114
+ <string>__TESTROOT__/Debug-iphonesimulator</string>
115
+ <key>__XPC_DYLD_FRAMEWORK_PATH</key>
116
+ <string>__TESTROOT__/Debug-iphonesimulator</string>
117
+ <key>__XPC_DYLD_LIBRARY_PATH</key>
118
+ <string>__TESTROOT__/Debug-iphonesimulator</string>
119
+ </dict>
120
+ <key>UITargetAppPath</key>
121
+ <string>__TESTROOT__/Debug-iphonesimulator/conductor-driver-ios.app</string>
122
+ <key>UserAttachmentLifetime</key>
123
+ <string>deleteOnSuccess</string>
124
+ </dict>
125
+ </dict>
126
+ </plist>
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@houwert/conductor",
3
+ "version": "0.2.0",
4
+ "description": "CLI tool for mobile app interactions — optimized for AI agents",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/DouweBos/conductor"
9
+ },
10
+ "homepage": "https://github.com/DouweBos/conductor",
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "bin": {
15
+ "conductor": "./dist/index.js"
16
+ },
17
+ "main": "./dist/index.js",
18
+ "files": [
19
+ "dist/",
20
+ "drivers/",
21
+ "skills/",
22
+ "proto/",
23
+ ".claude-plugin/"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsc",
27
+ "dev": "tsc --watch",
28
+ "test": "tsc -p tests/tsconfig.json && node dist-tests/tests/all-tests.js",
29
+ "lint": "eslint src && prettier --check src",
30
+ "lint:fix": "eslint src --fix && prettier --write src",
31
+ "postinstall": "node dist/postinstall.js"
32
+ },
33
+ "packageManager": "pnpm@9.0.0",
34
+ "dependencies": {
35
+ "@grpc/grpc-js": "^1.12.0",
36
+ "@grpc/proto-loader": "^0.7.15",
37
+ "js-yaml": "^4.1.1",
38
+ "minimist": "^1.2.8"
39
+ },
40
+ "devDependencies": {
41
+ "@types/js-yaml": "^4.0.9",
42
+ "@types/minimist": "^1.2.5",
43
+ "@types/node": "^20.0.0",
44
+ "@typescript-eslint/eslint-plugin": "^8.56.1",
45
+ "@typescript-eslint/parser": "^8.56.1",
46
+ "eslint": "^10.0.2",
47
+ "eslint-config-prettier": "^10.1.8",
48
+ "prettier": "^3.8.1",
49
+ "typescript": "^5.4.0",
50
+ "typescript-eslint": "^8.56.1"
51
+ }
52
+ }