@8bitbish/screenshot-service 1.3.15 → 1.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/bin/setup.js CHANGED
@@ -104,33 +104,29 @@ async function runSetupIos() {
104
104
  ok('Installed: ' + python);
105
105
  }
106
106
 
107
- // Sudoers (opt-in)
108
- step(3, 'Passwordless tunnel (optional)');
109
- console.log(' The iPhone screenshot tunnel runs under sudo. You can either:');
110
- console.log(' • ' + C.bold + 'Grant passwordless sudo' + C.reset + ' for this specific command (recommended for convenience)');
111
- console.log(' • Skip you will be prompted for your password once per terminal session');
112
- console.log('');
113
- const ans = await prompt('Grant passwordless sudo for the tunnel daemon only? [y/N]');
114
- if (/^[yY]/.test(ans)) {
115
- const username = execSync('whoami').toString().trim();
116
- const rule = `${username} ALL=(ALL) NOPASSWD: ${python} -m pymobiledevice3 remote tunneld`;
117
- const sudoersFile = '/etc/sudoers.d/screenshot-service';
118
- const scriptPath = path.join(tmpdir(), 'screenshot-service-setup.sh');
119
- writeFileSync(scriptPath, [
120
- '#!/bin/bash',
121
- `printf '%s\\n' '${rule}' > '${sudoersFile}'`,
122
- `chmod 440 '${sudoersFile}'`,
123
- ].join('\n'), { mode: 0o755 });
107
+ // Background tunnel service. Not optional any more: checkIosSetup() keys
108
+ // `ok` off the daemon, and the old on-demand path needed a password that a
109
+ // non-interactive host app can never supply.
110
+ step(3, 'Background tunnel service');
111
+ const { tunnelDaemonStatus, tunnelDaemonInstallScript } = require('../dist/setup');
112
+ if (tunnelDaemonStatus().ok) {
113
+ ok('Background tunnel service already installed');
114
+ } else {
115
+ console.log(' The device tunnel needs root. Installing it as a launchd service means it');
116
+ console.log(' starts at boot, restarts itself if it wedges, and captures never wait on');
117
+ console.log(' a password. macOS will ask for your password once.');
118
+ console.log('');
119
+ const scriptPath = path.join(tmpdir(), 'screenshot-service-tunneld-setup.sh');
120
+ writeFileSync(scriptPath, '#!/bin/bash\nset -e\n' + tunnelDaemonInstallScript() + '\n', { mode: 0o755 });
124
121
  try {
125
122
  execSync(`osascript -e 'do shell script "${scriptPath}" with administrator privileges'`, { stdio: 'pipe' });
126
- ok('Passwordless tunnel configured');
123
+ ok('Background tunnel service installed');
127
124
  } catch {
128
- warn('Sudoers setup cancelled — you will be prompted for your password each session.');
125
+ warn('Tunnel service setup was cancelled — iPhone capture will not work until it is installed.');
126
+ warn('Re-run this command, or use Device management → iOS in the app.');
129
127
  } finally {
130
128
  try { unlinkSync(scriptPath); } catch {}
131
129
  }
132
- } else {
133
- log(C.dim + ' Skipped. You will be prompted for your Mac password the first time per session.' + C.reset);
134
130
  }
135
131
 
136
132
  // Device instructions
@@ -0,0 +1,33 @@
1
+ declare class DvtHelper {
2
+ private proc;
3
+ private ready;
4
+ private pending;
5
+ private nextId;
6
+ private buf;
7
+ /** Sticky: once the worker has proven unusable, stop paying to retry it. */
8
+ private disabled;
9
+ private reset;
10
+ private start;
11
+ private onData;
12
+ private send;
13
+ /** True when the worker is usable. Never throws — callers fall back. */
14
+ available(): Promise<boolean>;
15
+ /** Connect up front so the first capture doesn't pay the handshake. */
16
+ warm(udid?: string | null): Promise<void>;
17
+ screenshot(path: string, udid?: string | null): Promise<Record<string, unknown>>;
18
+ foregroundApp(udid?: string | null): Promise<Record<string, unknown>>;
19
+ /**
20
+ * Screenshot and foreground-app together. Issued CONCURRENTLY on purpose:
21
+ * one DVT connection multiplexes channels, so proclist overlaps the image
22
+ * transfer and is close to free. Doing this with two CLI processes instead
23
+ * silently yields no image — the sessions collide.
24
+ */
25
+ capture(path: string, udid?: string | null, wantForegroundApp?: boolean): Promise<{
26
+ bundleId: string;
27
+ name: string;
28
+ } | null>;
29
+ stop(): void;
30
+ }
31
+ export declare const dvtHelper: DvtHelper;
32
+ export {};
33
+ //# sourceMappingURL=dvtHelper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dvtHelper.d.ts","sourceRoot":"","sources":["../dvtHelper.ts"],"names":[],"mappings":"AAqCA,cAAM,SAAS;IACb,OAAO,CAAC,IAAI,CAA6B;IACzC,OAAO,CAAC,KAAK,CAA8B;IAC3C,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,GAAG,CAAM;IACjB,4EAA4E;IAC5E,OAAO,CAAC,QAAQ,CAAS;IAEzB,OAAO,CAAC,KAAK;YAcC,KAAK;IAyCnB,OAAO,CAAC,MAAM;IAyBd,OAAO,CAAC,IAAI;IAiBZ,wEAAwE;IAClE,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAenC,uEAAuE;IACjE,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAKzC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI;IAK7C,aAAa,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI;IAKxC;;;;;OAKG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,iBAAiB,UAAO;kBAQ9C,MAAM;cAAQ,MAAM;;IAGhD,IAAI;CAKL;AAED,eAAO,MAAM,SAAS,WAAkB,CAAC"}
@@ -0,0 +1,204 @@
1
+ "use strict";
2
+ // ── Persistent pymobiledevice3 worker ────────────────────────────────────────
3
+ //
4
+ // Client for python/pmd3_helper.py. See that file for why it exists; briefly:
5
+ // every pmd3 CLI call pays ~1.8s of import + handshake before doing ~1s of
6
+ // work, and a capture needs two. Holding one session takes a screenshot plus
7
+ // foreground-app lookup from ~6.8s to ~2.5s.
8
+ //
9
+ // This is an OPTIMISATION, never a requirement. Every failure path falls back
10
+ // to the CLI, so a machine where the worker can't start is slow, not broken.
11
+ // Set SCREENSHOT_SERVICE_NO_HELPER=1 to force the CLI path.
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.dvtHelper = void 0;
14
+ const child_process_1 = require("child_process");
15
+ const fs_1 = require("fs");
16
+ const path_1 = require("path");
17
+ const logger_1 = require("./logger");
18
+ const python_1 = require("./setup/python");
19
+ /** Worker startup: interpreter boot + pymobiledevice3 import. */
20
+ const READY_TIMEOUT_MS = 15000;
21
+ /** Per-request ceiling. Screenshots measured ~2.5s; this is generous. */
22
+ const REQUEST_TIMEOUT_MS = 30000;
23
+ function helperScriptPath() {
24
+ // dist/dvtHelper.js -> dist/python/pmd3_helper.py (copied by copy-assets.js).
25
+ const candidates = [
26
+ (0, path_1.join)(__dirname, "python", "pmd3_helper.py"),
27
+ (0, path_1.join)(__dirname, "..", "python", "pmd3_helper.py"),
28
+ ];
29
+ return candidates.find(fs_1.existsSync) ?? null;
30
+ }
31
+ class DvtHelper {
32
+ constructor() {
33
+ this.proc = null;
34
+ this.ready = null;
35
+ this.pending = new Map();
36
+ this.nextId = 1;
37
+ this.buf = "";
38
+ /** Sticky: once the worker has proven unusable, stop paying to retry it. */
39
+ this.disabled = false;
40
+ }
41
+ reset(reason) {
42
+ const proc = this.proc;
43
+ this.proc = null;
44
+ this.ready = null;
45
+ for (const [id, p] of this.pending) {
46
+ clearTimeout(p.timer);
47
+ p.reject(new Error(`dvt helper ${reason}`));
48
+ this.pending.delete(id);
49
+ }
50
+ if (proc) {
51
+ try {
52
+ proc.kill();
53
+ }
54
+ catch { /* already gone */ }
55
+ }
56
+ }
57
+ async start() {
58
+ if (this.ready)
59
+ return this.ready;
60
+ this.ready = (async () => {
61
+ const script = helperScriptPath();
62
+ if (!script)
63
+ throw new Error("pmd3_helper.py not found in package");
64
+ const python = await (0, python_1.findPythonAsync)();
65
+ if (!python)
66
+ throw new Error("no python with pymobiledevice3");
67
+ const proc = (0, child_process_1.spawn)(python, [script], { stdio: ["pipe", "pipe", "pipe"] });
68
+ this.proc = proc;
69
+ // pymobiledevice3 logs to stderr; drain it so the pipe can't fill and
70
+ // block the child, but don't treat it as failure.
71
+ proc.stderr?.on("data", () => { });
72
+ proc.on("exit", (code) => {
73
+ if (this.proc === proc)
74
+ this.reset(`exited (code ${code})`);
75
+ });
76
+ proc.on("error", (err) => {
77
+ if (this.proc === proc)
78
+ this.reset(`spawn error: ${err.message}`);
79
+ });
80
+ await new Promise((resolve, reject) => {
81
+ const timer = setTimeout(() => reject(new Error("helper did not report ready in time")), READY_TIMEOUT_MS);
82
+ const onReady = () => { clearTimeout(timer); resolve(); };
83
+ proc.stdout?.on("data", (chunk) => this.onData(chunk, onReady));
84
+ });
85
+ })();
86
+ try {
87
+ await this.ready;
88
+ }
89
+ catch (e) {
90
+ this.reset("failed to start");
91
+ throw e;
92
+ }
93
+ return this.ready;
94
+ }
95
+ onData(chunk, onReady) {
96
+ this.buf += chunk.toString();
97
+ let nl;
98
+ while ((nl = this.buf.indexOf("\n")) >= 0) {
99
+ const line = this.buf.slice(0, nl);
100
+ this.buf = this.buf.slice(nl + 1);
101
+ if (!line.trim())
102
+ continue;
103
+ let msg;
104
+ try {
105
+ msg = JSON.parse(line);
106
+ }
107
+ catch {
108
+ continue; // stray output — not ours
109
+ }
110
+ if (msg.ready === true) {
111
+ onReady?.();
112
+ continue;
113
+ }
114
+ const id = msg.id;
115
+ if (id === undefined)
116
+ continue;
117
+ const p = this.pending.get(id);
118
+ if (!p)
119
+ continue;
120
+ this.pending.delete(id);
121
+ clearTimeout(p.timer);
122
+ if (msg.ok === true)
123
+ p.resolve(msg);
124
+ else
125
+ p.reject(new Error(String(msg.error ?? "helper error")));
126
+ }
127
+ }
128
+ send(action, extra = {}) {
129
+ const proc = this.proc;
130
+ if (!proc?.stdin)
131
+ return Promise.reject(new Error("dvt helper not running"));
132
+ const id = this.nextId++;
133
+ return new Promise((resolve, reject) => {
134
+ const timer = setTimeout(() => {
135
+ this.pending.delete(id);
136
+ // A timed-out worker is presumed wedged; drop it so the next call
137
+ // starts clean rather than queueing behind a dead session.
138
+ this.reset("request timed out");
139
+ reject(new Error(`dvt helper timed out after ${REQUEST_TIMEOUT_MS}ms`));
140
+ }, REQUEST_TIMEOUT_MS);
141
+ this.pending.set(id, { resolve, reject, timer });
142
+ proc.stdin.write(JSON.stringify({ id, action, ...extra }) + "\n");
143
+ });
144
+ }
145
+ /** True when the worker is usable. Never throws — callers fall back. */
146
+ async available() {
147
+ if (this.disabled)
148
+ return false;
149
+ if (process.env.SCREENSHOT_SERVICE_NO_HELPER)
150
+ return false;
151
+ try {
152
+ await this.start();
153
+ return true;
154
+ }
155
+ catch (e) {
156
+ this.disabled = true;
157
+ logger_1.log.warn("dvt helper unavailable; using the CLI path", {
158
+ reason: e instanceof Error ? e.message : String(e),
159
+ });
160
+ return false;
161
+ }
162
+ }
163
+ /** Connect up front so the first capture doesn't pay the handshake. */
164
+ async warm(udid) {
165
+ if (!(await this.available()))
166
+ return;
167
+ await this.send("ping", udid ? { udid } : {}).catch(() => { });
168
+ }
169
+ async screenshot(path, udid) {
170
+ await this.start();
171
+ return this.send("screenshot", { path, ...(udid ? { udid } : {}) });
172
+ }
173
+ async foregroundApp(udid) {
174
+ await this.start();
175
+ return this.send("foregroundApp", udid ? { udid } : {});
176
+ }
177
+ /**
178
+ * Screenshot and foreground-app together. Issued CONCURRENTLY on purpose:
179
+ * one DVT connection multiplexes channels, so proclist overlaps the image
180
+ * transfer and is close to free. Doing this with two CLI processes instead
181
+ * silently yields no image — the sessions collide.
182
+ */
183
+ async capture(path, udid, wantForegroundApp = true) {
184
+ await this.start();
185
+ const shot = this.send("screenshot", { path, ...(udid ? { udid } : {}) });
186
+ const fg = wantForegroundApp
187
+ ? this.send("foregroundApp", udid ? { udid } : {}).catch(() => null)
188
+ : Promise.resolve(null);
189
+ const [, fgRes] = await Promise.all([shot, fg]);
190
+ const app = fgRes?.app ?? null;
191
+ return app;
192
+ }
193
+ stop() {
194
+ if (!this.proc)
195
+ return;
196
+ try {
197
+ this.proc.stdin?.write(JSON.stringify({ action: "quit" }) + "\n");
198
+ }
199
+ catch { }
200
+ this.reset("stopped");
201
+ }
202
+ }
203
+ exports.dvtHelper = new DvtHelper();
204
+ //# sourceMappingURL=dvtHelper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dvtHelper.js","sourceRoot":"","sources":["../dvtHelper.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -26,6 +26,12 @@ const path_1 = require("path");
26
26
  const os_1 = require("os");
27
27
  const device_1 = require("./device");
28
28
  const logger_1 = require("./logger");
29
+ // Shared resolver — index.ts used to keep its own copy with a SHORTER candidate
30
+ // list, so a Mac with an unlinked (keg-only) brew python passed detection and
31
+ // then failed every capture. One list, one cache, one behaviour.
32
+ const python_1 = require("./setup/python");
33
+ const dvtHelper_1 = require("./dvtHelper");
34
+ const tunneld_1 = require("./setup/tunneld");
29
35
  var logger_2 = require("./logger");
30
36
  Object.defineProperty(exports, "setLogger", { enumerable: true, get: function () { return logger_2.setLogger; } });
31
37
  const android_device_1 = require("./android-device");
@@ -44,30 +50,6 @@ var wizard_1 = require("./wizard");
44
50
  Object.defineProperty(exports, "createSetupWindow", { enumerable: true, get: function () { return wizard_1.createSetupWindow; } });
45
51
  const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
46
52
  const PMD3 = ["-m", "pymobiledevice3"];
47
- // ── Python detection ──────────────────────────────────────────────────────────
48
- function findPython() {
49
- // pymobiledevice3 requires Python 3.11+. Prefer newer versions, then fall back to PATH `python3`.
50
- const candidates = [
51
- "/opt/homebrew/bin/python3.14",
52
- "/opt/homebrew/bin/python3.13",
53
- "/opt/homebrew/bin/python3.12",
54
- "/opt/homebrew/bin/python3.11",
55
- "/usr/local/bin/python3.14",
56
- "/usr/local/bin/python3.13",
57
- "/usr/local/bin/python3.12",
58
- "/usr/local/bin/python3.11",
59
- "python3",
60
- ];
61
- const probe = "import sys, pymobiledevice3; sys.exit(0 if sys.version_info >= (3, 11) else 1)";
62
- for (const p of candidates) {
63
- try {
64
- (0, child_process_1.execFileSync)(p, ["-c", probe], { stdio: "ignore" });
65
- return p;
66
- }
67
- catch { }
68
- }
69
- throw new Error("Python 3.11+ with pymobiledevice3 not found. Run: npx @8bitbish/screenshot-service setup-ios");
70
- }
71
53
  // ── Tunnel management ─────────────────────────────────────────────────────────
72
54
  /**
73
55
  * Process-liveness check only — does NOT confirm tunneld's HTTP server is
@@ -114,35 +96,79 @@ function killTunneldProcess() {
114
96
  // -n means "non-interactive" — sudo fails immediately if a password is needed.
115
97
  return !isTunnelRunning();
116
98
  }
99
+ /** Poll the health endpoint until it answers, or the budget runs out. */
100
+ async function waitForHealthy(budgetMs) {
101
+ const deadline = Date.now() + budgetMs;
102
+ while (Date.now() < deadline) {
103
+ if (await isTunnelHealthy())
104
+ return true;
105
+ await new Promise((r) => setTimeout(r, 300));
106
+ }
107
+ return false;
108
+ }
109
+ /**
110
+ * Ask launchd to kill and respawn the tunnel daemon. `kickstart -k` handles
111
+ * the zombie case (alive process, dead HTTP server) in one step, and the
112
+ * setup wizard installs a sudoers rule permitting exactly this one command
113
+ * without a password — so unlike the old `sudo pkill`, it works from a
114
+ * non-interactive host app.
115
+ *
116
+ * A failed kickstart is not fatal: KeepAlive may already be respawning the
117
+ * job, so we poll regardless and let the health check decide.
118
+ */
119
+ async function kickstartTunnelDaemon() {
120
+ try {
121
+ (0, child_process_1.execFileSync)("sudo", ["-n", "/bin/launchctl", "kickstart", "-k", `system/${tunneld_1.TUNNELD_LABEL}`], {
122
+ stdio: "ignore",
123
+ timeout: 5000,
124
+ });
125
+ }
126
+ catch {
127
+ logger_1.log.warn("launchctl kickstart failed (no sudoers grant, or launchd refused); relying on KeepAlive", {
128
+ label: tunneld_1.TUNNELD_LABEL,
129
+ });
130
+ }
131
+ // Must exceed ThrottleInterval: launchd may hold a respawn for the full
132
+ // throttle window, and a shorter budget would report failure a moment
133
+ // before the daemon returns. Throttle + 5s covers the tunnel's own startup.
134
+ return waitForHealthy((tunneld_1.TUNNELD_THROTTLE_SECONDS + 5) * 1000);
135
+ }
117
136
  async function startTunnel() {
118
- // Fast path: an existing tunneld that's actually healthy — no work to do.
137
+ // Fast path: a healthy tunnel — no work to do. On a machine with the
138
+ // LaunchDaemon this is the ONLY branch that ever runs, because launchd
139
+ // brought the tunnel up at boot.
119
140
  if (isTunnelRunning() && await isTunnelHealthy())
120
141
  return;
121
- // Zombie case: a tunneld process exists but its HTTP server is dead. This
122
- // happens after long uptimes / network changes / sleep pgrep says "alive"
123
- // but every pmd3 call fails with "Unable to connect to Tunneld", producing
124
- // downstream errors like "Screenshot failed — is the iPhone unlocked and
125
- // connected?" that point at the wrong root cause.
126
- //
127
- // tunneld runs as root (started with sudo). Killing it needs root too, and
128
- // Electron's non-interactive context can't prompt for a password. So we try
129
- // a best-effort passwordless kill; if that fails, throw a targeted error
130
- // that tells the user exactly what to do rather than futilely retrying.
142
+ // ── Managed daemon ────────────────────────────────────────────────────────
143
+ // launchd owns the tunnel's lifecycle. We never spawn it ourselves and never
144
+ // invoke sudo on the capture path; the worst case is asking launchd to
145
+ // recycle a wedged job.
146
+ const daemon = (0, tunneld_1.tunnelDaemonStatus)();
147
+ if (daemon.present) {
148
+ logger_1.log.warn("tunnel unhealthy; asking launchd to recycle the daemon", { label: tunneld_1.TUNNELD_LABEL });
149
+ if (await kickstartTunnelDaemon())
150
+ return;
151
+ throw new Error("The device tunnel isn't responding and launchd couldn't restart it. " +
152
+ `See ${tunneld_1.TUNNELD_LOG_PATH}, or re-run iOS setup from the app to reinstall the tunnel service.`);
153
+ }
154
+ // ── Legacy on-demand path ─────────────────────────────────────────────────
155
+ // Machines set up before the daemon existed. Kept working, but every branch
156
+ // here needs root and cannot prompt for a password, which is precisely why
157
+ // the daemon replaced it — so the errors point at setup, not at a log file.
131
158
  if (isTunnelRunning()) {
132
159
  logger_1.log.warn("tunneld process is alive but HTTP endpoint unreachable; attempting restart", { port: 49151 });
133
160
  const killed = killTunneldProcess();
134
161
  if (!killed) {
135
- const err = new Error("The Wi-Fi tunnel daemon (tunneld) is unresponsive but running as root, so this app can't restart it silently. " +
136
- "Open Terminal and run:\n" +
137
- " sudo pkill -f 'pymobiledevice3 remote tunneld'\n" +
138
- "Then try the action again — the app will start a fresh tunneld automatically.");
162
+ const err = new Error("The device tunnel is unresponsive and running as root, so this app can't restart it. " +
163
+ "Re-run iOS setup from the app (Device management → iOS) to install the background tunnel " +
164
+ "service, which recovers from this automatically. To unblock right now, run:\n" +
165
+ " sudo pkill -f 'pymobiledevice3 remote tunneld'");
139
166
  logger_1.log.error("tunneld zombie detected; passwordless kill failed", err);
140
167
  throw err;
141
168
  }
142
- // Give the OS a moment to reap the process before spawning a replacement.
143
169
  await new Promise((r) => setTimeout(r, 500));
144
170
  }
145
- const python = findPython();
171
+ const python = await (0, python_1.findPythonAsyncOrThrow)();
146
172
  // Open the log file synchronously so spawn gets a real fd. Using
147
173
  // createWriteStream here is racy — the stream's fd is null until the async
148
174
  // 'open' fires, and spawn rejects WriteStreams with fd: null as invalid stdio.
@@ -151,20 +177,15 @@ async function startTunnel() {
151
177
  detached: true,
152
178
  stdio: ["ignore", logFd, logFd],
153
179
  });
154
- // Spawn dup'd the fd for the child; close our parent-side copy to avoid leaking.
155
180
  (0, fs_1.closeSync)(logFd);
156
181
  child.unref();
157
- // Poll the HTTP endpoint until healthy — process being up isn't enough
158
- // (see zombie case above).
159
- const deadline = Date.now() + 10000;
160
- while (Date.now() < deadline) {
161
- if (await isTunnelHealthy()) {
162
- registerExitHandler();
163
- return;
164
- }
165
- await new Promise((r) => setTimeout(r, 300));
182
+ if (await waitForHealthy(10000)) {
183
+ registerExitHandler();
184
+ return;
166
185
  }
167
- throw new Error("Tunnel did not start within 10s. Check ~/.tunneld.log");
186
+ throw new Error("The device tunnel didn't start. This Mac still uses the old on-demand tunnel, which needs a " +
187
+ "password this app can't ask for. Re-run iOS setup from the app (Device management → iOS) to " +
188
+ `install the background tunnel service. Details: ${(0, path_1.join)((0, os_1.homedir)(), ".tunneld.log")}`);
168
189
  }
169
190
  /** Manually stop the tunnel. */
170
191
  function stopTunnel() {
@@ -353,12 +374,30 @@ async function getDeviceInfoCached(python, udid) {
353
374
  deviceInfoCache.set(key, { info, ts: Date.now() });
354
375
  return info;
355
376
  }
377
+ /** Fill in CFBundleShortVersionString for an app the worker already identified. */
378
+ async function appVersionFor(python, udid, app) {
379
+ try {
380
+ const { stdout } = await execFileAsync(python, await (0, device_1.pmd3Args)(udid, "apps", "query", app.bundleId), { timeout: 8000 });
381
+ const meta = JSON.parse(stdout)?.[app.bundleId] ?? {};
382
+ const short = meta["CFBundleShortVersionString"] ?? null;
383
+ const build = meta["CFBundleVersion"] ?? null;
384
+ return {
385
+ bundleId: app.bundleId,
386
+ name: app.name,
387
+ version: short && build ? `${short} (${build})` : short ?? build ?? null,
388
+ };
389
+ }
390
+ catch {
391
+ // Version is a nice-to-have; identity is the useful part.
392
+ return { bundleId: app.bundleId, name: app.name, version: null };
393
+ }
394
+ }
356
395
  async function triggerPhoneScreenshot(options) {
357
396
  const timeout = options?.timeout ?? 30000;
358
397
  const includeLocation = options?.includeLocation ?? false;
359
398
  const includeForegroundApp = options?.includeForegroundApp ?? true;
360
399
  let udid = options?.deviceId ?? null;
361
- const python = findPython();
400
+ const python = await (0, python_1.findPythonAsyncOrThrow)();
362
401
  // Start tunnel on first call — stays alive for the session
363
402
  await startTunnel();
364
403
  // If the caller didn't specify a device and the only iPhone we can reach is
@@ -381,8 +420,28 @@ async function triggerPhoneScreenshot(options) {
381
420
  const outPath = (0, path_1.join)((0, os_1.tmpdir)(), `iphone-screenshot-${Date.now()}.png`);
382
421
  const maxAttempts = 12;
383
422
  try {
423
+ // ── Fast path: the persistent worker ────────────────────────────────────
424
+ // One held DVT session instead of two fresh CLI processes. Screenshot and
425
+ // proclist are issued together and multiplex over the single connection,
426
+ // so the metadata is nearly free. ~2.5s here vs ~6.8s below.
427
+ //
428
+ // Purely an optimisation: anything unexpected drops through to the CLI
429
+ // path, so a machine where the worker won't run is slower, not broken.
430
+ let helperApp = null;
431
+ let helperCaptured = false;
432
+ if (await dvtHelper_1.dvtHelper.available()) {
433
+ try {
434
+ helperApp = await dvtHelper_1.dvtHelper.capture(outPath, udid, includeForegroundApp);
435
+ helperCaptured = (0, fs_1.existsSync)(outPath);
436
+ }
437
+ catch (e) {
438
+ logger_1.log.warn("dvt helper capture failed; falling back to the CLI path", {
439
+ error: e instanceof Error ? e.message : String(e),
440
+ });
441
+ }
442
+ }
384
443
  let lastErr = null;
385
- for (let i = 0; i < maxAttempts; i++) {
444
+ for (let i = 0; !helperCaptured && i < maxAttempts; i++) {
386
445
  try {
387
446
  await execFileAsync(python, await (0, device_1.pmd3Args)(udid, "developer", "dvt", "screenshot", outPath), { timeout });
388
447
  if ((0, fs_1.existsSync)(outPath)) {
@@ -406,11 +465,17 @@ async function triggerPhoneScreenshot(options) {
406
465
  }
407
466
  await new Promise((r) => setTimeout(r, 800));
408
467
  }
409
- // getForegroundApp uses dvt proclist + apps query (~3.7s combined). Make it
410
- // opt-out so consumers who don't care about app context can get a faster capture.
411
- const foregroundPromise = includeForegroundApp
412
- ? (0, device_1.getForegroundApp)(python, udid)
413
- : Promise.resolve(null);
468
+ // The worker returns the foreground app's identity but not its version:
469
+ // that comes from `apps query`, which is installation-proxy rather than
470
+ // DVT. Fetch it via the CLI only when an app is actually foregrounded —
471
+ // on the Home Screen (SpringBoard, isApplication false) there is nothing
472
+ // to query and the call is skipped entirely.
473
+ // TODO: move `apps query` into the worker too and drop this last spawn.
474
+ const foregroundPromise = helperCaptured
475
+ ? (helperApp ? appVersionFor(python, udid, helperApp) : Promise.resolve(null))
476
+ : includeForegroundApp
477
+ ? (0, device_1.getForegroundApp)(python, udid)
478
+ : Promise.resolve(null);
414
479
  const [image, device, foregroundApp, location] = await Promise.all([
415
480
  (0, promises_1.readFile)(outPath),
416
481
  deviceInfoPromise,
@@ -524,7 +589,7 @@ async function fetchLatestPhoneRecording(options) {
524
589
  const timeout = options?.timeout ?? 60000;
525
590
  const includeLocation = options?.includeLocation ?? false;
526
591
  let udid = options?.deviceId ?? null;
527
- const python = findPython();
592
+ const python = await (0, python_1.findPythonAsyncOrThrow)();
528
593
  await startTunnel();
529
594
  // Same auto-resolve as triggerPhoneScreenshot — host apps that don't know
530
595
  // about deviceId still need to reach iOS 17+ Wi-Fi-only iPhones.
@@ -693,7 +758,7 @@ const DEV_MODE_STICKY_TTL_MS = 30000;
693
758
  async function listIosDevices() {
694
759
  let python;
695
760
  try {
696
- python = findPython();
761
+ python = await (0, python_1.findPythonAsyncOrThrow)();
697
762
  }
698
763
  catch {
699
764
  return [];
@@ -890,7 +955,7 @@ async function unlinkIosDevice(options) {
890
955
  const udid = options.deviceId;
891
956
  if (!/^[0-9A-Fa-f-]{8,}$/.test(udid))
892
957
  throw new Error(`Invalid UDID: ${udid}`);
893
- const python = findPython();
958
+ const python = await (0, python_1.findPythonAsyncOrThrow)();
894
959
  try {
895
960
  await execFileAsync(python, await (0, device_1.pmd3Args)(udid, "lockdown", "unpair"), { timeout: 8000 });
896
961
  return;
@@ -1033,7 +1098,7 @@ async function getIosWirelessEnabled(options) {
1033
1098
  const udid = options?.deviceId ?? null;
1034
1099
  let python;
1035
1100
  try {
1036
- python = findPython();
1101
+ python = await (0, python_1.findPythonAsyncOrThrow)();
1037
1102
  }
1038
1103
  catch {
1039
1104
  return false;
@@ -1054,7 +1119,7 @@ async function getIosWirelessEnabled(options) {
1054
1119
  */
1055
1120
  async function enableIosDeveloperMode(options) {
1056
1121
  const udid = options?.deviceId ?? null;
1057
- const python = findPython();
1122
+ const python = await (0, python_1.findPythonAsyncOrThrow)();
1058
1123
  await startTunnel().catch(() => { }); // best-effort; amfi works over USB without tunnel
1059
1124
  await (0, device_1.enableDeveloperMode)(python, udid);
1060
1125
  }