opendevbrowser 0.0.12 → 0.0.15

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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +216 -28
  3. package/dist/chunk-JVBMT2O5.js +7173 -0
  4. package/dist/chunk-JVBMT2O5.js.map +1 -0
  5. package/dist/cli/index.js +2486 -589
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/index.js +1057 -194
  8. package/dist/index.js.map +1 -1
  9. package/dist/opendevbrowser.js +1057 -194
  10. package/dist/opendevbrowser.js.map +1 -1
  11. package/extension/dist/annotate-content.css +237 -0
  12. package/extension/dist/annotate-content.js +934 -0
  13. package/extension/dist/background.js +1194 -32
  14. package/extension/dist/logging.js +50 -0
  15. package/extension/dist/ops/dom-bridge.js +355 -0
  16. package/extension/dist/ops/ops-runtime.js +1249 -0
  17. package/extension/dist/ops/ops-session-store.js +189 -0
  18. package/extension/dist/ops/redaction.js +52 -0
  19. package/extension/dist/ops/snapshot-builder.js +4 -0
  20. package/extension/dist/ops/snapshot-shared.js +220 -0
  21. package/extension/dist/popup.js +370 -25
  22. package/extension/dist/relay-settings.js +1 -0
  23. package/extension/dist/services/CDPRouter.js +501 -103
  24. package/extension/dist/services/ConnectionManager.js +464 -57
  25. package/extension/dist/services/NativePortManager.js +182 -0
  26. package/extension/dist/services/RelayClient.js +227 -26
  27. package/extension/dist/services/TabManager.js +81 -0
  28. package/extension/dist/services/TargetSessionMap.js +146 -0
  29. package/extension/dist/services/cdp-router-commands.js +203 -0
  30. package/extension/dist/services/url-restrictions.js +41 -0
  31. package/extension/dist/types.js +3 -1
  32. package/extension/manifest.json +17 -3
  33. package/extension/popup.html +144 -0
  34. package/package.json +2 -2
  35. package/skills/AGENTS.md +34 -62
  36. package/skills/data-extraction/SKILL.md +95 -103
  37. package/skills/form-testing/SKILL.md +75 -82
  38. package/skills/login-automation/SKILL.md +76 -66
  39. package/skills/opendevbrowser-best-practices/SKILL.md +90 -49
  40. package/skills/opendevbrowser-continuity-ledger/SKILL.md +57 -23
  41. package/dist/chunk-WTFSMBVH.js +0 -2815
  42. package/dist/chunk-WTFSMBVH.js.map +0 -1
  43. package/extension/dist/popup.jsx +0 -150
package/dist/cli/index.js CHANGED
@@ -1,52 +1,26 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ DaemonClient,
4
+ EXIT_DISCONNECTED,
5
+ EXIT_EXECUTION,
6
+ EXIT_USAGE,
7
+ buildAnnotateResult,
8
+ callDaemon,
3
9
  createOpenDevBrowserCore,
10
+ createUsageError,
4
11
  extractExtension,
12
+ fetchDaemonStatusFromMetadata,
13
+ fetchWithTimeout,
14
+ formatErrorPayload,
5
15
  generateSecureToken,
6
- loadGlobalConfig
7
- } from "../chunk-WTFSMBVH.js";
8
-
9
- // src/cli/errors.ts
10
- var EXIT_SUCCESS = 0;
11
- var EXIT_USAGE = 1;
12
- var EXIT_EXECUTION = 2;
13
- var EXIT_DISCONNECTED = 10;
14
- var CliError = class extends Error {
15
- exitCode;
16
- constructor(message, exitCode) {
17
- super(message);
18
- this.exitCode = exitCode;
19
- }
20
- };
21
- function createUsageError(message) {
22
- return new CliError(message, EXIT_USAGE);
23
- }
24
- function createDisconnectedError(message) {
25
- return new CliError(message, EXIT_DISCONNECTED);
26
- }
27
- function toCliError(error, fallbackExitCode = EXIT_EXECUTION) {
28
- if (error instanceof CliError) {
29
- return error;
30
- }
31
- const message = error instanceof Error ? error.message : String(error);
32
- return new CliError(message, fallbackExitCode);
33
- }
34
- function formatErrorPayload(error) {
35
- return {
36
- success: false,
37
- error: error.message,
38
- exitCode: error.exitCode
39
- };
40
- }
41
- function resolveExitCode(result) {
42
- if (result.exitCode === null) {
43
- return null;
44
- }
45
- if (typeof result.exitCode === "number") {
46
- return result.exitCode;
47
- }
48
- return result.success ? EXIT_SUCCESS : EXIT_EXECUTION;
49
- }
16
+ getExtensionPath,
17
+ loadGlobalConfig,
18
+ readDaemonMetadata,
19
+ resolveExitCode,
20
+ startDaemon,
21
+ toCliError,
22
+ writeFileAtomic
23
+ } from "../chunk-JVBMT2O5.js";
50
24
 
51
25
  // src/cli/args.ts
