@btraut/browser-bridge 0.7.3 → 0.8.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/index.js CHANGED
@@ -76,6 +76,498 @@ var successEnvelopeSchema = (result) => import_zod.z.object({
76
76
  result
77
77
  });
78
78
 
79
+ // packages/shared/src/logging.ts
80
+ var import_node_fs2 = require("node:fs");
81
+ var import_node_path2 = require("node:path");
82
+
83
+ // packages/shared/src/runtime-config.ts
84
+ var import_node_fs = require("node:fs");
85
+ var import_node_path = require("node:path");
86
+ var DEFAULT_HOST = "127.0.0.1";
87
+ var LEGACY_DEFAULT_PORT = 3210;
88
+ var DETERMINISTIC_PORT_WINDOW = 2e3;
89
+ var ENV_CORE_HOST = "BROWSER_BRIDGE_CORE_HOST";
90
+ var ENV_VISION_HOST = "BROWSER_VISION_CORE_HOST";
91
+ var ENV_CORE_PORT = "BROWSER_BRIDGE_CORE_PORT";
92
+ var ENV_VISION_PORT = "BROWSER_VISION_CORE_PORT";
93
+ var RUNTIME_METADATA_RELATIVE_PATH = ".context/browser-bridge/dev.json";
94
+ var DEFAULT_LOG_DIRECTORY_RELATIVE_PATH = ".context/logs/browser-bridge";
95
+ var resolveCwd = (cwd) => (0, import_node_path.resolve)(cwd ?? process.cwd());
96
+ var resolveOptionalPath = (cwd, value) => {
97
+ if (!value) {
98
+ return void 0;
99
+ }
100
+ return (0, import_node_path.isAbsolute)(value) ? value : (0, import_node_path.resolve)(cwd, value);
101
+ };
102
+ var normalizeHost = (value) => {
103
+ if (typeof value !== "string") {
104
+ return void 0;
105
+ }
106
+ const trimmed = value.trim();
107
+ return trimmed.length > 0 ? trimmed : void 0;
108
+ };
109
+ var parsePort = (value, label, invalidPolicy) => {
110
+ if (value === void 0 || value === null) {
111
+ return void 0;
112
+ }
113
+ if (typeof value === "string" && value.trim().length === 0) {
114
+ return void 0;
115
+ }
116
+ const parsed = typeof value === "number" ? value : Number.parseInt(String(value), 10);
117
+ if (Number.isFinite(parsed) && parsed > 0) {
118
+ return Math.floor(parsed);
119
+ }
120
+ if (invalidPolicy === "throw") {
121
+ throw new Error(`Invalid ${label}: ${String(value)}`);
122
+ }
123
+ return void 0;
124
+ };
125
+ var hashString = (value) => {
126
+ let hash = 2166136261;
127
+ for (let index = 0; index < value.length; index += 1) {
128
+ hash ^= value.charCodeAt(index);
129
+ hash = Math.imul(hash, 16777619);
130
+ }
131
+ return hash >>> 0;
132
+ };
133
+ var normalizePathForHash = (value) => value.replace(/\\/g, "/").toLowerCase();
134
+ var sanitizeToken = (value) => value.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
135
+ var fallbackWorktreeId = (gitRoot) => {
136
+ const hash = hashString(normalizePathForHash(gitRoot)).toString(16).padStart(8, "0");
137
+ return `wt-${hash}`;
138
+ };
139
+ var extractWorktreeIdFromGitDir = (gitDir) => {
140
+ const normalized = gitDir.replace(/\\/g, "/");
141
+ const marker = "/.git/worktrees/";
142
+ const markerIndex = normalized.lastIndexOf(marker);
143
+ if (markerIndex < 0) {
144
+ return null;
145
+ }
146
+ const remainder = normalized.slice(markerIndex + marker.length);
147
+ const rawId = remainder.split("/")[0];
148
+ if (!rawId) {
149
+ return null;
150
+ }
151
+ const sanitized = sanitizeToken(rawId);
152
+ return sanitized.length > 0 ? sanitized : null;
153
+ };
154
+ var readWorktreeGitDir = (gitRoot) => {
155
+ const gitPath = (0, import_node_path.join)(gitRoot, ".git");
156
+ try {
157
+ const stats = (0, import_node_fs.statSync)(gitPath);
158
+ if (stats.isDirectory()) {
159
+ return gitPath;
160
+ }
161
+ if (!stats.isFile()) {
162
+ return null;
163
+ }
164
+ } catch {
165
+ return null;
166
+ }
167
+ try {
168
+ const raw = (0, import_node_fs.readFileSync)(gitPath, "utf8");
169
+ const match = raw.match(/^gitdir:\s*(.+)$/m);
170
+ if (!match?.[1]) {
171
+ return null;
172
+ }
173
+ const candidate = match[1].trim();
174
+ return (0, import_node_path.isAbsolute)(candidate) ? candidate : (0, import_node_path.resolve)(gitRoot, candidate);
175
+ } catch {
176
+ return null;
177
+ }
178
+ };
179
+ var resolveEnvHost = (env) => {
180
+ const bridgeHost = normalizeHost(env[ENV_CORE_HOST]);
181
+ if (bridgeHost) {
182
+ return bridgeHost;
183
+ }
184
+ return normalizeHost(env[ENV_VISION_HOST]);
185
+ };
186
+ var resolveEnvPortRaw = (env) => {
187
+ if (env[ENV_CORE_PORT] !== void 0) {
188
+ return env[ENV_CORE_PORT];
189
+ }
190
+ return env[ENV_VISION_PORT];
191
+ };
192
+ var sanitizeMetadata = (raw) => {
193
+ if (!raw || typeof raw !== "object") {
194
+ return null;
195
+ }
196
+ const candidate = raw;
197
+ const host = normalizeHost(candidate.host);
198
+ const port = parsePort(candidate.port, "port", "ignore");
199
+ const gitRoot = normalizeHost(candidate.git_root);
200
+ const worktreeId = normalizeHost(candidate.worktree_id);
201
+ const extensionId = normalizeHost(candidate.extension_id);
202
+ const updatedAt = normalizeHost(candidate.updated_at);
203
+ if (!host && port === void 0 && !gitRoot && !worktreeId && !extensionId && !updatedAt) {
204
+ return null;
205
+ }
206
+ return {
207
+ host,
208
+ port,
209
+ git_root: gitRoot,
210
+ worktree_id: worktreeId,
211
+ extension_id: extensionId,
212
+ updated_at: updatedAt
213
+ };
214
+ };
215
+ var findGitRoot = (cwd = process.cwd()) => {
216
+ let current = (0, import_node_path.resolve)(cwd);
217
+ while (true) {
218
+ if ((0, import_node_fs.existsSync)((0, import_node_path.join)(current, ".git"))) {
219
+ return current;
220
+ }
221
+ const parent = (0, import_node_path.dirname)(current);
222
+ if (parent === current) {
223
+ return null;
224
+ }
225
+ current = parent;
226
+ }
227
+ };
228
+ var resolveWorktreeId = ({
229
+ cwd,
230
+ gitRoot
231
+ } = {}) => {
232
+ const resolvedGitRoot = gitRoot ?? findGitRoot(resolveCwd(cwd));
233
+ if (!resolvedGitRoot) {
234
+ return null;
235
+ }
236
+ const gitDir = readWorktreeGitDir(resolvedGitRoot);
237
+ const parsedId = gitDir ? extractWorktreeIdFromGitDir(gitDir) : null;
238
+ if (parsedId) {
239
+ return parsedId;
240
+ }
241
+ return fallbackWorktreeId(resolvedGitRoot);
242
+ };
243
+ var resolveDeterministicCorePort = ({
244
+ cwd,
245
+ gitRoot
246
+ } = {}) => {
247
+ const resolvedGitRoot = gitRoot ?? findGitRoot(resolveCwd(cwd));
248
+ if (!resolvedGitRoot) {
249
+ return LEGACY_DEFAULT_PORT;
250
+ }
251
+ const seed = normalizePathForHash(resolvedGitRoot);
252
+ return LEGACY_DEFAULT_PORT + hashString(seed) % DETERMINISTIC_PORT_WINDOW;
253
+ };
254
+ var resolveRuntimeMetadataPath = ({
255
+ cwd,
256
+ gitRoot,
257
+ metadataPath
258
+ } = {}) => {
259
+ const resolvedCwd = resolveCwd(cwd);
260
+ const providedPath = resolveOptionalPath(resolvedCwd, metadataPath);
261
+ if (providedPath) {
262
+ return providedPath;
263
+ }
264
+ const root = gitRoot ?? findGitRoot(resolvedCwd) ?? resolvedCwd;
265
+ return (0, import_node_path.join)(root, RUNTIME_METADATA_RELATIVE_PATH);
266
+ };
267
+ var resolveLogDirectory = ({
268
+ cwd,
269
+ gitRoot,
270
+ logDir
271
+ } = {}) => {
272
+ const resolvedCwd = resolveCwd(cwd);
273
+ const providedPath = resolveOptionalPath(resolvedCwd, logDir);
274
+ if (providedPath) {
275
+ return providedPath;
276
+ }
277
+ const resolvedGitRoot = gitRoot === void 0 ? findGitRoot(resolvedCwd) : gitRoot ? (0, import_node_path.resolve)(gitRoot) : null;
278
+ return (0, import_node_path.join)(
279
+ resolvedGitRoot ?? resolvedCwd,
280
+ DEFAULT_LOG_DIRECTORY_RELATIVE_PATH
281
+ );
282
+ };
283
+ var readRuntimeMetadata = ({
284
+ cwd,
285
+ gitRoot,
286
+ metadataPath
287
+ } = {}) => {
288
+ const path9 = resolveRuntimeMetadataPath({ cwd, gitRoot, metadataPath });
289
+ if (!(0, import_node_fs.existsSync)(path9)) {
290
+ return null;
291
+ }
292
+ try {
293
+ const raw = (0, import_node_fs.readFileSync)(path9, "utf8");
294
+ const parsed = JSON.parse(raw);
295
+ return sanitizeMetadata(parsed);
296
+ } catch {
297
+ return null;
298
+ }
299
+ };
300
+ var writeRuntimeMetadata = (metadata, {
301
+ cwd,
302
+ gitRoot,
303
+ metadataPath
304
+ } = {}) => {
305
+ const path9 = resolveRuntimeMetadataPath({ cwd, gitRoot, metadataPath });
306
+ (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path9), { recursive: true });
307
+ (0, import_node_fs.writeFileSync)(path9, `${JSON.stringify(metadata, null, 2)}
308
+ `, "utf8");
309
+ return path9;
310
+ };
311
+ var resolveCoreRuntime = (options = {}) => {
312
+ const env = options.env ?? process.env;
313
+ const resolvedCwd = resolveCwd(options.cwd);
314
+ const gitRoot = options.gitRoot === void 0 ? findGitRoot(resolvedCwd) : options.gitRoot ? (0, import_node_path.resolve)(options.gitRoot) : null;
315
+ const metadataPath = resolveRuntimeMetadataPath({
316
+ cwd: resolvedCwd,
317
+ gitRoot,
318
+ metadataPath: options.metadataPath
319
+ });
320
+ const metadata = options.metadata === void 0 ? readRuntimeMetadata({ metadataPath }) : sanitizeMetadata(options.metadata);
321
+ const deterministicPort = resolveDeterministicCorePort({
322
+ cwd: resolvedCwd,
323
+ gitRoot
324
+ });
325
+ const optionHost = normalizeHost(options.host);
326
+ const envHost = resolveEnvHost(env);
327
+ const metadataHost = normalizeHost(metadata?.host);
328
+ const host = optionHost ?? envHost ?? metadataHost ?? DEFAULT_HOST;
329
+ const hostSource = optionHost ? "option" : envHost ? "env" : metadataHost ? "metadata" : "default";
330
+ const optionPort = parsePort(options.port, "port", "throw");
331
+ const envPort = parsePort(
332
+ resolveEnvPortRaw(env),
333
+ "port",
334
+ options.strictEnvPort ? "throw" : "ignore"
335
+ );
336
+ const metadataPort = parsePort(metadata?.port, "port", "ignore");
337
+ let port;
338
+ let portSource;
339
+ if (optionPort !== void 0) {
340
+ port = optionPort;
341
+ portSource = "option";
342
+ } else if (envPort !== void 0) {
343
+ port = envPort;
344
+ portSource = "env";
345
+ } else if (metadataPort !== void 0) {
346
+ port = metadataPort;
347
+ portSource = "metadata";
348
+ } else {
349
+ port = deterministicPort;
350
+ portSource = "deterministic";
351
+ }
352
+ return {
353
+ host,
354
+ port,
355
+ hostSource,
356
+ portSource,
357
+ metadataPath,
358
+ metadata,
359
+ gitRoot,
360
+ worktreeId: resolveWorktreeId({ cwd: resolvedCwd, gitRoot }),
361
+ deterministicPort
362
+ };
363
+ };
364
+
365
+ // packages/shared/src/logging.ts
366
+ var DEFAULT_LOG_ROTATION_MAX_BYTES = 10 * 1024 * 1024;
367
+ var DEFAULT_LOG_RETENTION = 20;
368
+ var DEFAULT_REDACT_KEYS = [
369
+ "authorization",
370
+ "cookie",
371
+ "setcookie",
372
+ "password",
373
+ "passwd",
374
+ "secret",
375
+ "token",
376
+ "accesstoken",
377
+ "refreshtoken",
378
+ "apikey",
379
+ "apikeyid",
380
+ "privatekey",
381
+ "clientsecret"
382
+ ];
383
+ var REDACTED_VALUE = "[REDACTED]";
384
+ var MAX_NORMALIZE_DEPTH = 8;
385
+ var LOG_LEVEL_PRIORITY = {
386
+ debug: 10,
387
+ info: 20,
388
+ warn: 30,
389
+ error: 40
390
+ };
391
+ var normalizeRedactionKey = (value) => value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
392
+ var sanitizeStreamName = (stream) => {
393
+ const sanitized = stream.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
394
+ if (sanitized.length === 0) {
395
+ throw new Error("Logger stream name must contain visible characters.");
396
+ }
397
+ return sanitized;
398
+ };
399
+ var normalizeForJson = (value, seen, depth = 0) => {
400
+ if (value === null) {
401
+ return null;
402
+ }
403
+ if (depth >= MAX_NORMALIZE_DEPTH) {
404
+ return "[MaxDepth]";
405
+ }
406
+ const valueType = typeof value;
407
+ if (valueType === "string" || valueType === "number" || valueType === "boolean") {
408
+ return value;
409
+ }
410
+ if (valueType === "bigint") {
411
+ return String(value);
412
+ }
413
+ if (valueType === "undefined" || valueType === "symbol" || valueType === "function") {
414
+ return String(value);
415
+ }
416
+ if (value instanceof Date) {
417
+ return value.toISOString();
418
+ }
419
+ if (value instanceof Error) {
420
+ return {
421
+ name: value.name,
422
+ message: value.message,
423
+ stack: value.stack ?? null
424
+ };
425
+ }
426
+ if (Array.isArray(value)) {
427
+ return value.map((item) => normalizeForJson(item, seen, depth + 1));
428
+ }
429
+ if (value instanceof Set) {
430
+ return Array.from(value.values()).map(
431
+ (item) => normalizeForJson(item, seen, depth + 1)
432
+ );
433
+ }
434
+ if (value instanceof Map) {
435
+ const mapped = {};
436
+ for (const [entryKey, entryValue] of value.entries()) {
437
+ mapped[String(entryKey)] = normalizeForJson(entryValue, seen, depth + 1);
438
+ }
439
+ return mapped;
440
+ }
441
+ if (typeof value === "object") {
442
+ const objectValue = value;
443
+ if (seen.has(objectValue)) {
444
+ return "[Circular]";
445
+ }
446
+ seen.add(objectValue);
447
+ const normalized = {};
448
+ for (const [key, nestedValue] of Object.entries(objectValue)) {
449
+ normalized[key] = normalizeForJson(nestedValue, seen, depth + 1);
450
+ }
451
+ seen.delete(objectValue);
452
+ return normalized;
453
+ }
454
+ return String(value);
455
+ };
456
+ var redactJson = (value, redactKeys) => {
457
+ if (Array.isArray(value)) {
458
+ return value.map((item) => redactJson(item, redactKeys));
459
+ }
460
+ if (!value || typeof value !== "object") {
461
+ return value;
462
+ }
463
+ const record = value;
464
+ const redacted = {};
465
+ for (const [key, nestedValue] of Object.entries(record)) {
466
+ if (redactKeys.has(normalizeRedactionKey(key))) {
467
+ redacted[key] = REDACTED_VALUE;
468
+ continue;
469
+ }
470
+ redacted[key] = redactJson(nestedValue, redactKeys);
471
+ }
472
+ return redacted;
473
+ };
474
+ var streamFilePath = (logDir, stream) => (0, import_node_path2.join)(logDir, `${stream}.jsonl`);
475
+ var rotationFilePath = (logDir, stream, index) => (0, import_node_path2.join)(logDir, `${stream}.${index}.jsonl`);
476
+ var rotateStreamFiles = (logDir, stream, retention) => {
477
+ if (retention <= 1) {
478
+ (0, import_node_fs2.rmSync)(streamFilePath(logDir, stream), { force: true });
479
+ return;
480
+ }
481
+ const maxArchiveIndex = retention - 1;
482
+ (0, import_node_fs2.rmSync)(rotationFilePath(logDir, stream, maxArchiveIndex), { force: true });
483
+ for (let index = maxArchiveIndex - 1; index >= 1; index -= 1) {
484
+ const source = rotationFilePath(logDir, stream, index);
485
+ if (!(0, import_node_fs2.existsSync)(source)) {
486
+ continue;
487
+ }
488
+ const target = rotationFilePath(logDir, stream, index + 1);
489
+ (0, import_node_fs2.renameSync)(source, target);
490
+ }
491
+ const current = streamFilePath(logDir, stream);
492
+ if ((0, import_node_fs2.existsSync)(current)) {
493
+ (0, import_node_fs2.renameSync)(current, rotationFilePath(logDir, stream, 1));
494
+ }
495
+ };
496
+ var rotateIfNeeded = (state, pendingLine) => {
497
+ if (!(0, import_node_fs2.existsSync)(state.filePath)) {
498
+ return;
499
+ }
500
+ const pendingBytes = Buffer.byteLength(pendingLine, "utf8");
501
+ const currentSize = (0, import_node_fs2.statSync)(state.filePath).size;
502
+ if (currentSize + pendingBytes <= state.maxBytes) {
503
+ return;
504
+ }
505
+ rotateStreamFiles(state.logDir, state.stream, state.retention);
506
+ };
507
+ var buildLogger = (state, bindings) => {
508
+ const log = (level, event, fields = {}) => {
509
+ if (LOG_LEVEL_PRIORITY[level] < LOG_LEVEL_PRIORITY[state.level]) {
510
+ return;
511
+ }
512
+ const payload = {
513
+ ts: state.now().toISOString(),
514
+ level,
515
+ stream: state.stream,
516
+ event,
517
+ ...bindings,
518
+ ...fields
519
+ };
520
+ const normalized = normalizeForJson(payload, /* @__PURE__ */ new WeakSet());
521
+ const redacted = redactJson(normalized, state.redactKeys);
522
+ const line = `${JSON.stringify(redacted)}
523
+ `;
524
+ try {
525
+ rotateIfNeeded(state, line);
526
+ (0, import_node_fs2.appendFileSync)(state.filePath, line, "utf8");
527
+ } catch {
528
+ }
529
+ };
530
+ return {
531
+ stream: state.stream,
532
+ level: state.level,
533
+ logDir: state.logDir,
534
+ filePath: state.filePath,
535
+ child: (childBindings) => buildLogger(state, {
536
+ ...bindings,
537
+ ...childBindings
538
+ }),
539
+ log,
540
+ debug: (event, fields) => log("debug", event, fields),
541
+ info: (event, fields) => log("info", event, fields),
542
+ warn: (event, fields) => log("warn", event, fields),
543
+ error: (event, fields) => log("error", event, fields)
544
+ };
545
+ };
546
+ var createJsonlLogger = (options) => {
547
+ const stream = sanitizeStreamName(options.stream);
548
+ const logDir = resolveLogDirectory({
549
+ cwd: options.cwd,
550
+ gitRoot: options.gitRoot,
551
+ logDir: options.logDir
552
+ });
553
+ (0, import_node_fs2.mkdirSync)(logDir, { recursive: true });
554
+ const maxBytes = typeof options.maxBytes === "number" && options.maxBytes > 0 ? Math.floor(options.maxBytes) : DEFAULT_LOG_ROTATION_MAX_BYTES;
555
+ const retention = typeof options.retention === "number" && options.retention > 0 ? Math.max(1, Math.floor(options.retention)) : DEFAULT_LOG_RETENTION;
556
+ const state = {
557
+ stream,
558
+ level: options.level ?? "debug",
559
+ logDir,
560
+ filePath: streamFilePath(logDir, stream),
561
+ maxBytes,
562
+ retention,
563
+ redactKeys: new Set(
564
+ (options.redactKeys ?? DEFAULT_REDACT_KEYS).map(normalizeRedactionKey)
565
+ ),
566
+ now: options.now ?? (() => /* @__PURE__ */ new Date())
567
+ };
568
+ return buildLogger(state, options.bindings ?? {});
569
+ };
570
+
79
571
  // packages/shared/src/schemas.ts
