@houwert/conductor 0.24.0 → 0.25.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/dist/runner.js CHANGED
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.detectFirstDevice = detectFirstDevice;
4
4
  exports.getDriver = getDriver;
5
5
  exports.prewarmDriver = prewarmDriver;
6
+ exports.inputServerInfo = inputServerInfo;
6
7
  exports.runDirect = runDirect;
7
8
  exports.spawnCommand = spawnCommand;
8
9
  exports.runInlineFlow = runInlineFlow;
@@ -243,6 +244,38 @@ async function prewarmDriver(deviceId) {
243
244
  /* best-effort: a later command will report a real failure */
244
245
  }
245
246
  }
247
+ /**
248
+ * Resolve the streaming-input socket for a session's device, starting the
249
+ * daemon (and its driver + input server) if needed. Returns the loopback
250
+ * WebSocket URL the host IDE connects to. Throws if the platform has no
251
+ * streaming input (web/vega) or the port never comes up.
252
+ */
253
+ async function inputServerInfo(sessionName = 'default') {
254
+ const deviceId = await resolveDeviceId(sessionName);
255
+ if (!deviceId) {
256
+ throw new Error('No device found. Connect a device or start a simulator, then run again.');
257
+ }
258
+ const platform = await (0, bootstrap_js_1.detectPlatform)(deviceId);
259
+ if (platform !== 'ios' && platform !== 'tvos' && platform !== 'android') {
260
+ throw new Error(`Streaming input is not available for ${platform} devices.`);
261
+ }
262
+ await (0, client_js_1.startDaemon)(deviceId);
263
+ // The input server starts just after the driver — poll status until its port appears.
264
+ const deadline = Date.now() + 60000;
265
+ while (Date.now() < deadline) {
266
+ const status = await (0, client_js_1.fetchDaemonStatus)(deviceId);
267
+ if (status && typeof status.inputPort === 'number') {
268
+ return {
269
+ device: deviceId,
270
+ platform,
271
+ inputPort: status.inputPort,
272
+ url: `ws://127.0.0.1:${status.inputPort}/input`,
273
+ };
274
+ }
275
+ await new Promise((r) => setTimeout(r, 500));
276
+ }
277
+ throw new Error(`Input server for ${deviceId} did not come up within timeout.`);
278
+ }
246
279
  /**
247
280
  * Execute a function with the driver for the given session.
248
281
  * Returns a RunResult for consistent error handling across commands.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@houwert/conductor",
3
- "version": "0.24.0",
3
+ "version": "0.25.0",
4
4
  "description": "CLI tool for mobile app interactions — optimized for AI agents",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -51,6 +51,19 @@ conductor assert-visible "Dashboard"
51
51
  | `conductor gesture <json\|--file path>` | Play a multi-touch path |
52
52
  | `conductor clipboard read` / `clipboard write <text>` / `paste` | Clipboard (iOS) |
53
53
  | `conductor list-options [command]` | List valid values for enumerated params |
54
+ | `conductor input-server` | Start (if needed) and print the streaming-input WebSocket URL for the device |
55
+
56
+ ## Streaming input (host IDEs)
57
+
58
+ For continuous, low-latency input (live drags, fast typing) a host IDE can open
59
+ one persistent WebSocket per device instead of spawning a command per event.
60
+ `conductor input-server` ensures the daemon + driver are up and prints the
61
+ loopback URL (`ws://127.0.0.1:<port>/input`; also in `daemon-status --json` as
62
+ `inputPort`). The server sends a `hello` with per-platform capabilities, then
63
+ accepts normalized (0..1) frames: `pointer{id,phase,x,y}`, `key{code,mods,down}`,
64
+ `text{value}`, `button{name}`, `scroll{x,y,dx,dy}`, `tvremote{button}`. Conductor
65
+ owns coord→device translation and keymaps. For scripted, one-off actions use the
66
+ discrete commands above — this is for interactive host UIs.
54
67
 
55
68
  ## Discovering valid values
56
69
 
@@ -23,6 +23,7 @@ conductor list-apps # installed app ids / package names
23
23
  | --------------------------------------------------------------------- | -------------------------------------------------------------------------- |
24
24
  | `conductor start-device --platform <ios\|android\|tvos\|web\|vega>` | Boot a simulator/emulator, start the web driver, or attach to a Vega VVD |
25
25
  | `conductor start-device --os-version <n> --device-type <name>` | Pick OS version + device type (creates if needed) |
26
+ | `conductor start-device --platform android --avd <name> --device-type <profile> --memory <mb>` | Create an Android AVD with a RAM floor (default 4096MB; only raises, creation-time only) |
26
27
  | `conductor stop-device [<name-or-id>] [--all]` | Shut down device(s) |
27
28
  | `conductor delete-device <name-or-id> [--all]` | Delete simulator(s)/AVD(s)/web session(s) |
28
29
  | `conductor set-location --lat <n> --lng <n>` | Set GPS coordinates |
@@ -73,7 +74,7 @@ screen recording, clipboard, `clear-state`/`uninstall-app`.
73
74
  | Command | Purpose |
74
75
  | ----------------------------------------------------- | ---------------------------------------------------------------------- |
75
76
  | `conductor install-app <path>` | Install .app / .ipa / .apk |
76
- | `conductor launch-app <appId>` | Launch app (saved to session); `--no-stop-app`, `--argument key=value` |
77
+ | `conductor launch-app <appId>` | Launch app (saved to session); `--no-stop-app`, `--argument key=value`, `--inject` |
77
78
  | `conductor stop-app [<appId>]` | Stop app |
78
79
  | `conductor uninstall-app <appId>` | Uninstall app |
79
80
  | `conductor copy-app <bundleId> --from <id> --to <id>` | Copy an installed app between iOS simulators |
@@ -29,6 +29,49 @@ conductor capture-ui --output /tmp/screen.json
29
29
  conductor tap-on @e5
30
30
  ```
31
31
 
32
+ ## Native in-process inspection (iOS/tvOS simulator)
33
+
34
+ The commands above observe the app **externally** (accessibility snapshots), so
35
+ they can't see real component colors, fonts, or the view-controller stack. When
36
+ you need that native detail, launch the app with an injected in-process library
37
+ and use the `native-*` commands. Requires `launch-app <appId> --inject` first
38
+ (iOS/tvOS simulator only).
39
+
40
+ | Command | Purpose |
41
+ |---|---|
42
+ | `conductor native-ping` | Verify the injected in-process control library is alive |
43
+ | `conductor native-inspect` | Real UIView/CALayer tree: resolved colors (`#RRGGBBAA`), fonts, text (incl. React Native Fabric), corner radius, borders, shadows, gradients, and each node's `absFrame` |
44
+ | `conductor native-nav` | Navigation state: `UINavigationController` stacks, tab selection, presented controllers, titles |
45
+ | `conductor native-screenshot --output <p.png>` | In-process PNG of the key window |
46
+ | `conductor native-image <x,y,w,h> --output <p.png>` | Extract a component as a PNG — pass a node's `absFrame` from `native-inspect` |
47
+ | `conductor native-snapshot <id> --output <p.png>` | Isolated PNG of one view's own content (transparent) — per-layer texture for a 3D explosion; `--with-subviews` composites the subtree |
48
+ | `conductor native-console [--since <n>]` / `native-network [--since <n>]` | App stdout/stderr + captured HTTP; poll with the returned `cursor` |
49
+ | `conductor native-heap --pattern <s> \| --class <name> \| --read <addr> [--key <keyPath>]` | Live-object browser (find classes/instances, read a property off an address) |
50
+ | `conductor native-appearance <light\|dark\|system> \| --direction <ltr\|rtl> \| --anim-speed <n>` | Force appearance / RTL / freeze animations app-wide |
51
+ | `conductor native-eval '<swift>'` | Compile & run arbitrary Swift inside the app (full UIKit / ObjC-runtime access); `--mode full` for a whole function body. e.g. `native-eval 'UIScreen.main.bounds'` |
52
+ | `conductor native-raw <path>` | Escape hatch — GET any in-process endpoint (e.g. `'/get?id=..&keyPath=layer.cornerRadius'`, `'/class?id=..'`, `'/responders?id=..'`, `'/swiftui'`, `'/defaults'`, `'/focus'`, `'/snapshots?scale=0.5'`). Full list in `packages/ios-inproc/README.md`. |
53
+ | `conductor native-view <id>` | Full property detail for one view (class chain, transform, layer, gestures, text/font) |
54
+ | `conductor native-set <id> <key> <value>` | **Live-edit** a property: alpha, hidden, backgroundColor, tintColor, cornerRadius, borderWidth, borderColor, frame, text, textColor. `text`/`textColor` work on RN Fabric text views too |
55
+ | `conductor native-props <id>` | React Native Fabric props: typed `ViewProps` + the raw JS prop bag (Fabric host views only) |
56
+
57
+ > **Editing RN Fabric text/props:** the native plane can't set text on `RCTParagraphComponentView` (no native setter) and `native-props` returns `rawProps: null` on Fabric. Edit through React instead with `conductor native-rn-set --react-tag <n> --path children --value '"…"'` (and read raw JSX props with `native-rn-props --react-tag <n>`). `reactTag` comes from this tree's `rn.reactTag`. See the conductor-metro-debugger skill. Dev builds only.
58
+ | `conductor native-constraints <id>` | Auto Layout constraints affecting a view + ambiguity |
59
+ | `conductor native-hittest <x,y>` | Topmost view at a point + ancestor chain (select-by-point) |
60
+ | `conductor native-highlight <id>` | Flash a highlight over the view on the device |
61
+ | `conductor native-find [--class <name>] [--text <s>]` | Search views by class and/or text |
62
+
63
+ Every `native-inspect` node has a stable `id` (for this launch). The Reveal-style loop:
64
+ inspect → pick an `id` → `native-view` for detail → `native-set` to edit live → see it
65
+ on the device. IDs are pointer-based and reset each launch, so re-inspect after relaunch.
66
+
67
+ ```bash
68
+ conductor launch-app com.example.app --inject
69
+ conductor native-inspect # tree with ids, colors, fonts, absFrame
70
+ conductor native-view 0x10280d0c0 # full detail for a view
71
+ conductor native-set 0x10280d0c0 backgroundColor '#FF3B30FF' # live-edit, visible on device
72
+ conductor native-image 816,286,288,288 --output /tmp/avatar.png
73
+ ```
74
+
32
75
  ## Assertions
33
76
 
34
77
  | Command | Purpose |
@@ -18,6 +18,16 @@ Playwright web.
18
18
  | `conductor debug component-tree [--port N]` | On-screen React component tree |
19
19
  | `conductor debug inspect-element <x,y>` | React component at a screen point |
20
20
  | `conductor debug log-registry [--source metro]` | Summarize recent Metro/Hermes console logs |
21
+ | `conductor native-rn-set --react-tag <n> --path <dot.path> --value <json>` | Live-edit an RN component's props via React DevTools `overrideProps` (text via `--path children`, color via `--path style.color`). Dev builds only |
22
+ | `conductor native-rn-props --react-tag <n>` | Raw JSX props (`memoizedProps`) of an RN fiber by reactTag — the JS-side truth for Fabric where native `/props` `rawProps` is null |
23
+
24
+ `--react-tag` comes from `native-inspect`'s `rn.reactTag`. `--value` is JSON (a bare
25
+ string works for text). `--path` is a dot path into props: `children`, `style.color`,
26
+ `style.fontSize`, `accessibilityLabel`. `style.color` works whether the component's
27
+ `style` is an object or a composed array. These drive React itself over Metro CDP, so
28
+ they need a **dev/debug build** (the DevTools backend must be active) and a running
29
+ Metro; on a release build they return a clear "not a dev build" error. Output is
30
+ `{"status":"ok","applied":true}` or `{"status":"error","message":"…"}`.
21
31
 
22
32
  ## Logs
23
33