@houwert/conductor 0.3.0 → 0.4.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.
@@ -39,6 +39,8 @@ APP CONTROL
39
39
  --clear-keychain Wipe keychain before launching
40
40
  --argument key=value Set launch argument (repeatable)
41
41
  stop-app [<appId>] Stop app (uses session appId if omitted)
42
+ clear-state [<appId>] Clear app data/state (uses session appId if omitted)
43
+ uninstall-app <appId> Uninstall app from device
42
44
 
43
45
  INTERACTIONS
44
46
  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
+ }
@@ -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
+ }
@@ -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();
@@ -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");
@@ -48,6 +50,8 @@ const COMMAND_HELP = {
48
50
  'copy-app': copy_app_js_1.HELP,
49
51
  'launch-app': launch_app_js_1.HELP,
50
52
  'stop-app': stop_app_js_1.HELP,
53
+ 'clear-state': clear_state_js_1.HELP,
54
+ 'uninstall-app': uninstall_app_js_1.HELP,
51
55
  tap: tap_js_1.HELP,
52
56
  type: type_js_1.HELP,
53
57
  'erase-text': erase_text_js_1.HELP,
@@ -240,6 +244,16 @@ async function main() {
240
244
  exitCode = await (0, stop_app_js_1.stopApp)(appId, opts, sessionName);
241
245
  break;
242
246
  }
247
+ case 'clear-state': {
248
+ const appId = rest[0];
249
+ exitCode = await (0, clear_state_js_1.clearState)(appId, opts, sessionName);
250
+ break;
251
+ }
252
+ case 'uninstall-app': {
253
+ const appId = rest[0] ?? '';
254
+ exitCode = await (0, uninstall_app_js_1.uninstallApp)(appId, opts, sessionName);
255
+ break;
256
+ }
243
257
  case 'tap': {
244
258
  const element = rest.join(' ');
245
259
  exitCode = await (0, tap_js_1.tap)(element, opts, sessionName, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@houwert/conductor",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "CLI tool for mobile app interactions — optimized for AI agents",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -124,6 +124,28 @@ conductor stop-app com.example.myapp
124
124
 
125
125
  ---
126
126
 
127
+ ### `clear-state [<appId>]`
128
+
129
+ 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`.
130
+
131
+ ```bash
132
+ conductor clear-state
133
+ conductor clear-state com.example.myapp
134
+ ```
135
+
136
+ ---
137
+
138
+ ### `uninstall-app <appId>`
139
+
140
+ Uninstall an app from the device. The app ID is required.
141
+
142
+ ```bash
143
+ conductor uninstall-app com.example.myapp
144
+ conductor uninstall-app com.example.myapp --device emulator-5554
145
+ ```
146
+
147
+ ---
148
+
127
149
  ### `tap <element>`
128
150
 
129
151
  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