80
572
  var import_zod2 = require("zod");
81
573
  var LocatorRoleSchema = import_zod2.z.object({
@@ -589,7 +1081,7 @@ var import_zod3 = require("zod");
589
1081
 
590
1082
  // packages/cli/src/core-client.ts
591
1083
  var import_node_child_process = require("node:child_process");
592
- var import_node_path = require("node:path");
1084
+ var import_node_path3 = require("node:path");
593
1085
  var import_promises = require("node:timers/promises");
594
1086
  var CoreClientError = class extends Error {
595
1087
  constructor(info) {
@@ -598,29 +1090,9 @@ var CoreClientError = class extends Error {
598
1090
  this.info = info;
599
1091
  }
600
1092
  };
601
- var DEFAULT_HOST = "127.0.0.1";
602
- var DEFAULT_PORT = 3210;
603
1093
  var DEFAULT_TIMEOUT_MS = 3e4;
604
1094
  var HEALTH_RETRY_MS = 250;
605
1095
  var HEALTH_ATTEMPTS = 20;
606
- var resolveHost = (host) => {
607
- const candidate = host?.trim() || process.env.BROWSER_BRIDGE_CORE_HOST || process.env.BROWSER_VISION_CORE_HOST;
608
- if (candidate && candidate.length > 0) {
609
- return candidate;
610
- }
611
- return DEFAULT_HOST;
612
- };
613
- var resolvePort = (port) => {
614
- const candidate = port ?? (process.env.BROWSER_BRIDGE_CORE_PORT ? Number.parseInt(process.env.BROWSER_BRIDGE_CORE_PORT, 10) : process.env.BROWSER_VISION_CORE_PORT ? Number.parseInt(process.env.BROWSER_VISION_CORE_PORT, 10) : void 0);
615
- if (candidate === void 0 || candidate === null) {
616
- return DEFAULT_PORT;
617
- }
618
- const parsed = typeof candidate === "number" ? candidate : Number.parseInt(candidate, 10);
619
- if (!Number.isFinite(parsed) || parsed <= 0) {
620
- throw new Error(`Invalid port: ${String(candidate)}`);
621
- }
622
- return parsed;
623
- };
624
1096
  var resolveTimeoutMs = (timeoutMs) => {
625
1097
  const candidate = timeoutMs ?? (process.env.BROWSER_BRIDGE_CORE_TIMEOUT_MS ? Number.parseInt(process.env.BROWSER_BRIDGE_CORE_TIMEOUT_MS, 10) : process.env.BROWSER_VISION_CORE_TIMEOUT_MS ? Number.parseInt(process.env.BROWSER_VISION_CORE_TIMEOUT_MS, 10) : void 0);
626
1098
  if (candidate === void 0 || candidate === null) {
@@ -633,21 +1105,48 @@ var resolveTimeoutMs = (timeoutMs) => {
633
1105
  return Math.floor(parsed);
634
1106
  };
635
1107
  var normalizePath = (path9) => path9.startsWith("/") ? path9 : `/${path9}`;
1108
+ var durationMs = (startedAt) => Number((Number(process.hrtime.bigint() - startedAt) / 1e6).toFixed(3));
636
1109
  var createCoreClient = (options = {}) => {
637
- const host = resolveHost(options.host);
638
- const port = resolvePort(options.port);
639
- const baseUrl = `http://${host}:${port}`;
1110
+ const logger = options.logger ?? createJsonlLogger({
1111
+ stream: "cli",
1112
+ cwd: options.cwd
1113
+ }).child({ scope: "core-client" });
1114
+ let runtime = resolveCoreRuntime({
1115
+ host: options.host,
1116
+ port: options.port,
1117
+ cwd: options.cwd,
1118
+ strictEnvPort: true
1119
+ });
1120
+ let baseUrl = `http://${runtime.host}:${runtime.port}`;
640
1121
  const timeoutMs = resolveTimeoutMs(options.timeoutMs);
641
1122
  const fetchImpl = options.fetchImpl ?? fetch;
642
1123
  const spawnImpl = options.spawnImpl ?? import_node_child_process.spawn;
643
1124
  const ensureDaemon = options.ensureDaemon ?? true;
1125
+ const allowRuntimeRefresh = options.host === void 0 && options.port === void 0 && process.env.BROWSER_BRIDGE_CORE_HOST === void 0 && process.env.BROWSER_VISION_CORE_HOST === void 0 && process.env.BROWSER_BRIDGE_CORE_PORT === void 0 && process.env.BROWSER_VISION_CORE_PORT === void 0;
1126
+ const refreshRuntime = () => {
1127
+ if (!allowRuntimeRefresh) {
1128
+ return;
1129
+ }
1130
+ runtime = resolveCoreRuntime({
1131
+ cwd: options.cwd,
1132
+ strictEnvPort: true
1133
+ });
1134
+ baseUrl = `http://${runtime.host}:${runtime.port}`;
1135
+ };
644
1136
  const requestJson = async (method, path9, body) => {
1137
+ const requestPath = normalizePath(path9);
1138
+ const startedAt = process.hrtime.bigint();
1139
+ logger.debug("cli.core.request.start", {
1140
+ method,
1141
+ path: requestPath,
1142
+ base_url: baseUrl
1143
+ });
645
1144
  const controller = new AbortController();
646
1145
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
647
1146
  try {
648
1147
  let response;
649
1148
  try {
650
- response = await fetchImpl(`${baseUrl}${normalizePath(path9)}`, {
1149
+ response = await fetchImpl(`${baseUrl}${requestPath}`, {
651
1150
  method,
652
1151
  headers: {
653
1152
  "content-type": "application/json"
@@ -657,6 +1156,13 @@ var createCoreClient = (options = {}) => {
657
1156
  });
658
1157
  } catch (error) {
659
1158
  if (controller.signal.aborted || error instanceof Error && error.name === "AbortError") {
1159
+ logger.warn("cli.core.request.timeout", {
1160
+ method,
1161
+ path: requestPath,
1162
+ base_url: baseUrl,
1163
+ timeout_ms: timeoutMs,
1164
+ duration_ms: durationMs(startedAt)
1165
+ });
660
1166
  throw new CoreClientError({
661
1167
  code: "TIMEOUT",
662
1168
  message: `Core request timed out after ${timeoutMs}ms.`,
@@ -664,20 +1170,50 @@ var createCoreClient = (options = {}) => {
664
1170
  details: {
665
1171
  timeout_ms: timeoutMs,
666
1172
  base_url: baseUrl,
667
- path: normalizePath(path9)
1173
+ path: requestPath
668
1174
  }
669
1175
  });
670
1176
  }
1177
+ logger.error("cli.core.request.failed", {
1178
+ method,
1179
+ path: requestPath,
1180
+ base_url: baseUrl,
1181
+ duration_ms: durationMs(startedAt),
1182
+ error
1183
+ });
671
1184
  throw error;
672
1185
  }
673
1186
  const raw = await response.text();
674
1187
  if (!raw) {
1188
+ logger.warn("cli.core.request.empty_response", {
1189
+ method,
1190
+ path: requestPath,
1191
+ base_url: baseUrl,
1192
+ status: response.status,
1193
+ duration_ms: durationMs(startedAt)
1194
+ });
675
1195
  throw new Error(`Empty response from Core (${response.status}).`);
676
1196
  }
677
1197
  try {
678
- return JSON.parse(raw);
1198
+ const parsed = JSON.parse(raw);
1199
+ logger.debug("cli.core.request.end", {
1200
+ method,
1201
+ path: requestPath,
1202
+ base_url: baseUrl,
1203
+ status: response.status,
1204
+ duration_ms: durationMs(startedAt)
1205
+ });
1206
+ return parsed;
679
1207
  } catch (error) {
680
1208
  const message = error instanceof Error ? error.message : "Unknown JSON parse error";
1209
+ logger.error("cli.core.request.invalid_json", {
1210
+ method,
1211
+ path: requestPath,
1212
+ base_url: baseUrl,
1213
+ status: response.status,
1214
+ duration_ms: durationMs(startedAt),
1215
+ error
1216
+ });
681
1217
  throw new Error(`Failed to parse Core response: ${message}`);
682
1218
  }
683
1219
  } finally {
@@ -697,56 +1233,108 @@ var createCoreClient = (options = {}) => {
697
1233
  });
698
1234
  } catch (error) {
699
1235
  if (controller.signal.aborted || error instanceof Error && error.name === "AbortError") {
1236
+ logger.warn("cli.core.health.timeout", {
1237
+ base_url: baseUrl,
1238
+ timeout_ms: timeoutMs
1239
+ });
700
1240
  return false;
701
1241
  }
1242
+ logger.warn("cli.core.health.fetch_failed", {
1243
+ base_url: baseUrl,
1244
+ error
1245
+ });
702
1246
  throw error;
703
1247
  }
704
1248
  if (!response.ok) {
1249
+ logger.warn("cli.core.health.non_ok", {
1250
+ base_url: baseUrl,
1251
+ status: response.status
1252
+ });
705
1253
  return false;
706
1254
  }
707
1255
  const data = await response.json().catch(() => null);
708
- return Boolean(data?.ok);
1256
+ const ok = Boolean(data?.ok);
1257
+ if (!ok) {
1258
+ logger.warn("cli.core.health.not_ready", {
1259
+ base_url: baseUrl
1260
+ });
1261
+ }
1262
+ return ok;
709
1263
  } finally {
710
1264
  clearTimeout(timeout);
711
1265
  }
712
- } catch {
1266
+ } catch (error) {
1267
+ logger.warn("cli.core.health.error", {
1268
+ base_url: baseUrl,
1269
+ error
1270
+ });
713
1271
  return false;
714
1272
  }
715
1273
  };
716
1274
  const spawnDaemon = () => {
717
- const coreEntry = (0, import_node_path.resolve)(__dirname, "api.js");
1275
+ const coreEntry = (0, import_node_path3.resolve)(__dirname, "api.js");
1276
+ const startOptions = [];
1277
+ if (runtime.hostSource === "option" || runtime.hostSource === "env") {
1278
+ startOptions.push(`host: ${JSON.stringify(runtime.host)}`);
1279
+ }
1280
+ if (runtime.portSource === "option" || runtime.portSource === "env") {
1281
+ startOptions.push(`port: ${runtime.port}`);
1282
+ }
718
1283
  const script = `const { startCoreServer } = require(${JSON.stringify(
719
1284
  coreEntry
720
1285
  )});
721
- startCoreServer({ host: ${JSON.stringify(
722
- host
723
- )}, port: ${port} })
1286
+ startCoreServer({ ${startOptions.join(
1287
+ ", "
1288
+ )} })
724
1289
  .catch((err) => { console.error(err); process.exit(1); });`;
1290
+ logger.info("cli.core.spawn.start", {
1291
+ host: runtime.host,
1292
+ port: runtime.port,
1293
+ host_source: runtime.hostSource,
1294
+ port_source: runtime.portSource
1295
+ });
725
1296
  const child = spawnImpl(process.execPath, ["-e", script], {
726
1297
  detached: true,
727
1298
  stdio: "ignore",
728
- env: {
729
- ...process.env,
730
- BROWSER_BRIDGE_CORE_HOST: host,
731
- BROWSER_BRIDGE_CORE_PORT: String(port),
732
- BROWSER_VISION_CORE_HOST: host,
733
- BROWSER_VISION_CORE_PORT: String(port)
734
- }
1299
+ env: { ...process.env }
1300
+ });
1301
+ child.on("error", (error) => {
1302
+ logger.error("cli.core.spawn.error", {
1303
+ host: runtime.host,
1304
+ port: runtime.port,
1305
+ error
1306
+ });
735
1307
  });
736
1308
  child.unref();
737
1309
  };
738
1310
  const ensureCoreRunning = async () => {
1311
+ refreshRuntime();
739
1312
  if (await checkHealth()) {
1313
+ logger.debug("cli.core.ensure_ready.already_running", {
1314
+ base_url: baseUrl
1315
+ });
740
1316
  return;
741
1317
  }
742
1318
  spawnDaemon();
743
1319
  for (let attempt = 0; attempt < HEALTH_ATTEMPTS; attempt += 1) {
744
1320
  await (0, import_promises.setTimeout)(HEALTH_RETRY_MS);
1321
+ refreshRuntime();
745
1322
  if (await checkHealth()) {
1323
+ logger.info("cli.core.ensure_ready.ready", {
1324
+ base_url: baseUrl,
1325
+ attempts: attempt + 1
1326
+ });
746
1327
  return;
747
1328
  }
748
1329
  }
749
- throw new Error(`Core daemon failed to start on ${host}:${port}.`);
1330
+ logger.error("cli.core.ensure_ready.failed", {
1331
+ host: runtime.host,
1332
+ port: runtime.port,
1333
+ attempts: HEALTH_ATTEMPTS
1334
+ });
1335
+ throw new Error(
1336
+ `Core daemon failed to start on ${runtime.host}:${runtime.port}.`
1337
+ );
750
1338
  };
751
1339
  let ensurePromise = null;
752
1340
  const ensureReady = async () => {
@@ -760,9 +1348,16 @@ startCoreServer({ host: ${JSON.stringify(
760
1348
  };
761
1349
  const post = async (path9, body) => {
762
1350
  await ensureReady();
1351
+ refreshRuntime();
763
1352
  return requestJson("POST", path9, body);
764
1353
  };
765
- return { baseUrl, ensureReady, post };
1354
+ return {
1355
+ get baseUrl() {
1356
+ return baseUrl;
1357
+ },
1358
+ ensureReady,
1359
+ post
1360
+ };
766
1361
  };
767
1362
 
768
1363
  // packages/cli/src/cli-output.ts
@@ -857,37 +1452,76 @@ var getGlobalOptions = (command) => {
857
1452
  const root = getRootCommand(command);
858
1453
  return root.opts();
859
1454
  };
860
- var createClientFromCommand = (command) => {
1455
+ var durationMs2 = (startedAt) => Number((Number(process.hrtime.bigint() - startedAt) / 1e6).toFixed(3));
1456
+ var commandLabel = (command) => {
1457
+ const pieces = command.name().split(" ").filter((piece) => piece.length > 0);
1458
+ return pieces.length > 0 ? pieces.join(".") : "unknown";
1459
+ };
1460
+ var createCliLogger = (command) => createJsonlLogger({ stream: "cli" }).child({
1461
+ scope: "cli-runtime",
1462
+ command: commandLabel(command)
1463
+ });
1464
+ var createClientFromCommand = (command, logger) => {
861
1465
  const options = getGlobalOptions(command);
862
1466
  return createCoreClient({
863
1467
  host: options.host,
864
1468
  port: options.port,
865
- ensureDaemon: options.daemon !== false
1469
+ ensureDaemon: options.daemon !== false,
1470
+ logger: logger?.child({ scope: "core-client" })
866
1471
  });
867
1472
  };
868
1473
  var runCommand = async (command, work) => {
869
1474
  const options = getGlobalOptions(command);
870
- const client = createClientFromCommand(command);
1475
+ const logger = createCliLogger(command);
1476
+ const startedAt = process.hrtime.bigint();
1477
+ logger.info("cli.command.start", {
1478
+ host: options.host ?? null,
1479
+ port: options.port ?? null,
1480
+ daemon: options.daemon !== false,
1481
+ json: Boolean(options.json)
1482
+ });
1483
+ const client = createClientFromCommand(command, logger);
871
1484
  try {
872
1485
  const envelope2 = await work(client, options);
873
1486
  outputEnvelope(envelope2, { json: Boolean(options.json) });
1487
+ logger.info("cli.command.end", {
1488
+ ok: envelope2.ok,
1489
+ duration_ms: durationMs2(startedAt)
1490
+ });
874
1491
  if (!envelope2.ok) {
875
1492
  process.exitCode = 1;
876
1493
  }
877
1494
  } catch (error) {
1495
+ logger.error("cli.command.error", {
1496
+ duration_ms: durationMs2(startedAt),
1497
+ error
1498
+ });
878
1499
  outputError(error, { json: Boolean(options.json) });
879
1500
  process.exitCode = 1;
880
1501
  }
881
1502
  };
882
1503
  var runLocal = async (command, work) => {
883
1504
  const options = getGlobalOptions(command);
1505
+ const logger = createCliLogger(command).child({ mode: "local" });
1506
+ const startedAt = process.hrtime.bigint();
1507
+ logger.info("cli.command.start", {
1508
+ json: Boolean(options.json)
1509
+ });
884
1510
  try {
885
1511
  const envelope2 = await work(options);
886
1512
  outputEnvelope(envelope2, { json: Boolean(options.json) });
1513
+ logger.info("cli.command.end", {
1514
+ ok: envelope2.ok,
1515
+ duration_ms: durationMs2(startedAt)
1516
+ });
887
1517
  if (!envelope2.ok) {
888
1518
  process.exitCode = 1;
889
1519
  }
890
1520
  } catch (error) {
1521
+ logger.error("cli.command.error", {
1522
+ duration_ms: durationMs2(startedAt),
1523
+ error
1524
+ });
891
1525
  outputError(error, { json: Boolean(options.json) });
892
1526
  process.exitCode = 1;
893
1527
  }
@@ -971,6 +1605,149 @@ var registerDiagnosticsCommands = (program2) => {
971
1605
  });
972
1606
  };
973
1607
 
1608
+ // packages/cli/src/open-path.ts
1609
+ var import_node_child_process2 = require("node:child_process");
1610
+ var openPath = async (target) => {
1611
+ const platform = process.platform;
1612
+ if (platform === "darwin") {
1613
+ const child2 = (0, import_node_child_process2.spawn)("open", [target], { detached: true, stdio: "ignore" });
1614
+ child2.unref();
1615
+ return;
1616
+ }
1617
+ if (platform === "win32") {
1618
+ const child2 = (0, import_node_child_process2.spawn)("cmd", ["/c", "start", "", target], {
1619
+ detached: true,
1620
+ stdio: "ignore"
1621
+ });
1622
+ child2.unref();
1623
+ return;
1624
+ }
1625
+ const child = (0, import_node_child_process2.spawn)("xdg-open", [target], {
1626
+ detached: true,
1627
+ stdio: "ignore"
1628
+ });
1629
+ child.unref();
1630
+ };
1631
+
1632
+ // packages/cli/src/commands/dev.ts
1633
+ var ENV_EXTENSION_ID = "BROWSER_BRIDGE_EXTENSION_ID";
1634
+ var ACTIVATION_FLAG_PARAM = "bb_activate";
1635
+ var ACTIVATION_PORT_PARAM = "corePort";
1636
+ var ACTIVATION_WORKTREE_PARAM = "worktreeId";
1637
+ var normalizeToken = (value) => {
1638
+ if (typeof value !== "string") {
1639
+ return void 0;
1640
+ }
1641
+ const trimmed = value.trim();
1642
+ return trimmed.length > 0 ? trimmed : void 0;
1643
+ };
1644
+ var resolveActivationExtensionId = (options) => {
1645
+ const optionId = normalizeToken(options.optionExtensionId);
1646
+ if (optionId) {
1647
+ return { extensionId: optionId, source: "flag" };
1648
+ }
1649
+ const envId = normalizeToken(options.envExtensionId);
1650
+ if (envId) {
1651
+ return { extensionId: envId, source: "env" };
1652
+ }
1653
+ const metadataId = normalizeToken(options.metadataExtensionId);
1654
+ if (metadataId) {
1655
+ return { extensionId: metadataId, source: "metadata" };
1656
+ }
1657
+ return null;
1658
+ };
1659
+ var buildActivationOptionsUrl = (options) => {
1660
+ const search = new URLSearchParams();
1661
+ search.set(ACTIVATION_FLAG_PARAM, "1");
1662
+ search.set(ACTIVATION_PORT_PARAM, String(options.corePort));
1663
+ if (options.worktreeId) {
1664
+ search.set(ACTIVATION_WORKTREE_PARAM, options.worktreeId);
1665
+ }
1666
+ return `chrome-extension://${options.extensionId}/options.html?${search.toString()}`;
1667
+ };
1668
+ var buildPersistedRuntimeMetadata = (runtime, extensionId) => ({
1669
+ host: runtime.host,
1670
+ port: runtime.port,
1671
+ git_root: runtime.gitRoot ?? runtime.metadata?.git_root,
1672
+ worktree_id: runtime.worktreeId ?? runtime.metadata?.worktree_id,
1673
+ extension_id: extensionId,
1674
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1675
+ });
1676
+ var resolveRuntimeForCommand = (options) => resolveCoreRuntime({
1677
+ host: options.host,
1678
+ port: options.port,
1679
+ strictEnvPort: true
1680
+ });
1681
+ var registerDevCommands = (program2) => {
1682
+ const dev = program2.command("dev").description("Development commands");
1683
+ dev.command("info").description("Print resolved runtime details for the current worktree").action(async (_options, command) => {
1684
+ await runLocal(command, async (globalOptions) => {
1685
+ const runtime = resolveRuntimeForCommand(globalOptions);
1686
+ const logDir = resolveLogDirectory({ gitRoot: runtime.gitRoot });
1687
+ return {
1688
+ ok: true,
1689
+ result: {
1690
+ host: runtime.host,
1691
+ hostSource: runtime.hostSource,
1692
+ port: runtime.port,
1693
+ portSource: runtime.portSource,
1694
+ deterministicPort: runtime.deterministicPort,
1695
+ worktreeId: runtime.worktreeId,
1696
+ metadataPath: runtime.metadataPath,
1697
+ logDir,
1698
+ metadataSnapshot: runtime.metadata
1699
+ }
1700
+ };
1701
+ });
1702
+ });
1703
+ dev.command("activate").description(
1704
+ "Persist worktree runtime metadata and open extension options for activation"
1705
+ ).option(
1706
+ "--extension-id <id>",
1707
+ "Chrome extension id to activate for this worktree"
1708
+ ).action(async (options, command) => {
1709
+ await runLocal(command, async (globalOptions) => {
1710
+ const runtime = resolveRuntimeForCommand(globalOptions);
1711
+ const extension = resolveActivationExtensionId({
1712
+ optionExtensionId: options.extensionId,
1713
+ envExtensionId: process.env[ENV_EXTENSION_ID],
1714
+ metadataExtensionId: runtime.metadata?.extension_id
1715
+ });
1716
+ if (!extension) {
1717
+ throw new CliError({
1718
+ code: "INVALID_ARGUMENT",
1719
+ message: "Missing extension id. Provide --extension-id <id>, set BROWSER_BRIDGE_EXTENSION_ID, or persist extension_id in metadata by running dev activate with --extension-id once.",
1720
+ retryable: false,
1721
+ details: {
1722
+ metadataPath: runtime.metadataPath
1723
+ }
1724
+ });
1725
+ }
1726
+ const metadataPath = writeRuntimeMetadata(
1727
+ buildPersistedRuntimeMetadata(runtime, extension.extensionId),
1728
+ { metadataPath: runtime.metadataPath }
1729
+ );
1730
+ const activationUrl = buildActivationOptionsUrl({
1731
+ extensionId: extension.extensionId,
1732
+ corePort: runtime.port,
1733
+ worktreeId: runtime.worktreeId
1734
+ });
1735
+ await openPath(activationUrl);
1736
+ return {
1737
+ ok: true,
1738
+ result: {
1739
+ extensionId: extension.extensionId,
1740
+ extensionIdSource: extension.source,
1741
+ host: runtime.host,
1742
+ port: runtime.port,
1743
+ metadataPath,
1744
+ activationUrl
1745
+ }
1746
+ };
1747
+ });
1748
+ });
1749
+ };
1750
+
974
1751
  // packages/cli/src/locator.ts
975
1752
  var buildLocator = (options) => {
976
1753
  const locator = {};
@@ -1418,39 +2195,36 @@ var registerInspectCommands = (program2) => {
1418
2195
  };
1419
2196
 
1420
2197
  // packages/mcp-adapter/src/core-client.ts
1421
- var DEFAULT_HOST2 = "127.0.0.1";
1422
- var DEFAULT_PORT2 = 3210;
1423
2198
  var DEFAULT_TIMEOUT_MS2 = 3e4;
1424
- var resolveHost2 = (host) => {
1425
- const candidate = host?.trim() || process.env.BROWSER_BRIDGE_CORE_HOST || process.env.BROWSER_VISION_CORE_HOST;
1426
- if (candidate && candidate.length > 0) {
1427
- return candidate;
1428
- }
1429
- return DEFAULT_HOST2;
1430
- };
1431
- var resolvePort2 = (port) => {
1432
- const candidate = port ?? (process.env.BROWSER_BRIDGE_CORE_PORT ? Number.parseInt(process.env.BROWSER_BRIDGE_CORE_PORT, 10) : process.env.BROWSER_VISION_CORE_PORT ? Number.parseInt(process.env.BROWSER_VISION_CORE_PORT, 10) : void 0);
1433
- if (candidate === void 0 || candidate === null) {
1434
- return DEFAULT_PORT2;
1435
- }
1436
- const parsed = typeof candidate === "number" ? candidate : Number.parseInt(candidate, 10);
1437
- if (!Number.isFinite(parsed) || parsed <= 0) {
1438
- throw new Error(`Invalid port: ${String(candidate)}`);
1439
- }
1440
- return parsed;
1441
- };
1442
2199
  var normalizePath2 = (path9) => path9.startsWith("/") ? path9 : `/${path9}`;
2200
+ var durationMs3 = (startedAt) => Number((Number(process.hrtime.bigint() - startedAt) / 1e6).toFixed(3));
1443
2201
  var createCoreClient2 = (options = {}) => {
1444
- const host = resolveHost2(options.host);
1445
- const port = resolvePort2(options.port);
2202
+ const logger = options.logger ?? createJsonlLogger({
2203
+ stream: "mcp-adapter",
2204
+ cwd: options.cwd
2205
+ }).child({ scope: "core-client" });
2206
+ const runtime = resolveCoreRuntime({
2207
+ host: options.host,
2208
+ port: options.port,
2209
+ cwd: options.cwd,
2210
+ strictEnvPort: true
2211
+ });
2212
+ const host = runtime.host;
2213
+ const port = runtime.port;
1446
2214
  const baseUrl = `http://${host}:${port}`;
1447
2215
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
1448
2216
  const fetchImpl = options.fetchImpl ?? fetch;
1449
2217
  const requestJson = async (path9, body) => {
2218
+ const requestPath = normalizePath2(path9);
2219
+ const startedAt = process.hrtime.bigint();
2220
+ logger.debug("mcp.core.request.start", {
2221
+ path: requestPath,
2222
+ base_url: baseUrl
2223
+ });
1450
2224
  const controller = new AbortController();
1451
2225
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
1452
2226
  try {
1453
- const response = await fetchImpl(`${baseUrl}${normalizePath2(path9)}`, {
2227
+ const response = await fetchImpl(`${baseUrl}${requestPath}`, {
1454
2228
  method: "POST",
1455
2229
  headers: {
1456
2230
  "content-type": "application/json"
@@ -1460,14 +2234,42 @@ var createCoreClient2 = (options = {}) => {
1460
2234
  });
1461
2235
  const raw = await response.text();
1462
2236
  if (!raw) {
2237
+ logger.warn("mcp.core.request.empty_response", {
2238
+ path: requestPath,
2239
+ base_url: baseUrl,
2240
+ status: response.status,
2241
+ duration_ms: durationMs3(startedAt)
2242
+ });
1463
2243
  throw new Error(`Empty response from Core (${response.status}).`);
1464
2244
  }
1465
2245
  try {
1466
- return JSON.parse(raw);
2246
+ const parsed = JSON.parse(raw);
2247
+ logger.debug("mcp.core.request.end", {
2248
+ path: requestPath,
2249
+ base_url: baseUrl,
2250
+ status: response.status,
2251
+ duration_ms: durationMs3(startedAt)
2252
+ });
2253
+ return parsed;
1467
2254
  } catch (error) {
1468
2255
  const message = error instanceof Error ? error.message : "Unknown JSON parse error";
2256
+ logger.error("mcp.core.request.invalid_json", {
2257
+ path: requestPath,
2258
+ base_url: baseUrl,
2259
+ status: response.status,
2260
+ duration_ms: durationMs3(startedAt),
2261
+ error
2262
+ });
1469
2263
  throw new Error(`Failed to parse Core response: ${message}`);
1470
2264
  }
2265
+ } catch (error) {
2266
+ logger.error("mcp.core.request.failed", {
2267
+ path: requestPath,
2268
+ base_url: baseUrl,
2269
+ duration_ms: durationMs3(startedAt),
2270
+ error
2271
+ });
2272
+ throw error;
1471
2273
  } finally {
1472
2274
  clearTimeout(timeout);
1473
2275
  }
@@ -1913,25 +2715,57 @@ var DEFAULT_SERVER_NAME = "browser-bridge";
1913
2715
  var DEFAULT_SERVER_VERSION = "0.0.0";
1914
2716
  var DEFAULT_HTTP_HOST = "127.0.0.1";
1915
2717
  var DEFAULT_HTTP_PATH = "/mcp";
2718
+ var durationMs4 = (startedAt) => Number((Number(process.hrtime.bigint() - startedAt) / 1e6).toFixed(3));
2719
+ var resolveAdapterLogger = (options) => options.logger ?? createJsonlLogger({
2720
+ stream: "mcp-adapter",
2721
+ cwd: options.cwd
2722
+ });
1916
2723
  var createMcpServer = (options = {}) => {
2724
+ const logger = resolveAdapterLogger(options);
1917
2725
  const server = new import_mcp.McpServer({
1918
2726
  name: options.name ?? DEFAULT_SERVER_NAME,
1919
2727
  version: options.version ?? DEFAULT_SERVER_VERSION
1920
2728
  });
1921
- const client = options.coreClient ?? createCoreClient2(options);
2729
+ const client = options.coreClient ?? createCoreClient2({
2730
+ ...options,
2731
+ logger: logger.child({ scope: "core-client" })
2732
+ });
1922
2733
  registerBrowserBridgeTools(server, client);
2734
+ logger.info("mcp.server.created", {
2735
+ name: options.name ?? DEFAULT_SERVER_NAME,
2736
+ version: options.version ?? DEFAULT_SERVER_VERSION,
2737
+ core_base_url: client.baseUrl
2738
+ });
1923
2739
  return { server, client };
1924
2740
  };
1925
2741
  var startMcpServer = async (options = {}) => {
1926
- const handle = createMcpServer(options);
2742
+ const logger = resolveAdapterLogger(options);
2743
+ logger.info("mcp.stdio.start.begin", {
2744
+ name: options.name ?? DEFAULT_SERVER_NAME,
2745
+ version: options.version ?? DEFAULT_SERVER_VERSION
2746
+ });
2747
+ const handle = createMcpServer({
2748
+ ...options,
2749
+ logger
2750
+ });
1927
2751
  const transport = new import_stdio.StdioServerTransport();
1928
- await handle.server.connect(transport);
2752
+ try {
2753
+ await handle.server.connect(transport);
2754
+ logger.info("mcp.stdio.start.ready", {
2755
+ core_base_url: handle.client.baseUrl
2756
+ });
2757
+ } catch (error) {
2758
+ logger.error("mcp.stdio.start.failed", {
2759
+ error
2760
+ });
2761
+ throw error;
2762
+ }
1929
2763
  return { ...handle, transport };
1930
2764
  };
1931
2765
  var readJsonBody = async (req, maxBytes = 5 * 1024 * 1024) => {
1932
2766
  const chunks = [];
1933
2767
  let total = 0;
1934
- await new Promise((resolve2, reject) => {
2768
+ await new Promise((resolve3, reject) => {
1935
2769
  req.on("data", (chunk) => {
1936
2770
  total += chunk.length;
1937
2771
  if (total > maxBytes) {
@@ -1941,7 +2775,7 @@ var readJsonBody = async (req, maxBytes = 5 * 1024 * 1024) => {
1941
2775
  }
1942
2776
  chunks.push(chunk);
1943
2777
  });
1944
- req.on("end", () => resolve2());
2778
+ req.on("end", () => resolve3());
1945
2779
  req.on("error", (err) => reject(err));
1946
2780
  });
1947
2781
  const raw = Buffer.concat(chunks).toString("utf8");
@@ -1960,10 +2794,19 @@ var getHeaderValue = (value) => {
1960
2794
  return void 0;
1961
2795
  };
1962
2796
  var startMcpHttpServer = async (options = {}) => {
2797
+ const logger = resolveAdapterLogger(options).child({ scope: "http-server" });
1963
2798
  const host = options.host ?? DEFAULT_HTTP_HOST;
1964
2799
  const port = typeof options.port === "number" ? options.port : 0;
1965
2800
  const path9 = options.path ?? DEFAULT_HTTP_PATH;
1966
- const client = options.coreClient ?? createCoreClient2(options);
2801
+ logger.info("mcp.http.start.begin", {
2802
+ host,
2803
+ port,
2804
+ path: path9
2805
+ });
2806
+ const client = options.coreClient ?? createCoreClient2({
2807
+ ...options,
2808
+ logger: logger.child({ scope: "core-client" })
2809
+ });
1967
2810
  const sessions = /* @__PURE__ */ new Map();
1968
2811
  const closeAllSessions = async () => {
1969
2812
  const entries = Array.from(sessions.values());
@@ -1982,23 +2825,52 @@ var startMcpHttpServer = async (options = {}) => {
1982
2825
  );
1983
2826
  };
1984
2827
  const httpServer = (0, import_node_http.createServer)(async (req, res) => {
2828
+ const startedAt = process.hrtime.bigint();
2829
+ const requestLogger = logger.child({
2830
+ scope: "http-request",
2831
+ request_id: (0, import_node_crypto.randomUUID)()
2832
+ });
2833
+ requestLogger.debug("mcp.http.request.start", {
2834
+ method: req.method,
2835
+ url: req.url ?? ""
2836
+ });
1985
2837
  try {
1986
2838
  const url = new URL(req.url ?? "", `http://${host}`);
1987
2839
  if (url.pathname !== path9) {
1988
2840
  res.writeHead(404, { "content-type": "application/json" });
1989
2841
  res.end(JSON.stringify({ error: "Not Found" }));
2842
+ requestLogger.warn("mcp.http.request.not_found", {
2843
+ method: req.method,
2844
+ pathname: url.pathname,
2845
+ expected_path: path9,
2846
+ status: 404,
2847
+ duration_ms: durationMs4(startedAt)
2848
+ });
1990
2849
  return;
1991
2850
  }
1992
2851
  const sessionId = getHeaderValue(req.headers["mcp-session-id"]);
2852
+ requestLogger.debug("mcp.http.request.session", {
2853
+ session_id: sessionId ?? null
2854
+ });
1993
2855
  const parsedBody = req.method === "POST" ? await readJsonBody(req) : void 0;
1994
2856
  if (sessionId) {
1995
2857
  const entry = sessions.get(sessionId);
1996
2858
  if (!entry) {
1997
2859
  res.writeHead(404, { "content-type": "application/json" });
1998
2860
  res.end(JSON.stringify({ error: "Unknown session." }));
2861
+ requestLogger.warn("mcp.http.request.unknown_session", {
2862
+ session_id: sessionId,
2863
+ status: 404,
2864
+ duration_ms: durationMs4(startedAt)
2865
+ });
1999
2866
  return;
2000
2867
  }
2001
2868
  await entry.transport.handleRequest(req, res, parsedBody);
2869
+ requestLogger.info("mcp.http.request.forwarded", {
2870
+ session_id: sessionId,
2871
+ status: res.statusCode,
2872
+ duration_ms: durationMs4(startedAt)
2873
+ });
2002
2874
  return;
2003
2875
  }
2004
2876
  if (req.method !== "POST" || !(0, import_types.isInitializeRequest)(parsedBody)) {
@@ -2008,6 +2880,11 @@ var startMcpHttpServer = async (options = {}) => {
2008
2880
  error: "Missing mcp-session-id header. First request must be initialize."
2009
2881
  })
2010
2882
  );
2883
+ requestLogger.warn("mcp.http.request.invalid_initialize", {
2884
+ method: req.method,
2885
+ status: 400,
2886
+ duration_ms: durationMs4(startedAt)
2887
+ });
2011
2888
  return;
2012
2889
  }
2013
2890
  let sessionEntry;
@@ -2016,6 +2893,9 @@ var startMcpHttpServer = async (options = {}) => {
2016
2893
  onsessioninitialized: (sid) => {
2017
2894
  if (sessionEntry) {
2018
2895
  sessions.set(sid, sessionEntry);
2896
+ logger.info("mcp.http.session.opened", {
2897
+ session_id: sid
2898
+ });
2019
2899
  }
2020
2900
  },
2021
2901
  onsessionclosed: async (sid) => {
@@ -2028,6 +2908,9 @@ var startMcpHttpServer = async (options = {}) => {
2028
2908
  entry.transport.close(),
2029
2909
  entry.server.close()
2030
2910
  ]);
2911
+ logger.info("mcp.http.session.closed", {
2912
+ session_id: sid
2913
+ });
2031
2914
  }
2032
2915
  });
2033
2916
  const sessionServer = new import_mcp.McpServer({
@@ -2038,7 +2921,18 @@ var startMcpHttpServer = async (options = {}) => {
2038
2921
  await sessionServer.connect(transport);
2039
2922
  sessionEntry = { transport, server: sessionServer };
2040
2923
  await transport.handleRequest(req, res, parsedBody);
2924
+ requestLogger.info("mcp.http.request.initialized", {
2925
+ status: res.statusCode,
2926
+ duration_ms: durationMs4(startedAt)
2927
+ });
2041
2928
  } catch (error) {
2929
+ requestLogger.error("mcp.http.request.error", {
2930
+ method: req.method,
2931
+ url: req.url ?? "",
2932
+ status: 500,
2933
+ duration_ms: durationMs4(startedAt),
2934
+ error
2935
+ });
2042
2936
  res.writeHead(500, { "content-type": "application/json" });
2043
2937
  res.end(
2044
2938
  JSON.stringify({
@@ -2047,29 +2941,43 @@ var startMcpHttpServer = async (options = {}) => {
2047
2941
  );
2048
2942
  }
2049
2943
  });
2050
- const resolvedPort = await new Promise((resolve2, reject) => {
2944
+ const resolvedPort = await new Promise((resolve3, reject) => {
2051
2945
  httpServer.listen(port, host, () => {
2052
2946
  const address = httpServer.address();
2053
- resolve2(typeof address === "object" && address ? address.port : port);
2947
+ resolve3(typeof address === "object" && address ? address.port : port);
2054
2948
  });
2055
2949
  httpServer.on("error", reject);
2056
2950
  });
2951
+ logger.info("mcp.http.start.ready", {
2952
+ host,
2953
+ port: resolvedPort,
2954
+ path: path9,
2955
+ core_base_url: client.baseUrl
2956
+ });
2057
2957
  return {
2058
2958
  client,
2059
2959
  host,
2060
2960
  port: resolvedPort,
2061
2961
  path: path9,
2062
2962
  close: async () => {
2063
- await new Promise((resolve2, reject) => {
2963
+ logger.info("mcp.http.stop.begin", {
2964
+ host,
2965
+ port: resolvedPort
2966
+ });
2967
+ await new Promise((resolve3, reject) => {
2064
2968
  httpServer.close((err) => {
2065
2969
  if (err) {
2066
2970
  reject(err);
2067
2971
  } else {
2068
- resolve2();
2972
+ resolve3();
2069
2973
  }
2070
2974
  });
2071
2975
  });
2072
2976
  await closeAllSessions();
2977
+ logger.info("mcp.http.stop.complete", {
2978
+ host,
2979
+ port: resolvedPort
2980
+ });
2073
2981
  }
2074
2982
  };
2075
2983
  };
@@ -2098,19 +3006,19 @@ var checkboxPrompt = async (options) => {
2098
3006
  };
2099
3007
 
2100
3008
  // packages/cli/src/installer/mcp-install.ts
2101
- var import_node_child_process2 = require("node:child_process");
3009
+ var import_node_child_process3 = require("node:child_process");
2102
3010
 
2103
3011
  // packages/cli/src/installer/cursor-mcp.ts
2104
3012
  var import_promises2 = __toESM(require("node:fs/promises"));
2105
3013
  var import_node_os = __toESM(require("node:os"));
2106
- var import_node_path2 = __toESM(require("node:path"));
3014
+ var import_node_path4 = __toESM(require("node:path"));
2107
3015
  var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2108
3016
  var resolveCursorUserSettingsPath = (options) => {
2109
3017
  const platform = options?.platform ?? process.platform;
2110
3018
  const homeDir = options?.homeDir ?? import_node_os.default.homedir();
2111
3019
  const env = options?.env ?? process.env;
2112
3020
  if (platform === "darwin") {
2113
- return import_node_path2.default.join(
3021
+ return import_node_path4.default.join(
2114
3022
  homeDir,
2115
3023
  "Library",
2116
3024
  "Application Support",
@@ -2126,13 +3034,13 @@ var resolveCursorUserSettingsPath = (options) => {
2126
3034
  "APPDATA is not set; cannot resolve Cursor settings path."
2127
3035
  );
2128
3036
  }
2129
- return import_node_path2.default.join(appData, "Cursor", "User", "settings.json");
3037
+ return import_node_path4.default.join(appData, "Cursor", "User", "settings.json");
2130
3038
  }
2131
- return import_node_path2.default.join(homeDir, ".config", "Cursor", "User", "settings.json");
3039
+ return import_node_path4.default.join(homeDir, ".config", "Cursor", "User", "settings.json");
2132
3040
  };
2133
3041
  var installCursorMcp = async (settingsPath) => {
2134
3042
  const filePath = settingsPath ?? resolveCursorUserSettingsPath();
2135
- const dir = import_node_path2.default.dirname(filePath);
3043
+ const dir = import_node_path4.default.dirname(filePath);
2136
3044
  await import_promises2.default.mkdir(dir, { recursive: true });
2137
3045
  let settings = {};
2138
3046
  try {
@@ -2168,15 +3076,15 @@ var installCursorMcp = async (settingsPath) => {
2168
3076
 
2169
3077
  // packages/cli/src/installer/mcp-install.ts
2170
3078
  var runQuiet = async (cmd, args) => {
2171
- await new Promise((resolve2, reject) => {
2172
- const child = (0, import_node_child_process2.spawn)(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
3079
+ await new Promise((resolve3, reject) => {
3080
+ const child = (0, import_node_child_process3.spawn)(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
2173
3081
  let stderr = "";
2174
3082
  child.stderr?.on("data", (chunk) => {
2175
3083
  stderr += String(chunk);
2176
3084
  });
2177
3085
  child.on("error", reject);
2178
3086
  child.on("exit", (code) => {
2179
- if (code === 0) resolve2();
3087
+ if (code === 0) resolve3();
2180
3088
  else {
2181
3089
  const suffix = stderr.trim() ? `: ${stderr.trim()}` : "";
2182
3090
  reject(new Error(`${cmd} exited with ${code ?? "unknown"}${suffix}`));
@@ -2344,10 +3252,10 @@ var import_turndown = __toESM(require("turndown"));
2344
3252
  // packages/core/src/artifacts.ts
2345
3253
  var import_promises3 = require("node:fs/promises");
2346
3254
  var import_node_os2 = __toESM(require("node:os"));
2347
- var import_node_path3 = __toESM(require("node:path"));
3255
+ var import_node_path5 = __toESM(require("node:path"));
2348
3256
  var ARTIFACTS_DIR_NAME = "browser-agent";
2349
3257
  var resolveTempRoot = () => process.env.TMPDIR || process.env.TEMP || process.env.TMP || import_node_os2.default.tmpdir();
2350
- var getArtifactRootDir = (sessionId) => import_node_path3.default.join(resolveTempRoot(), ARTIFACTS_DIR_NAME, sessionId);
3258
+ var getArtifactRootDir = (sessionId) => import_node_path5.default.join(resolveTempRoot(), ARTIFACTS_DIR_NAME, sessionId);
2351
3259
  var ensureArtifactRootDir = async (sessionId) => {
2352
3260
  const rootDir = getArtifactRootDir(sessionId);
2353
3261
  await (0, import_promises3.mkdir)(rootDir, { recursive: true });
@@ -2482,30 +3390,6 @@ var PROCESS_STARTED_AT = new Date(
2482
3390
  Date.now() - Math.floor(process.uptime() * 1e3)
2483
3391
  ).toISOString();
2484
3392
 
2485
- // packages/cli/src/open-path.ts
2486
- var import_node_child_process3 = require("node:child_process");
2487
- var openPath = async (target) => {
2488
- const platform = process.platform;
2489
- if (platform === "darwin") {
2490
- const child2 = (0, import_node_child_process3.spawn)("open", [target], { detached: true, stdio: "ignore" });
2491
- child2.unref();
2492
- return;
2493
- }
2494
- if (platform === "win32") {
2495
- const child2 = (0, import_node_child_process3.spawn)("cmd", ["/c", "start", "", target], {
2496
- detached: true,
2497
- stdio: "ignore"
2498
- });
2499
- child2.unref();
2500
- return;
2501
- }
2502
- const child = (0, import_node_child_process3.spawn)("xdg-open", [target], {
2503
- detached: true,
2504
- stdio: "ignore"
2505
- });
2506
- child.unref();
2507
- };
2508
-
2509
3393
  // packages/cli/src/commands/open-artifacts.ts
2510
3394
  var registerOpenArtifactsCommand = (program2) => {
2511
3395
  program2.command("open-artifacts").description("Open the artifact folder for a session").requiredOption("--session-id <id>", "Session identifier").action(async (options, command) => {
@@ -2558,60 +3442,60 @@ var registerSessionCommands = (program2) => {
2558
3442
  // packages/cli/src/commands/skill.ts
2559
3443
  var import_promises7 = __toESM(require("node:fs/promises"));
2560
3444
  var import_node_os4 = __toESM(require("node:os"));
2561
- var import_node_path8 = __toESM(require("node:path"));
3445
+ var import_node_path10 = __toESM(require("node:path"));
2562
3446
 
2563
3447
  // packages/cli/src/installer/harness-targets.ts
2564
3448
  var import_node_os3 = __toESM(require("node:os"));
2565
- var import_node_path4 = __toESM(require("node:path"));
3449
+ var import_node_path6 = __toESM(require("node:path"));
2566
3450
  var getDefaultHarnessTargets = (homeDir) => {
2567
3451
  const home = homeDir ?? import_node_os3.default.homedir();
2568
3452
  return [
2569
3453
  {
2570
3454
  id: "codex",
2571
3455
  label: "Codex",
2572
- skillsDir: import_node_path4.default.join(home, ".agents", "skills"),
3456
+ skillsDir: import_node_path6.default.join(home, ".agents", "skills"),
2573
3457
  supportsMcpInstall: true
2574
3458
  },
2575
3459
  {
2576
3460
  id: "claude",
2577
3461
  label: "Claude",
2578
- skillsDir: import_node_path4.default.join(home, ".claude", "skills"),
3462
+ skillsDir: import_node_path6.default.join(home, ".claude", "skills"),
2579
3463
  supportsMcpInstall: true
2580
3464
  },
2581
3465
  {
2582
3466
  id: "cursor",
2583
3467
  label: "Cursor",
2584
- skillsDir: import_node_path4.default.join(home, ".cursor", "skills"),
3468
+ skillsDir: import_node_path6.default.join(home, ".cursor", "skills"),
2585
3469
  supportsMcpInstall: true
2586
3470
  },
2587
3471
  {
2588
3472
  id: "factory",
2589
3473
  label: "Factory",
2590
- skillsDir: import_node_path4.default.join(home, ".factory", "skills"),
3474
+ skillsDir: import_node_path6.default.join(home, ".factory", "skills"),
2591
3475
  supportsMcpInstall: false
2592
3476
  },
2593
3477
  {
2594
3478
  id: "opencode",
2595
3479
  label: "OpenCode",
2596
- skillsDir: import_node_path4.default.join(home, ".opencode", "skills"),
3480
+ skillsDir: import_node_path6.default.join(home, ".opencode", "skills"),
2597
3481
  supportsMcpInstall: false
2598
3482
  },
2599
3483
  {
2600
3484
  id: "gemini",
2601
3485
  label: "Gemini",
2602
- skillsDir: import_node_path4.default.join(home, ".gemini", "skills"),
3486
+ skillsDir: import_node_path6.default.join(home, ".gemini", "skills"),
2603
3487
  supportsMcpInstall: false
2604
3488
  },
2605
3489
  {
2606
3490
  id: "github",
2607
3491
  label: "GitHub",
2608
- skillsDir: import_node_path4.default.join(home, ".github", "skills"),
3492
+ skillsDir: import_node_path6.default.join(home, ".github", "skills"),
2609
3493
  supportsMcpInstall: false
2610
3494
  },
2611
3495
  {
2612
3496
  id: "ampcode",
2613
3497
  label: "Ampcode",
2614
- skillsDir: import_node_path4.default.join(home, ".ampcode", "skills"),
3498
+ skillsDir: import_node_path6.default.join(home, ".ampcode", "skills"),
2615
3499
  supportsMcpInstall: false
2616
3500
  }
2617
3501
  ];
@@ -2619,7 +3503,7 @@ var getDefaultHarnessTargets = (homeDir) => {
2619
3503
 
2620
3504
  // packages/cli/src/installer/package-info.ts
2621
3505
  var import_promises4 = __toESM(require("node:fs/promises"));
2622
- var import_node_path5 = __toESM(require("node:path"));
3506
+ var import_node_path7 = __toESM(require("node:path"));
2623
3507
  var PACKAGE_NAME = "@btraut/browser-bridge";
2624
3508
  var tryReadJson = async (filePath) => {
2625
3509
  try {
@@ -2632,12 +3516,12 @@ var tryReadJson = async (filePath) => {
2632
3516
  var resolveCliPackageRootDir = async () => {
2633
3517
  let dir = __dirname;
2634
3518
  for (let i = 0; i < 12; i++) {
2635
- const candidate = import_node_path5.default.join(dir, "package.json");
3519
+ const candidate = import_node_path7.default.join(dir, "package.json");
2636
3520
  const parsed = await tryReadJson(candidate);
2637
3521
  if (parsed && parsed.name === PACKAGE_NAME) {
2638
3522
  return dir;
2639
3523
  }
2640
- const parent = import_node_path5.default.dirname(dir);
3524
+ const parent = import_node_path7.default.dirname(dir);
2641
3525
  if (parent === dir) {
2642
3526
  break;
2643
3527
  }
@@ -2649,7 +3533,7 @@ var resolveCliPackageRootDir = async () => {
2649
3533
  };
2650
3534
  var readCliPackageVersion = async () => {
2651
3535
  const rootDir = await resolveCliPackageRootDir();
2652
- const pkgPath = import_node_path5.default.join(rootDir, "package.json");
3536
+ const pkgPath = import_node_path7.default.join(rootDir, "package.json");
2653
3537
  const parsed = await tryReadJson(pkgPath);
2654
3538
  if (!parsed || typeof parsed.version !== "string" || !parsed.version) {
2655
3539
  throw new Error(`Unable to read version from ${pkgPath}`);
@@ -2658,14 +3542,14 @@ var readCliPackageVersion = async () => {
2658
3542
  };
2659
3543
  var resolveSkillSourceDir = async () => {
2660
3544
  const rootDir = await resolveCliPackageRootDir();
2661
- const packaged = import_node_path5.default.join(rootDir, "skills", "browser-bridge");
3545
+ const packaged = import_node_path7.default.join(rootDir, "skills", "browser-bridge");
2662
3546
  try {
2663
3547
  await import_promises4.default.stat(packaged);
2664
3548
  return packaged;
2665
3549
  } catch {
2666
3550
  }
2667
- const repoRoot = import_node_path5.default.resolve(rootDir, "..", "..");
2668
- const docsSkill = import_node_path5.default.join(repoRoot, "docs", "skills", "browser-bridge");
3551
+ const repoRoot = import_node_path7.default.resolve(rootDir, "..", "..");
3552
+ const docsSkill = import_node_path7.default.join(repoRoot, "docs", "skills", "browser-bridge");
2669
3553
  try {
2670
3554
  await import_promises4.default.stat(docsSkill);
2671
3555
  return docsSkill;
@@ -2678,16 +3562,16 @@ var resolveSkillSourceDir = async () => {
2678
3562
 
2679
3563
  // packages/cli/src/installer/skill-install.ts
2680
3564
  var import_promises6 = __toESM(require("node:fs/promises"));
2681
- var import_node_path7 = __toESM(require("node:path"));
3565
+ var import_node_path9 = __toESM(require("node:path"));
2682
3566
 
2683
3567
  // packages/cli/src/installer/skill-manifest.ts
2684
3568
  var import_promises5 = __toESM(require("node:fs/promises"));
2685
- var import_node_path6 = __toESM(require("node:path"));
3569
+ var import_node_path8 = __toESM(require("node:path"));
2686
3570
  var SKILL_MANIFEST_FILENAME = "skill.json";
2687
3571
  var readSkillManifest = async (skillDir) => {
2688
3572
  try {
2689
3573
  const raw = await import_promises5.default.readFile(
2690
- import_node_path6.default.join(skillDir, SKILL_MANIFEST_FILENAME),
3574
+ import_node_path8.default.join(skillDir, SKILL_MANIFEST_FILENAME),
2691
3575
  "utf8"
2692
3576
  );
2693
3577
  const parsed = JSON.parse(raw);
@@ -2702,7 +3586,7 @@ var readSkillManifest = async (skillDir) => {
2702
3586
  var writeSkillManifest = async (skillDir, version) => {
2703
3587
  const payload = { name: "browser-bridge", version };
2704
3588
  await import_promises5.default.writeFile(
2705
- import_node_path6.default.join(skillDir, SKILL_MANIFEST_FILENAME),
3589
+ import_node_path8.default.join(skillDir, SKILL_MANIFEST_FILENAME),
2706
3590
  JSON.stringify(payload, null, 2) + "\n",
2707
3591
  "utf8"
2708
3592
  );
@@ -2710,7 +3594,7 @@ var writeSkillManifest = async (skillDir, version) => {
2710
3594
 
2711
3595
  // packages/cli/src/installer/skill-install.ts
2712
3596
  var installBrowserBridgeSkill = async (options) => {
2713
- const destDir = import_node_path7.default.join(options.destSkillsDir, "browser-bridge");
3597
+ const destDir = import_node_path9.default.join(options.destSkillsDir, "browser-bridge");
2714
3598
  await import_promises6.default.mkdir(options.destSkillsDir, { recursive: true });
2715
3599
  await import_promises6.default.rm(destDir, { recursive: true, force: true });
2716
3600
  await import_promises6.default.cp(options.srcSkillDir, destDir, { recursive: true });
@@ -2722,9 +3606,9 @@ var installBrowserBridgeSkill = async (options) => {
2722
3606
  var getHarnessMarkerDir = (homeDir, harness) => {
2723
3607
  switch (harness) {
2724
3608
  case "codex":
2725
- return import_node_path8.default.join(homeDir, ".agents");
3609
+ return import_node_path10.default.join(homeDir, ".agents");
2726
3610
  default:
2727
- return import_node_path8.default.join(homeDir, `.${harness}`);
3611
+ return import_node_path10.default.join(homeDir, `.${harness}`);
2728
3612
  }
2729
3613
  };
2730
3614
  var registerSkillCommands = (program2) => {
@@ -2802,7 +3686,7 @@ var registerSkillCommands = (program2) => {
2802
3686
  const targets = getDefaultHarnessTargets();
2803
3687
  const rows = [];
2804
3688
  for (const t of targets) {
2805
- const skillDir = import_node_path8.default.join(t.skillsDir, "browser-bridge");
3689
+ const skillDir = import_node_path10.default.join(t.skillsDir, "browser-bridge");
2806
3690
  let installed = false;
2807
3691
  try {
2808
3692
  await import_promises7.default.stat(skillDir);
@@ -2830,7 +3714,7 @@ var registerSkillCommands = (program2) => {
2830
3714
  // packages/cli/src/commands/install.ts
2831
3715
  var import_promises8 = __toESM(require("node:fs/promises"));
2832
3716
  var import_node_os5 = __toESM(require("node:os"));
2833
- var import_node_path9 = __toESM(require("node:path"));
3717
+ var import_node_path11 = __toESM(require("node:path"));
2834
3718
  var formatInstallSummary = (options) => {
2835
3719
  const wantsSkill = options.setup.includes("skill");
2836
3720
  const wantsMcp = options.setup.includes("mcp");
@@ -2865,9 +3749,9 @@ var getHarnessMarkerDir2 = (harness) => {
2865
3749
  const homeDir = import_node_os5.default.homedir();
2866
3750
  switch (harness) {
2867
3751
  case "codex":
2868
- return import_node_path9.default.join(homeDir, ".agents");
3752
+ return import_node_path11.default.join(homeDir, ".agents");
2869
3753
  default:
2870
- return import_node_path9.default.join(homeDir, `.${harness}`);
3754
+ return import_node_path11.default.join(homeDir, `.${harness}`);
2871
3755
  }
2872
3756
  };
2873
3757
  var registerInstallCommand = (program2) => {
@@ -2962,6 +3846,7 @@ registerInspectCommands(program);
2962
3846
  registerArtifactsCommands(program);
2963
3847
  registerDiagnosticsCommands(program);
2964
3848
  registerDialogCommands(program);
3849
+ registerDevCommands(program);
2965
3850
  registerOpenArtifactsCommand(program);
2966
3851
  registerMcpCommand(program);
2967
3852
  registerSkillCommands(program);