@houwert/conductor 0.3.0 → 0.5.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.
- package/README.md +1 -1
- package/dist/commands/cheat-sheet.js +3 -0
- package/dist/commands/clear-state.js +27 -0
- package/dist/commands/install-app.js +51 -0
- package/dist/commands/list-apps.js +1 -1
- package/dist/commands/uninstall-app.js +31 -0
- package/dist/drivers/android.js +4 -0
- package/dist/drivers/flow-runner.js +15 -0
- package/dist/drivers/ios.js +5 -0
- package/dist/index.js +21 -0
- package/drivers/ios/conductor-driver-ios.zip +0 -0
- package/drivers/ios/conductor-driver-iosUITests-Runner.zip +0 -0
- package/drivers/tvos/conductor-driver-tvos.zip +0 -0
- package/drivers/tvos/conductor-driver-tvosUITests-Runner.zip +0 -0
- package/package.json +1 -1
- package/skills/conductor/SKILL.md +33 -0
- package/skills/conductor/references/flow-syntax.md +2 -0
package/README.md
CHANGED
|
@@ -66,7 +66,7 @@ Claude learns every available command, how to coordinate across devices, and how
|
|
|
66
66
|
|
|
67
67
|
| Capability | Commands |
|
|
68
68
|
|---|---|
|
|
69
|
-
| App lifecycle | `launch-app`, `stop-app`, `foreground-app`, `copy-app` |
|
|
69
|
+
| App lifecycle | `launch-app`, `stop-app`, `clear-state`, `uninstall-app`, `install-app`, `foreground-app`, `copy-app` |
|
|
70
70
|
| Interaction | `tap`, `type`, `scroll`, `scroll-until-visible`, `swipe`, `press-key`, `erase-text`, `hide-keyboard` |
|
|
71
71
|
| Inspection | `inspect`, `focused`, `screenshot`, `list-apps` |
|
|
72
72
|
| Assertions | `assert-visible`, `assert-not-visible` |
|
|
@@ -34,11 +34,14 @@ DEVICE MANAGEMENT
|
|
|
34
34
|
session --list List all device sessions
|
|
35
35
|
|
|
36
36
|
APP CONTROL
|
|
37
|
+
install-app <path> Install .app / .ipa / .apk onto device
|
|
37
38
|
launch-app <appId> [--device <id>] Launch app and save to session
|
|
38
39
|
--clear-state Wipe app data/state before launching
|
|
39
40
|
--clear-keychain Wipe keychain before launching
|
|
40
41
|
--argument key=value Set launch argument (repeatable)
|
|
41
42
|
stop-app [<appId>] Stop app (uses session appId if omitted)
|
|
43
|
+
clear-state [<appId>] Clear app data/state (uses session appId if omitted)
|
|
44
|
+
uninstall-app <appId> Uninstall app from device
|
|
42
45
|
|
|
43
46
|
INTERACTIONS
|
|
44
47
|
tap <element> Tap element by text
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HELP = void 0;
|
|
4
|
+
exports.clearState = clearState;
|
|
5
|
+
exports.HELP = ` clear-state [<appId>] Clear app data/state`;
|
|
6
|
+
const runner_js_1 = require("../runner.js");
|
|
7
|
+
const session_js_1 = require("../session.js");
|
|
8
|
+
const output_js_1 = require("../output.js");
|
|
9
|
+
async function clearState(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)('clear-state: 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
|
+
await driver.clearAppState(resolvedAppId);
|
|
18
|
+
}, sessionName);
|
|
19
|
+
if (result.success) {
|
|
20
|
+
(0, output_js_1.printSuccess)(`clear-state "${resolvedAppId}" — done`, opts);
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
(0, output_js_1.printError)(`clear-state "${resolvedAppId}" — failed\n${result.stderr}`, opts);
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HELP = void 0;
|
|
4
|
+
exports.installApp = installApp;
|
|
5
|
+
exports.HELP = ` install-app <path> Install .app / .ipa / .apk onto device`;
|
|
6
|
+
const runner_js_1 = require("../runner.js");
|
|
7
|
+
const session_js_1 = require("../session.js");
|
|
8
|
+
const output_js_1 = require("../output.js");
|
|
9
|
+
const bootstrap_js_1 = require("../drivers/bootstrap.js");
|
|
10
|
+
async function resolveDeviceId(sessionName) {
|
|
11
|
+
if (sessionName !== 'default')
|
|
12
|
+
return sessionName;
|
|
13
|
+
const session = await (0, session_js_1.getSession)(sessionName);
|
|
14
|
+
return session.deviceId ?? (await (0, runner_js_1.detectFirstDevice)());
|
|
15
|
+
}
|
|
16
|
+
async function installApp(appPath, opts = {}, sessionName = 'default') {
|
|
17
|
+
if (!appPath) {
|
|
18
|
+
(0, output_js_1.printError)('install-app requires <path> to a .app, .ipa, or .apk', opts);
|
|
19
|
+
return 1;
|
|
20
|
+
}
|
|
21
|
+
const deviceId = await resolveDeviceId(sessionName);
|
|
22
|
+
if (!deviceId) {
|
|
23
|
+
(0, output_js_1.printError)('No device found. Connect a device or start a simulator first.', opts);
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
26
|
+
const platform = await (0, bootstrap_js_1.detectPlatform)(deviceId);
|
|
27
|
+
if (platform === 'ios' || platform === 'tvos') {
|
|
28
|
+
const result = await (0, runner_js_1.spawnCommand)('xcrun', ['simctl', 'install', deviceId, appPath]);
|
|
29
|
+
if (!result.success) {
|
|
30
|
+
(0, output_js_1.printError)(`install-app failed: ${result.stderr}`, opts);
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const result = await (0, runner_js_1.spawnCommand)('adb', [
|
|
36
|
+
'-s',
|
|
37
|
+
deviceId,
|
|
38
|
+
'install',
|
|
39
|
+
'-r',
|
|
40
|
+
'-t',
|
|
41
|
+
'-g',
|
|
42
|
+
appPath,
|
|
43
|
+
]);
|
|
44
|
+
if (!result.success) {
|
|
45
|
+
(0, output_js_1.printError)(`install-app failed: ${result.stderr}`, opts);
|
|
46
|
+
return 1;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
(0, output_js_1.printSuccess)(`install-app "${appPath}" — done`, opts);
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
@@ -21,7 +21,7 @@ async function listApps(opts = {}, sessionName = 'default') {
|
|
|
21
21
|
}
|
|
22
22
|
const platform = await (0, bootstrap_js_1.detectPlatform)(deviceId);
|
|
23
23
|
let appIds;
|
|
24
|
-
if (platform === 'ios') {
|
|
24
|
+
if (platform === 'ios' || platform === 'tvos') {
|
|
25
25
|
const result = await (0, runner_js_1.spawnCommand)('bash', [
|
|
26
26
|
'-c',
|
|
27
27
|
`xcrun simctl listapps ${deviceId} | plutil -convert json - -o -`,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HELP = void 0;
|
|
4
|
+
exports.uninstallApp = uninstallApp;
|
|
5
|
+
exports.HELP = ` uninstall-app <appId> Uninstall app from device`;
|
|
6
|
+
const runner_js_1 = require("../runner.js");
|
|
7
|
+
const output_js_1 = require("../output.js");
|
|
8
|
+
const ios_js_1 = require("../drivers/ios.js");
|
|
9
|
+
const android_js_1 = require("../drivers/android.js");
|
|
10
|
+
async function uninstallApp(appId, opts = {}, sessionName = 'default') {
|
|
11
|
+
if (!appId) {
|
|
12
|
+
(0, output_js_1.printError)('uninstall-app requires <appId>', opts);
|
|
13
|
+
return 1;
|
|
14
|
+
}
|
|
15
|
+
const result = await (0, runner_js_1.runDirect)(async (driver) => {
|
|
16
|
+
if (driver instanceof ios_js_1.IOSDriver) {
|
|
17
|
+
await driver.uninstallApp(appId);
|
|
18
|
+
}
|
|
19
|
+
else if (driver instanceof android_js_1.AndroidDriver) {
|
|
20
|
+
await driver.uninstallApp(appId);
|
|
21
|
+
}
|
|
22
|
+
}, sessionName);
|
|
23
|
+
if (result.success) {
|
|
24
|
+
(0, output_js_1.printSuccess)(`uninstall-app "${appId}" — done`, opts);
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
(0, output_js_1.printError)(`uninstall-app "${appId}" — failed\n${result.stderr}`, opts);
|
|
29
|
+
return 1;
|
|
30
|
+
}
|
|
31
|
+
}
|
package/dist/drivers/android.js
CHANGED
|
@@ -208,6 +208,10 @@ class AndroidDriver {
|
|
|
208
208
|
async clearAppState(packageName) {
|
|
209
209
|
await this.adb(['shell', 'pm', 'clear', packageName]);
|
|
210
210
|
}
|
|
211
|
+
async uninstallApp(packageName) {
|
|
212
|
+
await this.adb(['shell', 'am', 'force-stop', packageName]).catch(() => { });
|
|
213
|
+
await this.adb(['uninstall', packageName]);
|
|
214
|
+
}
|
|
211
215
|
async clearKeychain() {
|
|
212
216
|
// No-op on Android — keychain is an iOS concept
|
|
213
217
|
}
|
|
@@ -987,6 +987,21 @@ async function executeCommandBody(key, val, driver, opts) {
|
|
|
987
987
|
await driver.clearKeychain();
|
|
988
988
|
break;
|
|
989
989
|
}
|
|
990
|
+
case 'uninstallApp': {
|
|
991
|
+
const appId = val == null || val === ''
|
|
992
|
+
? (opts.appId ??
|
|
993
|
+
(() => {
|
|
994
|
+
throw new Error('uninstallApp: no appId in command or flow header');
|
|
995
|
+
})())
|
|
996
|
+
: resolveAppId(val, 'uninstallApp');
|
|
997
|
+
if (driver instanceof ios_js_1.IOSDriver) {
|
|
998
|
+
await driver.uninstallApp(appId);
|
|
999
|
+
}
|
|
1000
|
+
else {
|
|
1001
|
+
await driver.uninstallApp(appId);
|
|
1002
|
+
}
|
|
1003
|
+
break;
|
|
1004
|
+
}
|
|
990
1005
|
// ── Keys ───────────────────────────────────────────────────────────────
|
|
991
1006
|
case 'pressKey': {
|
|
992
1007
|
const keyName = val.toUpperCase();
|
package/dist/drivers/ios.js
CHANGED
|
@@ -168,6 +168,11 @@ class IOSDriver {
|
|
|
168
168
|
await promises_1.default.rm(tmpDir, { recursive: true, force: true }).catch(() => { });
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
|
+
async uninstallApp(bundleId) {
|
|
172
|
+
const deviceId = this.requireDeviceId();
|
|
173
|
+
await this.simctl(['terminate', deviceId, bundleId]).catch(() => { });
|
|
174
|
+
await this.simctl(['uninstall', deviceId, bundleId]);
|
|
175
|
+
}
|
|
171
176
|
async clearKeychain() {
|
|
172
177
|
const deviceId = this.requireDeviceId();
|
|
173
178
|
await this.simctl(['keychain', deviceId, 'reset']);
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,8 @@ const verbose_js_1 = require("./verbose.js");
|
|
|
9
9
|
const list_devices_js_1 = require("./commands/list-devices.js");
|
|
10
10
|
const launch_app_js_1 = require("./commands/launch-app.js");
|
|
11
11
|
const stop_app_js_1 = require("./commands/stop-app.js");
|
|
12
|
+
const clear_state_js_1 = require("./commands/clear-state.js");
|
|
13
|
+
const uninstall_app_js_1 = require("./commands/uninstall-app.js");
|
|
12
14
|
const tap_js_1 = require("./commands/tap.js");
|
|
13
15
|
const type_js_1 = require("./commands/type.js");
|
|
14
16
|
const back_js_1 = require("./commands/back.js");
|
|
@@ -30,6 +32,7 @@ const run_parallel_js_1 = require("./commands/run-parallel.js");
|
|
|
30
32
|
const foreground_app_js_1 = require("./commands/foreground-app.js");
|
|
31
33
|
const list_apps_js_1 = require("./commands/list-apps.js");
|
|
32
34
|
const copy_app_js_1 = require("./commands/copy-app.js");
|
|
35
|
+
const install_app_js_1 = require("./commands/install-app.js");
|
|
33
36
|
const erase_text_js_1 = require("./commands/erase-text.js");
|
|
34
37
|
const assert_not_visible_js_1 = require("./commands/assert-not-visible.js");
|
|
35
38
|
const open_link_js_1 = require("./commands/open-link.js");
|
|
@@ -46,8 +49,11 @@ const COMMAND_HELP = {
|
|
|
46
49
|
'foreground-app': foreground_app_js_1.HELP,
|
|
47
50
|
'list-apps': list_apps_js_1.HELP,
|
|
48
51
|
'copy-app': copy_app_js_1.HELP,
|
|
52
|
+
'install-app': install_app_js_1.HELP,
|
|
49
53
|
'launch-app': launch_app_js_1.HELP,
|
|
50
54
|
'stop-app': stop_app_js_1.HELP,
|
|
55
|
+
'clear-state': clear_state_js_1.HELP,
|
|
56
|
+
'uninstall-app': uninstall_app_js_1.HELP,
|
|
51
57
|
tap: tap_js_1.HELP,
|
|
52
58
|
type: type_js_1.HELP,
|
|
53
59
|
'erase-text': erase_text_js_1.HELP,
|
|
@@ -219,6 +225,11 @@ async function main() {
|
|
|
219
225
|
exitCode = await (0, copy_app_js_1.copyApp)(bundleId, from ?? '', to ?? '', opts);
|
|
220
226
|
break;
|
|
221
227
|
}
|
|
228
|
+
case 'install-app': {
|
|
229
|
+
const appPath = rest[0] ?? '';
|
|
230
|
+
exitCode = await (0, install_app_js_1.installApp)(appPath, opts, sessionName);
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
222
233
|
case 'launch-app': {
|
|
223
234
|
const appId = rest[0] ?? '';
|
|
224
235
|
// --argument key=value (repeatable) → Record<string, string>
|
|
@@ -240,6 +251,16 @@ async function main() {
|
|
|
240
251
|
exitCode = await (0, stop_app_js_1.stopApp)(appId, opts, sessionName);
|
|
241
252
|
break;
|
|
242
253
|
}
|
|
254
|
+
case 'clear-state': {
|
|
255
|
+
const appId = rest[0];
|
|
256
|
+
exitCode = await (0, clear_state_js_1.clearState)(appId, opts, sessionName);
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
case 'uninstall-app': {
|
|
260
|
+
const appId = rest[0] ?? '';
|
|
261
|
+
exitCode = await (0, uninstall_app_js_1.uninstallApp)(appId, opts, sessionName);
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
243
264
|
case 'tap': {
|
|
244
265
|
const element = rest.join(' ');
|
|
245
266
|
exitCode = await (0, tap_js_1.tap)(element, opts, sessionName, {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -90,6 +90,17 @@ Both flags are required. The command reads the `.app` bundle path from the sourc
|
|
|
90
90
|
|
|
91
91
|
---
|
|
92
92
|
|
|
93
|
+
### `install-app <path>`
|
|
94
|
+
|
|
95
|
+
Install an app from a local `.app` bundle, `.ipa`, or `.apk` file onto the device.
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
conductor install-app ./build/MyApp.app
|
|
99
|
+
conductor install-app ./build/app-debug.apk --device emulator-5554
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
93
104
|
### `launch-app <appId>`
|
|
94
105
|
|
|
95
106
|
Launch an app by bundle ID and save it to the session.
|
|
@@ -124,6 +135,28 @@ conductor stop-app com.example.myapp
|
|
|
124
135
|
|
|
125
136
|
---
|
|
126
137
|
|
|
138
|
+
### `clear-state [<appId>]`
|
|
139
|
+
|
|
140
|
+
Clear app data/state without relaunching. Uses the session `appId` if not specified. On iOS this terminates the app, preserves the .app bundle, uninstalls, and reinstalls (clearing all user data). On Android this runs `pm clear`.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
conductor clear-state
|
|
144
|
+
conductor clear-state com.example.myapp
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
### `uninstall-app <appId>`
|
|
150
|
+
|
|
151
|
+
Uninstall an app from the device. The app ID is required.
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
conductor uninstall-app com.example.myapp
|
|
155
|
+
conductor uninstall-app com.example.myapp --device emulator-5554
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
127
160
|
### `tap <element>`
|
|
128
161
|
|
|
129
162
|
Tap a UI element by its text label or accessibility ID. **Try the most obvious text label or ID first** — e.g. `"Sign In"`, `"Submit"`, `"btn_login"`. Only run `inspect` to look up the exact identifier if your first attempt fails. **For icon-only buttons (no visible text label), always run `inspect` first** to find the accessibility ID before tapping.
|
|
@@ -26,6 +26,8 @@ appId: com.example.myapp
|
|
|
26
26
|
appId: com.other.app
|
|
27
27
|
- clearState # clear app data
|
|
28
28
|
- clearKeychain # clear keychain (iOS: full keychain; Android: account credentials)
|
|
29
|
+
- uninstallApp # uninstall app from device
|
|
30
|
+
- uninstallApp: com.other.app # uninstall a specific app
|
|
29
31
|
```
|
|
30
32
|
|
|
31
33
|
### Tapping
|