@houwert/conductor 0.33.1 → 0.33.2

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.
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.HELP = void 0;
7
+ exports.captureScreen = captureScreen;
7
8
  exports.screenshot = screenshot;
8
9
  exports.HELP = ` take-screenshot [<element>] [--output <path>] [--full-page] [--display <panel>]
9
10
  Take screenshot (--full-page: web only, capture entire scrollable page)
@@ -39,8 +40,8 @@ const roku_js_1 = require("../drivers/roku.js");
39
40
  const wait_js_1 = require("../drivers/wait.js");
40
41
  const direct_ios_selector_js_1 = require("../drivers/direct-ios-selector.js");
41
42
  const png_crop_js_1 = require("../png-crop.js");
42
- const DEFAULT_MARGIN_PX = 8;
43
43
  const exec = (0, util_1.promisify)(child_process_1.execFile);
44
+ const DEFAULT_MARGIN_PX = 8;
44
45
  /**
45
46
  * Grab the screen, pointing at the right panel on a multi-display device.
46
47
  *
@@ -50,15 +51,23 @@ const exec = (0, util_1.promisify)(child_process_1.execFile);
50
51
  * the live one through simctl instead. Ordinary devices keep the driver path.
51
52
  */
52
53
  async function captureScreen(driver, opts, displayOverride) {
53
- const deviceId = driver instanceof ios_js_1.IOSDriver ? driver.deviceId : undefined;
54
- if (!deviceId || (!displayOverride && !(driver instanceof ios_js_1.IOSDriver))) {
55
- return await driver.screenshot(opts);
54
+ if (!(driver instanceof ios_js_1.IOSDriver)) {
55
+ if (displayOverride) {
56
+ throw new Error('--display is iOS-only; other platforms expose a single screen');
57
+ }
58
+ return { buffer: await driver.screenshot(opts), redirected: false };
59
+ }
60
+ const deviceId = driver.deviceId;
61
+ if (!deviceId) {
62
+ if (displayOverride)
63
+ throw new Error('--display needs a device to query for its displays');
64
+ return { buffer: await driver.screenshot(opts), redirected: false };
56
65
  }
57
66
  const displays = await (0, devicectl_js_1.listDisplays)(deviceId).catch(() => []);
58
67
  if (!displays.length) {
59
68
  if (displayOverride)
60
69
  throw new Error("could not read this device's displays");
61
- return await driver.screenshot(opts);
70
+ return { buffer: await driver.screenshot(opts), redirected: false };
62
71
  }
63
72
  const choice = (0, ios_displays_js_1.pickCaptureDisplay)(displays, displayOverride);
64
73
  if (choice.error)
@@ -66,7 +75,7 @@ async function captureScreen(driver, opts, displayOverride) {
66
75
  const primary = displays.find((d) => d.primary);
67
76
  // Nothing to redirect: the driver already captures the primary panel.
68
77
  if (choice.displayId === null || (primary && choice.displayId === primary.displayId)) {
69
- return await driver.screenshot(opts);
78
+ return { buffer: await driver.screenshot(opts), redirected: false };
70
79
  }
71
80
  const file = path_1.default.join(os_1.default.tmpdir(), `conductor-shot-${Date.now()}.png`);
72
81
  try {
@@ -79,7 +88,10 @@ async function captureScreen(driver, opts, displayOverride) {
79
88
  String(choice.displayId),
80
89
  file,
81
90
  ]);
82
- return await promises_1.default.readFile(file);
91
+ return { buffer: await promises_1.default.readFile(file), redirected: true };
92
+ }
93
+ catch (err) {
94
+ throw new Error(`could not capture display ${choice.displayId}: ${err instanceof Error ? err.message : String(err)}`);
83
95
  }
84
96
  finally {
85
97
  await promises_1.default.unlink(file).catch(() => { });
@@ -115,8 +127,13 @@ async function screenshot(outputPath, opts = {}, sessionName = 'default', fullPa
115
127
  : '';
116
128
  const margin = flags.margin ?? DEFAULT_MARGIN_PX;
117
129
  const result = await (0, runner_js_1.runDirect)(async (driver) => {
118
- const buf = await captureScreen(driver, { fullPage }, flags.display);
130
+ const { buffer: buf, redirected } = await captureScreen(driver, { fullPage }, flags.display);
119
131
  let out = buf;
132
+ if (sel && redirected) {
133
+ throw new Error('cropping to an element is not supported on this display yet — the panel is ' +
134
+ 'rotated relative to the accessibility coordinate space. Re-run with ' +
135
+ '--display cover, or fold the device, to crop against the main panel.');
136
+ }
120
137
  if (sel) {
121
138
  let el;
122
139
  let hierarchyW;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@houwert/conductor",
3
- "version": "0.33.1",
3
+ "version": "0.33.2",
4
4
  "description": "CLI tool for mobile app interactions — optimized for AI agents",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -45,6 +45,11 @@ Pass `--display cover` or `--display inner` to pin it to one panel, or a display
45
45
  id for anything else the device has attached (CarPlay, an external screen). An
46
46
  unknown value lists that device's displays with their ids.
47
47
 
48
+ Cropping a screenshot to an element (`take-screenshot <element>`) only works
49
+ against the main panel: the inner panel's framebuffer is rotated relative to the
50
+ accessibility coordinate space, so conductor refuses the crop there instead of
51
+ returning the wrong region. Fold the device or pass `--display cover` to crop.
52
+
48
53
  Angles are in degrees, 0 (shut) to 180 (flat). `closed`/`book`/`open` map to
49
54
  0/130/180. Named poses always swap the display; an arbitrary mid-way angle sets
50
55
  the hinge but may leave the cover display active, because the system decides