@houwert/conductor 0.6.0 → 0.7.1
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/daemon/client.js +75 -7
- package/dist/daemon/server.js +29 -4
- package/dist/daemon/web-server.js +158 -30
- package/dist/index.js +12 -1
- 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 +1 -1
- package/skills/skills.yaml +1 -1
package/dist/daemon/client.js
CHANGED
|
@@ -19,20 +19,36 @@ const verbose_js_1 = require("../verbose.js");
|
|
|
19
19
|
const bootstrap_js_1 = require("../drivers/bootstrap.js");
|
|
20
20
|
const STARTUP_POLL_MS = 200;
|
|
21
21
|
const STARTUP_MAX_WAIT_MS = 10000;
|
|
22
|
-
async function
|
|
22
|
+
async function fetchStatus(sessionName) {
|
|
23
23
|
return new Promise((resolve) => {
|
|
24
24
|
const req = http_1.default.get({ socketPath: (0, protocol_js_1.socketPath)(sessionName), path: '/status' }, (res) => {
|
|
25
|
-
res.
|
|
26
|
-
|
|
25
|
+
if (res.statusCode !== 200) {
|
|
26
|
+
res.resume();
|
|
27
|
+
resolve(null);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const chunks = [];
|
|
31
|
+
res.on('data', (c) => chunks.push(c));
|
|
32
|
+
res.on('end', () => {
|
|
33
|
+
try {
|
|
34
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString('utf-8')));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
resolve(null);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
27
40
|
});
|
|
28
41
|
req.setTimeout(500);
|
|
29
42
|
req.on('timeout', () => {
|
|
30
43
|
req.destroy();
|
|
31
|
-
resolve(
|
|
44
|
+
resolve(null);
|
|
32
45
|
});
|
|
33
|
-
req.on('error', () => resolve(
|
|
46
|
+
req.on('error', () => resolve(null));
|
|
34
47
|
});
|
|
35
48
|
}
|
|
49
|
+
async function socketExists(sessionName) {
|
|
50
|
+
return (await fetchStatus(sessionName)) !== null;
|
|
51
|
+
}
|
|
36
52
|
async function waitForDaemon(sessionName) {
|
|
37
53
|
const deadline = Date.now() + STARTUP_MAX_WAIT_MS;
|
|
38
54
|
while (Date.now() < deadline) {
|
|
@@ -42,9 +58,61 @@ async function waitForDaemon(sessionName) {
|
|
|
42
58
|
}
|
|
43
59
|
return false;
|
|
44
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Wait until a process with the given PID is no longer running.
|
|
63
|
+
* Uses `process.kill(pid, 0)` which throws if the process is gone.
|
|
64
|
+
*/
|
|
65
|
+
async function waitForProcessExit(pid, timeoutMs = 5000) {
|
|
66
|
+
const deadline = Date.now() + timeoutMs;
|
|
67
|
+
while (Date.now() < deadline) {
|
|
68
|
+
try {
|
|
69
|
+
process.kill(pid, 0);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* True when the running daemon's CDP attachment matches the current process's
|
|
79
|
+
* `CONDUCTOR_CDP_URL` / `CONDUCTOR_CDP_TARGET_ID` env. A mismatch means the
|
|
80
|
+
* daemon would control the wrong browser (e.g. a standalone Playwright
|
|
81
|
+
* instance when the caller expects to drive an embedded webview), so the
|
|
82
|
+
* daemon must be restarted with the correct env.
|
|
83
|
+
*/
|
|
84
|
+
function daemonMatchesCdpEnv(status) {
|
|
85
|
+
const expectedCdpUrl = process.env.CONDUCTOR_CDP_URL ?? '';
|
|
86
|
+
const expectedCdpTargetId = process.env.CONDUCTOR_CDP_TARGET_ID ?? '';
|
|
87
|
+
const actualCdpUrl = status.cdpUrl ?? '';
|
|
88
|
+
const actualCdpTargetId = status.cdpTargetId ?? '';
|
|
89
|
+
return actualCdpUrl === expectedCdpUrl && actualCdpTargetId === expectedCdpTargetId;
|
|
90
|
+
}
|
|
45
91
|
async function startDaemon(sessionName = 'default') {
|
|
46
|
-
|
|
47
|
-
|
|
92
|
+
const existing = await fetchStatus(sessionName);
|
|
93
|
+
if (existing) {
|
|
94
|
+
if (daemonMatchesCdpEnv(existing))
|
|
95
|
+
return true;
|
|
96
|
+
(0, verbose_js_1.log)(`daemon [${sessionName}] CDP env mismatch ` +
|
|
97
|
+
`(daemon cdpUrl="${existing.cdpUrl ?? ''}" targetId="${existing.cdpTargetId ?? ''}", ` +
|
|
98
|
+
`env cdpUrl="${process.env.CONDUCTOR_CDP_URL ?? ''}" targetId="${process.env.CONDUCTOR_CDP_TARGET_ID ?? ''}") — restarting`);
|
|
99
|
+
// Capture the PID before stopDaemon removes the pidfile so we can wait
|
|
100
|
+
// for the old process to actually exit before respawning. Otherwise the
|
|
101
|
+
// old daemon's cleanup handler may unlink the new daemon's socket.
|
|
102
|
+
let oldPid;
|
|
103
|
+
try {
|
|
104
|
+
const raw = fs_1.default.readFileSync((0, protocol_js_1.pidFile)(sessionName), 'utf-8').trim();
|
|
105
|
+
const n = parseInt(raw, 10);
|
|
106
|
+
if (!isNaN(n))
|
|
107
|
+
oldPid = n;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
/* no pid — continue */
|
|
111
|
+
}
|
|
112
|
+
await stopDaemon(sessionName);
|
|
113
|
+
if (oldPid !== undefined)
|
|
114
|
+
await waitForProcessExit(oldPid);
|
|
115
|
+
}
|
|
48
116
|
const serverScript = path_1.default.join(__dirname, 'server.js');
|
|
49
117
|
(0, verbose_js_1.log)(`daemon [${sessionName}] not running — spawning ${serverScript}`);
|
|
50
118
|
const child = (0, child_process_1.spawn)(process.execPath, [serverScript, sessionName], {
|
package/dist/daemon/server.js
CHANGED
|
@@ -25,6 +25,22 @@ const web_server_js_1 = require("./web-server.js");
|
|
|
25
25
|
const log_collector_js_1 = require("./log-collector.js");
|
|
26
26
|
const session_js_1 = require("../session.js");
|
|
27
27
|
const sessionName = process.argv[2] ?? 'default';
|
|
28
|
+
/**
|
|
29
|
+
* CDP URL for connecting to an external browser (e.g. Stagehand's embedded
|
|
30
|
+
* webview). When set, the web driver attaches via Playwright's connectOverCDP
|
|
31
|
+
* instead of launching its own browser.
|
|
32
|
+
*
|
|
33
|
+
* Set by the host IDE (Stagehand) via the agent subprocess environment.
|
|
34
|
+
*/
|
|
35
|
+
const cdpUrl = process.env.CONDUCTOR_CDP_URL || undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Optional CDP target ID to pick a specific page when the host app exposes
|
|
38
|
+
* multiple webviews over one CDP endpoint (e.g. one per workspace in
|
|
39
|
+
* Stagehand). When set, the web driver finds the page whose underlying
|
|
40
|
+
* `Target.targetId` matches and attaches to it, instead of falling back to
|
|
41
|
+
* URL heuristics.
|
|
42
|
+
*/
|
|
43
|
+
const cdpTargetId = process.env.CONDUCTOR_CDP_TARGET_ID || undefined;
|
|
28
44
|
const SOCKET_PATH = (0, protocol_js_1.socketPath)(sessionName);
|
|
29
45
|
const PID_FILE = (0, protocol_js_1.pidFile)(sessionName);
|
|
30
46
|
const LOG_FILE = (0, protocol_js_1.logFile)(sessionName);
|
|
@@ -75,7 +91,7 @@ async function ensureDriverRunning() {
|
|
|
75
91
|
await (0, bootstrap_js_1.startTvOSDriver)(sessionName, driverPort, /* dismissAfterLaunch */ false);
|
|
76
92
|
}
|
|
77
93
|
else if (driverPlatform === 'web') {
|
|
78
|
-
await (0, web_server_js_1.startWebServer)(driverPort, (0, bootstrap_js_1.webBrowserName)(sessionName), dlog);
|
|
94
|
+
await (0, web_server_js_1.startWebServer)(driverPort, (0, bootstrap_js_1.webBrowserName)(sessionName), dlog, cdpUrl, cdpTargetId);
|
|
79
95
|
}
|
|
80
96
|
else {
|
|
81
97
|
await (0, bootstrap_js_1.startAndroidDriver)(sessionName, driverPort);
|
|
@@ -96,6 +112,7 @@ async function main() {
|
|
|
96
112
|
fs_1.default.mkdirSync(path_1.default.dirname(PID_FILE), { recursive: true });
|
|
97
113
|
fs_1.default.writeFileSync(PID_FILE, String(process.pid));
|
|
98
114
|
dlog(`daemon started pid=${process.pid} session=${sessionName}`);
|
|
115
|
+
dlog(`env CONDUCTOR_CDP_URL=${cdpUrl ?? '<unset>'} CONDUCTOR_CDP_TARGET_ID=${cdpTargetId ?? '<unset>'}`); // kept intentionally — useful for future diagnosis of CDP attachment issues
|
|
99
116
|
// Remove stale socket
|
|
100
117
|
try {
|
|
101
118
|
fs_1.default.unlinkSync(SOCKET_PATH);
|
|
@@ -212,7 +229,13 @@ async function main() {
|
|
|
212
229
|
resetIdleTimer();
|
|
213
230
|
const parsed = url_1.default.parse(req.url ?? '/', true);
|
|
214
231
|
if (req.method === 'GET' && parsed.pathname === '/status') {
|
|
215
|
-
jsonResponse(res, {
|
|
232
|
+
jsonResponse(res, {
|
|
233
|
+
ok: true,
|
|
234
|
+
platform: driverPlatform,
|
|
235
|
+
driverPort,
|
|
236
|
+
cdpUrl: cdpUrl ?? null,
|
|
237
|
+
cdpTargetId: cdpTargetId ?? null,
|
|
238
|
+
});
|
|
216
239
|
return;
|
|
217
240
|
}
|
|
218
241
|
if (req.method === 'GET' && parsed.pathname === '/logs') {
|
|
@@ -277,7 +300,9 @@ async function main() {
|
|
|
277
300
|
await (0, bootstrap_js_1.installDriver)(sessionName);
|
|
278
301
|
dlog(`Driver installation complete`);
|
|
279
302
|
}
|
|
280
|
-
else if (platform === 'web') {
|
|
303
|
+
else if (platform === 'web' && !cdpUrl) {
|
|
304
|
+
// Only install Playwright browser when launching standalone.
|
|
305
|
+
// In CDP mode we attach to the host app's browser (e.g. Electron).
|
|
281
306
|
const browser = (0, bootstrap_js_1.webBrowserName)(sessionName);
|
|
282
307
|
await (0, bootstrap_js_1.ensurePlaywrightBrowser)(browser, dlog);
|
|
283
308
|
}
|
|
@@ -291,7 +316,7 @@ async function main() {
|
|
|
291
316
|
await (0, bootstrap_js_1.startTvOSDriver)(sessionName, driverPort, /* dismissAfterLaunch */ true);
|
|
292
317
|
}
|
|
293
318
|
else if (platform === 'web') {
|
|
294
|
-
await (0, web_server_js_1.startWebServer)(driverPort, (0, bootstrap_js_1.webBrowserName)(sessionName), dlog);
|
|
319
|
+
await (0, web_server_js_1.startWebServer)(driverPort, (0, bootstrap_js_1.webBrowserName)(sessionName), dlog, cdpUrl, cdpTargetId);
|
|
295
320
|
}
|
|
296
321
|
else {
|
|
297
322
|
await (0, bootstrap_js_1.startAndroidDriver)(sessionName, driverPort);
|
|
@@ -426,19 +426,107 @@ let _browser = null;
|
|
|
426
426
|
let _context = null;
|
|
427
427
|
let _page = null;
|
|
428
428
|
let _server = null;
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
429
|
+
/**
|
|
430
|
+
* True when connected to an external browser via CDP (e.g. Stagehand's
|
|
431
|
+
* embedded webview). In this mode we must NOT close the browser on shutdown
|
|
432
|
+
* — we only disconnect.
|
|
433
|
+
*/
|
|
434
|
+
let _cdpMode = false;
|
|
435
|
+
async function startWebServer(port, browserName = 'chromium', dlog = () => { }, cdpUrl, cdpTargetId) {
|
|
436
|
+
if (cdpUrl) {
|
|
437
|
+
// ── CDP mode: attach to an existing browser (e.g. Electron webview) ───
|
|
438
|
+
dlog(`Connecting to existing browser via CDP: ${cdpUrl}`);
|
|
439
|
+
_browser = await playwright_core_1.chromium.connectOverCDP(cdpUrl);
|
|
440
|
+
_cdpMode = true;
|
|
441
|
+
// Use an existing context and page. The host app (e.g. Stagehand) already
|
|
442
|
+
// created them — we just take a handle.
|
|
443
|
+
const contexts = _browser.contexts();
|
|
444
|
+
if (contexts.length === 0) {
|
|
445
|
+
throw new Error('No browser contexts found via CDP — is the webview loaded?');
|
|
446
|
+
}
|
|
447
|
+
// Match the specific CDP target ID provided by the host app. Required —
|
|
448
|
+
// with multiple pages sharing one CDP port (Electron host window, webviews,
|
|
449
|
+
// DevTools), "guess the right one" is a foot-gun that ends in navigating
|
|
450
|
+
// the wrong window. If no targetId is supplied, or the supplied one can't
|
|
451
|
+
// be resolved to a Playwright Page, we throw rather than attaching to an
|
|
452
|
+
// arbitrary target.
|
|
453
|
+
if (!cdpTargetId) {
|
|
454
|
+
throw new Error('CDP mode requires CONDUCTOR_CDP_TARGET_ID — set it to the specific page target to control.');
|
|
455
|
+
}
|
|
456
|
+
dlog(`Selecting CDP target by ID: ${cdpTargetId}`);
|
|
457
|
+
const pageDiag = [];
|
|
458
|
+
outer: for (const ctx of contexts) {
|
|
459
|
+
for (const page of ctx.pages()) {
|
|
460
|
+
try {
|
|
461
|
+
const session = await ctx.newCDPSession(page);
|
|
462
|
+
const info = (await session.send('Target.getTargetInfo'));
|
|
463
|
+
await session.detach().catch(() => { });
|
|
464
|
+
const t = info.targetInfo;
|
|
465
|
+
pageDiag.push(`page targetId=${t?.targetId ?? '?'} type=${t?.type ?? '?'} url=${t?.url ?? page.url()}`);
|
|
466
|
+
if (t?.targetId === cdpTargetId) {
|
|
467
|
+
_context = ctx;
|
|
468
|
+
_page = page;
|
|
469
|
+
break outer;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
catch (err) {
|
|
473
|
+
pageDiag.push(`page url=${page.url()} getTargetInfo failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (!_page) {
|
|
478
|
+
// Enumerate all CDP targets to produce an actionable error — in
|
|
479
|
+
// particular so we can distinguish "wrong targetId" from "target exists
|
|
480
|
+
// but Playwright can't see it as a Page" (the latter happens when the
|
|
481
|
+
// host embeds content as a <webview> guest — type="webview" in CDP —
|
|
482
|
+
// rather than as a top-level page).
|
|
483
|
+
let diagExtra = '';
|
|
484
|
+
let expectedType;
|
|
485
|
+
try {
|
|
486
|
+
const browserSession = await _browser.newBrowserCDPSession();
|
|
487
|
+
const all = (await browserSession.send('Target.getTargets'));
|
|
488
|
+
await browserSession.detach().catch(() => { });
|
|
489
|
+
const enumerated = all.targetInfos
|
|
490
|
+
?.map((t) => ` - ${t.type.padEnd(10)} ${t.targetId.slice(0, 8)} ${t.url}`)
|
|
491
|
+
.join('\n') ?? '(none)';
|
|
492
|
+
const expected = all.targetInfos?.find((t) => t.targetId === cdpTargetId);
|
|
493
|
+
expectedType = expected?.type;
|
|
494
|
+
diagExtra =
|
|
495
|
+
`\nPages visible to Playwright (${pageDiag.length}):\n` +
|
|
496
|
+
pageDiag.map((l) => ` - ${l}`).join('\n') +
|
|
497
|
+
`\nAll CDP targets (${all.targetInfos?.length ?? 0}):\n${enumerated}\n` +
|
|
498
|
+
(expected
|
|
499
|
+
? `Requested target exists but type="${expected.type}" — Playwright only surfaces type="page".`
|
|
500
|
+
: `Requested target not present in Target.getTargets — stale or wrong ID.`);
|
|
501
|
+
}
|
|
502
|
+
catch (err) {
|
|
503
|
+
diagExtra = `\nTarget enumeration failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
504
|
+
}
|
|
505
|
+
dlog(`CDP target id ${cdpTargetId} not matched by any Playwright Page.${diagExtra}`);
|
|
506
|
+
throw new Error(`CDP target ${cdpTargetId} not reachable as a Playwright Page` +
|
|
507
|
+
(expectedType
|
|
508
|
+
? ` (exists as type="${expectedType}" — Playwright only surfaces type="page")`
|
|
509
|
+
: ` (not in target list)`));
|
|
510
|
+
}
|
|
511
|
+
attachConsoleListeners(_page);
|
|
512
|
+
dlog(`CDP connected — page: ${_page.url()}`);
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
// ── Standalone mode: launch a fresh browser ───────────────────────────
|
|
516
|
+
const browserType = browserName === 'firefox' ? playwright_core_1.firefox : browserName === 'webkit' ? playwright_core_1.webkit : playwright_core_1.chromium;
|
|
517
|
+
dlog(`Launching ${browserName} browser...`);
|
|
518
|
+
_browser = await browserType.launch({
|
|
519
|
+
headless: false,
|
|
520
|
+
args: browserName === 'chromium' ? ['--disable-search-engine-choice-screen'] : undefined,
|
|
521
|
+
});
|
|
522
|
+
_context = await _browser.newContext({
|
|
523
|
+
viewport: DEFAULT_VIEWPORT,
|
|
524
|
+
});
|
|
525
|
+
_page = await _context.newPage();
|
|
526
|
+
attachConsoleListeners(_page);
|
|
527
|
+
dlog(`Browser ready, page created`);
|
|
528
|
+
_cdpMode = false;
|
|
529
|
+
}
|
|
442
530
|
_server = http_1.default.createServer(async (req, res) => {
|
|
443
531
|
try {
|
|
444
532
|
await handleRequest(req, res, dlog);
|
|
@@ -462,18 +550,38 @@ async function stopWebServer() {
|
|
|
462
550
|
_server.close();
|
|
463
551
|
_server = null;
|
|
464
552
|
}
|
|
465
|
-
if (
|
|
466
|
-
|
|
553
|
+
if (_cdpMode) {
|
|
554
|
+
// CDP mode: we don't own the browser — just release our handles.
|
|
555
|
+
// Do NOT close the page, context, or browser.
|
|
467
556
|
_page = null;
|
|
468
|
-
}
|
|
469
|
-
if (_context) {
|
|
470
|
-
await _context.close().catch(() => { });
|
|
471
557
|
_context = null;
|
|
558
|
+
if (_browser) {
|
|
559
|
+
// Playwright's connectOverCDP browser supports disconnect() but not close().
|
|
560
|
+
try {
|
|
561
|
+
_browser.close().catch(() => { });
|
|
562
|
+
}
|
|
563
|
+
catch {
|
|
564
|
+
/* not all CDP browsers support close gracefully */
|
|
565
|
+
}
|
|
566
|
+
_browser = null;
|
|
567
|
+
}
|
|
472
568
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
569
|
+
else {
|
|
570
|
+
// Standalone mode: we launched the browser, so tear it all down.
|
|
571
|
+
if (_page) {
|
|
572
|
+
await _page.close().catch(() => { });
|
|
573
|
+
_page = null;
|
|
574
|
+
}
|
|
575
|
+
if (_context) {
|
|
576
|
+
await _context.close().catch(() => { });
|
|
577
|
+
_context = null;
|
|
578
|
+
}
|
|
579
|
+
if (_browser) {
|
|
580
|
+
await _browser.close().catch(() => { });
|
|
581
|
+
_browser = null;
|
|
582
|
+
}
|
|
476
583
|
}
|
|
584
|
+
_cdpMode = false;
|
|
477
585
|
}
|
|
478
586
|
/** Playwright / CDP errors when the tab, context, or session died but our JS refs still exist. */
|
|
479
587
|
function isClosedLikeError(err) {
|
|
@@ -483,20 +591,40 @@ function isClosedLikeError(err) {
|
|
|
483
591
|
/**
|
|
484
592
|
* Drop the context and open a fresh one. Used when newPage/goto fails after the user closed
|
|
485
593
|
* the last tab (Chromium can quit the window) or the CDP target is gone while isConnected stays true.
|
|
594
|
+
*
|
|
595
|
+
* In CDP mode, we can't create new contexts — the host app owns the browser.
|
|
596
|
+
* Instead we try to re-acquire an existing context/page.
|
|
486
597
|
*/
|
|
487
598
|
async function recreateBrowserContext(dlog) {
|
|
488
599
|
dlog?.('Web driver: recreating browser context');
|
|
489
|
-
if (_context) {
|
|
490
|
-
await _context.close().catch(() => { });
|
|
491
|
-
_context = null;
|
|
492
|
-
}
|
|
493
|
-
_page = null;
|
|
494
600
|
if (!_browser) {
|
|
495
601
|
throw new Error('No page available');
|
|
496
602
|
}
|
|
497
603
|
if (!_browser.isConnected()) {
|
|
498
604
|
throw new Error('Browser has been closed. Restart the web driver (e.g. conductor daemon-start --device web).');
|
|
499
605
|
}
|
|
606
|
+
if (_cdpMode) {
|
|
607
|
+
// In CDP mode, try to re-acquire a page from existing contexts.
|
|
608
|
+
_page = null;
|
|
609
|
+
_context = null;
|
|
610
|
+
const contexts = _browser.contexts();
|
|
611
|
+
for (const ctx of contexts) {
|
|
612
|
+
const pages = ctx.pages();
|
|
613
|
+
const candidate = pages.find((p) => !p.isClosed());
|
|
614
|
+
if (candidate) {
|
|
615
|
+
_context = ctx;
|
|
616
|
+
_page = candidate;
|
|
617
|
+
attachConsoleListeners(_page);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
throw new Error('No live pages found via CDP — is the webview still open?');
|
|
622
|
+
}
|
|
623
|
+
if (_context) {
|
|
624
|
+
await _context.close().catch(() => { });
|
|
625
|
+
_context = null;
|
|
626
|
+
}
|
|
627
|
+
_page = null;
|
|
500
628
|
_context = await _browser.newContext({
|
|
501
629
|
viewport: DEFAULT_VIEWPORT,
|
|
502
630
|
});
|
|
@@ -629,6 +757,10 @@ async function handleRequest(req, res, dlog) {
|
|
|
629
757
|
jsonResponse(res, { url: (await getPage(dlog)).url() });
|
|
630
758
|
return;
|
|
631
759
|
}
|
|
760
|
+
case '/runningApp': {
|
|
761
|
+
jsonResponse(res, { runningAppBundleId: (await getPage(dlog)).url() });
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
632
764
|
case '/title': {
|
|
633
765
|
jsonResponse(res, { title: await (await getPage(dlog)).title() });
|
|
634
766
|
return;
|
|
@@ -779,10 +911,6 @@ async function handleRequest(req, res, dlog) {
|
|
|
779
911
|
jsonResponse(res, { ok: true });
|
|
780
912
|
return;
|
|
781
913
|
}
|
|
782
|
-
case '/runningApp': {
|
|
783
|
-
jsonResponse(res, { runningAppBundleId: (await getPage(dlog)).url() });
|
|
784
|
-
return;
|
|
785
|
-
}
|
|
786
914
|
case '/eraseText': {
|
|
787
915
|
const count = body['count'] ?? 50;
|
|
788
916
|
const p = await getPage(dlog);
|
package/dist/index.js
CHANGED
|
@@ -46,6 +46,9 @@ const delete_device_js_1 = require("./commands/delete-device.js");
|
|
|
46
46
|
const logs_js_1 = require("./commands/logs.js");
|
|
47
47
|
const device_picker_js_1 = require("./device-picker.js");
|
|
48
48
|
const update_check_js_1 = require("./update-check.js");
|
|
49
|
+
const pkg_root_js_1 = require("./pkg-root.js");
|
|
50
|
+
const fs_1 = __importDefault(require("fs"));
|
|
51
|
+
const path_1 = __importDefault(require("path"));
|
|
49
52
|
const COMMAND_HELP = {
|
|
50
53
|
'start-device': start_device_js_1.HELP,
|
|
51
54
|
'delete-device': delete_device_js_1.HELP,
|
|
@@ -96,6 +99,7 @@ const OPTIONS_HELP = `Options:
|
|
|
96
99
|
--platform <p> Filter to devices of this platform (ios, android, tvos, web)
|
|
97
100
|
--json Output as machine-readable JSON
|
|
98
101
|
--verbose, -v Log daemon calls, fallbacks, and raw output
|
|
102
|
+
--version, -V Print version number
|
|
99
103
|
--help, -h Show this help`;
|
|
100
104
|
const HELP = `Usage: conductor <command> [args] [options]
|
|
101
105
|
|
|
@@ -109,6 +113,7 @@ async function main() {
|
|
|
109
113
|
boolean: [
|
|
110
114
|
'json',
|
|
111
115
|
'help',
|
|
116
|
+
'version',
|
|
112
117
|
'clear',
|
|
113
118
|
'list',
|
|
114
119
|
'verbose',
|
|
@@ -155,12 +160,18 @@ async function main() {
|
|
|
155
160
|
'metro-port',
|
|
156
161
|
'level',
|
|
157
162
|
],
|
|
158
|
-
alias: { h: 'help', v: 'verbose' },
|
|
163
|
+
alias: { h: 'help', v: 'verbose', V: 'version' },
|
|
159
164
|
});
|
|
160
165
|
if (argv['verbose'])
|
|
161
166
|
(0, verbose_js_1.setVerbose)(true);
|
|
162
167
|
const [command, ...rest] = argv._;
|
|
163
168
|
const opts = { json: argv['json'] };
|
|
169
|
+
if (argv['version']) {
|
|
170
|
+
const pkgRoot = (0, pkg_root_js_1.findPkgRoot)(__dirname);
|
|
171
|
+
const pkg = JSON.parse(fs_1.default.readFileSync(path_1.default.join(pkgRoot, 'package.json'), 'utf-8'));
|
|
172
|
+
console.log(pkg.version);
|
|
173
|
+
process.exit(0);
|
|
174
|
+
}
|
|
164
175
|
// Handle help and unknown commands before device resolution —
|
|
165
176
|
// no point prompting for a device if we're just printing help or erroring out.
|
|
166
177
|
if (!command || argv['help']) {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED