@houwert/conductor 0.12.2 → 0.13.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/README.md CHANGED
@@ -38,34 +38,9 @@ One agent writes the feature. Another taps through the app. They talk. It works.
38
38
  npm install -g @houwert/conductor
39
39
  ```
40
40
 
41
- That's it. The postinstall script registers Conductor as a Claude Code plugin automaticallyClaude gains full mobile UI control without any extra steps.
41
+ That's it. Conductor is a pure CLI no Claude Code plugin or skill is registered. Wire it into your agent however you like (a custom `CLAUDE.md`, a project skill, a slash command it's up to you). Run `conductor --help` for the full command reference, or `conductor <command> --help` for per-command flags.
42
42
 
43
- ## 🧠 Claude Skills
44
-
45
- The plugin registers itself globally in `~/.claude/plugins/` and ships two skill files:
46
-
47
- ```
48
- skills/conductor/
49
- ├── SKILL.md # Full command reference and agent workflow guide
50
- └── references/
51
- └── flow-syntax.md # Maestro YAML flow syntax reference
52
- ```
53
-
54
- Claude learns every available command, how to coordinate across devices, and how to write and run Maestro YAML flows. See [`skills/conductor/SKILL.md`](./skills/conductor/SKILL.md) for the full reference.
55
-
56
- ### Install modes
57
-
58
- | Command | What it does |
59
- |---|---|
60
- | `npm install -g @houwert/conductor` | Registers or updates the global Claude Code plugin (via package postinstall) |
61
- | `conductor install-plugin` | Re-register or update the global Claude Code plugin (same as postinstall) |
62
- | `conductor install-plugin --check` | Print whether the global plugin is registered (no changes) |
63
- | `conductor install-skills` | Copy skills into `.claude/skills/conductor/` in the current project |
64
- | `conductor install-skills --check` | Print whether local skills are installed (no changes) |
65
- | `conductor install-web` | Install a Playwright browser for web automation (default: chromium) |
66
- | `conductor install-web --check` | Print which Playwright browsers are installed (no changes) |
67
-
68
- ### 📱 What Claude can do
43
+ ### 📱 What the CLI can do
69
44
 
70
45
  | Capability | Commands |
71
46
  |---|---|
@@ -76,6 +51,7 @@ Claude learns every available command, how to coordinate across devices, and how
76
51
  | Navigation | `open-link`, `back` |
77
52
  | Flows | `run-flow`, `run-flow-inline`, `run-parallel` |
78
53
  | Devices | `start-device`, `list-devices`, `set-location`, `set-orientation` |
54
+ | Web setup | `install-web [browser]` (installs a Playwright browser; `--check` prints status) |
79
55
 
80
56
  ## 🔨 Building locally
81
57
 
@@ -113,7 +89,6 @@ make build-cli # CLI TypeScript only
113
89
  make build-ios-driver # iOS XCTest driver
114
90
  make build-android-driver # Android instrumentation driver
115
91
  make package-cli # Bundle drivers into CLI package
116
- make copy-skills # Copy skills/ into packages/cli/skills/ (build artifact)
117
92
  ```
118
93
 
119
94
  ## 🗂️ Repository structure
@@ -1,78 +1,10 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.HELP_INSTALL_WEB = exports.HELP_INSTALL_SKILLS = exports.HELP_INSTALL_PLUGIN = void 0;
7
- exports.installPluginCli = installPluginCli;
8
- exports.installSkillsCli = installSkillsCli;
3
+ exports.HELP_INSTALL_WEB = void 0;
9
4
  exports.installWebCli = installWebCli;
10
- exports.installLocalSkills = installLocalSkills;
11
- exports.installPlugin = installPlugin;
12
- exports.HELP_INSTALL_PLUGIN = ` install-plugin [--check] Register/update the global Claude Code plugin (status only with --check)`;
13
- exports.HELP_INSTALL_SKILLS = ` install-skills [--check] Copy skills into local .claude/skills/ (status only with --check)`;
14
5
  exports.HELP_INSTALL_WEB = ` install-web [--check] [browser] Install Playwright browser (chromium, firefox, webkit) (status only with --check)`;
15
- const fs_1 = __importDefault(require("fs"));
16
- const os_1 = __importDefault(require("os"));
17
- const path_1 = __importDefault(require("path"));
18
6
  const output_js_1 = require("../output.js");
19
- const pkg_root_js_1 = require("../pkg-root.js");
20
7
  const bootstrap_js_1 = require("../drivers/bootstrap.js");
21
- async function installPluginCli(opts, check) {
22
- try {
23
- if (check) {
24
- return checkPluginInstallStatus(opts);
25
- }
26
- const version = installPlugin();
27
- const pluginCacheDir = path_1.default.join(os_1.default.homedir(), '.claude', 'plugins', 'cache', 'conductor', 'conductor', version);
28
- (0, output_js_1.printSuccess)(`Conductor plugin installed (v${version}) → ${pluginCacheDir}`, opts);
29
- return 0;
30
- }
31
- catch (err) {
32
- const message = err instanceof Error ? err.message : String(err);
33
- (0, output_js_1.printError)(`Install failed: ${message}`, opts);
34
- return 1;
35
- }
36
- }
37
- function checkPluginInstallStatus(opts) {
38
- const installedPluginsPath = path_1.default.join(os_1.default.homedir(), '.claude', 'plugins', 'installed_plugins.json');
39
- let pluginVersion = null;
40
- if (fs_1.default.existsSync(installedPluginsPath)) {
41
- const installed = JSON.parse(fs_1.default.readFileSync(installedPluginsPath, 'utf8'));
42
- const plugins = Array.isArray(installed.plugins) ? installed.plugins : [];
43
- const entry = plugins.find((p) => p.name === 'conductor');
44
- if (entry)
45
- pluginVersion = entry.version;
46
- }
47
- if (opts.json) {
48
- (0, output_js_1.printData)({ globalPlugin: { installed: pluginVersion !== null, version: pluginVersion } }, opts);
49
- }
50
- else {
51
- if (pluginVersion) {
52
- console.log(`Global plugin: installed (v${pluginVersion})`);
53
- }
54
- else {
55
- console.log('Global plugin: not installed');
56
- console.log('Run `npm install -g @houwert/conductor` or `conductor install-plugin` to register it.');
57
- }
58
- }
59
- return 0;
60
- }
61
- async function installSkillsCli(opts, check) {
62
- try {
63
- if (check) {
64
- return checkSkillsInstallStatus(opts);
65
- }
66
- installLocalSkills();
67
- (0, output_js_1.printSuccess)('Conductor skills installed → .claude/skills/conductor/', opts);
68
- return 0;
69
- }
70
- catch (err) {
71
- const message = err instanceof Error ? err.message : String(err);
72
- (0, output_js_1.printError)(`Install failed: ${message}`, opts);
73
- return 1;
74
- }
75
- }
76
8
  async function installWebCli(opts, check, browserArg) {
77
9
  try {
78
10
  if (check) {
@@ -86,23 +18,6 @@ async function installWebCli(opts, check, browserArg) {
86
18
  return 1;
87
19
  }
88
20
  }
89
- function checkSkillsInstallStatus(opts) {
90
- const localSkillsPath = path_1.default.join(process.cwd(), '.claude', 'skills', 'conductor', 'SKILL.md');
91
- const hasLocalSkills = fs_1.default.existsSync(localSkillsPath);
92
- if (opts.json) {
93
- (0, output_js_1.printData)({ localSkills: { installed: hasLocalSkills } }, opts);
94
- }
95
- else {
96
- if (hasLocalSkills) {
97
- console.log('Local skills: installed → .claude/skills/conductor/');
98
- }
99
- else {
100
- console.log('Local skills: not installed');
101
- console.log('Run `conductor install-skills` to copy skills into this project.');
102
- }
103
- }
104
- return 0;
105
- }
106
21
  function checkWebInstallStatus(opts) {
107
22
  const webBrowsers = {
108
23
  chromium: (0, bootstrap_js_1.isPlaywrightBrowserInstalled)('chromium'),
@@ -126,46 +41,6 @@ function checkWebInstallStatus(opts) {
126
41
  }
127
42
  return 0;
128
43
  }
129
- function installLocalSkills() {
130
- const pkgRoot = (0, pkg_root_js_1.findPkgRoot)(__dirname);
131
- const skillsSrc = path_1.default.join(pkgRoot, 'skills', 'conductor');
132
- if (!fs_1.default.existsSync(skillsSrc)) {
133
- throw new Error(`No skills found at ${skillsSrc}`);
134
- }
135
- const skillsDest = path_1.default.join(process.cwd(), '.claude', 'skills', 'conductor');
136
- copyDir(skillsSrc, skillsDest);
137
- }
138
- function installPlugin() {
139
- const pkgRoot = (0, pkg_root_js_1.findPkgRoot)(__dirname);
140
- const pkgJsonPath = path_1.default.join(pkgRoot, 'package.json');
141
- const pkg = JSON.parse(fs_1.default.readFileSync(pkgJsonPath, 'utf8'));
142
- const version = pkg.version;
143
- const pluginCacheDir = path_1.default.join(os_1.default.homedir(), '.claude', 'plugins', 'cache', 'conductor', 'conductor', version);
144
- fs_1.default.mkdirSync(pluginCacheDir, { recursive: true });
145
- const skillsSrc = path_1.default.join(pkgRoot, 'skills', 'conductor');
146
- if (fs_1.default.existsSync(skillsSrc)) {
147
- copyDir(skillsSrc, path_1.default.join(pluginCacheDir, 'skills', 'conductor'));
148
- }
149
- const pluginJsonSrc = path_1.default.join(pkgRoot, '.claude-plugin', 'plugin.json');
150
- if (fs_1.default.existsSync(pluginJsonSrc)) {
151
- const pluginMetaDir = path_1.default.join(pluginCacheDir, '.claude-plugin');
152
- fs_1.default.mkdirSync(pluginMetaDir, { recursive: true });
153
- fs_1.default.copyFileSync(pluginJsonSrc, path_1.default.join(pluginMetaDir, 'plugin.json'));
154
- }
155
- const installedPluginsPath = path_1.default.join(os_1.default.homedir(), '.claude', 'plugins', 'installed_plugins.json');
156
- let installed = { plugins: [] };
157
- if (fs_1.default.existsSync(installedPluginsPath)) {
158
- installed = JSON.parse(fs_1.default.readFileSync(installedPluginsPath, 'utf8'));
159
- if (!Array.isArray(installed.plugins)) {
160
- installed.plugins = [];
161
- }
162
- }
163
- installed.plugins = installed.plugins.filter((p) => p.name !== 'conductor');
164
- installed.plugins.push({ name: 'conductor', version, path: pluginCacheDir });
165
- fs_1.default.mkdirSync(path_1.default.dirname(installedPluginsPath), { recursive: true });
166
- fs_1.default.writeFileSync(installedPluginsPath, JSON.stringify(installed, null, 2));
167
- return version;
168
- }
169
44
  async function installWebBrowser(browserArg, opts) {
170
45
  const validBrowsers = ['chromium', 'firefox', 'webkit'];
171
46
  let browserName = 'chromium';
@@ -190,16 +65,3 @@ async function installWebBrowser(browserArg, opts) {
190
65
  return 1;
191
66
  }
192
67
  }
193
- function copyDir(src, dest) {
194
- fs_1.default.mkdirSync(dest, { recursive: true });
195
- for (const entry of fs_1.default.readdirSync(src, { withFileTypes: true })) {
196
- const srcPath = path_1.default.join(src, entry.name);
197
- const destPath = path_1.default.join(dest, entry.name);
198
- if (entry.isDirectory()) {
199
- copyDir(srcPath, destPath);
200
- }
201
- else {
202
- fs_1.default.copyFileSync(srcPath, destPath);
203
- }
204
- }
205
- }
@@ -3,12 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.HELP = void 0;
4
4
  exports.logs = logs;
5
5
  exports.HELP = ` logs [--source <source>] [--level <level>] Stream app logs (console, Metro, or device)
6
- --source <source> Log source: metro, device, or auto (default: auto)
6
+ --source <source> Filter by source: metro, device (default: both)
7
7
  --level <level> Minimum level: verbose, debug, log, info, warn, error
8
- --metro Enable Metro logs for React Native apps (auto-discovers port)
9
- --metro-port <port> Override Metro dev server port (skips auto-discovery)
10
- --target <n> Metro debugger target index (when multiple devices share one Metro)
11
- --list List available Metro debugger targets and exit
8
+ --list List Metro debugger targets for this device and exit
12
9
  --recent <n> Return the last N buffered log entries and exit (agent-friendly)
13
10
  --duration <seconds> Stream logs for N seconds, then exit
14
11
  --json Output as NDJSON (one JSON object per line)`;
@@ -18,6 +15,7 @@ const android_js_1 = require("../drivers/android.js");
18
15
  const web_js_1 = require("../drivers/web.js");
19
16
  const types_js_1 = require("../drivers/log-sources/types.js");
20
17
  const metro_js_1 = require("../drivers/log-sources/metro.js");
18
+ const metro_discovery_js_1 = require("../drivers/log-sources/metro-discovery.js");
21
19
  const daemon_js_1 = require("../drivers/log-sources/daemon.js");
22
20
  const client_js_1 = require("../daemon/client.js");
23
21
  const bootstrap_js_1 = require("../drivers/bootstrap.js");
@@ -44,71 +42,100 @@ function formatEntry(entry, opts) {
44
42
  }
45
43
  return line;
46
44
  }
47
- async function logs(opts = {}, sessionName = 'default', { source = 'auto', level, metro, metroPort, target, list, recent, duration } = {}) {
48
- // --list: query Metro targets and print them without starting a log stream
45
+ async function resolvePlatformAndDevice(sessionName) {
46
+ try {
47
+ const driver = await (0, runner_js_1.getDriver)(sessionName);
48
+ let platform = 'unknown';
49
+ if (driver instanceof ios_js_1.IOSDriver)
50
+ platform = driver.platform;
51
+ else if (driver instanceof android_js_1.AndroidDriver)
52
+ platform = 'android';
53
+ else if (driver instanceof web_js_1.WebDriver)
54
+ platform = 'web';
55
+ else
56
+ platform = await (0, bootstrap_js_1.detectPlatform)(sessionName);
57
+ return { platform, deviceId: sessionName };
58
+ }
59
+ catch {
60
+ return null;
61
+ }
62
+ }
63
+ async function logs(opts = {}, sessionName = 'default', { source, level, list, recent, duration } = {}) {
64
+ const sourceFilter = source === 'metro' || source === 'device' ? source : undefined;
65
+ // --list: resolve the device's Metro port deterministically, then print its targets.
49
66
  if (list) {
50
67
  try {
51
- const targets = await (0, metro_js_1.fetchTargets)(metroPort ?? 8081, 'localhost');
52
- const withWs = targets.filter((t) => t.webSocketDebuggerUrl);
53
- if (withWs.length === 0) {
54
- if (opts.json) {
55
- console.log(JSON.stringify({ status: 'ok', targets: [] }));
56
- }
68
+ const ctx = await resolvePlatformAndDevice(sessionName);
69
+ if (!ctx) {
70
+ const msg = 'Could not resolve device session for --list';
71
+ if (opts.json)
72
+ console.log(JSON.stringify({ status: 'error', message: msg }));
73
+ else
74
+ console.error(`✗ logs --list — ${msg}`);
75
+ return 1;
76
+ }
77
+ const port = await (0, metro_discovery_js_1.discoverMetroPortForDevice)(ctx.platform, ctx.deviceId);
78
+ if (port === null) {
79
+ if (opts.json)
80
+ console.log(JSON.stringify({ status: 'ok', port: null, targets: [] }));
57
81
  else {
58
- console.log('No Metro debugger targets found. Is the app running?');
82
+ console.log('No Metro connection detected for this device. The daemon will keep trying — ' +
83
+ 'launch the React Native app on this device and retry, or confirm the app is not RN.');
59
84
  }
60
85
  return 0;
61
86
  }
87
+ const allTargets = await (0, metro_js_1.fetchTargets)(port, 'localhost');
88
+ const displayName = await (0, metro_discovery_js_1.getDeviceDisplayName)(ctx.platform, ctx.deviceId);
89
+ const deviceTargets = displayName ? (0, metro_discovery_js_1.targetsForDevice)(allTargets, displayName) : [];
62
90
  if (opts.json) {
63
- const items = withWs.map((t, i) => ({
64
- index: i,
65
- title: t.title ?? null,
66
- description: t.description ?? null,
67
- deviceName: t.deviceName ?? null,
68
- deviceId: t.deviceId ?? null,
69
- appId: t.appId ?? null,
70
- logicalDeviceId: t.reactNative?.logicalDeviceId ?? null,
91
+ console.log(JSON.stringify({
92
+ status: 'ok',
93
+ port,
94
+ deviceName: displayName,
95
+ targets: deviceTargets.map((t, i) => ({
96
+ index: i,
97
+ title: t.title ?? null,
98
+ description: t.description ?? null,
99
+ deviceName: t.deviceName ?? null,
100
+ appId: t.appId ?? null,
101
+ logicalDeviceId: t.reactNative?.logicalDeviceId ?? null,
102
+ webSocketDebuggerUrl: t.webSocketDebuggerUrl ?? null,
103
+ })),
71
104
  }));
72
- console.log(JSON.stringify({ status: 'ok', targets: items }));
73
105
  }
74
106
  else {
75
- console.log('Metro debugger targets:');
76
- for (let i = 0; i < withWs.length; i++) {
77
- const t = withWs[i];
78
- const label = t.title ?? t.deviceName ?? '(unnamed)';
79
- const desc = t.description ? ` — ${t.description}` : '';
80
- console.log(` ${i}: ${label}${desc}`);
107
+ console.log(`Metro on port ${port} (device: ${displayName ?? 'unknown'})`);
108
+ if (deviceTargets.length === 0) {
109
+ console.log(' No targets for this device.');
110
+ }
111
+ else {
112
+ for (let i = 0; i < deviceTargets.length; i++) {
113
+ const t = deviceTargets[i];
114
+ const desc = t.description ? ` — ${t.description}` : '';
115
+ console.log(` ${i}: ${t.title ?? '(unnamed)'}${desc}`);
116
+ }
81
117
  }
82
118
  }
83
119
  return 0;
84
120
  }
85
121
  catch (err) {
86
122
  const msg = err instanceof Error ? err.message : String(err);
87
- if (opts.json) {
123
+ if (opts.json)
88
124
  console.log(JSON.stringify({ status: 'error', message: msg }));
89
- }
90
- else {
91
- console.error(`\u2717 logs --list \u2014 ${msg}`);
92
- }
125
+ else
126
+ console.error(`✗ logs --list — ${msg}`);
93
127
  return 1;
94
128
  }
95
129
  }
96
130
  try {
97
131
  // ── Snapshot mode (--recent N) ──────────────────────────────────────────
98
- // Single fetch from the daemon's log buffer, print, and exit immediately.
99
- // This is the primary agent-friendly mode.
100
132
  if (recent !== undefined) {
101
- // Ensure daemon is running (starts it if needed)
102
133
  await (0, runner_js_1.getDriver)(sessionName);
103
134
  const minSeverity = level ? (types_js_1.LEVEL_SEVERITY[level] ?? 0) : 0;
104
- // --metro with explicit port use that port; --metro without port → auto-discover
105
- const metroOpt = metro ? (metroPort ?? 'auto') : undefined;
106
- const entries = await (0, client_js_1.fetchDaemonLogs)(sessionName, {
107
- limit: recent,
108
- level,
109
- metro: metroOpt,
110
- });
135
+ const entries = await (0, client_js_1.fetchDaemonLogs)(sessionName, { limit: recent, level });
111
136
  for (const entry of entries) {
137
+ if (sourceFilter && entry.source !== sourceFilter)
138
+ continue;
112
139
  const entrySeverity = types_js_1.LEVEL_SEVERITY[entry.level] ?? 0;
113
140
  if (entrySeverity < minSeverity)
114
141
  continue;
@@ -116,41 +143,12 @@ async function logs(opts = {}, sessionName = 'default', { source = 'auto', level
116
143
  }
117
144
  return 0;
118
145
  }
119
- // ── Determine platform for streaming modes ─────────────────────────────
120
- // When source is explicitly 'metro', skip device resolution entirely —
121
- // Metro runs on the host, so we don't need a running driver or session.
122
- let _platform = 'unknown';
123
- if (source !== 'metro') {
124
- const driver = await (0, runner_js_1.getDriver)(sessionName);
125
- if (driver instanceof ios_js_1.IOSDriver) {
126
- _platform = driver.platform;
127
- }
128
- else if (driver instanceof android_js_1.AndroidDriver) {
129
- _platform = 'android';
130
- }
131
- else if (driver instanceof web_js_1.WebDriver) {
132
- _platform = 'web';
133
- }
134
- else {
135
- _platform = await (0, bootstrap_js_1.detectPlatform)(sessionName);
136
- }
137
- }
146
+ // ── Streaming modes ─────────────────────────────────────────────────────
147
+ // Ensure daemon is running; its log collector auto-discovers Metro.
148
+ await (0, runner_js_1.getDriver)(sessionName);
138
149
  const minSeverity = level ? (types_js_1.LEVEL_SEVERITY[level] ?? 0) : 0;
139
- // ── Create log source ──────────────────────────────────────────────────
140
- let logSource;
141
- if (source === 'metro') {
142
- // Explicit --source metro: connect directly to Metro via CLI
143
- logSource = new metro_js_1.MetroLogSource(metroPort ?? 8081, 'localhost', target);
144
- await logSource.connect();
145
- }
146
- else {
147
- // Device logs via daemon. When --metro is set, pass the metro port
148
- // (or 'auto' for auto-discovery) so the daemon finds Metro for this device.
149
- const metroOpt = metro ? (metroPort ?? 'auto') : undefined;
150
- logSource = new daemon_js_1.DaemonLogSource(sessionName, metroOpt);
151
- await logSource.connect();
152
- }
153
- // Set up graceful shutdown
150
+ const logSource = new daemon_js_1.DaemonLogSource(sessionName);
151
+ await logSource.connect();
154
152
  const cleanup = () => {
155
153
  logSource.disconnect();
156
154
  process.exit(0);
@@ -158,12 +156,13 @@ async function logs(opts = {}, sessionName = 'default', { source = 'auto', level
158
156
  process.on('SIGINT', cleanup);
159
157
  process.on('SIGTERM', cleanup);
160
158
  logSource.onEntry((entry) => {
159
+ if (sourceFilter && entry.source !== sourceFilter)
160
+ return;
161
161
  const entrySeverity = types_js_1.LEVEL_SEVERITY[entry.level] ?? 0;
162
162
  if (entrySeverity < minSeverity)
163
163
  return;
164
164
  console.log(formatEntry(entry, opts));
165
165
  });
166
- // ── Duration mode (--duration N) ───────────────────────────────────────
167
166
  if (duration !== undefined) {
168
167
  await new Promise((resolve) => {
169
168
  setTimeout(() => {
@@ -173,11 +172,8 @@ async function logs(opts = {}, sessionName = 'default', { source = 'auto', level
173
172
  });
174
173
  return 0;
175
174
  }
176
- // ── Streaming mode (default) ───────────────────────────────────────────
177
- // Keep the process alive — the log source streams entries via callbacks
178
- await new Promise(() => {
179
- // Never resolves — exits via SIGINT/SIGTERM
180
- });
175
+ // Streaming never resolves; exits via SIGINT/SIGTERM
176
+ await new Promise(() => { });
181
177
  return 0;
182
178
  }
183
179
  catch (err) {
@@ -186,7 +182,7 @@ async function logs(opts = {}, sessionName = 'default', { source = 'auto', level
186
182
  console.log(JSON.stringify({ status: 'error', message: msg }));
187
183
  }
188
184
  else {
189
- console.error(`\u2717 logs \u2014 ${msg}`);
185
+ console.error(`✗ logs ${msg}`);
190
186
  }
191
187
  return 1;
192
188
  }
@@ -205,9 +205,8 @@ async function findRunningWebSession(browserName) {
205
205
  * Fetch buffered log entries from the daemon's /logs HTTP endpoint.
206
206
  * Used by `conductor logs --recent` for snapshot access.
207
207
  *
208
- * Pass `metro` port to opt in to Metro auto-discovery for React Native apps.
209
- * The daemon will start polling Metro's /json endpoint for a debugger target
210
- * matching this device and merge JS console entries into the log buffer.
208
+ * The daemon always auto-discovers Metro for the device in this session and
209
+ * merges JS console entries (source='metro') alongside platform logs.
211
210
  */
212
211
  async function fetchDaemonLogs(sessionName, opts = {}) {
213
212
  const params = new URLSearchParams();
@@ -217,12 +216,6 @@ async function fetchDaemonLogs(sessionName, opts = {}) {
217
216
  params.set('level', opts.level);
218
217
  if (opts.limit)
219
218
  params.set('limit', String(opts.limit));
220
- if (opts.metro === 'auto') {
221
- params.set('metro', '');
222
- }
223
- else if (opts.metro) {
224
- params.set('metro', String(opts.metro));
225
- }
226
219
  const qs = params.toString();
227
220
  const reqPath = qs ? `/logs?${qs}` : '/logs';
228
221
  return new Promise((resolve, reject) => {