52
26
  var SHORT_FLAGS = {
@@ -61,13 +35,18 @@ function expandShortFlags(args) {
61
35
  return args.map((arg) => SHORT_FLAGS[arg] ?? arg);
62
36
  }
63
37
  function parseSkillsMode(args) {
38
+ const hasLocal = args.includes("--skills-local");
39
+ const hasGlobal = args.includes("--skills-global");
40
+ if (hasLocal && hasGlobal) {
41
+ throw createUsageError("Choose either --skills-local or --skills-global.");
42
+ }
64
43
  if (args.includes("--no-skills")) {
65
44
  return "none";
66
45
  }
67
- if (args.includes("--skills-local")) {
46
+ if (hasLocal) {
68
47
  return "local";
69
48
  }
70
- if (args.includes("--skills-global")) {
49
+ if (hasGlobal) {
71
50
  return "global";
72
51
  }
73
52
  return "global";
@@ -89,21 +68,44 @@ function parseOutputFormat(args) {
89
68
  }
90
69
  throw createUsageError(`Invalid --output-format: ${value ?? "missing"}`);
91
70
  }
71
+ function parseTransport(args) {
72
+ const transportFlag = args.find((arg) => arg.startsWith("--transport"));
73
+ if (!transportFlag) {
74
+ return "relay";
75
+ }
76
+ let value;
77
+ if (transportFlag.includes("=")) {
78
+ value = transportFlag.split("=", 2)[1];
79
+ } else {
80
+ const index = args.indexOf(transportFlag);
81
+ value = index >= 0 ? args[index + 1] : void 0;
82
+ }
83
+ if (value === "relay" || value === "native") {
84
+ return value;
85
+ }
86
+ throw createUsageError(`Invalid --transport: ${value ?? "missing"}`);
87
+ }
92
88
  function parseArgs(argv) {
93
89
  let args = expandShortFlags(argv.slice(2));
94
90
  let commandOverride = null;
95
91
  if (args[0] && !args[0].startsWith("-")) {
96
92
  const candidate = args[0];
97
- if (candidate === "install" || candidate === "update" || candidate === "uninstall" || candidate === "help" || candidate === "version" || candidate === "serve" || candidate === "run" || candidate === "launch" || candidate === "connect" || candidate === "disconnect" || candidate === "status" || candidate === "goto" || candidate === "wait" || candidate === "snapshot" || candidate === "click" || candidate === "type" || candidate === "select" || candidate === "scroll") {
93
+ if (candidate === "install" || candidate === "update" || candidate === "uninstall" || candidate === "help" || candidate === "version" || candidate === "serve" || candidate === "daemon" || candidate === "native" || candidate === "run" || candidate === "launch" || candidate === "connect" || candidate === "disconnect" || candidate === "status" || candidate === "goto" || candidate === "wait" || candidate === "snapshot" || candidate === "click" || candidate === "hover" || candidate === "press" || candidate === "check" || candidate === "uncheck" || candidate === "type" || candidate === "select" || candidate === "scroll" || candidate === "scroll-into-view" || candidate === "targets-list" || candidate === "target-use" || candidate === "target-new" || candidate === "target-close" || candidate === "page" || candidate === "pages" || candidate === "page-close" || candidate === "dom-html" || candidate === "dom-text" || candidate === "dom-attr" || candidate === "dom-value" || candidate === "dom-visible" || candidate === "dom-enabled" || candidate === "dom-checked" || candidate === "clone-page" || candidate === "clone-component" || candidate === "perf" || candidate === "screenshot" || candidate === "console-poll" || candidate === "network-poll" || candidate === "annotate") {
98
94
  commandOverride = candidate;
99
95
  args = args.slice(1);
100
96
  } else {
101
97
  throw createUsageError(`Unknown command: ${candidate}`);
102
98
  }
103
99
  }
100
+ const hasGlobal = args.includes("--global");
101
+ const hasLocal = args.includes("--local");
102
+ if (hasGlobal && hasLocal) {
103
+ throw createUsageError("Choose either --global or --local.");
104
+ }
104
105
  const skillsMode = parseSkillsMode(args);
105
106
  const fullInstall = args.includes("--full");
106
107
  const outputFormat = parseOutputFormat(args);
108
+ const transport = commandOverride === "annotate" ? "relay" : parseTransport(args);
107
109
  if (commandOverride === "help" || args.includes("--help") || args.includes("-h")) {
108
110
  return {
109
111
  command: "help",
@@ -112,6 +114,7 @@ function parseArgs(argv) {
112
114
  noInteractive: false,
113
115
  quiet: false,
114
116
  outputFormat,
117
+ transport,
115
118
  skillsMode,
116
119
  fullInstall,
117
120
  rawArgs: args
@@ -125,6 +128,7 @@ function parseArgs(argv) {
125
128
  noInteractive: false,
126
129
  quiet: false,
127
130
  outputFormat,
131
+ transport,
128
132
  skillsMode,
129
133
  fullInstall,
130
134
  rawArgs: args
@@ -140,6 +144,7 @@ function parseArgs(argv) {
140
144
  noInteractive: false,
141
145
  quiet: false,
142
146
  outputFormat,
147
+ transport,
143
148
  skillsMode,
144
149
  fullInstall,
145
150
  rawArgs: args
@@ -156,6 +161,7 @@ function parseArgs(argv) {
156
161
  noInteractive: noPrompt2,
157
162
  quiet: args.includes("--quiet"),
158
163
  outputFormat,
164
+ transport,
159
165
  skillsMode,
160
166
  fullInstall,
161
167
  rawArgs: args
@@ -215,16 +221,48 @@ function parseArgs(argv) {
215
221
  "--submit",
216
222
  "--values",
217
223
  "--dy",
224
+ "--key",
225
+ "--attr",
226
+ "--name",
227
+ "--target-id",
228
+ "--tab-id",
229
+ "--include-urls",
230
+ "--path",
231
+ "--since-seq",
232
+ "--max",
233
+ "--daemon",
234
+ "--transport",
218
235
  "--no-extension",
219
236
  "--extension-only",
237
+ "--extension-legacy",
220
238
  "--wait-for-extension",
221
239
  "--wait-timeout-ms",
222
240
  "--skills-global",
223
241
  "--skills-local",
224
- "--no-skills"
242
+ "--no-skills",
243
+ "--screenshot-mode",
244
+ "--debug",
245
+ "--context"
246
+ ]);
247
+ const validEqualsFlags = /* @__PURE__ */ new Set([
248
+ "--output-format",
249
+ "--transport",
250
+ "--session-id",
251
+ "--url",
252
+ "--screenshot-mode",
253
+ "--context",
254
+ "--timeout-ms",
255
+ "--target-id",
256
+ "--tab-id"
225
257
  ]);
226
258
  for (const arg of args) {
227
259
  if (arg.startsWith("--") && !validFlags.has(arg)) {
260
+ if (arg.includes("=")) {
261
+ const baseFlag = arg.split("=", 2)[0] ?? "";
262
+ if (validEqualsFlags.has(baseFlag)) {
263
+ continue;
264
+ }
265
+ }
228
266
  throw createUsageError(`Unknown flag: ${arg}`);
229
267
  }
230
268
  if (arg.startsWith("-") && !arg.startsWith("--") && !SHORT_FLAGS[arg]) {
@@ -239,6 +277,7 @@ function parseArgs(argv) {
239
277
  noInteractive,
240
278
  quiet,
241
279
  outputFormat,
280
+ transport,
242
281
  skillsMode,
243
282
  fullInstall,
244
283
  rawArgs: args
@@ -256,18 +295,46 @@ COMMANDS:
256
295
  update Clear cached plugin to trigger reinstall
257
296
  uninstall Remove plugin from config
258
297
  serve Start or stop the local daemon
298
+ daemon Install/uninstall/status daemon auto-start
299
+ native Install/uninstall/status native messaging host
259
300
  run Execute a JSON script in a single process
260
301
  launch Launch a managed browser session via daemon
261
302
  connect Connect to an existing browser via daemon
262
303
  disconnect Disconnect a daemon session
263
- status Get daemon session status
304
+ status Get daemon status (or session status with --session-id)
264
305
  goto Navigate current session to a URL
265
306
  wait Wait for load or a ref to appear
266
307
  snapshot Capture a snapshot of the active page
267
308
  click Click an element by ref
309
+ hover Hover an element by ref
310
+ press Press a keyboard key
311
+ check Check a checkbox by ref
312
+ uncheck Uncheck a checkbox by ref
268
313
  type Type into an element by ref
269
314
  select Select values in a select by ref
270
315
  scroll Scroll the page or element by ref
316
+ scroll-into-view Scroll an element into view by ref
317
+ targets-list List page targets
318
+ target-use Focus a target by id
319
+ target-new Open a new target
320
+ target-close Close a target by id
321
+ page Open or focus a named page
322
+ pages List named pages
323
+ page-close Close a named page
324
+ dom-html Capture HTML for a ref
325
+ dom-text Capture text for a ref
326
+ dom-attr Capture attribute value for a ref
327
+ dom-value Capture input value for a ref
328
+ dom-visible Check visibility for a ref
329
+ dom-enabled Check enabled state for a ref
330
+ dom-checked Check checked state for a ref
331
+ clone-page Clone the active page to React
332
+ clone-component Clone a component by ref
333
+ perf Capture performance metrics
334
+ screenshot Capture a screenshot
335
+ console-poll Poll console events
336
+ network-poll Poll network events
337
+ annotate Request interactive annotations (direct or relay)
271
338
  help Show this help message
272
339
  version Show version
273
340
 
@@ -286,6 +353,7 @@ INSTALL OPTIONS:
286
353
  --no-interactive Alias of --no-prompt
287
354
  --quiet Suppress non-error output
288
355
  --output-format Output format: text (default), json, stream-json
356
+ --transport Transport: relay (default) or native
289
357
  --skills-global Install bundled skills to ~/.config/opencode/skill (default)
290
358
  --skills-local Install bundled skills to ./.opencode/skill
291
359
  --no-skills Skip installing bundled skills
@@ -300,6 +368,7 @@ EXAMPLES:
300
368
  npx opendevbrowser --no-skills # Skip skill installation
301
369
  npx opendevbrowser --update # Update plugin
302
370
  npx opendevbrowser --uninstall --global # Remove from global config
371
+ npx opendevbrowser native install <extension-id> # Install native host
303
372
  `.trim();
304
373
  }
305
374
  function detectOutputFormat(argv) {
@@ -321,64 +390,32 @@ function getCommand(name) {
321
390
  }
322
391
 
323
392
  // src/cli/installers/global.ts
324
- import * as fs4 from "fs";
393
+ import * as fs3 from "fs";
325
394
 
326
395
  // src/cli/utils/config.ts
327
- import * as fs2 from "fs";
328
- import * as path2 from "path";
329
- import * as os from "os";
330
- import { parse as parseJsonc, modify, applyEdits } from "jsonc-parser";
331
-
332
- // src/utils/fs.ts
333
396
  import * as fs from "fs";
334
397
  import * as path from "path";
335
- import * as crypto from "crypto";
336
- function writeFileAtomic(filePath, content, options = {}) {
337
- const { encoding = "utf-8", mode } = options;
338
- const dir = path.dirname(filePath);
339
- const hash = crypto.randomBytes(8).toString("hex");
340
- const tempPath = path.join(dir, `.${path.basename(filePath)}.${process.pid}.${hash}.tmp`);
341
- try {
342
- if (!fs.existsSync(dir)) {
343
- fs.mkdirSync(dir, { recursive: true });
344
- }
345
- const writeOptions = { encoding };
346
- if (mode !== void 0) {
347
- writeOptions.mode = mode;
348
- }
349
- fs.writeFileSync(tempPath, content, writeOptions);
350
- fs.renameSync(tempPath, filePath);
351
- } catch (error) {
352
- try {
353
- if (fs.existsSync(tempPath)) {
354
- fs.unlinkSync(tempPath);
355
- }
356
- } catch {
357
- }
358
- throw error;
359
- }
360
- }
361
-
362
- // src/cli/utils/config.ts
398
+ import * as os from "os";
399
+ import { parse as parseJsonc, modify, applyEdits } from "jsonc-parser";
363
400
  var PLUGIN_NAME = "opendevbrowser";
364
401
  var SCHEMA_URL = "https://opencode.ai/config.json";
365
402
  function getGlobalConfigPath() {
366
- const configDir = process.env.OPENCODE_CONFIG_DIR || path2.join(os.homedir(), ".config", "opencode");
367
- return path2.join(configDir, "opencode.json");
403
+ const configDir = process.env.OPENCODE_CONFIG_DIR || path.join(os.homedir(), ".config", "opencode");
404
+ return path.join(configDir, "opencode.json");
368
405
  }
369
406
  function getLocalConfigPath() {
370
- return path2.join(process.cwd(), "opencode.json");
407
+ return path.join(process.cwd(), "opencode.json");
371
408
  }
372
409
  function ensureDir(dirPath) {
373
- if (!fs2.existsSync(dirPath)) {
374
- fs2.mkdirSync(dirPath, { recursive: true });
410
+ if (!fs.existsSync(dirPath)) {
411
+ fs.mkdirSync(dirPath, { recursive: true });
375
412
  }
376
413
  }
377
414
  function readConfig(configPath) {
378
- if (!fs2.existsSync(configPath)) {
415
+ if (!fs.existsSync(configPath)) {
379
416
  return { content: "", config: {} };
380
417
  }
381
- const content = fs2.readFileSync(configPath, "utf-8");
418
+ const content = fs.readFileSync(configPath, "utf-8");
382
419
  const errors = [];
383
420
  const parsed = parseJsonc(content, errors, { allowTrailingComma: true });
384
421
  if (errors.length > 0) {
@@ -430,10 +467,10 @@ function removePluginFromContent(content, pluginName = PLUGIN_NAME) {
430
467
  }
431
468
 
432
469
  // src/cli/templates/config.ts
433
- import * as fs3 from "fs";
434
- import * as path3 from "path";
470
+ import * as fs2 from "fs";
471
+ import * as path2 from "path";
435
472
  import * as os2 from "os";
436
- function buildConfigTemplate(token) {
473
+ function buildConfigTemplate(relayToken, daemonToken) {
437
474
  return `{
438
475
  // OpenDevBrowser Plugin Configuration
439
476
  // See: https://github.com/anthropics/opendevbrowser#configuration
@@ -497,7 +534,9 @@ function buildConfigTemplate(token) {
497
534
  },
498
535
 
499
536
  "relayPort": 8787,
500
- "relayToken": "${token}",
537
+ "relayToken": "${relayToken}",
538
+ "daemonPort": 8788,
539
+ "daemonToken": "${daemonToken}",
501
540
 
502
541
  "flags": [],
503
542
 
@@ -507,22 +546,23 @@ function buildConfigTemplate(token) {
507
546
  }
508
547
  function getPluginConfigPath(mode) {
509
548
  if (mode === "global") {
510
- const configDir = process.env.OPENCODE_CONFIG_DIR || path3.join(os2.homedir(), ".config", "opencode");
511
- return path3.join(configDir, "opendevbrowser.jsonc");
549
+ const configDir = process.env.OPENCODE_CONFIG_DIR || path2.join(os2.homedir(), ".config", "opencode");
550
+ return path2.join(configDir, "opendevbrowser.jsonc");
512
551
  }
513
- return path3.join(process.cwd(), "opendevbrowser.jsonc");
552
+ return path2.join(process.cwd(), "opendevbrowser.jsonc");
514
553
  }
515
554
  function createPluginConfig(mode) {
516
555
  const configPath = getPluginConfigPath(mode);
517
- if (fs3.existsSync(configPath)) {
556
+ if (fs2.existsSync(configPath)) {
518
557
  return { created: false, path: configPath };
519
558
  }
520
- const dir = path3.dirname(configPath);
521
- if (!fs3.existsSync(dir)) {
522
- fs3.mkdirSync(dir, { recursive: true });
559
+ const dir = path2.dirname(configPath);
560
+ if (!fs2.existsSync(dir)) {
561
+ fs2.mkdirSync(dir, { recursive: true });
523
562
  }
524
- const token = generateSecureToken();
525
- writeFileAtomic(configPath, buildConfigTemplate(token));
563
+ const relayToken = generateSecureToken();
564
+ const daemonToken = generateSecureToken();
565
+ writeFileAtomic(configPath, buildConfigTemplate(relayToken, daemonToken));
526
566
  return { created: true, path: configPath };
527
567
  }
528
568
 
@@ -542,7 +582,7 @@ function installGlobal(withConfig = false) {
542
582
  }
543
583
  const newContent = updateConfigContent(content, "opendevbrowser");
544
584
  ensureDir(configPath.replace(/[/\\][^/\\]+$/, ""));
545
- fs4.writeFileSync(configPath, newContent, "utf-8");
585
+ fs3.writeFileSync(configPath, newContent, "utf-8");
546
586
  if (withConfig) {
547
587
  createPluginConfig("global");
548
588
  }
@@ -566,7 +606,7 @@ function installGlobal(withConfig = false) {
566
606
  }
567
607
 
568
608
  // src/cli/installers/local.ts
569
- import * as fs5 from "fs";
609
+ import * as fs4 from "fs";
570
610
  function installLocal(withConfig = false) {
571
611
  const configPath = getLocalConfigPath();
572
612
  try {
@@ -581,7 +621,7 @@ function installLocal(withConfig = false) {
581
621
  };
582
622
  }
583
623
  const newContent = updateConfigContent(content, "opendevbrowser");
584
- fs5.writeFileSync(configPath, newContent, "utf-8");
624
+ fs4.writeFileSync(configPath, newContent, "utf-8");
585
625
  if (withConfig) {
586
626
  createPluginConfig("local");
587
627
  }
@@ -605,12 +645,12 @@ function installLocal(withConfig = false) {
605
645
  }
606
646
 
607
647
  // src/cli/installers/skills.ts
608
- import * as fs7 from "fs";
609
- import * as path5 from "path";
610
-
611
- // src/cli/utils/skills.ts
612
648
  import * as fs6 from "fs";
613
649
  import * as path4 from "path";
650
+
651
+ // src/cli/utils/skills.ts
652
+ import * as fs5 from "fs";
653
+ import * as path3 from "path";
614
654
  import * as os3 from "os";
615
655
  import { fileURLToPath } from "url";
616
656
  var PACKAGE_NAME = "opendevbrowser";
@@ -619,17 +659,17 @@ var cachedPackageRoot = null;
619
659
  function findPackageRoot(startDir) {
620
660
  let current = startDir;
621
661
  while (true) {
622
- const pkgPath = path4.join(current, "package.json");
623
- if (fs6.existsSync(pkgPath)) {
662
+ const pkgPath = path3.join(current, "package.json");
663
+ if (fs5.existsSync(pkgPath)) {
624
664
  try {
625
- const parsed = JSON.parse(fs6.readFileSync(pkgPath, "utf-8"));
665
+ const parsed = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
626
666
  if (parsed.name === PACKAGE_NAME) {
627
667
  return current;
628
668
  }
629
669
  } catch {
630
670
  }
631
671
  }
632
- const parent = path4.dirname(current);
672
+ const parent = path3.dirname(current);
633
673
  if (parent === current) {
634
674
  break;
635
675
  }
@@ -639,23 +679,23 @@ function findPackageRoot(startDir) {
639
679
  }
640
680
  function getPackageRoot() {
641
681
  if (cachedPackageRoot) return cachedPackageRoot;
642
- const moduleDir = path4.dirname(fileURLToPath(import.meta.url));
682
+ const moduleDir = path3.dirname(fileURLToPath(import.meta.url));
643
683
  cachedPackageRoot = findPackageRoot(moduleDir);
644
684
  return cachedPackageRoot;
645
685
  }
646
686
  function getBundledSkillsDir() {
647
- const skillsDir = path4.join(getPackageRoot(), "skills");
648
- if (!fs6.existsSync(skillsDir)) {
687
+ const skillsDir = path3.join(getPackageRoot(), "skills");
688
+ if (!fs5.existsSync(skillsDir)) {
649
689
  throw new Error(`Bundled skills directory not found at ${skillsDir}`);
650
690
  }
651
691
  return skillsDir;
652
692
  }
653
693
  function getGlobalSkillDir() {
654
- const configDir = process.env.OPENCODE_CONFIG_DIR || path4.join(os3.homedir(), ".config", "opencode");
655
- return path4.join(configDir, SKILL_DIR_NAME);
694
+ const configDir = process.env.OPENCODE_CONFIG_DIR || path3.join(os3.homedir(), ".config", "opencode");
695
+ return path3.join(configDir, SKILL_DIR_NAME);
656
696
  }
657
697
  function getLocalSkillDir() {
658
- return path4.join(process.cwd(), ".opencode", SKILL_DIR_NAME);
698
+ return path3.join(process.cwd(), ".opencode", SKILL_DIR_NAME);
659
699
  }
660
700
 
661
701
  // src/cli/installers/skills.ts
@@ -665,18 +705,18 @@ function installSkills(mode) {
665
705
  const skipped = [];
666
706
  try {
667
707
  const sourceDir = getBundledSkillsDir();
668
- const entries = fs7.readdirSync(sourceDir, { withFileTypes: true });
708
+ const entries = fs6.readdirSync(sourceDir, { withFileTypes: true });
669
709
  ensureDir(targetDir);
670
710
  for (const entry of entries) {
671
711
  if (!entry.isDirectory()) continue;
672
712
  const skillName = entry.name;
673
- const sourcePath = path5.join(sourceDir, skillName);
674
- const targetPath = path5.join(targetDir, skillName);
675
- if (fs7.existsSync(targetPath)) {
713
+ const sourcePath = path4.join(sourceDir, skillName);
714
+ const targetPath = path4.join(targetDir, skillName);
715
+ if (fs6.existsSync(targetPath)) {
676
716
  skipped.push(skillName);
677
717
  continue;
678
718
  }
679
- fs7.cpSync(sourcePath, targetPath, { recursive: true });
719
+ fs6.cpSync(sourcePath, targetPath, { recursive: true });
680
720
  installed.push(skillName);
681
721
  }
682
722
  const summary = `Skills ${mode} install: ${installed.length} installed${skipped.length ? `, ${skipped.length} skipped` : ""} (${targetDir})`;
@@ -700,31 +740,31 @@ function installSkills(mode) {
700
740
  }
701
741
 
702
742
  // src/cli/commands/update.ts
703
- import * as fs8 from "fs";
704
- import * as path6 from "path";
743
+ import * as fs7 from "fs";
744
+ import * as path5 from "path";
705
745
  import * as os4 from "os";
706
746
  var PLUGIN_NAME2 = "opendevbrowser";
707
747
  function getCacheDir() {
708
- return process.env.OPENCODE_CACHE_DIR || path6.join(os4.homedir(), ".cache", "opencode");
748
+ return process.env.OPENCODE_CACHE_DIR || path5.join(os4.homedir(), ".cache", "opencode");
709
749
  }
710
750
  function rmdir(dirPath) {
711
751
  const cacheDir = getCacheDir();
712
- const resolvedCache = path6.resolve(cacheDir);
713
- const resolvedPath = path6.resolve(dirPath);
714
- if (!resolvedPath.startsWith(resolvedCache + path6.sep) || resolvedPath === resolvedCache) {
752
+ const resolvedCache = path5.resolve(cacheDir);
753
+ const resolvedPath = path5.resolve(dirPath);
754
+ if (!resolvedPath.startsWith(resolvedCache + path5.sep) || resolvedPath === resolvedCache) {
715
755
  throw new Error(`Security: refusing to delete path outside cache directory: ${dirPath}`);
716
756
  }
717
- if (fs8.existsSync(dirPath)) {
718
- fs8.rmSync(dirPath, { recursive: true, force: true });
757
+ if (fs7.existsSync(dirPath)) {
758
+ fs7.rmSync(dirPath, { recursive: true, force: true });
719
759
  }
720
760
  }
721
761
  function runUpdate() {
722
762
  const cacheDir = getCacheDir();
723
- const nodeModulesDir = path6.join(cacheDir, "node_modules");
724
- const pluginCacheDir = path6.join(nodeModulesDir, PLUGIN_NAME2);
763
+ const nodeModulesDir = path5.join(cacheDir, "node_modules");
764
+ const pluginCacheDir = path5.join(nodeModulesDir, PLUGIN_NAME2);
725
765
  try {
726
- if (!fs8.existsSync(pluginCacheDir)) {
727
- if (fs8.existsSync(nodeModulesDir)) {
766
+ if (!fs7.existsSync(pluginCacheDir)) {
767
+ if (fs7.existsSync(nodeModulesDir)) {
728
768
  rmdir(nodeModulesDir);
729
769
  return {
730
770
  success: true,
@@ -755,20 +795,20 @@ function runUpdate() {
755
795
  }
756
796
 
757
797
  // src/cli/commands/uninstall.ts
758
- import * as fs9 from "fs";
759
- import * as path7 from "path";
798
+ import * as fs8 from "fs";
799
+ import * as path6 from "path";
760
800
  import * as os5 from "os";
761
801
  function getPluginConfigPath2(mode) {
762
802
  if (mode === "global") {
763
- const configDir = process.env.OPENCODE_CONFIG_DIR || path7.join(os5.homedir(), ".config", "opencode");
764
- return path7.join(configDir, "opendevbrowser.jsonc");
803
+ const configDir = process.env.OPENCODE_CONFIG_DIR || path6.join(os5.homedir(), ".config", "opencode");
804
+ return path6.join(configDir, "opendevbrowser.jsonc");
765
805
  }
766
- return path7.join(process.cwd(), "opendevbrowser.jsonc");
806
+ return path6.join(process.cwd(), "opendevbrowser.jsonc");
767
807
  }
768
808
  function removePluginConfigFile(mode) {
769
809
  const configPath = getPluginConfigPath2(mode);
770
- if (fs9.existsSync(configPath)) {
771
- fs9.unlinkSync(configPath);
810
+ if (fs8.existsSync(configPath)) {
811
+ fs8.unlinkSync(configPath);
772
812
  return true;
773
813
  }
774
814
  return false;
@@ -787,7 +827,7 @@ function runUninstall(mode, deleteConfigFile = false) {
787
827
  };
788
828
  }
789
829
  const newContent = removePluginFromContent(content, "opendevbrowser");
790
- fs9.writeFileSync(configPath, newContent, "utf-8");
830
+ fs8.writeFileSync(configPath, newContent, "utf-8");
791
831
  let configFileDeleted = false;
792
832
  if (deleteConfigFile) {
793
833
  configFileDeleted = removePluginConfigFile(mode);
@@ -826,331 +866,399 @@ function findInstalledConfigs() {
826
866
  return { global, local };
827
867
  }
828
868
 
829
- // src/cli/daemon.ts
830
- import { createServer } from "http";
831
- import { timingSafeEqual } from "crypto";
832
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3, existsSync as existsSync8 } from "fs";
833
- import { homedir as homedir6 } from "os";
834
- import { join as join8 } from "path";
835
-
836
- // src/cli/daemon-commands.ts
837
- async function handleDaemonCommand(core, request) {
838
- const params = request.params ?? {};
839
- switch (request.name) {
840
- case "session.launch":
841
- return launchWithRelay(core, params);
842
- case "session.connect":
843
- return core.manager.connect({
844
- wsEndpoint: optionalString(params.wsEndpoint),
845
- host: optionalString(params.host),
846
- port: optionalNumber(params.port)
847
- });
848
- case "session.disconnect":
849
- await core.manager.disconnect(requireString(params.sessionId, "sessionId"), optionalBoolean(params.closeBrowser) ?? false);
850
- return { ok: true };
851
- case "session.status":
852
- return core.manager.status(requireString(params.sessionId, "sessionId"));
853
- case "nav.goto":
854
- return core.manager.goto(
855
- requireString(params.sessionId, "sessionId"),
856
- requireString(params.url, "url"),
857
- requireWaitUntil(params.waitUntil),
858
- optionalNumber(params.timeoutMs) ?? 3e4
859
- );
860
- case "nav.wait":
861
- if (typeof params.ref === "string") {
862
- return core.manager.waitForRef(
863
- requireString(params.sessionId, "sessionId"),
864
- requireString(params.ref, "ref"),
865
- requireState(params.state),
866
- optionalNumber(params.timeoutMs) ?? 3e4
867
- );
868
- }
869
- return core.manager.waitForLoad(
870
- requireString(params.sessionId, "sessionId"),
871
- requireWaitUntil(params.until),
872
- optionalNumber(params.timeoutMs) ?? 3e4
873
- );
874
- case "nav.snapshot":
875
- return core.manager.snapshot(
876
- requireString(params.sessionId, "sessionId"),
877
- requireSnapshotMode(params.mode),
878
- optionalNumber(params.maxChars) ?? 16e3,
879
- optionalString(params.cursor)
880
- );
881
- case "interact.click":
882
- return core.manager.click(
883
- requireString(params.sessionId, "sessionId"),
884
- requireString(params.ref, "ref")
885
- );
886
- case "interact.type":
887
- return core.manager.type(
888
- requireString(params.sessionId, "sessionId"),
889
- requireString(params.ref, "ref"),
890
- requireString(params.text, "text"),
891
- optionalBoolean(params.clear) ?? false,
892
- optionalBoolean(params.submit) ?? false
893
- );
894
- case "interact.select":
895
- return core.manager.select(
896
- requireString(params.sessionId, "sessionId"),
897
- requireString(params.ref, "ref"),
898
- requireStringArray(params.values, "values")
899
- );
900
- case "interact.scroll":
901
- return core.manager.scroll(
902
- requireString(params.sessionId, "sessionId"),
903
- optionalNumber(params.dy) ?? 0,
904
- optionalString(params.ref)
905
- );
906
- default:
907
- throw new Error(`Unknown daemon command: ${request.name}`);
869
+ // src/cli/utils/parse.ts
870
+ function parseNumberFlag(value, flag, options = {}) {
871
+ const parsed = Number(value);
872
+ if (!Number.isFinite(parsed)) {
873
+ throw createUsageError(`Invalid ${flag}: ${value}`);
874
+ }
875
+ const requireInteger = options.integer ?? true;
876
+ if (requireInteger && !Number.isInteger(parsed)) {
877
+ throw createUsageError(`Invalid ${flag}: ${value}`);
878
+ }
879
+ if (typeof options.min === "number" && parsed < options.min) {
880
+ throw createUsageError(`Invalid ${flag}: ${value}`);
908
881
  }
882
+ if (typeof options.max === "number" && parsed > options.max) {
883
+ throw createUsageError(`Invalid ${flag}: ${value}`);
884
+ }
885
+ return parsed;
909
886
  }
910
- async function launchWithRelay(core, params) {
911
- let relayStatus = core.relay.status();
912
- const relayUrl = core.relay.getCdpUrl();
913
- const noExtension = optionalBoolean(params.noExtension) ?? false;
914
- const extensionOnly = optionalBoolean(params.extensionOnly) ?? false;
915
- const waitForExtension = optionalBoolean(params.waitForExtension) ?? false;
916
- const waitTimeoutMs = optionalNumber(params.waitTimeoutMs) ?? 3e4;
917
- if (waitForExtension) {
918
- const connected = await waitForRelay(core.relay, waitTimeoutMs);
919
- if (connected) {
920
- relayStatus = core.relay.status();
921
- }
887
+
888
+ // src/cli/commands/native.ts
889
+ import * as fs9 from "fs";
890
+ import * as path7 from "path";
891
+ import { homedir as homedir6 } from "os";
892
+ import { execFileSync } from "child_process";
893
+ import { fileURLToPath as fileURLToPath2 } from "url";
894
+ var EXTENSION_ID_RE = /^[a-p]{32}$/;
895
+ var EXTENSION_NAME = "OpenDevBrowser Relay";
896
+ var ANNOTATION_COMMAND_NAME = "toggle-annotation";
897
+ var normalizeExtensionId = (value) => {
898
+ if (!value) return null;
899
+ const trimmed = value.trim();
900
+ if (!trimmed) return null;
901
+ return EXTENSION_ID_RE.test(trimmed) ? trimmed : null;
902
+ };
903
+ var requireExtensionId = (value) => {
904
+ if (!value) {
905
+ throw createUsageError("Missing extension ID. Usage: opendevbrowser native install <extension-id>");
922
906
  }
923
- const useRelay = Boolean(!noExtension && relayStatus.extensionConnected && relayUrl);
924
- let relayWarning = null;
925
- if (extensionOnly && !useRelay) {
926
- throw new Error("Extension not connected; use --no-extension to launch a new browser.");
907
+ const normalized = normalizeExtensionId(value);
908
+ if (!normalized) {
909
+ throw createUsageError("Invalid extension ID format. Expected 32 characters (a-p).");
927
910
  }
928
- if (useRelay && relayUrl) {
929
- try {
930
- const result2 = await core.manager.connectRelay(relayUrl);
931
- return { ...result2, warnings: result2.warnings ?? [] };
932
- } catch (error) {
933
- if (extensionOnly) {
934
- throw error instanceof Error ? error : new Error("Extension relay connection failed.");
935
- }
936
- relayWarning = "Relay connection failed; falling back to managed Chrome.";
937
- }
911
+ return normalized;
912
+ };
913
+ var parseNativeArgs = (rawArgs) => {
914
+ const subcommand = rawArgs[0];
915
+ if (subcommand !== "install" && subcommand !== "uninstall" && subcommand !== "status") {
916
+ throw createUsageError("Usage: opendevbrowser native <install|uninstall|status> [extension-id]");
938
917
  }
939
- if (relayUrl && !noExtension) {
940
- relayWarning ??= "Extension not connected; launching managed Chrome instead.";
918
+ if (subcommand === "install") {
919
+ const extensionId = requireExtensionId(rawArgs[1]);
920
+ return { subcommand, extensionId };
941
921
  }
942
- const result = await core.manager.launch({
943
- profile: optionalString(params.profile),
944
- headless: optionalBoolean(params.headless),
945
- startUrl: optionalString(params.startUrl),
946
- chromePath: optionalString(params.chromePath),
947
- flags: optionalStringArray(params.flags),
948
- persistProfile: optionalBoolean(params.persistProfile)
949
- });
950
- const warnings = [
951
- ...result.warnings ?? [],
952
- ...relayWarning ? [relayWarning] : []
953
- ];
954
- return { ...result, warnings };
955
- }
956
- function requireString(value, label) {
957
- if (typeof value !== "string" || !value.trim()) {
958
- throw new Error(`Missing ${label}`);
922
+ return { subcommand };
923
+ };
924
+ var getManifestDir = () => {
925
+ if (process.platform === "darwin") {
926
+ return path7.join(process.env.HOME || "", "Library", "Application Support", "Google", "Chrome", "NativeMessagingHosts");
959
927
  }
960
- return value;
961
- }
962
- function requireStringArray(value, label) {
963
- if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
964
- throw new Error(`Invalid ${label}`);
928
+ if (process.platform === "linux") {
929
+ return path7.join(process.env.HOME || "", ".config", "google-chrome", "NativeMessagingHosts");
965
930
  }
966
- return value;
967
- }
968
- function optionalString(value) {
969
- return typeof value === "string" ? value : void 0;
970
- }
971
- function optionalStringArray(value) {
972
- return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
973
- }
974
- function optionalNumber(value) {
975
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
976
- }
977
- function optionalBoolean(value) {
978
- return typeof value === "boolean" ? value : void 0;
979
- }
980
- function requireWaitUntil(value) {
981
- if (value === "domcontentloaded" || value === "load" || value === "networkidle") {
982
- return value;
931
+ if (process.platform === "win32") {
932
+ const base = process.env.LOCALAPPDATA || (process.env.USERPROFILE ? path7.join(process.env.USERPROFILE, "AppData", "Local") : "");
933
+ if (!base) {
934
+ throw createUsageError("LOCALAPPDATA is not set. Unable to locate NativeMessagingHosts directory.");
935
+ }
936
+ return path7.join(base, "Google", "Chrome", "User Data", "NativeMessagingHosts");
983
937
  }
984
- return "load";
985
- }
986
- function requireSnapshotMode(value) {
987
- if (value === "actionables") return "actionables";
988
- return "outline";
989
- }
990
- function requireState(value) {
991
- if (value === "visible" || value === "hidden") return value;
992
- return "attached";
993
- }
994
- async function waitForRelay(relay, timeoutMs) {
995
- const start = Date.now();
996
- while (Date.now() - start < timeoutMs) {
997
- if (relay.status().extensionConnected) {
998
- return true;
938
+ throw createUsageError(`Native messaging is not supported on ${process.platform}.`);
939
+ };
940
+ var getScriptsDir = () => {
941
+ const __filename = fileURLToPath2(import.meta.url);
942
+ const startDir = path7.dirname(__filename);
943
+ const rootsToScan = [startDir, process.cwd()];
944
+ for (const root of rootsToScan) {
945
+ let current = path7.resolve(root);
946
+ while (true) {
947
+ const scriptsDir = path7.join(current, "scripts", "native");
948
+ const packageJsonPath = path7.join(current, "package.json");
949
+ if (fs9.existsSync(scriptsDir) && fs9.existsSync(packageJsonPath)) {
950
+ return scriptsDir;
951
+ }
952
+ const parent = path7.dirname(current);
953
+ if (parent === current) {
954
+ break;
955
+ }
956
+ current = parent;
999
957
  }
1000
- await new Promise((resolve2) => setTimeout(resolve2, 500));
1001
958
  }
1002
- return false;
1003
- }
1004
-
1005
- // src/cli/daemon.ts
1006
- var DEFAULT_DAEMON_PORT = 8788;
1007
- function getCacheRoot() {
1008
- const base = process.env.OPENCODE_CACHE_DIR ?? process.env.XDG_CACHE_HOME ?? join8(homedir6(), ".cache");
1009
- return join8(base, "opendevbrowser");
1010
- }
1011
- function getDaemonMetadataPath() {
1012
- return join8(getCacheRoot(), "daemon.json");
1013
- }
1014
- function readDaemonMetadata() {
1015
- const metadataPath = getDaemonMetadataPath();
1016
- if (!existsSync8(metadataPath)) {
1017
- return null;
959
+ throw createUsageError("Unable to locate scripts/native directory.");
960
+ };
961
+ var getHostScriptPath = () => {
962
+ return path7.join(getScriptsDir(), "host.cjs");
963
+ };
964
+ var getManifestPath = () => {
965
+ return path7.join(getManifestDir(), "com.opendevbrowser.native.json");
966
+ };
967
+ var getWrapperPath = () => {
968
+ const wrapperName = process.platform === "win32" ? "com.opendevbrowser.native.cmd" : "com.opendevbrowser.native.sh";
969
+ return path7.join(getManifestDir(), wrapperName);
970
+ };
971
+ var readManifest = (manifestPath) => {
972
+ try {
973
+ const raw = fs9.readFileSync(manifestPath, "utf8");
974
+ const data = JSON.parse(raw);
975
+ const origins = Array.isArray(data.allowed_origins) ? data.allowed_origins : [];
976
+ const match = origins.find((origin) => origin.startsWith("chrome-extension://"));
977
+ if (!match) return { extensionId: null };
978
+ const id = match.replace("chrome-extension://", "").replace("/", "");
979
+ return { extensionId: EXTENSION_ID_RE.test(id) ? id : null };
980
+ } catch {
981
+ return { extensionId: null };
982
+ }
983
+ };
984
+ var runScript = (script, args) => {
985
+ if (process.platform === "win32") {
986
+ execFileSync("powershell", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", script, ...args], { stdio: "pipe" });
987
+ return;
1018
988
  }
989
+ execFileSync("bash", [script, ...args], { stdio: "pipe" });
990
+ };
991
+ var readRegistryPath = () => {
992
+ if (process.platform !== "win32") return null;
993
+ const key = "HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\com.opendevbrowser.native";
1019
994
  try {
1020
- const content = readFileSync3(metadataPath, "utf-8");
1021
- return JSON.parse(content);
995
+ const output = execFileSync("reg", ["query", key, "/ve"], { encoding: "utf8" });
996
+ const lines = output.split(/\r?\n/);
997
+ for (const line of lines) {
998
+ if (line.includes("REG_SZ")) {
999
+ const parts = line.trim().split(/\s{2,}/);
1000
+ return parts[parts.length - 1] || null;
1001
+ }
1002
+ }
1003
+ return null;
1022
1004
  } catch {
1023
1005
  return null;
1024
1006
  }
1025
- }
1026
- function writeDaemonMetadata(state) {
1027
- const metadataPath = getDaemonMetadataPath();
1028
- mkdirSync4(join8(getCacheRoot()), { recursive: true });
1029
- writeFileSync5(metadataPath, JSON.stringify(state, null, 2), { encoding: "utf-8", mode: 384 });
1030
- }
1031
- function clearDaemonMetadata() {
1032
- const metadataPath = getDaemonMetadataPath();
1007
+ };
1008
+ var normalizePath = (value) => {
1033
1009
  try {
1034
- unlinkSync3(metadataPath);
1010
+ return fs9.realpathSync(value);
1035
1011
  } catch {
1012
+ return path7.resolve(value);
1036
1013
  }
1037
- }
1038
- function isAuthorized(request, token) {
1039
- const header = request.headers.authorization ?? "";
1040
- if (!header.startsWith("Bearer ")) {
1041
- return false;
1014
+ };
1015
+ var getChromeUserDataRoots = () => {
1016
+ if (process.platform === "darwin") {
1017
+ return [
1018
+ path7.join(homedir6(), "Library", "Application Support", "Google", "Chrome"),
1019
+ path7.join(homedir6(), "Library", "Application Support", "Chromium"),
1020
+ path7.join(homedir6(), "Library", "Application Support", "BraveSoftware", "Brave-Browser")
1021
+ ];
1042
1022
  }
1043
- const received = header.slice("Bearer ".length).trim();
1044
- const expectedBuf = Buffer.from(token, "utf-8");
1045
- const receivedBuf = Buffer.from(received, "utf-8");
1046
- if (expectedBuf.length !== receivedBuf.length) {
1047
- timingSafeEqual(expectedBuf, expectedBuf);
1048
- return false;
1023
+ if (process.platform === "linux") {
1024
+ return [
1025
+ path7.join(homedir6(), ".config", "google-chrome"),
1026
+ path7.join(homedir6(), ".config", "chromium"),
1027
+ path7.join(homedir6(), ".config", "BraveSoftware", "Brave-Browser")
1028
+ ];
1049
1029
  }
1050
- return timingSafeEqual(expectedBuf, receivedBuf);
1051
- }
1052
- function sendJson(response, status, payload) {
1053
- response.writeHead(status, {
1054
- "Content-Type": "application/json",
1055
- "Cache-Control": "no-store"
1056
- });
1057
- response.end(JSON.stringify(payload));
1058
- }
1059
- async function startDaemon(options = {}) {
1060
- const config = options.config ?? loadGlobalConfig();
1061
- const port = options.port ?? DEFAULT_DAEMON_PORT;
1062
- const token = options.token ?? generateSecureToken();
1063
- const core = createOpenDevBrowserCore({
1064
- directory: options.directory ?? process.cwd(),
1065
- worktree: options.worktree ?? null,
1066
- config
1067
- });
1068
- await core.ensureRelay(config.relayPort);
1069
- const server = createServer(async (request, response) => {
1070
- if (!isAuthorized(request, token)) {
1071
- sendJson(response, 401, { error: "Unauthorized" });
1072
- return;
1030
+ if (process.platform === "win32") {
1031
+ const base = process.env.LOCALAPPDATA || (process.env.USERPROFILE ? path7.join(process.env.USERPROFILE, "AppData", "Local") : "");
1032
+ if (!base) return [];
1033
+ return [
1034
+ path7.join(base, "Google", "Chrome", "User Data"),
1035
+ path7.join(base, "Chromium", "User Data"),
1036
+ path7.join(base, "BraveSoftware", "Brave-Browser", "User Data")
1037
+ ];
1038
+ }
1039
+ return [];
1040
+ };
1041
+ var PROFILE_PREFERENCES_FILES = ["Preferences", "Secure Preferences"];
1042
+ var getProfileDirs = (root) => {
1043
+ try {
1044
+ const entries = fs9.readdirSync(root, { withFileTypes: true });
1045
+ return entries.filter((entry) => entry.isDirectory() && (entry.name === "Default" || entry.name.startsWith("Profile "))).map((entry) => path7.join(root, entry.name)).filter((dir) => PROFILE_PREFERENCES_FILES.some((filename) => fs9.existsSync(path7.join(dir, filename))));
1046
+ } catch {
1047
+ return [];
1048
+ }
1049
+ };
1050
+ var readProfilePreferences = (profileDir) => {
1051
+ const records = [];
1052
+ for (const filename of PROFILE_PREFERENCES_FILES) {
1053
+ try {
1054
+ const raw = fs9.readFileSync(path7.join(profileDir, filename), "utf8");
1055
+ records.push(JSON.parse(raw));
1056
+ } catch {
1073
1057
  }
1074
- const url = new URL(request.url ?? "/", "http://127.0.0.1");
1075
- if (request.method === "GET" && url.pathname === "/status") {
1076
- sendJson(response, 200, {
1077
- ok: true,
1078
- pid: process.pid,
1079
- relay: core.relay.status()
1080
- });
1081
- return;
1058
+ }
1059
+ return records;
1060
+ };
1061
+ var findExtensionIdInCommands = (preferences) => {
1062
+ const extensionCommands = preferences.extensions;
1063
+ const commandMaps = [
1064
+ extensionCommands?.commands,
1065
+ preferences.account_values?.extensions?.commands
1066
+ ];
1067
+ for (const commandMap of commandMaps) {
1068
+ if (!commandMap) {
1069
+ continue;
1082
1070
  }
1083
- if (request.method === "POST" && url.pathname === "/stop") {
1084
- sendJson(response, 200, { ok: true });
1085
- await stop();
1086
- return;
1071
+ for (const value of Object.values(commandMap)) {
1072
+ if (typeof value !== "object" || value === null) {
1073
+ continue;
1074
+ }
1075
+ const entry = value;
1076
+ const commandName = typeof entry.command_name === "string" ? entry.command_name : null;
1077
+ const extensionId = typeof entry.extension === "string" ? entry.extension : null;
1078
+ if (commandName === ANNOTATION_COMMAND_NAME && extensionId && EXTENSION_ID_RE.test(extensionId)) {
1079
+ return extensionId;
1080
+ }
1087
1081
  }
1088
- if (request.method === "POST" && url.pathname === "/command") {
1089
- try {
1090
- const body = await readJson(request);
1091
- const data = await handleDaemonCommand(core, body);
1092
- sendJson(response, 200, { ok: true, data });
1093
- } catch (error) {
1094
- const message = error instanceof Error ? error.message : String(error);
1095
- sendJson(response, 400, { ok: false, error: message });
1082
+ }
1083
+ return null;
1084
+ };
1085
+ var findExtensionIdInPreferences = (preferences, extensionPath) => {
1086
+ const extensions = preferences.extensions;
1087
+ const settings = extensions?.settings;
1088
+ if (!settings) return null;
1089
+ const normalizedTargetPath = extensionPath ? normalizePath(extensionPath) : null;
1090
+ let nameMatch = null;
1091
+ for (const [id, entry] of Object.entries(settings)) {
1092
+ if (!EXTENSION_ID_RE.test(id) || typeof entry !== "object" || entry === null) {
1093
+ continue;
1094
+ }
1095
+ const record = entry;
1096
+ const recordPath = typeof record.path === "string" ? record.path : null;
1097
+ if (recordPath && normalizedTargetPath) {
1098
+ if (normalizePath(recordPath) === normalizedTargetPath) {
1099
+ return { id, matchedBy: "path" };
1096
1100
  }
1097
- return;
1098
1101
  }
1099
- sendJson(response, 404, { error: "Not found" });
1100
- });
1101
- await new Promise((resolve2, reject) => {
1102
- server.once("error", reject);
1103
- server.listen(port, "127.0.0.1", () => resolve2());
1104
- });
1105
- const state = {
1106
- port,
1107
- token,
1108
- pid: process.pid,
1109
- relayPort: config.relayPort,
1110
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
1111
- };
1112
- writeDaemonMetadata(state);
1113
- const stop = async () => {
1114
- clearDaemonMetadata();
1115
- core.cleanup();
1116
- await new Promise((resolve2) => {
1117
- server.close(() => resolve2());
1118
- });
1102
+ const manifest = record.manifest;
1103
+ const name = typeof manifest?.name === "string" ? manifest.name : null;
1104
+ if (!nameMatch && name === EXTENSION_NAME) {
1105
+ nameMatch = id;
1106
+ }
1107
+ }
1108
+ if (nameMatch) {
1109
+ return { id: nameMatch, matchedBy: "name" };
1110
+ }
1111
+ return null;
1112
+ };
1113
+ var getExtensionPathCandidates = () => {
1114
+ const candidates = /* @__PURE__ */ new Set();
1115
+ const primary = getExtensionPath();
1116
+ if (primary) {
1117
+ candidates.add(normalizePath(primary));
1118
+ }
1119
+ const cwdExtension = path7.join(process.cwd(), "extension");
1120
+ if (fs9.existsSync(path7.join(cwdExtension, "manifest.json"))) {
1121
+ candidates.add(normalizePath(cwdExtension));
1122
+ }
1123
+ if (candidates.size === 0) {
1124
+ return [null];
1125
+ }
1126
+ return [...candidates];
1127
+ };
1128
+ var getNativeStatusSnapshot = () => {
1129
+ const hostScript = getHostScriptPath();
1130
+ const manifestPath = getManifestPath();
1131
+ const wrapperPath = getWrapperPath();
1132
+ const registryPath = readRegistryPath();
1133
+ let installed = false;
1134
+ let manifestExists = false;
1135
+ let wrapperExists = false;
1136
+ let extensionIdValue = null;
1137
+ if (fs9.existsSync(manifestPath)) {
1138
+ manifestExists = true;
1139
+ installed = true;
1140
+ const manifest = readManifest(manifestPath);
1141
+ extensionIdValue = manifest.extensionId;
1142
+ }
1143
+ if (fs9.existsSync(wrapperPath)) {
1144
+ wrapperExists = true;
1145
+ }
1146
+ if (!manifestExists || !wrapperExists) {
1147
+ installed = false;
1148
+ }
1149
+ if (process.platform === "win32" && !registryPath) {
1150
+ installed = false;
1151
+ }
1152
+ return {
1153
+ installed,
1154
+ manifestPath: manifestExists ? manifestPath : null,
1155
+ wrapperPath: wrapperExists ? wrapperPath : null,
1156
+ hostScriptPath: hostScript,
1157
+ extensionId: extensionIdValue,
1158
+ registryPath
1119
1159
  };
1120
- process.on("SIGINT", () => {
1121
- stop().catch(() => {
1122
- });
1123
- });
1124
- process.on("SIGTERM", () => {
1125
- stop().catch(() => {
1126
- });
1127
- });
1128
- return { state, stop };
1129
- }
1130
- function readJson(request) {
1131
- return new Promise((resolve2, reject) => {
1132
- let data = "";
1133
- request.setEncoding("utf8");
1134
- request.on("data", (chunk) => {
1135
- data += chunk;
1136
- });
1137
- request.on("end", () => {
1138
- try {
1139
- const parsed = JSON.parse(data || "{}");
1140
- if (!parsed || typeof parsed !== "object") {
1141
- reject(new Error("Invalid JSON body"));
1142
- return;
1160
+ };
1161
+ function discoverExtensionId() {
1162
+ const extensionPaths = getExtensionPathCandidates();
1163
+ const roots = getChromeUserDataRoots();
1164
+ for (const root of roots) {
1165
+ for (const profileDir of getProfileDirs(root)) {
1166
+ let nameFallback = null;
1167
+ let commandFallback = null;
1168
+ for (const preferences of readProfilePreferences(profileDir)) {
1169
+ for (const extensionPath of extensionPaths) {
1170
+ const match = findExtensionIdInPreferences(preferences, extensionPath);
1171
+ if (!match) {
1172
+ continue;
1173
+ }
1174
+ if (match.matchedBy === "path") {
1175
+ return { extensionId: match.id, matchedBy: match.matchedBy };
1176
+ }
1177
+ if (!nameFallback) {
1178
+ nameFallback = match;
1179
+ }
1180
+ }
1181
+ if (!commandFallback) {
1182
+ commandFallback = findExtensionIdInCommands(preferences);
1143
1183
  }
1144
- resolve2(parsed);
1145
- } catch (error) {
1146
- reject(error);
1147
1184
  }
1148
- });
1149
- request.on("error", reject);
1150
- });
1185
+ if (nameFallback) {
1186
+ return { extensionId: nameFallback.id, matchedBy: nameFallback.matchedBy };
1187
+ }
1188
+ if (commandFallback) {
1189
+ return { extensionId: commandFallback, matchedBy: "command" };
1190
+ }
1191
+ }
1192
+ }
1193
+ return { extensionId: null };
1194
+ }
1195
+ function installNativeHost(extensionId) {
1196
+ const normalized = normalizeExtensionId(extensionId);
1197
+ if (!normalized) {
1198
+ return {
1199
+ success: false,
1200
+ message: "Invalid extension ID format. Expected 32 characters (a-p).",
1201
+ exitCode: EXIT_EXECUTION
1202
+ };
1203
+ }
1204
+ const hostScript = getHostScriptPath();
1205
+ if (!fs9.existsSync(hostScript)) {
1206
+ return {
1207
+ success: false,
1208
+ message: `Native host not found at ${hostScript}.`,
1209
+ exitCode: EXIT_EXECUTION
1210
+ };
1211
+ }
1212
+ const scriptsDir = getScriptsDir();
1213
+ const manifestPath = getManifestPath();
1214
+ const installScript = process.platform === "win32" ? path7.join(scriptsDir, "install.ps1") : path7.join(scriptsDir, "install.sh");
1215
+ try {
1216
+ runScript(installScript, [normalized]);
1217
+ return {
1218
+ success: true,
1219
+ message: `Native host installed for extension ${normalized}.`,
1220
+ data: { manifestPath }
1221
+ };
1222
+ } catch (error) {
1223
+ const message = error instanceof Error ? error.message : String(error);
1224
+ return {
1225
+ success: false,
1226
+ message: `Native install failed: ${message}`,
1227
+ exitCode: EXIT_EXECUTION
1228
+ };
1229
+ }
1230
+ }
1231
+ async function runNativeCommand(args) {
1232
+ const { subcommand, extensionId } = parseNativeArgs(args.rawArgs);
1233
+ const scriptsDir = getScriptsDir();
1234
+ const uninstallScript = process.platform === "win32" ? path7.join(scriptsDir, "uninstall.ps1") : path7.join(scriptsDir, "uninstall.sh");
1235
+ if (subcommand === "install") {
1236
+ return installNativeHost(extensionId);
1237
+ }
1238
+ if (subcommand === "uninstall") {
1239
+ try {
1240
+ runScript(uninstallScript, []);
1241
+ return { success: true, message: "Native host uninstalled." };
1242
+ } catch (error) {
1243
+ const message2 = error instanceof Error ? error.message : String(error);
1244
+ return { success: false, message: `Native uninstall failed: ${message2}`, exitCode: EXIT_EXECUTION };
1245
+ }
1246
+ }
1247
+ const data = getNativeStatusSnapshot();
1248
+ if (!data.installed) {
1249
+ return {
1250
+ success: false,
1251
+ message: "Native host not installed.",
1252
+ data,
1253
+ exitCode: EXIT_DISCONNECTED
1254
+ };
1255
+ }
1256
+ const message = data.extensionId ? `Native host installed for extension ${data.extensionId}.` : "Native host installed (extension id missing).";
1257
+ return { success: true, message, data };
1151
1258
  }
1152
1259
 
1153
1260
  // src/cli/commands/serve.ts
1261
+ var daemonHandle = null;
1154
1262
  function parseServeArgs(rawArgs) {
1155
1263
  const parsed = { stop: false };
1156
1264
  for (let i = 0; i < rawArgs.length; i += 1) {
@@ -1164,12 +1272,16 @@ function parseServeArgs(rawArgs) {
1164
1272
  if (!value) {
1165
1273
  throw createUsageError("Missing value for --port");
1166
1274
  }
1167
- parsed.port = Number(value);
1275
+ parsed.port = parseNumberFlag(value, "--port", { min: 1, max: 65535 });
1168
1276
  i += 1;
1169
1277
  continue;
1170
1278
  }
1171
1279
  if (arg?.startsWith("--port=")) {
1172
- parsed.port = Number(arg.split("=", 2)[1]);
1280
+ const value = arg.split("=", 2)[1];
1281
+ if (!value) {
1282
+ throw createUsageError("Missing value for --port");
1283
+ }
1284
+ parsed.port = parseNumberFlag(value, "--port", { min: 1, max: 65535 });
1173
1285
  continue;
1174
1286
  }
1175
1287
  if (arg === "--token") {
@@ -1182,7 +1294,11 @@ function parseServeArgs(rawArgs) {
1182
1294
  continue;
1183
1295
  }
1184
1296
  if (arg?.startsWith("--token=")) {
1185
- parsed.token = arg.split("=", 2)[1];
1297
+ const value = arg.split("=", 2)[1];
1298
+ if (!value) {
1299
+ throw createUsageError("Missing value for --token");
1300
+ }
1301
+ parsed.token = value;
1186
1302
  continue;
1187
1303
  }
1188
1304
  }
@@ -1193,10 +1309,15 @@ async function runServe(args) {
1193
1309
  if (serveArgs.stop) {
1194
1310
  const metadata = readDaemonMetadata();
1195
1311
  if (!metadata) {
1312
+ if (daemonHandle) {
1313
+ await daemonHandle.stop();
1314
+ daemonHandle = null;
1315
+ return { success: true, message: "Daemon stopped." };
1316
+ }
1196
1317
  return { success: false, message: "Daemon not running.", exitCode: EXIT_DISCONNECTED };
1197
1318
  }
1198
1319
  try {
1199
- const response = await fetch(`http://127.0.0.1:${metadata.port}/stop`, {
1320
+ const response = await fetchWithTimeout(`http://127.0.0.1:${metadata.port}/stop`, {
1200
1321
  method: "POST",
1201
1322
  headers: { Authorization: `Bearer ${metadata.token}` }
1202
1323
  });
@@ -1205,22 +1326,367 @@ async function runServe(args) {
1205
1326
  }
1206
1327
  return { success: true, message: "Daemon stopped." };
1207
1328
  } catch (error) {
1208
- const message = error instanceof Error ? error.message : String(error);
1209
- return { success: false, message: `Failed to stop daemon: ${message}`, exitCode: EXIT_EXECUTION };
1329
+ const message2 = error instanceof Error ? error.message : String(error);
1330
+ return { success: false, message: `Failed to stop daemon: ${message2}`, exitCode: EXIT_EXECUTION };
1331
+ }
1332
+ }
1333
+ const config = loadGlobalConfig();
1334
+ let nativeStatus = getNativeStatusSnapshot();
1335
+ let nativeMessage = null;
1336
+ if (!nativeStatus.installed) {
1337
+ const discovered = discoverExtensionId();
1338
+ const extensionId = config.nativeExtensionId ?? discovered.extensionId ?? null;
1339
+ const usedDiscovery = !config.nativeExtensionId && Boolean(discovered.extensionId);
1340
+ if (extensionId) {
1341
+ const installResult = installNativeHost(extensionId);
1342
+ if (installResult.success) {
1343
+ const suffix = usedDiscovery && discovered.matchedBy ? ` (auto-detected by ${discovered.matchedBy})` : "";
1344
+ nativeMessage = `${installResult.message ?? "Native host installed."}${suffix}`;
1345
+ nativeStatus = getNativeStatusSnapshot();
1346
+ } else {
1347
+ nativeMessage = `Native host install skipped: ${installResult.message ?? "unknown error"}`;
1348
+ }
1349
+ } else {
1350
+ nativeMessage = "Native host not installed. Set nativeExtensionId in opendevbrowser.jsonc to auto-install.";
1210
1351
  }
1211
1352
  }
1212
- const { state } = await startDaemon({
1353
+ const handle = await startDaemon({
1213
1354
  port: serveArgs.port,
1214
- token: serveArgs.token
1355
+ token: serveArgs.token,
1356
+ config
1215
1357
  });
1358
+ daemonHandle = handle;
1359
+ const { state } = handle;
1360
+ const baseMessage = `Daemon running on 127.0.0.1:${state.port} (relay ${state.relayPort})`;
1361
+ const message = nativeMessage ? `${baseMessage}
1362
+ ${nativeMessage}` : baseMessage;
1216
1363
  return {
1217
1364
  success: true,
1218
- message: `Daemon running on 127.0.0.1:${state.port}`,
1219
- data: { port: state.port, pid: state.pid, relayPort: state.relayPort },
1365
+ message,
1366
+ data: { port: state.port, pid: state.pid, relayPort: state.relayPort, native: nativeStatus },
1220
1367
  exitCode: null
1221
1368
  };
1222
1369
  }
1223
1370
 
1371
+ // src/cli/daemon-autostart.ts
1372
+ import { execFileSync as execFileSync2 } from "child_process";
1373
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
1374
+ import { homedir as homedir7 } from "os";
1375
+ import { dirname as dirname5, join as join8, resolve as resolve3 } from "path";
1376
+ import { fileURLToPath as fileURLToPath3 } from "url";
1377
+ var MAC_LABEL = "com.opendevbrowser.daemon";
1378
+ var WIN_TASK_NAME = "OpenDevBrowser Daemon";
1379
+ var defaultDeps = () => ({
1380
+ platform: process.platform,
1381
+ argv1: process.argv[1] ?? "",
1382
+ moduleUrl: import.meta.url,
1383
+ uid: typeof process.getuid === "function" ? process.getuid() : 0,
1384
+ homedir: homedir7,
1385
+ existsSync: existsSync8,
1386
+ mkdirSync: mkdirSync3,
1387
+ writeFileSync: writeFileSync4,
1388
+ unlinkSync: unlinkSync2,
1389
+ execFileSync: execFileSync2
1390
+ });
1391
+ var resolveCliPathFromModule = (moduleUrl, exists) => {
1392
+ const modulePath = fileURLToPath3(moduleUrl);
1393
+ const candidate = resolve3(dirname5(modulePath), "..", "index.js");
1394
+ if (!exists(candidate)) {
1395
+ throw new Error(`CLI entrypoint not found at ${candidate}`);
1396
+ }
1397
+ return candidate;
1398
+ };
1399
+ var resolveCliEntrypoint = (deps = {}) => {
1400
+ const resolved = { ...defaultDeps(), ...deps };
1401
+ const exists = resolved.existsSync;
1402
+ let cliPath = null;
1403
+ if (resolved.argv1) {
1404
+ const candidate = resolve3(resolved.argv1);
1405
+ if (exists(candidate)) {
1406
+ cliPath = candidate;
1407
+ }
1408
+ }
1409
+ if (!cliPath) {
1410
+ cliPath = resolveCliPathFromModule(resolved.moduleUrl, exists);
1411
+ }
1412
+ const nodePath = process.execPath;
1413
+ const args = [cliPath, "serve"];
1414
+ const command = `"${nodePath}" "${cliPath}" serve`;
1415
+ return { nodePath, cliPath, args, command };
1416
+ };
1417
+ var getLaunchAgentPath = (home = homedir7()) => {
1418
+ return join8(home, "Library", "LaunchAgents", `${MAC_LABEL}.plist`);
1419
+ };
1420
+ var buildLaunchAgentPlist = (entrypoint, options = {}) => {
1421
+ const label = options.label ?? MAC_LABEL;
1422
+ const stdoutPath = options.stdoutPath ?? join8(homedir7(), "Library", "Logs", "opendevbrowser-daemon.log");
1423
+ const stderrPath = options.stderrPath ?? join8(homedir7(), "Library", "Logs", "opendevbrowser-daemon.err.log");
1424
+ const programArgs = [entrypoint.nodePath, ...entrypoint.args].map((value) => ` <string>${value}</string>`).join("\n");
1425
+ return [
1426
+ '<?xml version="1.0" encoding="UTF-8"?>',
1427
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
1428
+ '<plist version="1.0">',
1429
+ "<dict>",
1430
+ ` <key>Label</key>`,
1431
+ ` <string>${label}</string>`,
1432
+ " <key>ProgramArguments</key>",
1433
+ " <array>",
1434
+ programArgs,
1435
+ " </array>",
1436
+ " <key>RunAtLoad</key>",
1437
+ " <true/>",
1438
+ " <key>KeepAlive</key>",
1439
+ " <true/>",
1440
+ " <key>StandardOutPath</key>",
1441
+ ` <string>${stdoutPath}</string>`,
1442
+ " <key>StandardErrorPath</key>",
1443
+ ` <string>${stderrPath}</string>`,
1444
+ "</dict>",
1445
+ "</plist>",
1446
+ ""
1447
+ ].join("\n");
1448
+ };
1449
+ var buildWindowsTaskArgs = (entrypoint, taskName = WIN_TASK_NAME) => {
1450
+ const command = `"${entrypoint.nodePath}" "${entrypoint.cliPath}" serve`;
1451
+ const args = [
1452
+ "/Create",
1453
+ "/TN",
1454
+ taskName,
1455
+ "/TR",
1456
+ command,
1457
+ "/SC",
1458
+ "ONLOGON",
1459
+ "/RL",
1460
+ "LIMITED",
1461
+ "/F"
1462
+ ];
1463
+ return { taskName, command, args };
1464
+ };
1465
+ var runCommand = (exec, command, args, ignoreFailure = false) => {
1466
+ try {
1467
+ exec(command, args, { stdio: "ignore" });
1468
+ } catch (error) {
1469
+ if (ignoreFailure) return;
1470
+ const message = error instanceof Error ? error.message : String(error);
1471
+ throw new Error(`${command} ${args.join(" ")} failed: ${message}`);
1472
+ }
1473
+ };
1474
+ var installMacAutostart = (deps = {}) => {
1475
+ const resolved = { ...defaultDeps(), ...deps };
1476
+ const entrypoint = resolveCliEntrypoint(resolved);
1477
+ const plistPath = getLaunchAgentPath(resolved.homedir());
1478
+ resolved.mkdirSync(dirname5(plistPath), { recursive: true });
1479
+ resolved.writeFileSync(plistPath, buildLaunchAgentPlist(entrypoint), { encoding: "utf-8" });
1480
+ const uid = resolved.uid;
1481
+ runCommand(resolved.execFileSync, "launchctl", ["bootout", `gui/${uid}`, plistPath], true);
1482
+ runCommand(resolved.execFileSync, "launchctl", ["bootstrap", `gui/${uid}`, plistPath]);
1483
+ runCommand(resolved.execFileSync, "launchctl", ["enable", `gui/${uid}/${MAC_LABEL}`], true);
1484
+ runCommand(resolved.execFileSync, "launchctl", ["kickstart", "-k", `gui/${uid}/${MAC_LABEL}`], true);
1485
+ return {
1486
+ platform: "darwin",
1487
+ supported: true,
1488
+ installed: true,
1489
+ location: plistPath,
1490
+ label: MAC_LABEL,
1491
+ command: entrypoint.command
1492
+ };
1493
+ };
1494
+ var uninstallMacAutostart = (deps = {}) => {
1495
+ const resolved = { ...defaultDeps(), ...deps };
1496
+ const plistPath = getLaunchAgentPath(resolved.homedir());
1497
+ const uid = resolved.uid;
1498
+ runCommand(resolved.execFileSync, "launchctl", ["bootout", `gui/${uid}`, plistPath], true);
1499
+ if (resolved.existsSync(plistPath)) {
1500
+ resolved.unlinkSync(plistPath);
1501
+ }
1502
+ return {
1503
+ platform: "darwin",
1504
+ supported: true,
1505
+ installed: false,
1506
+ location: plistPath,
1507
+ label: MAC_LABEL
1508
+ };
1509
+ };
1510
+ var isWindowsTaskInstalled = (deps = {}) => {
1511
+ const resolved = { ...defaultDeps(), ...deps };
1512
+ try {
1513
+ resolved.execFileSync("schtasks", ["/Query", "/TN", WIN_TASK_NAME], { stdio: "ignore" });
1514
+ return true;
1515
+ } catch {
1516
+ return false;
1517
+ }
1518
+ };
1519
+ var installWindowsAutostart = (deps = {}) => {
1520
+ const resolved = { ...defaultDeps(), ...deps };
1521
+ const entrypoint = resolveCliEntrypoint(resolved);
1522
+ const { args } = buildWindowsTaskArgs(entrypoint, WIN_TASK_NAME);
1523
+ runCommand(resolved.execFileSync, "schtasks", args);
1524
+ return {
1525
+ platform: "win32",
1526
+ supported: true,
1527
+ installed: true,
1528
+ taskName: WIN_TASK_NAME,
1529
+ command: entrypoint.command
1530
+ };
1531
+ };
1532
+ var uninstallWindowsAutostart = (deps = {}) => {
1533
+ const resolved = { ...defaultDeps(), ...deps };
1534
+ runCommand(resolved.execFileSync, "schtasks", ["/Delete", "/TN", WIN_TASK_NAME, "/F"], true);
1535
+ return {
1536
+ platform: "win32",
1537
+ supported: true,
1538
+ installed: false,
1539
+ taskName: WIN_TASK_NAME
1540
+ };
1541
+ };
1542
+ var getAutostartStatus = (deps = {}) => {
1543
+ const resolved = { ...defaultDeps(), ...deps };
1544
+ const platform = resolved.platform;
1545
+ if (platform === "darwin") {
1546
+ const location = getLaunchAgentPath(resolved.homedir());
1547
+ return {
1548
+ platform,
1549
+ supported: true,
1550
+ installed: resolved.existsSync(location),
1551
+ location,
1552
+ label: MAC_LABEL
1553
+ };
1554
+ }
1555
+ if (platform === "win32") {
1556
+ return {
1557
+ platform,
1558
+ supported: true,
1559
+ installed: isWindowsTaskInstalled(resolved),
1560
+ taskName: WIN_TASK_NAME
1561
+ };
1562
+ }
1563
+ return {
1564
+ platform,
1565
+ supported: false,
1566
+ installed: false
1567
+ };
1568
+ };
1569
+ var installAutostart = (deps = {}) => {
1570
+ const platform = deps.platform ?? process.platform;
1571
+ if (platform === "darwin") {
1572
+ return installMacAutostart(deps);
1573
+ }
1574
+ if (platform === "win32") {
1575
+ return installWindowsAutostart(deps);
1576
+ }
1577
+ return {
1578
+ platform,
1579
+ supported: false,
1580
+ installed: false
1581
+ };
1582
+ };
1583
+ var uninstallAutostart = (deps = {}) => {
1584
+ const platform = deps.platform ?? process.platform;
1585
+ if (platform === "darwin") {
1586
+ return uninstallMacAutostart(deps);
1587
+ }
1588
+ if (platform === "win32") {
1589
+ return uninstallWindowsAutostart(deps);
1590
+ }
1591
+ return {
1592
+ platform,
1593
+ supported: false,
1594
+ installed: false
1595
+ };
1596
+ };
1597
+
1598
+ // src/cli/commands/daemon.ts
1599
+ var parseDaemonArgs = (rawArgs) => {
1600
+ const subcommand = rawArgs[0];
1601
+ if (subcommand === "install" || subcommand === "uninstall" || subcommand === "status") {
1602
+ return { subcommand };
1603
+ }
1604
+ throw createUsageError("Usage: opendevbrowser daemon <install|uninstall|status>");
1605
+ };
1606
+ var stopDaemonIfRunning = async () => {
1607
+ const metadata = readDaemonMetadata();
1608
+ if (!metadata) {
1609
+ return false;
1610
+ }
1611
+ try {
1612
+ const response = await fetchWithTimeout(`http://127.0.0.1:${metadata.port}/stop`, {
1613
+ method: "POST",
1614
+ headers: { Authorization: `Bearer ${metadata.token}` }
1615
+ });
1616
+ return response.ok;
1617
+ } catch {
1618
+ return false;
1619
+ }
1620
+ };
1621
+ var buildStatusMessage = (autostart, running) => {
1622
+ if (!autostart.supported) {
1623
+ return `Daemon autostart is not supported on ${autostart.platform}.`;
1624
+ }
1625
+ const installed = autostart.installed ? "installed" : "not installed";
1626
+ const runningText = running ? "running" : "not running";
1627
+ const location = autostart.location ? ` at ${autostart.location}` : "";
1628
+ const task = autostart.taskName ? ` (${autostart.taskName})` : "";
1629
+ return `Autostart ${installed}${location}${task}. Daemon is ${runningText}.`;
1630
+ };
1631
+ async function runDaemonCommand(args) {
1632
+ const { subcommand } = parseDaemonArgs(args.rawArgs);
1633
+ if (subcommand === "install") {
1634
+ const result = installAutostart();
1635
+ if (!result.supported) {
1636
+ return {
1637
+ success: false,
1638
+ message: `Daemon autostart is not supported on ${result.platform}.`,
1639
+ data: result,
1640
+ exitCode: EXIT_EXECUTION
1641
+ };
1642
+ }
1643
+ return {
1644
+ success: true,
1645
+ message: `Daemon autostart installed (${result.platform}).`,
1646
+ data: result
1647
+ };
1648
+ }
1649
+ if (subcommand === "uninstall") {
1650
+ const result = uninstallAutostart();
1651
+ if (!result.supported) {
1652
+ return {
1653
+ success: false,
1654
+ message: `Daemon autostart is not supported on ${result.platform}.`,
1655
+ data: result,
1656
+ exitCode: EXIT_EXECUTION
1657
+ };
1658
+ }
1659
+ await stopDaemonIfRunning();
1660
+ return {
1661
+ success: true,
1662
+ message: `Daemon autostart removed (${result.platform}).`,
1663
+ data: result
1664
+ };
1665
+ }
1666
+ const autostart = getAutostartStatus();
1667
+ const daemonStatus = await fetchDaemonStatusFromMetadata();
1668
+ const running = Boolean(daemonStatus);
1669
+ const message = buildStatusMessage(autostart, running);
1670
+ const data = {
1671
+ installed: autostart.installed,
1672
+ running,
1673
+ autostart: autostart.supported ? autostart : void 0
1674
+ };
1675
+ if (!running) {
1676
+ return {
1677
+ success: false,
1678
+ message,
1679
+ data,
1680
+ exitCode: EXIT_DISCONNECTED
1681
+ };
1682
+ }
1683
+ return {
1684
+ success: true,
1685
+ message,
1686
+ data: { ...data, status: daemonStatus }
1687
+ };
1688
+ }
1689
+
1224
1690
  // src/cli/commands/run.ts
1225
1691
  import { readFileSync as readFileSync4 } from "fs";
1226
1692
 
@@ -1302,7 +1768,9 @@ function parseRunArgs(rawArgs) {
1302
1768
  continue;
1303
1769
  }
1304
1770
  if (arg?.startsWith("--start-url=")) {
1305
- parsed.startUrl = arg.split("=", 2)[1];
1771
+ const value = arg.split("=", 2)[1];
1772
+ if (!value) throw createUsageError("Missing value for --start-url");
1773
+ parsed.startUrl = value;
1306
1774
  continue;
1307
1775
  }
1308
1776
  if (arg === "--flag") {
@@ -1313,20 +1781,22 @@ function parseRunArgs(rawArgs) {
1313
1781
  continue;
1314
1782
  }
1315
1783
  if (arg?.startsWith("--flag=")) {
1316
- parsed.flags.push(arg.split("=", 2)[1]);
1784
+ const value = arg.split("=", 2)[1];
1785
+ if (!value) throw createUsageError("Missing value for --flag");
1786
+ parsed.flags.push(value);
1317
1787
  continue;
1318
1788
  }
1319
1789
  }
1320
1790
  return parsed;
1321
1791
  }
1322
1792
  function readScriptFromStdin() {
1323
- return new Promise((resolve2, reject) => {
1793
+ return new Promise((resolve4, reject) => {
1324
1794
  let data = "";
1325
1795
  process.stdin.setEncoding("utf8");
1326
1796
  process.stdin.on("data", (chunk) => {
1327
1797
  data += chunk;
1328
1798
  });
1329
- process.stdin.on("end", () => resolve2(data));
1799
+ process.stdin.on("end", () => resolve4(data));
1330
1800
  process.stdin.on("error", reject);
1331
1801
  });
1332
1802
  }
@@ -1380,36 +1850,6 @@ async function runScriptCommand(args) {
1380
1850
  }
1381
1851
  }
1382
1852
 
1383
- // src/cli/client.ts
1384
- async function callDaemon(command, params) {
1385
- const metadata = readDaemonMetadata();
1386
- if (!metadata) {
1387
- throw createDisconnectedError("Daemon not running. Start with `opendevbrowser serve`.");
1388
- }
1389
- let response;
1390
- try {
1391
- response = await fetch(`http://127.0.0.1:${metadata.port}/command`, {
1392
- method: "POST",
1393
- headers: {
1394
- "Content-Type": "application/json",
1395
- Authorization: `Bearer ${metadata.token}`
1396
- },
1397
- body: JSON.stringify({ name: command, params: params ?? {} })
1398
- });
1399
- } catch {
1400
- throw createDisconnectedError("Daemon not running. Start with `opendevbrowser serve`.");
1401
- }
1402
- if (!response.ok) {
1403
- const message = await response.text();
1404
- throw new CliError(`Daemon error: ${message || response.status}`, EXIT_EXECUTION);
1405
- }
1406
- const payload = await response.json();
1407
- if (!payload.ok) {
1408
- throw new CliError(payload.error || "Daemon command failed.", EXIT_EXECUTION);
1409
- }
1410
- return payload.data;
1411
- }
1412
-
1413
1853
  // src/cli/commands/session/launch.ts
1414
1854
  function parseLaunchArgs(rawArgs) {
1415
1855
  const parsed = { flags: [] };
@@ -1464,6 +1904,10 @@ function parseLaunchArgs(rawArgs) {
1464
1904
  parsed.extensionOnly = true;
1465
1905
  continue;
1466
1906
  }
1907
+ if (arg === "--extension-legacy") {
1908
+ parsed.extensionLegacy = true;
1909
+ continue;
1910
+ }
1467
1911
  if (arg === "--wait-for-extension") {
1468
1912
  parsed.waitForExtension = true;
1469
1913
  continue;
@@ -1471,12 +1915,14 @@ function parseLaunchArgs(rawArgs) {
1471
1915
  if (arg === "--wait-timeout-ms") {
1472
1916
  const value = rawArgs[i + 1];
1473
1917
  if (!value) throw createUsageError("Missing value for --wait-timeout-ms");
1474
- parsed.waitTimeoutMs = Number(value);
1918
+ parsed.waitTimeoutMs = parseNumberFlag(value, "--wait-timeout-ms", { min: 1 });
1475
1919
  i += 1;
1476
1920
  continue;
1477
1921
  }
1478
1922
  if (arg?.startsWith("--wait-timeout-ms=")) {
1479
- parsed.waitTimeoutMs = Number(arg.split("=", 2)[1]);
1923
+ const value = arg.split("=", 2)[1];
1924
+ if (!value) throw createUsageError("Missing value for --wait-timeout-ms");
1925
+ parsed.waitTimeoutMs = parseNumberFlag(value, "--wait-timeout-ms", { min: 1 });
1480
1926
  continue;
1481
1927
  }
1482
1928
  if (arg === "--flag") {
@@ -1487,7 +1933,9 @@ function parseLaunchArgs(rawArgs) {
1487
1933
  continue;
1488
1934
  }
1489
1935
  if (arg?.startsWith("--flag=")) {
1490
- parsed.flags.push(arg.split("=", 2)[1]);
1936
+ const value = arg.split("=", 2)[1];
1937
+ if (!value) throw createUsageError("Missing value for --flag");
1938
+ parsed.flags.push(value);
1491
1939
  continue;
1492
1940
  }
1493
1941
  }
@@ -1495,12 +1943,82 @@ function parseLaunchArgs(rawArgs) {
1495
1943
  }
1496
1944
  async function runSessionLaunch(args) {
1497
1945
  const launchArgs = parseLaunchArgs(args.rawArgs);
1498
- const result = await callDaemon("session.launch", launchArgs);
1499
- return {
1500
- success: true,
1501
- message: `Session launched: ${result.sessionId}`,
1502
- data: result
1503
- };
1946
+ try {
1947
+ const result = await callDaemon("session.launch", launchArgs);
1948
+ return {
1949
+ success: true,
1950
+ message: `Session launched: ${result.sessionId}`,
1951
+ data: result
1952
+ };
1953
+ } catch (error) {
1954
+ if (args.noInteractive) {
1955
+ throw error;
1956
+ }
1957
+ const message = error instanceof Error ? error.message : "";
1958
+ const lower = message.toLowerCase();
1959
+ const isExtensionFailure = message.includes("Extension not connected") || message.includes("Extension relay connection failed") || lower.includes("unauthorized");
1960
+ if (!isExtensionFailure) {
1961
+ throw error;
1962
+ }
1963
+ const retry = await promptYesNo(
1964
+ lower.includes("unauthorized") ? "Relay token mismatch detected. Open the extension popup and click Connect to refresh pairing, then retry now?" : "Extension not connected. Open the extension popup and click Connect, then retry now?",
1965
+ false
1966
+ );
1967
+ if (retry) {
1968
+ try {
1969
+ const result = await callDaemon("session.launch", { ...launchArgs, waitForExtension: true });
1970
+ return {
1971
+ success: true,
1972
+ message: `Session launched: ${result.sessionId}`,
1973
+ data: result
1974
+ };
1975
+ } catch (retryError) {
1976
+ error = retryError;
1977
+ }
1978
+ }
1979
+ const proceedManaged = await promptYesNo("Proceed with a managed session (headed)?", false);
1980
+ if (proceedManaged) {
1981
+ const useHeadless = await promptYesNo("Run headless instead?", false);
1982
+ const result = await callDaemon("session.launch", {
1983
+ ...launchArgs,
1984
+ noExtension: true,
1985
+ headless: useHeadless ? true : false
1986
+ });
1987
+ return {
1988
+ success: true,
1989
+ message: `Session launched: ${result.sessionId}`,
1990
+ data: result
1991
+ };
1992
+ }
1993
+ const proceedCdp = await promptYesNo("Proceed with CDPConnect (requires Chrome --remote-debugging-port=9222)?", false);
1994
+ if (proceedCdp) {
1995
+ const result = await callDaemon("session.connect", {});
1996
+ return {
1997
+ success: true,
1998
+ message: `Session connected: ${result.sessionId}`,
1999
+ data: result
2000
+ };
2001
+ }
2002
+ throw error;
2003
+ }
2004
+ }
2005
+ function promptYesNo(question, defaultYes) {
2006
+ if (!process.stdin.isTTY) {
2007
+ return Promise.resolve(false);
2008
+ }
2009
+ const suffix = defaultYes ? " [Y/n] " : " [y/N] ";
2010
+ return new Promise((resolve4) => {
2011
+ process.stdout.write(`${question}${suffix}`);
2012
+ process.stdin.setEncoding("utf8");
2013
+ process.stdin.once("data", (data) => {
2014
+ const input = data.toString().trim().toLowerCase();
2015
+ if (!input) {
2016
+ resolve4(defaultYes);
2017
+ return;
2018
+ }
2019
+ resolve4(input === "y" || input === "yes");
2020
+ });
2021
+ });
1504
2022
  }
1505
2023
 
1506
2024
  // src/cli/commands/session/connect.ts
@@ -1527,18 +2045,26 @@ function parseConnectArgs(rawArgs) {
1527
2045
  continue;
1528
2046
  }
1529
2047
  if (arg?.startsWith("--host=")) {
1530
- parsed.host = arg.split("=", 2)[1];
2048
+ const value = arg.split("=", 2)[1];
2049
+ if (!value) throw createUsageError("Missing value for --host");
2050
+ parsed.host = value;
1531
2051
  continue;
1532
2052
  }
1533
2053
  if (arg === "--cdp-port") {
1534
2054
  const value = rawArgs[i + 1];
1535
2055
  if (!value) throw createUsageError("Missing value for --cdp-port");
1536
- parsed.port = Number(value);
2056
+ parsed.port = parseNumberFlag(value, "--cdp-port", { min: 1, max: 65535 });
1537
2057
  i += 1;
1538
2058
  continue;
1539
2059
  }
1540
2060
  if (arg?.startsWith("--cdp-port=")) {
1541
- parsed.port = Number(arg.split("=", 2)[1]);
2061
+ const value = arg.split("=", 2)[1];
2062
+ if (!value) throw createUsageError("Missing value for --cdp-port");
2063
+ parsed.port = parseNumberFlag(value, "--cdp-port", { min: 1, max: 65535 });
2064
+ continue;
2065
+ }
2066
+ if (arg === "--extension-legacy") {
2067
+ parsed.extensionLegacy = true;
1542
2068
  continue;
1543
2069
  }
1544
2070
  }
@@ -1582,7 +2108,7 @@ async function runSessionDisconnect(args) {
1582
2108
  if (!sessionId) {
1583
2109
  throw createUsageError("Missing --session-id");
1584
2110
  }
1585
- await callDaemon("session.disconnect", { sessionId, closeBrowser });
2111
+ await callDaemon("session.disconnect", { sessionId, closeBrowser }, { timeoutMs: 2e4 });
1586
2112
  return { success: true, message: `Session disconnected: ${sessionId}` };
1587
2113
  }
1588
2114
 
@@ -1614,6 +2140,76 @@ async function runSessionStatus(args) {
1614
2140
  return { success: true, message: `Session status: ${sessionId}`, data: result };
1615
2141
  }
1616
2142
 
2143
+ // src/cli/commands/status.ts
2144
+ var parseStatusArgs2 = (rawArgs) => {
2145
+ const parsed = { daemon: false };
2146
+ for (let i = 0; i < rawArgs.length; i += 1) {
2147
+ const arg = rawArgs[i];
2148
+ if (arg === "--daemon") {
2149
+ parsed.daemon = true;
2150
+ continue;
2151
+ }
2152
+ if (arg === "--session-id") {
2153
+ const value = rawArgs[i + 1];
2154
+ if (!value) throw createUsageError("Missing value for --session-id");
2155
+ parsed.sessionId = value;
2156
+ i += 1;
2157
+ continue;
2158
+ }
2159
+ if (arg?.startsWith("--session-id=")) {
2160
+ parsed.sessionId = arg.split("=", 2)[1];
2161
+ continue;
2162
+ }
2163
+ }
2164
+ return parsed;
2165
+ };
2166
+ async function runStatus(args) {
2167
+ const { sessionId, daemon } = parseStatusArgs2(args.rawArgs);
2168
+ if (sessionId && daemon) {
2169
+ throw createUsageError("Use --session-id or --daemon, not both.");
2170
+ }
2171
+ if (sessionId) {
2172
+ return runSessionStatus(args);
2173
+ }
2174
+ if (!daemon && args.transport === "native") {
2175
+ const nativeStatus2 = getNativeStatusSnapshot();
2176
+ if (!nativeStatus2.installed) {
2177
+ return {
2178
+ success: false,
2179
+ message: "Native host not installed.",
2180
+ data: nativeStatus2,
2181
+ exitCode: EXIT_DISCONNECTED
2182
+ };
2183
+ }
2184
+ return {
2185
+ success: true,
2186
+ message: nativeStatus2.extensionId ? `Native host installed for extension ${nativeStatus2.extensionId}.` : "Native host installed.",
2187
+ data: nativeStatus2
2188
+ };
2189
+ }
2190
+ const daemonStatus = await fetchDaemonStatusFromMetadata();
2191
+ if (!daemonStatus) {
2192
+ throw createUsageError("Daemon not running. Start with `opendevbrowser serve`.");
2193
+ }
2194
+ const nativeStatus = getNativeStatusSnapshot();
2195
+ const baseMessage = [
2196
+ `Daemon OK (pid=${daemonStatus.pid})`,
2197
+ `Relay: port=${daemonStatus.relay.port ?? "n/a"} ext=${daemonStatus.relay.extensionConnected ? "on" : "off"} handshake=${daemonStatus.relay.extensionHandshakeComplete ? "on" : "off"} cdp=${daemonStatus.relay.cdpConnected ? "on" : "off"} annotate=${daemonStatus.relay.annotationConnected ? "on" : "off"} ops=${daemonStatus.relay.opsConnected ? "on" : "off"} pairing=${daemonStatus.relay.pairingRequired ? "on" : "off"} health=${daemonStatus.relay.health?.reason ?? "n/a"}`,
2198
+ `Native: ${nativeStatus.installed ? "installed" : "not installed"}${nativeStatus.extensionId ? ` (${nativeStatus.extensionId})` : ""}`,
2199
+ daemonStatus.relay.lastHandshakeError ? `Relay last handshake error: ${daemonStatus.relay.lastHandshakeError.code} (${daemonStatus.relay.lastHandshakeError.message})` : "Relay last handshake error: none",
2200
+ "Legend: ext=extension websocket, handshake=extension handshake, cdp=active /cdp client, annotate=annotation channel, ops=ops clients, pairing=token required, health=relay status"
2201
+ ].join("\n");
2202
+ const message = daemon || args.outputFormat !== "text" ? baseMessage : [
2203
+ "Warning: `status` defaults to daemon status. Use --daemon explicitly or --session-id for session status.",
2204
+ baseMessage
2205
+ ].join("\n");
2206
+ return {
2207
+ success: true,
2208
+ message,
2209
+ data: { ...daemonStatus, native: nativeStatus }
2210
+ };
2211
+ }
2212
+
1617
2213
  // src/cli/commands/nav/goto.ts
1618
2214
  function parseGotoArgs(rawArgs) {
1619
2215
  const parsed = {};
@@ -1655,12 +2251,14 @@ function parseGotoArgs(rawArgs) {
1655
2251
  if (arg === "--timeout-ms") {
1656
2252
  const value = rawArgs[i + 1];
1657
2253
  if (!value) throw createUsageError("Missing value for --timeout-ms");
1658
- parsed.timeoutMs = Number(value);
2254
+ parsed.timeoutMs = parseNumberFlag(value, "--timeout-ms", { min: 1 });
1659
2255
  i += 1;
1660
2256
  continue;
1661
2257
  }
1662
2258
  if (arg?.startsWith("--timeout-ms=")) {
1663
- parsed.timeoutMs = Number(arg.split("=", 2)[1]);
2259
+ const value = arg.split("=", 2)[1];
2260
+ if (!value) throw createUsageError("Missing value for --timeout-ms");
2261
+ parsed.timeoutMs = parseNumberFlag(value, "--timeout-ms", { min: 1 });
1664
2262
  continue;
1665
2263
  }
1666
2264
  }
@@ -1726,12 +2324,14 @@ function parseWaitArgs(rawArgs) {
1726
2324
  if (arg === "--timeout-ms") {
1727
2325
  const value = rawArgs[i + 1];
1728
2326
  if (!value) throw createUsageError("Missing value for --timeout-ms");
1729
- parsed.timeoutMs = Number(value);
2327
+ parsed.timeoutMs = parseNumberFlag(value, "--timeout-ms", { min: 1 });
1730
2328
  i += 1;
1731
2329
  continue;
1732
2330
  }
1733
2331
  if (arg?.startsWith("--timeout-ms=")) {
1734
- parsed.timeoutMs = Number(arg.split("=", 2)[1]);
2332
+ const value = arg.split("=", 2)[1];
2333
+ if (!value) throw createUsageError("Missing value for --timeout-ms");
2334
+ parsed.timeoutMs = parseNumberFlag(value, "--timeout-ms", { min: 1 });
1735
2335
  continue;
1736
2336
  }
1737
2337
  }
@@ -1803,104 +2403,1030 @@ async function runSnapshot(args) {
1803
2403
  return { success: true, message: "Snapshot captured.", data: result };
1804
2404
  }
1805
2405
 
1806
- // src/cli/commands/interact/click.ts
1807
- function parseClickArgs(rawArgs) {
2406
+ // src/cli/commands/annotate.ts
2407
+ var requireValue = (value, flag) => {
2408
+ if (!value) throw createUsageError(`Missing value for ${flag}`);
2409
+ return value;
2410
+ };
2411
+ var requireScreenshotMode = (value) => {
2412
+ if (value === "visible" || value === "full" || value === "none") {
2413
+ return value;
2414
+ }
2415
+ throw createUsageError(`Invalid --screenshot-mode: ${value}`);
2416
+ };
2417
+ var requireTransport = (value) => {
2418
+ if (value === "auto" || value === "direct" || value === "relay") {
2419
+ return value;
2420
+ }
2421
+ throw createUsageError(`Invalid --transport: ${value}`);
2422
+ };
2423
+ var parseAnnotateArgs = (rawArgs) => {
1808
2424
  const parsed = {};
1809
2425
  for (let i = 0; i < rawArgs.length; i += 1) {
1810
2426
  const arg = rawArgs[i];
1811
2427
  if (arg === "--session-id") {
1812
- const value = rawArgs[i + 1];
1813
- if (!value) throw createUsageError("Missing value for --session-id");
2428
+ const value = requireValue(rawArgs[i + 1], "--session-id");
1814
2429
  parsed.sessionId = value;
1815
2430
  i += 1;
1816
2431
  continue;
1817
2432
  }
1818
2433
  if (arg?.startsWith("--session-id=")) {
1819
- parsed.sessionId = arg.split("=", 2)[1];
2434
+ const value = requireValue(arg.split("=", 2)[1], "--session-id");
2435
+ parsed.sessionId = value;
1820
2436
  continue;
1821
2437
  }
1822
- if (arg === "--ref") {
1823
- const value = rawArgs[i + 1];
1824
- if (!value) throw createUsageError("Missing value for --ref");
1825
- parsed.ref = value;
2438
+ if (arg === "--url") {
2439
+ const value = requireValue(rawArgs[i + 1], "--url");
2440
+ parsed.url = value;
1826
2441
  i += 1;
1827
2442
  continue;
1828
2443
  }
1829
- if (arg?.startsWith("--ref=")) {
1830
- parsed.ref = arg.split("=", 2)[1];
2444
+ if (arg?.startsWith("--url=")) {
2445
+ const value = requireValue(arg.split("=", 2)[1], "--url");
2446
+ parsed.url = value;
1831
2447
  continue;
1832
2448
  }
1833
- }
1834
- return parsed;
1835
- }
1836
- async function runClick(args) {
1837
- const { sessionId, ref } = parseClickArgs(args.rawArgs);
1838
- if (!sessionId) throw createUsageError("Missing --session-id");
1839
- if (!ref) throw createUsageError("Missing --ref");
1840
- const result = await callDaemon("interact.click", { sessionId, ref });
1841
- return { success: true, message: "Click complete.", data: result };
1842
- }
1843
-
1844
- // src/cli/commands/interact/type.ts
1845
- function parseTypeArgs(rawArgs) {
1846
- const parsed = {};
1847
- for (let i = 0; i < rawArgs.length; i += 1) {
1848
- const arg = rawArgs[i];
1849
- if (arg === "--session-id") {
1850
- const value = rawArgs[i + 1];
1851
- if (!value) throw createUsageError("Missing value for --session-id");
1852
- parsed.sessionId = value;
2449
+ if (arg === "--screenshot-mode") {
2450
+ const value = requireValue(rawArgs[i + 1], "--screenshot-mode");
2451
+ parsed.screenshotMode = requireScreenshotMode(value);
1853
2452
  i += 1;
1854
2453
  continue;
1855
2454
  }
1856
- if (arg?.startsWith("--session-id=")) {
1857
- parsed.sessionId = arg.split("=", 2)[1];
2455
+ if (arg?.startsWith("--screenshot-mode=")) {
2456
+ const value = requireValue(arg.split("=", 2)[1], "--screenshot-mode");
2457
+ parsed.screenshotMode = requireScreenshotMode(value);
1858
2458
  continue;
1859
2459
  }
1860
- if (arg === "--ref") {
1861
- const value = rawArgs[i + 1];
1862
- if (!value) throw createUsageError("Missing value for --ref");
1863
- parsed.ref = value;
2460
+ if (arg === "--transport") {
2461
+ const value = requireValue(rawArgs[i + 1], "--transport");
2462
+ parsed.transport = requireTransport(value);
1864
2463
  i += 1;
1865
2464
  continue;
1866
2465
  }
1867
- if (arg?.startsWith("--ref=")) {
1868
- parsed.ref = arg.split("=", 2)[1];
2466
+ if (arg?.startsWith("--transport=")) {
2467
+ const value = requireValue(arg.split("=", 2)[1], "--transport");
2468
+ parsed.transport = requireTransport(value);
1869
2469
  continue;
1870
2470
  }
1871
- if (arg === "--text") {
1872
- const value = rawArgs[i + 1];
1873
- if (!value) throw createUsageError("Missing value for --text");
1874
- parsed.text = value;
2471
+ if (arg === "--target-id") {
2472
+ const value = requireValue(rawArgs[i + 1], "--target-id");
2473
+ parsed.targetId = value;
1875
2474
  i += 1;
1876
2475
  continue;
1877
2476
  }
1878
- if (arg?.startsWith("--text=")) {
1879
- parsed.text = arg.split("=", 2)[1];
2477
+ if (arg?.startsWith("--target-id=")) {
2478
+ const value = requireValue(arg.split("=", 2)[1], "--target-id");
2479
+ parsed.targetId = value;
1880
2480
  continue;
1881
2481
  }
1882
- if (arg === "--clear") {
1883
- parsed.clear = true;
2482
+ if (arg === "--tab-id") {
2483
+ const value = requireValue(rawArgs[i + 1], "--tab-id");
2484
+ parsed.tabId = parseNumberFlag(value, "--tab-id", { min: 1 });
2485
+ i += 1;
1884
2486
  continue;
1885
2487
  }
1886
- if (arg === "--submit") {
2488
+ if (arg?.startsWith("--tab-id=")) {
2489
+ const value = requireValue(arg.split("=", 2)[1], "--tab-id");
2490
+ parsed.tabId = parseNumberFlag(value, "--tab-id", { min: 1 });
2491
+ continue;
2492
+ }
2493
+ if (arg === "--debug") {
2494
+ parsed.debug = true;
2495
+ continue;
2496
+ }
2497
+ if (arg === "--context") {
2498
+ const value = requireValue(rawArgs[i + 1], "--context");
2499
+ parsed.context = value;
2500
+ i += 1;
2501
+ continue;
2502
+ }
2503
+ if (arg?.startsWith("--context=")) {
2504
+ const value = requireValue(arg.split("=", 2)[1], "--context");
2505
+ parsed.context = value;
2506
+ continue;
2507
+ }
2508
+ if (arg === "--timeout-ms") {
2509
+ const value = requireValue(rawArgs[i + 1], "--timeout-ms");
2510
+ parsed.timeoutMs = parseNumberFlag(value, "--timeout-ms", { min: 1 });
2511
+ i += 1;
2512
+ continue;
2513
+ }
2514
+ if (arg?.startsWith("--timeout-ms=")) {
2515
+ const value = requireValue(arg.split("=", 2)[1], "--timeout-ms");
2516
+ parsed.timeoutMs = parseNumberFlag(value, "--timeout-ms", { min: 1 });
2517
+ continue;
2518
+ }
2519
+ }
2520
+ return parsed;
2521
+ };
2522
+ async function runAnnotate(args) {
2523
+ const { sessionId, url, screenshotMode, debug, context, timeoutMs, transport, targetId, tabId } = parseAnnotateArgs(args.rawArgs);
2524
+ if (!sessionId) throw createUsageError("Missing --session-id");
2525
+ const client = new DaemonClient({ autoRenew: true });
2526
+ const callTimeoutMs = typeof timeoutMs === "number" ? timeoutMs + 1e4 : void 0;
2527
+ try {
2528
+ const response = await client.call("annotate", {
2529
+ sessionId,
2530
+ transport,
2531
+ targetId,
2532
+ tabId,
2533
+ url,
2534
+ screenshotMode,
2535
+ debug,
2536
+ context,
2537
+ timeoutMs
2538
+ }, { timeoutMs: callTimeoutMs });
2539
+ if (response.status !== "ok" || !response.payload) {
2540
+ const message2 = response.error?.message ?? "Annotation failed.";
2541
+ throw new Error(message2);
2542
+ }
2543
+ const { message, details, screenshots } = await buildAnnotateResult(response.payload);
2544
+ return { success: true, message, data: { details, screenshots } };
2545
+ } finally {
2546
+ await client.releaseBinding().catch(() => {
2547
+ });
2548
+ }
2549
+ }
2550
+
2551
+ // src/cli/commands/interact/click.ts
2552
+ function parseClickArgs(rawArgs) {
2553
+ const parsed = {};
2554
+ for (let i = 0; i < rawArgs.length; i += 1) {
2555
+ const arg = rawArgs[i];
2556
+ if (arg === "--session-id") {
2557
+ const value = rawArgs[i + 1];
2558
+ if (!value) throw createUsageError("Missing value for --session-id");
2559
+ parsed.sessionId = value;
2560
+ i += 1;
2561
+ continue;
2562
+ }
2563
+ if (arg?.startsWith("--session-id=")) {
2564
+ parsed.sessionId = arg.split("=", 2)[1];
2565
+ continue;
2566
+ }
2567
+ if (arg === "--ref") {
2568
+ const value = rawArgs[i + 1];
2569
+ if (!value) throw createUsageError("Missing value for --ref");
2570
+ parsed.ref = value;
2571
+ i += 1;
2572
+ continue;
2573
+ }
2574
+ if (arg?.startsWith("--ref=")) {
2575
+ parsed.ref = arg.split("=", 2)[1];
2576
+ continue;
2577
+ }
2578
+ }
2579
+ return parsed;
2580
+ }
2581
+ async function runClick(args) {
2582
+ const { sessionId, ref } = parseClickArgs(args.rawArgs);
2583
+ if (!sessionId) throw createUsageError("Missing --session-id");
2584
+ if (!ref) throw createUsageError("Missing --ref");
2585
+ const result = await callDaemon("interact.click", { sessionId, ref });
2586
+ return { success: true, message: "Click complete.", data: result };
2587
+ }
2588
+
2589
+ // src/cli/commands/interact/hover.ts
2590
+ function parseHoverArgs(rawArgs) {
2591
+ const parsed = {};
2592
+ for (let i = 0; i < rawArgs.length; i += 1) {
2593
+ const arg = rawArgs[i];
2594
+ if (arg === "--session-id") {
2595
+ const value = rawArgs[i + 1];
2596
+ if (!value) throw createUsageError("Missing value for --session-id");
2597
+ parsed.sessionId = value;
2598
+ i += 1;
2599
+ continue;
2600
+ }
2601
+ if (arg?.startsWith("--session-id=")) {
2602
+ parsed.sessionId = arg.split("=", 2)[1];
2603
+ continue;
2604
+ }
2605
+ if (arg === "--ref") {
2606
+ const value = rawArgs[i + 1];
2607
+ if (!value) throw createUsageError("Missing value for --ref");
2608
+ parsed.ref = value;
2609
+ i += 1;
2610
+ continue;
2611
+ }
2612
+ if (arg?.startsWith("--ref=")) {
2613
+ parsed.ref = arg.split("=", 2)[1];
2614
+ continue;
2615
+ }
2616
+ }
2617
+ return parsed;
2618
+ }
2619
+ async function runHover(args) {
2620
+ const { sessionId, ref } = parseHoverArgs(args.rawArgs);
2621
+ if (!sessionId) throw createUsageError("Missing --session-id");
2622
+ if (!ref) throw createUsageError("Missing --ref");
2623
+ const result = await callDaemon("interact.hover", { sessionId, ref });
2624
+ return { success: true, message: "Hover complete.", data: result };
2625
+ }
2626
+
2627
+ // src/cli/commands/interact/press.ts
2628
+ function parsePressArgs(rawArgs) {
2629
+ const parsed = {};
2630
+ for (let i = 0; i < rawArgs.length; i += 1) {
2631
+ const arg = rawArgs[i];
2632
+ if (arg === "--session-id") {
2633
+ const value = rawArgs[i + 1];
2634
+ if (!value) throw createUsageError("Missing value for --session-id");
2635
+ parsed.sessionId = value;
2636
+ i += 1;
2637
+ continue;
2638
+ }
2639
+ if (arg?.startsWith("--session-id=")) {
2640
+ parsed.sessionId = arg.split("=", 2)[1];
2641
+ continue;
2642
+ }
2643
+ if (arg === "--key") {
2644
+ const value = rawArgs[i + 1];
2645
+ if (!value) throw createUsageError("Missing value for --key");
2646
+ parsed.key = value;
2647
+ i += 1;
2648
+ continue;
2649
+ }
2650
+ if (arg?.startsWith("--key=")) {
2651
+ parsed.key = arg.split("=", 2)[1];
2652
+ continue;
2653
+ }
2654
+ if (arg === "--ref") {
2655
+ const value = rawArgs[i + 1];
2656
+ if (!value) throw createUsageError("Missing value for --ref");
2657
+ parsed.ref = value;
2658
+ i += 1;
2659
+ continue;
2660
+ }
2661
+ if (arg?.startsWith("--ref=")) {
2662
+ parsed.ref = arg.split("=", 2)[1];
2663
+ continue;
2664
+ }
2665
+ }
2666
+ return parsed;
2667
+ }
2668
+ async function runPress(args) {
2669
+ const { sessionId, key, ref } = parsePressArgs(args.rawArgs);
2670
+ if (!sessionId) throw createUsageError("Missing --session-id");
2671
+ if (!key) throw createUsageError("Missing --key");
2672
+ const result = await callDaemon("interact.press", { sessionId, key, ref });
2673
+ return { success: true, message: "Key press complete.", data: result };
2674
+ }
2675
+
2676
+ // src/cli/commands/interact/check.ts
2677
+ function parseCheckArgs(rawArgs) {
2678
+ const parsed = {};
2679
+ for (let i = 0; i < rawArgs.length; i += 1) {
2680
+ const arg = rawArgs[i];
2681
+ if (arg === "--session-id") {
2682
+ const value = rawArgs[i + 1];
2683
+ if (!value) throw createUsageError("Missing value for --session-id");
2684
+ parsed.sessionId = value;
2685
+ i += 1;
2686
+ continue;
2687
+ }
2688
+ if (arg?.startsWith("--session-id=")) {
2689
+ parsed.sessionId = arg.split("=", 2)[1];
2690
+ continue;
2691
+ }
2692
+ if (arg === "--ref") {
2693
+ const value = rawArgs[i + 1];
2694
+ if (!value) throw createUsageError("Missing value for --ref");
2695
+ parsed.ref = value;
2696
+ i += 1;
2697
+ continue;
2698
+ }
2699
+ if (arg?.startsWith("--ref=")) {
2700
+ parsed.ref = arg.split("=", 2)[1];
2701
+ continue;
2702
+ }
2703
+ }
2704
+ return parsed;
2705
+ }
2706
+ async function runCheck(args) {
2707
+ const { sessionId, ref } = parseCheckArgs(args.rawArgs);
2708
+ if (!sessionId) throw createUsageError("Missing --session-id");
2709
+ if (!ref) throw createUsageError("Missing --ref");
2710
+ const result = await callDaemon("interact.check", { sessionId, ref });
2711
+ return { success: true, message: "Check complete.", data: result };
2712
+ }
2713
+
2714
+ // src/cli/commands/interact/uncheck.ts
2715
+ function parseUncheckArgs(rawArgs) {
2716
+ const parsed = {};
2717
+ for (let i = 0; i < rawArgs.length; i += 1) {
2718
+ const arg = rawArgs[i];
2719
+ if (arg === "--session-id") {
2720
+ const value = rawArgs[i + 1];
2721
+ if (!value) throw createUsageError("Missing value for --session-id");
2722
+ parsed.sessionId = value;
2723
+ i += 1;
2724
+ continue;
2725
+ }
2726
+ if (arg?.startsWith("--session-id=")) {
2727
+ parsed.sessionId = arg.split("=", 2)[1];
2728
+ continue;
2729
+ }
2730
+ if (arg === "--ref") {
2731
+ const value = rawArgs[i + 1];
2732
+ if (!value) throw createUsageError("Missing value for --ref");
2733
+ parsed.ref = value;
2734
+ i += 1;
2735
+ continue;
2736
+ }
2737
+ if (arg?.startsWith("--ref=")) {
2738
+ parsed.ref = arg.split("=", 2)[1];
2739
+ continue;
2740
+ }
2741
+ }
2742
+ return parsed;
2743
+ }
2744
+ async function runUncheck(args) {
2745
+ const { sessionId, ref } = parseUncheckArgs(args.rawArgs);
2746
+ if (!sessionId) throw createUsageError("Missing --session-id");
2747
+ if (!ref) throw createUsageError("Missing --ref");
2748
+ const result = await callDaemon("interact.uncheck", { sessionId, ref });
2749
+ return { success: true, message: "Uncheck complete.", data: result };
2750
+ }
2751
+
2752
+ // src/cli/commands/interact/type.ts
2753
+ function parseTypeArgs(rawArgs) {
2754
+ const parsed = {};
2755
+ for (let i = 0; i < rawArgs.length; i += 1) {
2756
+ const arg = rawArgs[i];
2757
+ if (arg === "--session-id") {
2758
+ const value = rawArgs[i + 1];
2759
+ if (!value) throw createUsageError("Missing value for --session-id");
2760
+ parsed.sessionId = value;
2761
+ i += 1;
2762
+ continue;
2763
+ }
2764
+ if (arg?.startsWith("--session-id=")) {
2765
+ parsed.sessionId = arg.split("=", 2)[1];
2766
+ continue;
2767
+ }
2768
+ if (arg === "--ref") {
2769
+ const value = rawArgs[i + 1];
2770
+ if (!value) throw createUsageError("Missing value for --ref");
2771
+ parsed.ref = value;
2772
+ i += 1;
2773
+ continue;
2774
+ }
2775
+ if (arg?.startsWith("--ref=")) {
2776
+ parsed.ref = arg.split("=", 2)[1];
2777
+ continue;
2778
+ }
2779
+ if (arg === "--text") {
2780
+ const value = rawArgs[i + 1];
2781
+ if (!value) throw createUsageError("Missing value for --text");
2782
+ parsed.text = value;
2783
+ i += 1;
2784
+ continue;
2785
+ }
2786
+ if (arg?.startsWith("--text=")) {
2787
+ parsed.text = arg.split("=", 2)[1];
2788
+ continue;
2789
+ }
2790
+ if (arg === "--clear") {
2791
+ parsed.clear = true;
2792
+ continue;
2793
+ }
2794
+ if (arg === "--submit") {
1887
2795
  parsed.submit = true;
1888
2796
  continue;
1889
2797
  }
1890
2798
  }
1891
2799
  return parsed;
1892
2800
  }
1893
- async function runType(args) {
1894
- const { sessionId, ref, text, clear, submit } = parseTypeArgs(args.rawArgs);
2801
+ async function runType(args) {
2802
+ const { sessionId, ref, text, clear, submit } = parseTypeArgs(args.rawArgs);
2803
+ if (!sessionId) throw createUsageError("Missing --session-id");
2804
+ if (!ref) throw createUsageError("Missing --ref");
2805
+ if (!text) throw createUsageError("Missing --text");
2806
+ const result = await callDaemon("interact.type", { sessionId, ref, text, clear, submit });
2807
+ return { success: true, message: "Type complete.", data: result };
2808
+ }
2809
+
2810
+ // src/cli/commands/interact/select.ts
2811
+ function parseSelectArgs(rawArgs) {
2812
+ const parsed = {};
2813
+ for (let i = 0; i < rawArgs.length; i += 1) {
2814
+ const arg = rawArgs[i];
2815
+ if (arg === "--session-id") {
2816
+ const value = rawArgs[i + 1];
2817
+ if (!value) throw createUsageError("Missing value for --session-id");
2818
+ parsed.sessionId = value;
2819
+ i += 1;
2820
+ continue;
2821
+ }
2822
+ if (arg?.startsWith("--session-id=")) {
2823
+ parsed.sessionId = arg.split("=", 2)[1];
2824
+ continue;
2825
+ }
2826
+ if (arg === "--ref") {
2827
+ const value = rawArgs[i + 1];
2828
+ if (!value) throw createUsageError("Missing value for --ref");
2829
+ parsed.ref = value;
2830
+ i += 1;
2831
+ continue;
2832
+ }
2833
+ if (arg?.startsWith("--ref=")) {
2834
+ parsed.ref = arg.split("=", 2)[1];
2835
+ continue;
2836
+ }
2837
+ if (arg === "--values") {
2838
+ const value = rawArgs[i + 1];
2839
+ if (!value) throw createUsageError("Missing value for --values");
2840
+ parsed.values = value.split(",").map((entry) => entry.trim()).filter(Boolean);
2841
+ i += 1;
2842
+ continue;
2843
+ }
2844
+ if (arg?.startsWith("--values=")) {
2845
+ const value = arg.split("=", 2)[1];
2846
+ if (!value) throw createUsageError("Missing value for --values");
2847
+ parsed.values = value.split(",").map((entry) => entry.trim()).filter(Boolean);
2848
+ continue;
2849
+ }
2850
+ }
2851
+ return parsed;
2852
+ }
2853
+ async function runSelect(args) {
2854
+ const { sessionId, ref, values } = parseSelectArgs(args.rawArgs);
2855
+ if (!sessionId) throw createUsageError("Missing --session-id");
2856
+ if (!ref) throw createUsageError("Missing --ref");
2857
+ if (!values || values.length === 0) throw createUsageError("Missing --values");
2858
+ const result = await callDaemon("interact.select", { sessionId, ref, values });
2859
+ return { success: true, message: "Select complete.", data: result };
2860
+ }
2861
+
2862
+ // src/cli/commands/interact/scroll.ts
2863
+ function parseScrollArgs(rawArgs) {
2864
+ const parsed = {};
2865
+ for (let i = 0; i < rawArgs.length; i += 1) {
2866
+ const arg = rawArgs[i];
2867
+ if (arg === "--session-id") {
2868
+ const value = rawArgs[i + 1];
2869
+ if (!value) throw createUsageError("Missing value for --session-id");
2870
+ parsed.sessionId = value;
2871
+ i += 1;
2872
+ continue;
2873
+ }
2874
+ if (arg?.startsWith("--session-id=")) {
2875
+ parsed.sessionId = arg.split("=", 2)[1];
2876
+ continue;
2877
+ }
2878
+ if (arg === "--ref") {
2879
+ const value = rawArgs[i + 1];
2880
+ if (!value) throw createUsageError("Missing value for --ref");
2881
+ parsed.ref = value;
2882
+ i += 1;
2883
+ continue;
2884
+ }
2885
+ if (arg?.startsWith("--ref=")) {
2886
+ parsed.ref = arg.split("=", 2)[1];
2887
+ continue;
2888
+ }
2889
+ if (arg === "--dy") {
2890
+ const value = rawArgs[i + 1];
2891
+ if (!value) throw createUsageError("Missing value for --dy");
2892
+ parsed.dy = Number(value);
2893
+ i += 1;
2894
+ continue;
2895
+ }
2896
+ if (arg?.startsWith("--dy=")) {
2897
+ parsed.dy = Number(arg.split("=", 2)[1]);
2898
+ continue;
2899
+ }
2900
+ }
2901
+ return parsed;
2902
+ }
2903
+ async function runScroll(args) {
2904
+ const { sessionId, ref, dy } = parseScrollArgs(args.rawArgs);
2905
+ if (!sessionId) throw createUsageError("Missing --session-id");
2906
+ if (typeof dy !== "number" || Number.isNaN(dy)) throw createUsageError("Missing --dy");
2907
+ const result = await callDaemon("interact.scroll", { sessionId, ref, dy });
2908
+ return { success: true, message: "Scroll complete.", data: result };
2909
+ }
2910
+
2911
+ // src/cli/commands/interact/scroll-into-view.ts
2912
+ function parseScrollIntoViewArgs(rawArgs) {
2913
+ const parsed = {};
2914
+ for (let i = 0; i < rawArgs.length; i += 1) {
2915
+ const arg = rawArgs[i];
2916
+ if (arg === "--session-id") {
2917
+ const value = rawArgs[i + 1];
2918
+ if (!value) throw createUsageError("Missing value for --session-id");
2919
+ parsed.sessionId = value;
2920
+ i += 1;
2921
+ continue;
2922
+ }
2923
+ if (arg?.startsWith("--session-id=")) {
2924
+ parsed.sessionId = arg.split("=", 2)[1];
2925
+ continue;
2926
+ }
2927
+ if (arg === "--ref") {
2928
+ const value = rawArgs[i + 1];
2929
+ if (!value) throw createUsageError("Missing value for --ref");
2930
+ parsed.ref = value;
2931
+ i += 1;
2932
+ continue;
2933
+ }
2934
+ if (arg?.startsWith("--ref=")) {
2935
+ parsed.ref = arg.split("=", 2)[1];
2936
+ continue;
2937
+ }
2938
+ }
2939
+ return parsed;
2940
+ }
2941
+ async function runScrollIntoView(args) {
2942
+ const { sessionId, ref } = parseScrollIntoViewArgs(args.rawArgs);
2943
+ if (!sessionId) throw createUsageError("Missing --session-id");
2944
+ if (!ref) throw createUsageError("Missing --ref");
2945
+ const result = await callDaemon("interact.scrollIntoView", { sessionId, ref });
2946
+ return { success: true, message: "Scroll into view complete.", data: result };
2947
+ }
2948
+
2949
+ // src/cli/commands/targets/list.ts
2950
+ function parseTargetsListArgs(rawArgs) {
2951
+ const parsed = {};
2952
+ for (let i = 0; i < rawArgs.length; i += 1) {
2953
+ const arg = rawArgs[i];
2954
+ if (arg === "--session-id") {
2955
+ const value = rawArgs[i + 1];
2956
+ if (!value) throw createUsageError("Missing value for --session-id");
2957
+ parsed.sessionId = value;
2958
+ i += 1;
2959
+ continue;
2960
+ }
2961
+ if (arg?.startsWith("--session-id=")) {
2962
+ parsed.sessionId = arg.split("=", 2)[1];
2963
+ continue;
2964
+ }
2965
+ if (arg === "--include-urls") {
2966
+ parsed.includeUrls = true;
2967
+ }
2968
+ }
2969
+ return parsed;
2970
+ }
2971
+ async function runTargetsList(args) {
2972
+ const { sessionId, includeUrls } = parseTargetsListArgs(args.rawArgs);
2973
+ if (!sessionId) throw createUsageError("Missing --session-id");
2974
+ const result = await callDaemon("targets.list", { sessionId, includeUrls });
2975
+ return { success: true, message: `Targets listed for session: ${sessionId}`, data: result };
2976
+ }
2977
+
2978
+ // src/cli/commands/targets/use.ts
2979
+ function parseTargetUseArgs(rawArgs) {
2980
+ const parsed = {};
2981
+ for (let i = 0; i < rawArgs.length; i += 1) {
2982
+ const arg = rawArgs[i];
2983
+ if (arg === "--session-id") {
2984
+ const value = rawArgs[i + 1];
2985
+ if (!value) throw createUsageError("Missing value for --session-id");
2986
+ parsed.sessionId = value;
2987
+ i += 1;
2988
+ continue;
2989
+ }
2990
+ if (arg?.startsWith("--session-id=")) {
2991
+ parsed.sessionId = arg.split("=", 2)[1];
2992
+ continue;
2993
+ }
2994
+ if (arg === "--target-id") {
2995
+ const value = rawArgs[i + 1];
2996
+ if (!value) throw createUsageError("Missing value for --target-id");
2997
+ parsed.targetId = value;
2998
+ i += 1;
2999
+ continue;
3000
+ }
3001
+ if (arg?.startsWith("--target-id=")) {
3002
+ parsed.targetId = arg.split("=", 2)[1];
3003
+ continue;
3004
+ }
3005
+ }
3006
+ return parsed;
3007
+ }
3008
+ async function runTargetUse(args) {
3009
+ const { sessionId, targetId } = parseTargetUseArgs(args.rawArgs);
3010
+ if (!sessionId) throw createUsageError("Missing --session-id");
3011
+ if (!targetId) throw createUsageError("Missing --target-id");
3012
+ const result = await callDaemon("targets.use", { sessionId, targetId });
3013
+ return { success: true, message: `Target selected: ${targetId}`, data: result };
3014
+ }
3015
+
3016
+ // src/cli/commands/targets/new.ts
3017
+ function parseTargetNewArgs(rawArgs) {
3018
+ const parsed = {};
3019
+ for (let i = 0; i < rawArgs.length; i += 1) {
3020
+ const arg = rawArgs[i];
3021
+ if (arg === "--session-id") {
3022
+ const value = rawArgs[i + 1];
3023
+ if (!value) throw createUsageError("Missing value for --session-id");
3024
+ parsed.sessionId = value;
3025
+ i += 1;
3026
+ continue;
3027
+ }
3028
+ if (arg?.startsWith("--session-id=")) {
3029
+ parsed.sessionId = arg.split("=", 2)[1];
3030
+ continue;
3031
+ }
3032
+ if (arg === "--url") {
3033
+ const value = rawArgs[i + 1];
3034
+ if (!value) throw createUsageError("Missing value for --url");
3035
+ parsed.url = value;
3036
+ i += 1;
3037
+ continue;
3038
+ }
3039
+ if (arg?.startsWith("--url=")) {
3040
+ parsed.url = arg.split("=", 2)[1];
3041
+ continue;
3042
+ }
3043
+ }
3044
+ return parsed;
3045
+ }
3046
+ async function runTargetNew(args) {
3047
+ const { sessionId, url } = parseTargetNewArgs(args.rawArgs);
3048
+ if (!sessionId) throw createUsageError("Missing --session-id");
3049
+ const result = await callDaemon("targets.new", { sessionId, url });
3050
+ return { success: true, message: "Target created.", data: result };
3051
+ }
3052
+
3053
+ // src/cli/commands/targets/close.ts
3054
+ function parseTargetCloseArgs(rawArgs) {
3055
+ const parsed = {};
3056
+ for (let i = 0; i < rawArgs.length; i += 1) {
3057
+ const arg = rawArgs[i];
3058
+ if (arg === "--session-id") {
3059
+ const value = rawArgs[i + 1];
3060
+ if (!value) throw createUsageError("Missing value for --session-id");
3061
+ parsed.sessionId = value;
3062
+ i += 1;
3063
+ continue;
3064
+ }
3065
+ if (arg?.startsWith("--session-id=")) {
3066
+ parsed.sessionId = arg.split("=", 2)[1];
3067
+ continue;
3068
+ }
3069
+ if (arg === "--target-id") {
3070
+ const value = rawArgs[i + 1];
3071
+ if (!value) throw createUsageError("Missing value for --target-id");
3072
+ parsed.targetId = value;
3073
+ i += 1;
3074
+ continue;
3075
+ }
3076
+ if (arg?.startsWith("--target-id=")) {
3077
+ parsed.targetId = arg.split("=", 2)[1];
3078
+ continue;
3079
+ }
3080
+ }
3081
+ return parsed;
3082
+ }
3083
+ async function runTargetClose(args) {
3084
+ const { sessionId, targetId } = parseTargetCloseArgs(args.rawArgs);
3085
+ if (!sessionId) throw createUsageError("Missing --session-id");
3086
+ if (!targetId) throw createUsageError("Missing --target-id");
3087
+ await callDaemon("targets.close", { sessionId, targetId });
3088
+ return { success: true, message: `Target closed: ${targetId}` };
3089
+ }
3090
+
3091
+ // src/cli/commands/pages/open.ts
3092
+ function parsePageOpenArgs(rawArgs) {
3093
+ const parsed = {};
3094
+ for (let i = 0; i < rawArgs.length; i += 1) {
3095
+ const arg = rawArgs[i];
3096
+ if (arg === "--session-id") {
3097
+ const value = rawArgs[i + 1];
3098
+ if (!value) throw createUsageError("Missing value for --session-id");
3099
+ parsed.sessionId = value;
3100
+ i += 1;
3101
+ continue;
3102
+ }
3103
+ if (arg?.startsWith("--session-id=")) {
3104
+ parsed.sessionId = arg.split("=", 2)[1];
3105
+ continue;
3106
+ }
3107
+ if (arg === "--name") {
3108
+ const value = rawArgs[i + 1];
3109
+ if (!value) throw createUsageError("Missing value for --name");
3110
+ parsed.name = value;
3111
+ i += 1;
3112
+ continue;
3113
+ }
3114
+ if (arg?.startsWith("--name=")) {
3115
+ parsed.name = arg.split("=", 2)[1];
3116
+ continue;
3117
+ }
3118
+ if (arg === "--url") {
3119
+ const value = rawArgs[i + 1];
3120
+ if (!value) throw createUsageError("Missing value for --url");
3121
+ parsed.url = value;
3122
+ i += 1;
3123
+ continue;
3124
+ }
3125
+ if (arg?.startsWith("--url=")) {
3126
+ parsed.url = arg.split("=", 2)[1];
3127
+ continue;
3128
+ }
3129
+ }
3130
+ return parsed;
3131
+ }
3132
+ async function runPageOpen(args) {
3133
+ const { sessionId, name, url } = parsePageOpenArgs(args.rawArgs);
3134
+ if (!sessionId) throw createUsageError("Missing --session-id");
3135
+ if (!name) throw createUsageError("Missing --name");
3136
+ const result = await callDaemon("page.open", { sessionId, name, url });
3137
+ return { success: true, message: `Page ready: ${name}`, data: result };
3138
+ }
3139
+
3140
+ // src/cli/commands/pages/list.ts
3141
+ function parsePagesListArgs(rawArgs) {
3142
+ const parsed = {};
3143
+ for (let i = 0; i < rawArgs.length; i += 1) {
3144
+ const arg = rawArgs[i];
3145
+ if (arg === "--session-id") {
3146
+ const value = rawArgs[i + 1];
3147
+ if (!value) throw createUsageError("Missing value for --session-id");
3148
+ parsed.sessionId = value;
3149
+ i += 1;
3150
+ continue;
3151
+ }
3152
+ if (arg?.startsWith("--session-id=")) {
3153
+ parsed.sessionId = arg.split("=", 2)[1];
3154
+ continue;
3155
+ }
3156
+ }
3157
+ return parsed;
3158
+ }
3159
+ async function runPagesList(args) {
3160
+ const { sessionId } = parsePagesListArgs(args.rawArgs);
3161
+ if (!sessionId) throw createUsageError("Missing --session-id");
3162
+ const result = await callDaemon("page.list", { sessionId });
3163
+ return { success: true, message: `Pages listed for session: ${sessionId}`, data: result };
3164
+ }
3165
+
3166
+ // src/cli/commands/pages/close.ts
3167
+ function parsePageCloseArgs(rawArgs) {
3168
+ const parsed = {};
3169
+ for (let i = 0; i < rawArgs.length; i += 1) {
3170
+ const arg = rawArgs[i];
3171
+ if (arg === "--session-id") {
3172
+ const value = rawArgs[i + 1];
3173
+ if (!value) throw createUsageError("Missing value for --session-id");
3174
+ parsed.sessionId = value;
3175
+ i += 1;
3176
+ continue;
3177
+ }
3178
+ if (arg?.startsWith("--session-id=")) {
3179
+ parsed.sessionId = arg.split("=", 2)[1];
3180
+ continue;
3181
+ }
3182
+ if (arg === "--name") {
3183
+ const value = rawArgs[i + 1];
3184
+ if (!value) throw createUsageError("Missing value for --name");
3185
+ parsed.name = value;
3186
+ i += 1;
3187
+ continue;
3188
+ }
3189
+ if (arg?.startsWith("--name=")) {
3190
+ parsed.name = arg.split("=", 2)[1];
3191
+ continue;
3192
+ }
3193
+ }
3194
+ return parsed;
3195
+ }
3196
+ async function runPageClose(args) {
3197
+ const { sessionId, name } = parsePageCloseArgs(args.rawArgs);
3198
+ if (!sessionId) throw createUsageError("Missing --session-id");
3199
+ if (!name) throw createUsageError("Missing --name");
3200
+ await callDaemon("page.close", { sessionId, name });
3201
+ return { success: true, message: `Page closed: ${name}` };
3202
+ }
3203
+
3204
+ // src/cli/commands/dom/html.ts
3205
+ function parseDomHtmlArgs(rawArgs) {
3206
+ const parsed = {};
3207
+ for (let i = 0; i < rawArgs.length; i += 1) {
3208
+ const arg = rawArgs[i];
3209
+ if (arg === "--session-id") {
3210
+ const value = rawArgs[i + 1];
3211
+ if (!value) throw createUsageError("Missing value for --session-id");
3212
+ parsed.sessionId = value;
3213
+ i += 1;
3214
+ continue;
3215
+ }
3216
+ if (arg?.startsWith("--session-id=")) {
3217
+ parsed.sessionId = arg.split("=", 2)[1];
3218
+ continue;
3219
+ }
3220
+ if (arg === "--ref") {
3221
+ const value = rawArgs[i + 1];
3222
+ if (!value) throw createUsageError("Missing value for --ref");
3223
+ parsed.ref = value;
3224
+ i += 1;
3225
+ continue;
3226
+ }
3227
+ if (arg?.startsWith("--ref=")) {
3228
+ parsed.ref = arg.split("=", 2)[1];
3229
+ continue;
3230
+ }
3231
+ if (arg === "--max-chars") {
3232
+ const value = rawArgs[i + 1];
3233
+ if (!value) throw createUsageError("Missing value for --max-chars");
3234
+ parsed.maxChars = Number(value);
3235
+ i += 1;
3236
+ continue;
3237
+ }
3238
+ if (arg?.startsWith("--max-chars=")) {
3239
+ parsed.maxChars = Number(arg.split("=", 2)[1]);
3240
+ continue;
3241
+ }
3242
+ }
3243
+ return parsed;
3244
+ }
3245
+ async function runDomHtml(args) {
3246
+ const { sessionId, ref, maxChars } = parseDomHtmlArgs(args.rawArgs);
3247
+ if (!sessionId) throw createUsageError("Missing --session-id");
3248
+ if (!ref) throw createUsageError("Missing --ref");
3249
+ const result = await callDaemon("dom.getHtml", { sessionId, ref, maxChars });
3250
+ return { success: true, message: "DOM HTML captured.", data: result };
3251
+ }
3252
+
3253
+ // src/cli/commands/dom/text.ts
3254
+ function parseDomTextArgs(rawArgs) {
3255
+ const parsed = {};
3256
+ for (let i = 0; i < rawArgs.length; i += 1) {
3257
+ const arg = rawArgs[i];
3258
+ if (arg === "--session-id") {
3259
+ const value = rawArgs[i + 1];
3260
+ if (!value) throw createUsageError("Missing value for --session-id");
3261
+ parsed.sessionId = value;
3262
+ i += 1;
3263
+ continue;
3264
+ }
3265
+ if (arg?.startsWith("--session-id=")) {
3266
+ parsed.sessionId = arg.split("=", 2)[1];
3267
+ continue;
3268
+ }
3269
+ if (arg === "--ref") {
3270
+ const value = rawArgs[i + 1];
3271
+ if (!value) throw createUsageError("Missing value for --ref");
3272
+ parsed.ref = value;
3273
+ i += 1;
3274
+ continue;
3275
+ }
3276
+ if (arg?.startsWith("--ref=")) {
3277
+ parsed.ref = arg.split("=", 2)[1];
3278
+ continue;
3279
+ }
3280
+ if (arg === "--max-chars") {
3281
+ const value = rawArgs[i + 1];
3282
+ if (!value) throw createUsageError("Missing value for --max-chars");
3283
+ parsed.maxChars = Number(value);
3284
+ i += 1;
3285
+ continue;
3286
+ }
3287
+ if (arg?.startsWith("--max-chars=")) {
3288
+ parsed.maxChars = Number(arg.split("=", 2)[1]);
3289
+ continue;
3290
+ }
3291
+ }
3292
+ return parsed;
3293
+ }
3294
+ async function runDomText(args) {
3295
+ const { sessionId, ref, maxChars } = parseDomTextArgs(args.rawArgs);
3296
+ if (!sessionId) throw createUsageError("Missing --session-id");
3297
+ if (!ref) throw createUsageError("Missing --ref");
3298
+ const result = await callDaemon("dom.getText", { sessionId, ref, maxChars });
3299
+ return { success: true, message: "DOM text captured.", data: result };
3300
+ }
3301
+
3302
+ // src/cli/commands/dom/attr.ts
3303
+ function parseDomAttrArgs(rawArgs) {
3304
+ const parsed = {};
3305
+ for (let i = 0; i < rawArgs.length; i += 1) {
3306
+ const arg = rawArgs[i];
3307
+ if (arg === "--session-id") {
3308
+ const value = rawArgs[i + 1];
3309
+ if (!value) throw createUsageError("Missing value for --session-id");
3310
+ parsed.sessionId = value;
3311
+ i += 1;
3312
+ continue;
3313
+ }
3314
+ if (arg?.startsWith("--session-id=")) {
3315
+ parsed.sessionId = arg.split("=", 2)[1];
3316
+ continue;
3317
+ }
3318
+ if (arg === "--ref") {
3319
+ const value = rawArgs[i + 1];
3320
+ if (!value) throw createUsageError("Missing value for --ref");
3321
+ parsed.ref = value;
3322
+ i += 1;
3323
+ continue;
3324
+ }
3325
+ if (arg?.startsWith("--ref=")) {
3326
+ parsed.ref = arg.split("=", 2)[1];
3327
+ continue;
3328
+ }
3329
+ if (arg === "--attr") {
3330
+ const value = rawArgs[i + 1];
3331
+ if (!value) throw createUsageError("Missing value for --attr");
3332
+ parsed.attr = value;
3333
+ i += 1;
3334
+ continue;
3335
+ }
3336
+ if (arg?.startsWith("--attr=")) {
3337
+ parsed.attr = arg.split("=", 2)[1];
3338
+ continue;
3339
+ }
3340
+ }
3341
+ return parsed;
3342
+ }
3343
+ async function runDomAttr(args) {
3344
+ const { sessionId, ref, attr } = parseDomAttrArgs(args.rawArgs);
3345
+ if (!sessionId) throw createUsageError("Missing --session-id");
3346
+ if (!ref) throw createUsageError("Missing --ref");
3347
+ if (!attr) throw createUsageError("Missing --attr");
3348
+ const result = await callDaemon("dom.getAttr", { sessionId, ref, name: attr });
3349
+ return { success: true, message: "DOM attribute captured.", data: result };
3350
+ }
3351
+
3352
+ // src/cli/commands/dom/value.ts
3353
+ function parseDomValueArgs(rawArgs) {
3354
+ const parsed = {};
3355
+ for (let i = 0; i < rawArgs.length; i += 1) {
3356
+ const arg = rawArgs[i];
3357
+ if (arg === "--session-id") {
3358
+ const value = rawArgs[i + 1];
3359
+ if (!value) throw createUsageError("Missing value for --session-id");
3360
+ parsed.sessionId = value;
3361
+ i += 1;
3362
+ continue;
3363
+ }
3364
+ if (arg?.startsWith("--session-id=")) {
3365
+ parsed.sessionId = arg.split("=", 2)[1];
3366
+ continue;
3367
+ }
3368
+ if (arg === "--ref") {
3369
+ const value = rawArgs[i + 1];
3370
+ if (!value) throw createUsageError("Missing value for --ref");
3371
+ parsed.ref = value;
3372
+ i += 1;
3373
+ continue;
3374
+ }
3375
+ if (arg?.startsWith("--ref=")) {
3376
+ parsed.ref = arg.split("=", 2)[1];
3377
+ continue;
3378
+ }
3379
+ }
3380
+ return parsed;
3381
+ }
3382
+ async function runDomValue(args) {
3383
+ const { sessionId, ref } = parseDomValueArgs(args.rawArgs);
3384
+ if (!sessionId) throw createUsageError("Missing --session-id");
3385
+ if (!ref) throw createUsageError("Missing --ref");
3386
+ const result = await callDaemon("dom.getValue", { sessionId, ref });
3387
+ return { success: true, message: "DOM value captured.", data: result };
3388
+ }
3389
+
3390
+ // src/cli/commands/dom/visible.ts
3391
+ function parseDomVisibleArgs(rawArgs) {
3392
+ const parsed = {};
3393
+ for (let i = 0; i < rawArgs.length; i += 1) {
3394
+ const arg = rawArgs[i];
3395
+ if (arg === "--session-id") {
3396
+ const value = rawArgs[i + 1];
3397
+ if (!value) throw createUsageError("Missing value for --session-id");
3398
+ parsed.sessionId = value;
3399
+ i += 1;
3400
+ continue;
3401
+ }
3402
+ if (arg?.startsWith("--session-id=")) {
3403
+ parsed.sessionId = arg.split("=", 2)[1];
3404
+ continue;
3405
+ }
3406
+ if (arg === "--ref") {
3407
+ const value = rawArgs[i + 1];
3408
+ if (!value) throw createUsageError("Missing value for --ref");
3409
+ parsed.ref = value;
3410
+ i += 1;
3411
+ continue;
3412
+ }
3413
+ if (arg?.startsWith("--ref=")) {
3414
+ parsed.ref = arg.split("=", 2)[1];
3415
+ continue;
3416
+ }
3417
+ }
3418
+ return parsed;
3419
+ }
3420
+ async function runDomVisible(args) {
3421
+ const { sessionId, ref } = parseDomVisibleArgs(args.rawArgs);
1895
3422
  if (!sessionId) throw createUsageError("Missing --session-id");
1896
3423
  if (!ref) throw createUsageError("Missing --ref");
1897
- if (!text) throw createUsageError("Missing --text");
1898
- const result = await callDaemon("interact.type", { sessionId, ref, text, clear, submit });
1899
- return { success: true, message: "Type complete.", data: result };
3424
+ const result = await callDaemon("dom.isVisible", { sessionId, ref });
3425
+ return { success: true, message: "Visibility checked.", data: result };
1900
3426
  }
1901
3427
 
1902
- // src/cli/commands/interact/select.ts
1903
- function parseSelectArgs(rawArgs) {
3428
+ // src/cli/commands/dom/enabled.ts
3429
+ function parseDomEnabledArgs(rawArgs) {
1904
3430
  const parsed = {};
1905
3431
  for (let i = 0; i < rawArgs.length; i += 1) {
1906
3432
  const arg = rawArgs[i];
@@ -1926,31 +3452,83 @@ function parseSelectArgs(rawArgs) {
1926
3452
  parsed.ref = arg.split("=", 2)[1];
1927
3453
  continue;
1928
3454
  }
1929
- if (arg === "--values") {
3455
+ }
3456
+ return parsed;
3457
+ }
3458
+ async function runDomEnabled(args) {
3459
+ const { sessionId, ref } = parseDomEnabledArgs(args.rawArgs);
3460
+ if (!sessionId) throw createUsageError("Missing --session-id");
3461
+ if (!ref) throw createUsageError("Missing --ref");
3462
+ const result = await callDaemon("dom.isEnabled", { sessionId, ref });
3463
+ return { success: true, message: "Enabled state checked.", data: result };
3464
+ }
3465
+
3466
+ // src/cli/commands/dom/checked.ts
3467
+ function parseDomCheckedArgs(rawArgs) {
3468
+ const parsed = {};
3469
+ for (let i = 0; i < rawArgs.length; i += 1) {
3470
+ const arg = rawArgs[i];
3471
+ if (arg === "--session-id") {
1930
3472
  const value = rawArgs[i + 1];
1931
- if (!value) throw createUsageError("Missing value for --values");
1932
- parsed.values = value.split(",").map((entry) => entry.trim()).filter(Boolean);
3473
+ if (!value) throw createUsageError("Missing value for --session-id");
3474
+ parsed.sessionId = value;
1933
3475
  i += 1;
1934
3476
  continue;
1935
3477
  }
1936
- if (arg?.startsWith("--values=")) {
1937
- parsed.values = arg.split("=", 2)[1].split(",").map((entry) => entry.trim()).filter(Boolean);
3478
+ if (arg?.startsWith("--session-id=")) {
3479
+ parsed.sessionId = arg.split("=", 2)[1];
3480
+ continue;
3481
+ }
3482
+ if (arg === "--ref") {
3483
+ const value = rawArgs[i + 1];
3484
+ if (!value) throw createUsageError("Missing value for --ref");
3485
+ parsed.ref = value;
3486
+ i += 1;
3487
+ continue;
3488
+ }
3489
+ if (arg?.startsWith("--ref=")) {
3490
+ parsed.ref = arg.split("=", 2)[1];
1938
3491
  continue;
1939
3492
  }
1940
3493
  }
1941
3494
  return parsed;
1942
3495
  }
1943
- async function runSelect(args) {
1944
- const { sessionId, ref, values } = parseSelectArgs(args.rawArgs);
3496
+ async function runDomChecked(args) {
3497
+ const { sessionId, ref } = parseDomCheckedArgs(args.rawArgs);
1945
3498
  if (!sessionId) throw createUsageError("Missing --session-id");
1946
3499
  if (!ref) throw createUsageError("Missing --ref");
1947
- if (!values || values.length === 0) throw createUsageError("Missing --values");
1948
- const result = await callDaemon("interact.select", { sessionId, ref, values });
1949
- return { success: true, message: "Select complete.", data: result };
3500
+ const result = await callDaemon("dom.isChecked", { sessionId, ref });
3501
+ return { success: true, message: "Checked state reported.", data: result };
1950
3502
  }
1951
3503
 
1952
- // src/cli/commands/interact/scroll.ts
1953
- function parseScrollArgs(rawArgs) {
3504
+ // src/cli/commands/export/clone-page.ts
3505
+ function parseClonePageArgs(rawArgs) {
3506
+ const parsed = {};
3507
+ for (let i = 0; i < rawArgs.length; i += 1) {
3508
+ const arg = rawArgs[i];
3509
+ if (arg === "--session-id") {
3510
+ const value = rawArgs[i + 1];
3511
+ if (!value) throw createUsageError("Missing value for --session-id");
3512
+ parsed.sessionId = value;
3513
+ i += 1;
3514
+ continue;
3515
+ }
3516
+ if (arg?.startsWith("--session-id=")) {
3517
+ parsed.sessionId = arg.split("=", 2)[1];
3518
+ continue;
3519
+ }
3520
+ }
3521
+ return parsed;
3522
+ }
3523
+ async function runClonePage(args) {
3524
+ const { sessionId } = parseClonePageArgs(args.rawArgs);
3525
+ if (!sessionId) throw createUsageError("Missing --session-id");
3526
+ const result = await callDaemon("export.clonePage", { sessionId });
3527
+ return { success: true, message: "Page cloned.", data: result };
3528
+ }
3529
+
3530
+ // src/cli/commands/export/clone-component.ts
3531
+ function parseCloneComponentArgs(rawArgs) {
1954
3532
  const parsed = {};
1955
3533
  for (let i = 0; i < rawArgs.length; i += 1) {
1956
3534
  const arg = rawArgs[i];
@@ -1976,26 +3554,174 @@ function parseScrollArgs(rawArgs) {
1976
3554
  parsed.ref = arg.split("=", 2)[1];
1977
3555
  continue;
1978
3556
  }
1979
- if (arg === "--dy") {
3557
+ }
3558
+ return parsed;
3559
+ }
3560
+ async function runCloneComponent(args) {
3561
+ const { sessionId, ref } = parseCloneComponentArgs(args.rawArgs);
3562
+ if (!sessionId) throw createUsageError("Missing --session-id");
3563
+ if (!ref) throw createUsageError("Missing --ref");
3564
+ const result = await callDaemon("export.cloneComponent", { sessionId, ref });
3565
+ return { success: true, message: "Component cloned.", data: result };
3566
+ }
3567
+
3568
+ // src/cli/commands/devtools/perf.ts
3569
+ function parsePerfArgs(rawArgs) {
3570
+ const parsed = {};
3571
+ for (let i = 0; i < rawArgs.length; i += 1) {
3572
+ const arg = rawArgs[i];
3573
+ if (arg === "--session-id") {
1980
3574
  const value = rawArgs[i + 1];
1981
- if (!value) throw createUsageError("Missing value for --dy");
1982
- parsed.dy = Number(value);
3575
+ if (!value) throw createUsageError("Missing value for --session-id");
3576
+ parsed.sessionId = value;
1983
3577
  i += 1;
1984
3578
  continue;
1985
3579
  }
1986
- if (arg?.startsWith("--dy=")) {
1987
- parsed.dy = Number(arg.split("=", 2)[1]);
3580
+ if (arg?.startsWith("--session-id=")) {
3581
+ parsed.sessionId = arg.split("=", 2)[1];
1988
3582
  continue;
1989
3583
  }
1990
3584
  }
1991
3585
  return parsed;
1992
3586
  }
1993
- async function runScroll(args) {
1994
- const { sessionId, ref, dy } = parseScrollArgs(args.rawArgs);
3587
+ async function runPerf(args) {
3588
+ const { sessionId } = parsePerfArgs(args.rawArgs);
1995
3589
  if (!sessionId) throw createUsageError("Missing --session-id");
1996
- if (typeof dy !== "number" || Number.isNaN(dy)) throw createUsageError("Missing --dy");
1997
- const result = await callDaemon("interact.scroll", { sessionId, ref, dy });
1998
- return { success: true, message: "Scroll complete.", data: result };
3590
+ const result = await callDaemon("devtools.perf", { sessionId });
3591
+ return { success: true, message: "Performance metrics captured.", data: result };
3592
+ }
3593
+
3594
+ // src/cli/commands/devtools/screenshot.ts
3595
+ function parseScreenshotArgs(rawArgs) {
3596
+ const parsed = {};
3597
+ for (let i = 0; i < rawArgs.length; i += 1) {
3598
+ const arg = rawArgs[i];
3599
+ if (arg === "--session-id") {
3600
+ const value = rawArgs[i + 1];
3601
+ if (!value) throw createUsageError("Missing value for --session-id");
3602
+ parsed.sessionId = value;
3603
+ i += 1;
3604
+ continue;
3605
+ }
3606
+ if (arg?.startsWith("--session-id=")) {
3607
+ parsed.sessionId = arg.split("=", 2)[1];
3608
+ continue;
3609
+ }
3610
+ if (arg === "--path") {
3611
+ const value = rawArgs[i + 1];
3612
+ if (!value) throw createUsageError("Missing value for --path");
3613
+ parsed.path = value;
3614
+ i += 1;
3615
+ continue;
3616
+ }
3617
+ if (arg?.startsWith("--path=")) {
3618
+ parsed.path = arg.split("=", 2)[1];
3619
+ continue;
3620
+ }
3621
+ }
3622
+ return parsed;
3623
+ }
3624
+ async function runScreenshot(args) {
3625
+ const { sessionId, path: path8 } = parseScreenshotArgs(args.rawArgs);
3626
+ if (!sessionId) throw createUsageError("Missing --session-id");
3627
+ const result = await callDaemon("page.screenshot", { sessionId, path: path8 });
3628
+ return { success: true, message: "Screenshot captured.", data: result };
3629
+ }
3630
+
3631
+ // src/cli/commands/devtools/console-poll.ts
3632
+ function parseConsolePollArgs(rawArgs) {
3633
+ const parsed = {};
3634
+ for (let i = 0; i < rawArgs.length; i += 1) {
3635
+ const arg = rawArgs[i];
3636
+ if (arg === "--session-id") {
3637
+ const value = rawArgs[i + 1];
3638
+ if (!value) throw createUsageError("Missing value for --session-id");
3639
+ parsed.sessionId = value;
3640
+ i += 1;
3641
+ continue;
3642
+ }
3643
+ if (arg?.startsWith("--session-id=")) {
3644
+ parsed.sessionId = arg.split("=", 2)[1];
3645
+ continue;
3646
+ }
3647
+ if (arg === "--since-seq") {
3648
+ const value = rawArgs[i + 1];
3649
+ if (!value) throw createUsageError("Missing value for --since-seq");
3650
+ parsed.sinceSeq = Number(value);
3651
+ i += 1;
3652
+ continue;
3653
+ }
3654
+ if (arg?.startsWith("--since-seq=")) {
3655
+ parsed.sinceSeq = Number(arg.split("=", 2)[1]);
3656
+ continue;
3657
+ }
3658
+ if (arg === "--max") {
3659
+ const value = rawArgs[i + 1];
3660
+ if (!value) throw createUsageError("Missing value for --max");
3661
+ parsed.max = Number(value);
3662
+ i += 1;
3663
+ continue;
3664
+ }
3665
+ if (arg?.startsWith("--max=")) {
3666
+ parsed.max = Number(arg.split("=", 2)[1]);
3667
+ continue;
3668
+ }
3669
+ }
3670
+ return parsed;
3671
+ }
3672
+ async function runConsolePoll(args) {
3673
+ const { sessionId, sinceSeq, max } = parseConsolePollArgs(args.rawArgs);
3674
+ if (!sessionId) throw createUsageError("Missing --session-id");
3675
+ const result = await callDaemon("devtools.consolePoll", { sessionId, sinceSeq, max });
3676
+ return { success: true, message: "Console events polled.", data: result };
3677
+ }
3678
+
3679
+ // src/cli/commands/devtools/network-poll.ts
3680
+ function parseNetworkPollArgs(rawArgs) {
3681
+ const parsed = {};
3682
+ for (let i = 0; i < rawArgs.length; i += 1) {
3683
+ const arg = rawArgs[i];
3684
+ if (arg === "--session-id") {
3685
+ const value = rawArgs[i + 1];
3686
+ if (!value) throw createUsageError("Missing value for --session-id");
3687
+ parsed.sessionId = value;
3688
+ i += 1;
3689
+ continue;
3690
+ }
3691
+ if (arg?.startsWith("--session-id=")) {
3692
+ parsed.sessionId = arg.split("=", 2)[1];
3693
+ continue;
3694
+ }
3695
+ if (arg === "--since-seq") {
3696
+ const value = rawArgs[i + 1];
3697
+ if (!value) throw createUsageError("Missing value for --since-seq");
3698
+ parsed.sinceSeq = Number(value);
3699
+ i += 1;
3700
+ continue;
3701
+ }
3702
+ if (arg?.startsWith("--since-seq=")) {
3703
+ parsed.sinceSeq = Number(arg.split("=", 2)[1]);
3704
+ continue;
3705
+ }
3706
+ if (arg === "--max") {
3707
+ const value = rawArgs[i + 1];
3708
+ if (!value) throw createUsageError("Missing value for --max");
3709
+ parsed.max = Number(value);
3710
+ i += 1;
3711
+ continue;
3712
+ }
3713
+ if (arg?.startsWith("--max=")) {
3714
+ parsed.max = Number(arg.split("=", 2)[1]);
3715
+ continue;
3716
+ }
3717
+ }
3718
+ return parsed;
3719
+ }
3720
+ async function runNetworkPoll(args) {
3721
+ const { sessionId, sinceSeq, max } = parseNetworkPollArgs(args.rawArgs);
3722
+ if (!sessionId) throw createUsageError("Missing --session-id");
3723
+ const result = await callDaemon("devtools.networkPoll", { sessionId, sinceSeq, max });
3724
+ return { success: true, message: "Network events polled.", data: result };
1999
3725
  }
2000
3726
 
2001
3727
  // src/cli/index.ts
@@ -2005,7 +3731,7 @@ async function promptInstallMode() {
2005
3731
  console.log("Non-interactive mode detected. Using global install.");
2006
3732
  return "global";
2007
3733
  }
2008
- return new Promise((resolve2) => {
3734
+ return new Promise((resolve4) => {
2009
3735
  console.log("\nWhere would you like to install opendevbrowser?\n");
2010
3736
  console.log(" 1. Global (~/.config/opencode/opencode.json)");
2011
3737
  console.log(" 2. Local (./opencode.json in this project)\n");
@@ -2025,23 +3751,23 @@ async function promptInstallMode() {
2025
3751
  resolved = true;
2026
3752
  const input = data.toString().trim();
2027
3753
  if (input === "2") {
2028
- resolve2("local");
3754
+ resolve4("local");
2029
3755
  } else {
2030
- resolve2("global");
3756
+ resolve4("global");
2031
3757
  }
2032
3758
  });
2033
3759
  process.stdin.once("close", () => {
2034
3760
  cleanup();
2035
3761
  if (resolved) return;
2036
3762
  resolved = true;
2037
- resolve2("global");
3763
+ resolve4("global");
2038
3764
  });
2039
3765
  timeoutId = setTimeout(() => {
2040
3766
  timeoutId = null;
2041
3767
  if (resolved) return;
2042
3768
  resolved = true;
2043
3769
  console.log("\nTimeout - using global install.");
2044
- resolve2("global");
3770
+ resolve4("global");
2045
3771
  }, 3e4);
2046
3772
  });
2047
3773
  }
@@ -2061,7 +3787,7 @@ async function promptUninstallMode() {
2061
3787
  console.log("Plugin found in both global and local configs. Use --global or --local flag.");
2062
3788
  return null;
2063
3789
  }
2064
- return new Promise((resolve2) => {
3790
+ return new Promise((resolve4) => {
2065
3791
  console.log("\nopendevbrowser is installed in multiple locations:\n");
2066
3792
  console.log(" 1. Global (~/.config/opencode/opencode.json)");
2067
3793
  console.log(" 2. Local (./opencode.json)");
@@ -2071,15 +3797,15 @@ async function promptUninstallMode() {
2071
3797
  process.stdin.once("data", (data) => {
2072
3798
  const input = data.toString().trim();
2073
3799
  if (input === "1") {
2074
- resolve2("global");
3800
+ resolve4("global");
2075
3801
  } else if (input === "2") {
2076
- resolve2("local");
3802
+ resolve4("local");
2077
3803
  } else {
2078
- resolve2(null);
3804
+ resolve4(null);
2079
3805
  }
2080
3806
  });
2081
3807
  process.stdin.once("close", () => {
2082
- resolve2(null);
3808
+ resolve4(null);
2083
3809
  });
2084
3810
  });
2085
3811
  }
@@ -2176,6 +3902,22 @@ async function main() {
2176
3902
  mode = await promptInstallMode();
2177
3903
  }
2178
3904
  const result2 = mode === "global" ? installGlobal(args.withConfig) : installLocal(args.withConfig);
3905
+ const maybeInstallAutostart = () => {
3906
+ const status = getAutostartStatus();
3907
+ if (!status.supported) {
3908
+ return { status, installed: false, message: `Autostart not supported on ${status.platform}.` };
3909
+ }
3910
+ if (status.installed) {
3911
+ return { status, installed: true, message: "Autostart already installed." };
3912
+ }
3913
+ try {
3914
+ const result3 = installAutostart();
3915
+ return { status: result3, installed: result3.installed, message: `Autostart installed (${result3.platform}).` };
3916
+ } catch (error) {
3917
+ const message = error instanceof Error ? error.message : String(error);
3918
+ return { status, installed: false, message };
3919
+ }
3920
+ };
2179
3921
  if (args.outputFormat !== "text") {
2180
3922
  const payload = {
2181
3923
  alreadyInstalled: result2.alreadyInstalled
@@ -2192,6 +3934,13 @@ async function main() {
2192
3934
  payload.extensionError = error instanceof Error ? error.message : String(error);
2193
3935
  }
2194
3936
  }
3937
+ if (result2.success && !result2.alreadyInstalled) {
3938
+ const autostart = maybeInstallAutostart();
3939
+ payload.autostart = autostart.status;
3940
+ if (!autostart.installed) {
3941
+ payload.autostartError = autostart.message;
3942
+ }
3943
+ }
2195
3944
  return { success: result2.success, message: result2.message, data: payload };
2196
3945
  }
2197
3946
  log(result2.message);
@@ -2220,6 +3969,14 @@ async function main() {
2220
3969
  warn(`Extension pre-extraction failed: ${message}`);
2221
3970
  }
2222
3971
  }
3972
+ if (result2.success && !result2.alreadyInstalled) {
3973
+ const autostart = maybeInstallAutostart();
3974
+ if (autostart.installed) {
3975
+ log(autostart.message);
3976
+ } else {
3977
+ warn(`Autostart install skipped: ${autostart.message}`);
3978
+ }
3979
+ }
2223
3980
  if (result2.success && !result2.alreadyInstalled) {
2224
3981
  log("\nNext steps:");
2225
3982
  log(" 1. Start or restart OpenCode");
@@ -2234,6 +3991,16 @@ async function main() {
2234
3991
  description: "Start or stop the local daemon",
2235
3992
  run: async () => runServe(args)
2236
3993
  });
3994
+ registerCommand({
3995
+ name: "daemon",
3996
+ description: "Install/uninstall/status daemon auto-start",
3997
+ run: async () => runDaemonCommand(args)
3998
+ });
3999
+ registerCommand({
4000
+ name: "native",
4001
+ description: "Install/uninstall/status native messaging host",
4002
+ run: async () => runNativeCommand(args)
4003
+ });
2237
4004
  registerCommand({
2238
4005
  name: "run",
2239
4006
  description: "Execute a JSON script in a single process",
@@ -2256,8 +4023,8 @@ async function main() {
2256
4023
  });
2257
4024
  registerCommand({
2258
4025
  name: "status",
2259
- description: "Get daemon session status",
2260
- run: async () => runSessionStatus(args)
4026
+ description: "Get daemon or session status",
4027
+ run: async () => runStatus(args)
2261
4028
  });
2262
4029
  registerCommand({
2263
4030
  name: "goto",
@@ -2274,11 +4041,36 @@ async function main() {
2274
4041
  description: "Capture a snapshot of the active page",
2275
4042
  run: async () => runSnapshot(args)
2276
4043
  });
4044
+ registerCommand({
4045
+ name: "annotate",
4046
+ description: "Request interactive annotations (extension relay)",
4047
+ run: async () => runAnnotate(args)
4048
+ });
2277
4049
  registerCommand({
2278
4050
  name: "click",
2279
4051
  description: "Click an element by ref",
2280
4052
  run: async () => runClick(args)
2281
4053
  });
4054
+ registerCommand({
4055
+ name: "hover",
4056
+ description: "Hover an element by ref",
4057
+ run: async () => runHover(args)
4058
+ });
4059
+ registerCommand({
4060
+ name: "press",
4061
+ description: "Press a keyboard key",
4062
+ run: async () => runPress(args)
4063
+ });
4064
+ registerCommand({
4065
+ name: "check",
4066
+ description: "Check a checkbox by ref",
4067
+ run: async () => runCheck(args)
4068
+ });
4069
+ registerCommand({
4070
+ name: "uncheck",
4071
+ description: "Uncheck a checkbox by ref",
4072
+ run: async () => runUncheck(args)
4073
+ });
2282
4074
  registerCommand({
2283
4075
  name: "type",
2284
4076
  description: "Type into an element by ref",
@@ -2294,6 +4086,111 @@ async function main() {
2294
4086
  description: "Scroll the page or element by ref",
2295
4087
  run: async () => runScroll(args)
2296
4088
  });
4089
+ registerCommand({
4090
+ name: "scroll-into-view",
4091
+ description: "Scroll an element into view by ref",
4092
+ run: async () => runScrollIntoView(args)
4093
+ });
4094
+ registerCommand({
4095
+ name: "targets-list",
4096
+ description: "List page targets",
4097
+ run: async () => runTargetsList(args)
4098
+ });
4099
+ registerCommand({
4100
+ name: "target-use",
4101
+ description: "Focus a target by id",
4102
+ run: async () => runTargetUse(args)
4103
+ });
4104
+ registerCommand({
4105
+ name: "target-new",
4106
+ description: "Open a new target",
4107
+ run: async () => runTargetNew(args)
4108
+ });
4109
+ registerCommand({
4110
+ name: "target-close",
4111
+ description: "Close a target by id",
4112
+ run: async () => runTargetClose(args)
4113
+ });
4114
+ registerCommand({
4115
+ name: "page",
4116
+ description: "Open or focus a named page",
4117
+ run: async () => runPageOpen(args)
4118
+ });
4119
+ registerCommand({
4120
+ name: "pages",
4121
+ description: "List named pages",
4122
+ run: async () => runPagesList(args)
4123
+ });
4124
+ registerCommand({
4125
+ name: "page-close",
4126
+ description: "Close a named page",
4127
+ run: async () => runPageClose(args)
4128
+ });
4129
+ registerCommand({
4130
+ name: "dom-html",
4131
+ description: "Capture HTML for a ref",
4132
+ run: async () => runDomHtml(args)
4133
+ });
4134
+ registerCommand({
4135
+ name: "dom-text",
4136
+ description: "Capture text for a ref",
4137
+ run: async () => runDomText(args)
4138
+ });
4139
+ registerCommand({
4140
+ name: "dom-attr",
4141
+ description: "Capture attribute value for a ref",
4142
+ run: async () => runDomAttr(args)
4143
+ });
4144
+ registerCommand({
4145
+ name: "dom-value",
4146
+ description: "Capture input value for a ref",
4147
+ run: async () => runDomValue(args)
4148
+ });
4149
+ registerCommand({
4150
+ name: "dom-visible",
4151
+ description: "Check visibility for a ref",
4152
+ run: async () => runDomVisible(args)
4153
+ });
4154
+ registerCommand({
4155
+ name: "dom-enabled",
4156
+ description: "Check enabled state for a ref",
4157
+ run: async () => runDomEnabled(args)
4158
+ });
4159
+ registerCommand({
4160
+ name: "dom-checked",
4161
+ description: "Check checked state for a ref",
4162
+ run: async () => runDomChecked(args)
4163
+ });
4164
+ registerCommand({
4165
+ name: "clone-page",
4166
+ description: "Clone the active page to React",
4167
+ run: async () => runClonePage(args)
4168
+ });
4169
+ registerCommand({
4170
+ name: "clone-component",
4171
+ description: "Clone a component by ref",
4172
+ run: async () => runCloneComponent(args)
4173
+ });
4174
+ registerCommand({
4175
+ name: "perf",
4176
+ description: "Capture performance metrics",
4177
+ run: async () => runPerf(args)
4178
+ });
4179
+ registerCommand({
4180
+ name: "screenshot",
4181
+ description: "Capture a screenshot",
4182
+ run: async () => runScreenshot(args)
4183
+ });
4184
+ registerCommand({
4185
+ name: "console-poll",
4186
+ description: "Poll console events",
4187
+ run: async () => runConsolePoll(args)
4188
+ });
4189
+ registerCommand({
4190
+ name: "network-poll",
4191
+ description: "Poll network events",
4192
+ run: async () => runNetworkPoll(args)
4193
+ });
2297
4194
  const command = getCommand(args.command);
2298
4195
  if (!command) {
2299
4196
  throw new Error(`Unknown command: ${args.command}`);