opendevbrowser 0.0.24 → 0.0.26

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 (61) hide show
  1. package/README.md +1 -1
  2. package/dist/browser/fingerprint/canary.d.ts.map +1 -1
  3. package/dist/{chunk-K2TEHJCV.js → chunk-AVQL6WAS.js} +2856 -694
  4. package/dist/chunk-AVQL6WAS.js.map +1 -0
  5. package/dist/{chunk-5I6TZRVS.js → chunk-GTTYIAI7.js} +1053 -474
  6. package/dist/chunk-GTTYIAI7.js.map +1 -0
  7. package/dist/cli/commands/daemon.d.ts +25 -0
  8. package/dist/cli/commands/daemon.d.ts.map +1 -1
  9. package/dist/cli/commands/inspiredesign.d.ts.map +1 -1
  10. package/dist/cli/commands/serve.d.ts +10 -13
  11. package/dist/cli/commands/serve.d.ts.map +1 -1
  12. package/dist/cli/commands/status.d.ts.map +1 -1
  13. package/dist/cli/daemon-client.d.ts +13 -0
  14. package/dist/cli/daemon-client.d.ts.map +1 -1
  15. package/dist/cli/daemon-commands.d.ts.map +1 -1
  16. package/dist/cli/daemon-status-policy.d.ts +6 -0
  17. package/dist/cli/daemon-status-policy.d.ts.map +1 -0
  18. package/dist/cli/daemon-status.d.ts +1 -0
  19. package/dist/cli/daemon-status.d.ts.map +1 -1
  20. package/dist/cli/daemon.d.ts +12 -2
  21. package/dist/cli/daemon.d.ts.map +1 -1
  22. package/dist/cli/help.d.ts.map +1 -1
  23. package/dist/cli/index.js +188 -108
  24. package/dist/cli/index.js.map +1 -1
  25. package/dist/cli/remote-manager.d.ts +8 -6
  26. package/dist/cli/remote-manager.d.ts.map +1 -1
  27. package/dist/cli/utils/http.d.ts.map +1 -1
  28. package/dist/daemon-fingerprint.json +3 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +147 -46
  31. package/dist/index.js.map +1 -1
  32. package/dist/inspiredesign/brief-expansion.d.ts +41 -0
  33. package/dist/inspiredesign/brief-expansion.d.ts.map +1 -0
  34. package/dist/inspiredesign/handoff.d.ts +3 -1
  35. package/dist/inspiredesign/handoff.d.ts.map +1 -1
  36. package/dist/opendevbrowser.d.ts.map +1 -1
  37. package/dist/opendevbrowser.js +147 -46
  38. package/dist/opendevbrowser.js.map +1 -1
  39. package/dist/providers/inspiredesign-capture-mode.d.ts +3 -0
  40. package/dist/providers/inspiredesign-capture-mode.d.ts.map +1 -0
  41. package/dist/providers/inspiredesign-capture.d.ts +8 -1
  42. package/dist/providers/inspiredesign-capture.d.ts.map +1 -1
  43. package/dist/providers/inspiredesign-contract.d.ts +30 -0
  44. package/dist/providers/inspiredesign-contract.d.ts.map +1 -1
  45. package/dist/providers/renderer.d.ts +2 -1
  46. package/dist/providers/renderer.d.ts.map +1 -1
  47. package/dist/providers/workflows.d.ts +2 -0
  48. package/dist/providers/workflows.d.ts.map +1 -1
  49. package/dist/{providers-6YVHKTOJ.js → providers-T2FQJCF6.js} +2 -2
  50. package/dist/tools/index.d.ts.map +1 -1
  51. package/dist/tools/inspiredesign_run.d.ts.map +1 -1
  52. package/dist/tools/status.d.ts.map +1 -1
  53. package/extension/manifest.json +1 -1
  54. package/package.json +1 -1
  55. package/skills/opendevbrowser-best-practices/SKILL.md +3 -2
  56. package/skills/opendevbrowser-design-agent/SKILL.md +1 -0
  57. package/skills/opendevbrowser-design-agent/assets/templates/inspiredesign-advanced-brief.v1.json +1370 -0
  58. package/skills/opendevbrowser-design-agent/scripts/validate-skill-assets.sh +2 -0
  59. package/dist/chunk-5I6TZRVS.js.map +0 -1
  60. package/dist/chunk-K2TEHJCV.js.map +0 -1
  61. /package/dist/{providers-6YVHKTOJ.js.map → providers-T2FQJCF6.js.map} +0 -0
package/dist/cli/index.js CHANGED
@@ -2,6 +2,14 @@
2
2
  import {
3
3
  CLI_COMMANDS,
4
4
  CLI_COMMAND_HELP_DETAILS,
5
+ DEFAULT_CLICK_TRANSPORT_TIMEOUT_MS,
6
+ DEFAULT_DAEMON_STATUS_FETCH_OPTIONS,
7
+ DEFAULT_DIALOG_TRANSPORT_TIMEOUT_MS,
8
+ DEFAULT_REVIEW_TRANSPORT_TIMEOUT_MS,
9
+ DEFAULT_SCREENSHOT_TRANSPORT_TIMEOUT_MS,
10
+ DEFAULT_SNAPSHOT_TRANSPORT_TIMEOUT_MS,
11
+ DEFAULT_TARGET_CREATION_TRANSPORT_TIMEOUT_MS,
12
+ DEFAULT_WORKFLOW_TRANSPORT_TIMEOUT_MS,
5
13
  DaemonClient,
6
14
  EXIT_DISCONNECTED,
7
15
  EXIT_EXECUTION,
@@ -12,6 +20,8 @@ import {
12
20
  VALID_FLAGS,
13
21
  buildAnnotateResult,
14
22
  callDaemon,
23
+ createDaemonStopHeaders,
24
+ createDisconnectedError,
15
25
  createOpenDevBrowserCore,
16
26
  createUsageError,
17
27
  extractExtension,
@@ -21,9 +31,9 @@ import {
21
31
  formatErrorPayload,
22
32
  generateSecureToken,
23
33
  getChromeUserDataRoots,
24
- getCurrentDaemonFingerprint,
25
34
  getExtensionPath,
26
35
  getProfileDirs,
36
+ isCurrentDaemonFingerprint,
27
37
  loadGlobalConfig,
28
38
  onboarding_metadata_default,
29
39
  readDaemonMetadata,
@@ -31,7 +41,7 @@ import {
31
41
  resolveExitCode,
32
42
  startDaemon,
33
43
  toCliError
34
- } from "../chunk-5I6TZRVS.js";
44
+ } from "../chunk-GTTYIAI7.js";
35
45
  import "../chunk-STGGGVYT.js";
36
46
  import {
37
47
  createNoOpSkillRemovalResult,
@@ -60,10 +70,11 @@ import {
60
70
  INSPIREDESIGN_HANDOFF_GUIDANCE,
61
71
  cleanupExpiredArtifacts,
62
72
  isChallengeAutomationMode,
73
+ resolveInspiredesignCaptureMode,
63
74
  setDefaultLogSink,
64
75
  stderrSink,
65
76
  summarizePrimaryProviderIssue
66
- } from "../chunk-K2TEHJCV.js";
77
+ } from "../chunk-AVQL6WAS.js";
67
78
  import "../chunk-FUSXMW3G.js";
68
79
 
69
80
  // src/cli/args.ts
@@ -421,7 +432,7 @@ var HELP_FLAG_GROUPS = [
421
432
  { flag: "--region", description: "Region or country hint for provider selection. Treat it as advisory unless output metadata reports `region_authoritative=true`." },
422
433
  { flag: "--sort", description: "Sort mode for shopping results." },
423
434
  { flag: "--brief", description: "Inspiredesign brief describing the target design direction." },
424
- { flag: "--capture-mode", description: "Inspiredesign capture mode: off (default) or deep." },
435
+ { flag: "--capture-mode", description: "Inspiredesign capture mode: off or deep. Any --url forces deep." },
425
436
  { flag: "--include-prototype-guidance", description: "Include inspiredesign prototype guidance in workflow output." },
426
437
  { flag: "--product-url", description: "Target product URL for product-video workflows." },
427
438
  { flag: "--product-name", description: "Product name override for product-video workflows." },
@@ -505,10 +516,11 @@ var HELP_ONBOARDING_ENTRIES = [
505
516
  },
506
517
  {
507
518
  label: "inspiredesign_followthrough",
508
- description: "After inspiredesign finishes, continue in Canvas with the emitted request template and load the canvas-contract design-agent lane before patching.",
519
+ description: "After inspiredesign finishes, read advanced-brief.md first, then continue in Canvas with the emitted request template and load the canvas-contract design-agent lane before patching.",
509
520
  details: [
510
521
  { label: "quick:", value: INSPIREDESIGN_HANDOFF_COMMANDS.loadBestPractices },
511
522
  { label: "design:", value: INSPIREDESIGN_HANDOFF_COMMANDS.loadDesignAgent },
523
+ { label: "brief:", value: INSPIREDESIGN_HANDOFF_GUIDANCE.reviewAdvancedBrief },
512
524
  { label: "prep:", value: INSPIREDESIGN_HANDOFF_GUIDANCE.prepareCanvasPlanRequest },
513
525
  { label: "run:", value: INSPIREDESIGN_HANDOFF_COMMANDS.continueInCanvas }
514
526
  ]
@@ -1517,7 +1529,15 @@ async function runNativeCommand(args) {
1517
1529
  // src/cli/commands/serve.ts
1518
1530
  var daemonHandle = null;
1519
1531
  var PS_MAX_BUFFER = 8 * 1024 * 1024;
1532
+ var DAEMON_SHUTDOWN_POLL_ATTEMPTS = 10;
1533
+ var DAEMON_SHUTDOWN_POLL_DELAY_MS = 100;
1534
+ var DAEMON_SHUTDOWN_STATUS_TIMEOUT_MS = 250;
1535
+ var DAEMON_STOP_TIMEOUT_MS = 1e3;
1536
+ var MIN_PORT = 1;
1537
+ var MAX_PORT = 65535;
1520
1538
  var SERVE_COMMAND_PATTERN = /(?:^|\s)(?:\S*[\\/])?(?:opendevbrowser|dist[\\/]+cli[\\/]+index\.js)(?=\s|$).*?\bserve\b/;
1539
+ var SERVE_PORT_SPLIT_PATTERN = /(?:^|\s)--port\s+(\d+)(?=\s|$)/;
1540
+ var SERVE_PORT_EQUALS_PATTERN = /(?:^|\s)--port=(\d+)(?=\s|$)/;
1521
1541
  var SERVE_STOP_PATTERN = /(?:^|\s)--stop(?:\s|$)/;
1522
1542
  var CURRENT_UID = typeof process.getuid === "function" ? process.getuid() : null;
1523
1543
  var CURRENT_EXECUTABLE = process.execPath;
@@ -1536,29 +1556,6 @@ async function resolveExistingDaemon(port, tokens) {
1536
1556
  function isPositivePid(value) {
1537
1557
  return typeof value === "number" && Number.isInteger(value) && value > 0;
1538
1558
  }
1539
- function rememberStalePid(staleDaemonPids, pid) {
1540
- if (isPositivePid(pid)) {
1541
- staleDaemonPids.add(pid);
1542
- }
1543
- }
1544
- async function stopDaemonOnPort(port, token) {
1545
- try {
1546
- const response = await fetchWithTimeout(`http://127.0.0.1:${port}/stop`, {
1547
- method: "POST",
1548
- headers: { Authorization: `Bearer ${token}` }
1549
- });
1550
- return response.ok;
1551
- } catch {
1552
- return false;
1553
- }
1554
- }
1555
- async function stopStaleDaemon(port, daemon, staleDaemonPids) {
1556
- rememberStalePid(staleDaemonPids, daemon.status.pid);
1557
- const stopped = await stopDaemonOnPort(port, daemon.token);
1558
- if (!stopped && isPositivePid(daemon.status.pid)) {
1559
- terminateProcess(daemon.status.pid);
1560
- }
1561
- }
1562
1559
  function parseServeArgs(rawArgs) {
1563
1560
  const parsed = { stop: false };
1564
1561
  for (let i = 0; i < rawArgs.length; i += 1) {
@@ -1572,7 +1569,7 @@ function parseServeArgs(rawArgs) {
1572
1569
  if (!value) {
1573
1570
  throw createUsageError("Missing value for --port");
1574
1571
  }
1575
- parsed.port = parseNumberFlag(value, "--port", { min: 1, max: 65535 });
1572
+ parsed.port = parseNumberFlag(value, "--port", { min: MIN_PORT, max: MAX_PORT });
1576
1573
  i += 1;
1577
1574
  continue;
1578
1575
  }
@@ -1581,7 +1578,7 @@ function parseServeArgs(rawArgs) {
1581
1578
  if (!value) {
1582
1579
  throw createUsageError("Missing value for --port");
1583
1580
  }
1584
- parsed.port = parseNumberFlag(value, "--port", { min: 1, max: 65535 });
1581
+ parsed.port = parseNumberFlag(value, "--port", { min: MIN_PORT, max: MAX_PORT });
1585
1582
  continue;
1586
1583
  }
1587
1584
  if (arg === "--token") {
@@ -1629,6 +1626,14 @@ function parseServeProcessSnapshot(line) {
1629
1626
  command
1630
1627
  };
1631
1628
  }
1629
+ function parseServeCommandPort(command) {
1630
+ const rawPort = command.match(SERVE_PORT_EQUALS_PATTERN)?.[1] ?? command.match(SERVE_PORT_SPLIT_PATTERN)?.[1];
1631
+ if (!rawPort) {
1632
+ return null;
1633
+ }
1634
+ const port = Number.parseInt(rawPort, 10);
1635
+ return Number.isInteger(port) && port >= MIN_PORT && port <= MAX_PORT ? port : null;
1636
+ }
1632
1637
  function listServeProcessSnapshots() {
1633
1638
  const result = spawnSync("ps", ["-axww", "-o", "pid=,uid=,command="], {
1634
1639
  encoding: "utf-8",
@@ -1651,6 +1656,9 @@ function isCurrentExecutableServeProcess(snapshot) {
1651
1656
  }
1652
1657
  return !SERVE_STOP_PATTERN.test(snapshot.command);
1653
1658
  }
1659
+ function isRequestedPortServeProcess(snapshot, requestedPort) {
1660
+ return isCurrentExecutableServeProcess(snapshot) && parseServeCommandPort(snapshot.command) === requestedPort;
1661
+ }
1654
1662
  function terminateProcess(pid) {
1655
1663
  if (!Number.isInteger(pid) || pid <= 0 || pid === process.pid || pid === process.ppid) {
1656
1664
  return false;
@@ -1666,9 +1674,9 @@ function terminateProcess(pid) {
1666
1674
  }
1667
1675
  return true;
1668
1676
  }
1669
- function cleanupCompetingServeProcesses(keepPid) {
1677
+ function cleanupCompetingServeProcesses(requestedPort, keepPid) {
1670
1678
  const candidates = listServeProcessSnapshots().filter((snapshot) => {
1671
- if (!isCurrentExecutableServeProcess(snapshot)) {
1679
+ if (!isRequestedPortServeProcess(snapshot, requestedPort)) {
1672
1680
  return false;
1673
1681
  }
1674
1682
  if (snapshot.pid === process.pid || snapshot.pid === process.ppid) {
@@ -1690,6 +1698,85 @@ function cleanupCompetingServeProcesses(keepPid) {
1690
1698
  }
1691
1699
  return clearedPids;
1692
1700
  }
1701
+ function terminateServeProcessByPid(pid) {
1702
+ if (!isPositivePid(pid)) {
1703
+ return false;
1704
+ }
1705
+ const snapshot = listServeProcessSnapshots().find((item) => item.pid === pid);
1706
+ return snapshot ? isCurrentExecutableServeProcess(snapshot) && terminateProcess(pid) : false;
1707
+ }
1708
+ function buildStaleStopMessage(metadata) {
1709
+ const pid = isPositivePid(metadata.pid) ? ` pid=${metadata.pid}` : "";
1710
+ return `Daemon rejected stale stop request for 127.0.0.1:${metadata.port}${pid}. Run \`opendevbrowser status --daemon\` to inspect the active daemon, then restart from the current install if needed.`;
1711
+ }
1712
+ function buildProtectedMismatchMessage(port, status) {
1713
+ return `Daemon on 127.0.0.1:${port} pid=${status.pid} is protected by a different opendevbrowser build. Run \`opendevbrowser status --daemon\` to inspect it, then restart from the current install.`;
1714
+ }
1715
+ async function waitForDaemonShutdown(port, token) {
1716
+ for (let attempt = 0; attempt < DAEMON_SHUTDOWN_POLL_ATTEMPTS; attempt += 1) {
1717
+ const status = await fetchDaemonStatus(port, token, { timeoutMs: DAEMON_SHUTDOWN_STATUS_TIMEOUT_MS });
1718
+ if (!status?.ok) {
1719
+ return true;
1720
+ }
1721
+ await new Promise((resolve5) => setTimeout(resolve5, DAEMON_SHUTDOWN_POLL_DELAY_MS));
1722
+ }
1723
+ return false;
1724
+ }
1725
+ async function stopMismatchedDaemon(port, daemon) {
1726
+ let response;
1727
+ try {
1728
+ response = await fetchWithTimeout(`http://127.0.0.1:${port}/stop`, {
1729
+ method: "POST",
1730
+ headers: createDaemonStopHeaders(daemon.token, "serve.upgrade")
1731
+ }, DAEMON_STOP_TIMEOUT_MS);
1732
+ } catch (error) {
1733
+ const status = await fetchDaemonStatus(port, daemon.token, {
1734
+ timeoutMs: DAEMON_SHUTDOWN_STATUS_TIMEOUT_MS
1735
+ });
1736
+ if (!status?.ok) {
1737
+ return null;
1738
+ }
1739
+ const message = error instanceof Error ? error.message : String(error);
1740
+ return `Failed to stop mismatched daemon on 127.0.0.1:${port}: ${message}.`;
1741
+ }
1742
+ if (response.status === 409) {
1743
+ return buildProtectedMismatchMessage(port, daemon.status);
1744
+ }
1745
+ if (!response.ok) {
1746
+ return `Failed to stop mismatched daemon on 127.0.0.1:${port}: stop returned ${response.status}.`;
1747
+ }
1748
+ if (await waitForDaemonShutdown(port, daemon.token)) {
1749
+ return null;
1750
+ }
1751
+ if (terminateServeProcessByPid(daemon.status.pid)) {
1752
+ return null;
1753
+ }
1754
+ return `Timed out waiting for mismatched daemon on 127.0.0.1:${port} to stop.`;
1755
+ }
1756
+ async function prepareExistingDaemon(port, daemon) {
1757
+ if (isCurrentDaemonFingerprint(daemon.status.fingerprint)) {
1758
+ return null;
1759
+ }
1760
+ return await stopMismatchedDaemon(port, daemon);
1761
+ }
1762
+ function buildAlreadyRunningResult(port, status, fallbackRelayPort, clearedCount) {
1763
+ const relayPort = status.relay.port ?? fallbackRelayPort;
1764
+ const staleNote = clearedCount > 0 ? `
1765
+ Cleared ${clearedCount} stale daemon process${clearedCount === 1 ? "" : "es"}.` : "";
1766
+ return {
1767
+ success: true,
1768
+ message: `Daemon already running on 127.0.0.1:${port} (pid=${status.pid}, relay ${relayPort}).${staleNote}`,
1769
+ data: {
1770
+ port,
1771
+ pid: status.pid,
1772
+ relayPort,
1773
+ alreadyRunning: true,
1774
+ staleDaemonsCleared: clearedCount,
1775
+ relay: status.relay
1776
+ },
1777
+ exitCode: null
1778
+ };
1779
+ }
1693
1780
  async function runServe(args) {
1694
1781
  const serveArgs = parseServeArgs(args.rawArgs);
1695
1782
  if (serveArgs.stop) {
@@ -1705,8 +1792,11 @@ async function runServe(args) {
1705
1792
  try {
1706
1793
  const response = await fetchWithTimeout(`http://127.0.0.1:${metadata2.port}/stop`, {
1707
1794
  method: "POST",
1708
- headers: { Authorization: `Bearer ${metadata2.token}` }
1795
+ headers: createDaemonStopHeaders(metadata2.token, "serve.stop")
1709
1796
  });
1797
+ if (response.status === 409) {
1798
+ return { success: false, message: buildStaleStopMessage(metadata2), exitCode: EXIT_EXECUTION };
1799
+ }
1710
1800
  if (!response.ok) {
1711
1801
  throw new Error(`Stop failed (${response.status})`);
1712
1802
  }
@@ -1721,34 +1811,23 @@ async function runServe(args) {
1721
1811
  const metadata = readDaemonMetadata();
1722
1812
  const metadataToken = metadata?.port === requestedPort ? metadata.token : void 0;
1723
1813
  const tokenCandidates = resolveTokenCandidates(serveArgs.token, metadataToken, config.daemonToken);
1724
- const currentFingerprint = getCurrentDaemonFingerprint();
1725
1814
  const existingDaemon = await resolveExistingDaemon(requestedPort, tokenCandidates);
1726
- const staleDaemonPids = new Set(cleanupCompetingServeProcesses(existingDaemon?.status.pid));
1815
+ const staleDaemonPids = /* @__PURE__ */ new Set();
1727
1816
  const staleCleared = () => staleDaemonPids.size;
1728
- let replacedStaleFingerprint = false;
1729
1817
  if (existingDaemon) {
1730
- const fingerprintMatches = existingDaemon.status.fingerprint === currentFingerprint;
1731
- if (fingerprintMatches) {
1732
- const relayPort = existingDaemon.status.relay.port ?? config.relayPort;
1733
- const clearedCount2 = staleCleared();
1734
- const staleNote2 = clearedCount2 > 0 ? `
1735
- Cleared ${clearedCount2} stale daemon process${clearedCount2 === 1 ? "" : "es"}.` : "";
1736
- return {
1737
- success: true,
1738
- message: `Daemon already running on 127.0.0.1:${requestedPort} (pid=${existingDaemon.status.pid}, relay ${relayPort}).${staleNote2}`,
1739
- data: {
1740
- port: requestedPort,
1741
- pid: existingDaemon.status.pid,
1742
- relayPort,
1743
- alreadyRunning: true,
1744
- staleDaemonsCleared: clearedCount2,
1745
- relay: existingDaemon.status.relay
1746
- },
1747
- exitCode: null
1748
- };
1818
+ const mismatchMessage = await prepareExistingDaemon(requestedPort, existingDaemon);
1819
+ if (mismatchMessage) {
1820
+ return { success: false, message: mismatchMessage, exitCode: EXIT_EXECUTION };
1821
+ }
1822
+ if (isCurrentDaemonFingerprint(existingDaemon.status.fingerprint)) {
1823
+ for (const pid of cleanupCompetingServeProcesses(requestedPort, existingDaemon.status.pid)) {
1824
+ staleDaemonPids.add(pid);
1825
+ }
1826
+ return buildAlreadyRunningResult(requestedPort, existingDaemon.status, config.relayPort, staleCleared());
1749
1827
  }
1750
- await stopStaleDaemon(requestedPort, existingDaemon, staleDaemonPids);
1751
- replacedStaleFingerprint = true;
1828
+ }
1829
+ for (const pid of cleanupCompetingServeProcesses(requestedPort)) {
1830
+ staleDaemonPids.add(pid);
1752
1831
  }
1753
1832
  let nativeStatus = getNativeStatusSnapshot();
1754
1833
  let nativeMessage = null;
@@ -1791,35 +1870,17 @@ Cleared ${clearedCount2} stale daemon process${clearedCount2 === 1 ? "" : "es"}.
1791
1870
  }
1792
1871
  const runningDaemon = await resolveExistingDaemon(requestedPort, tokenCandidates);
1793
1872
  if (runningDaemon) {
1794
- const fingerprintMatches = runningDaemon.status.fingerprint === currentFingerprint;
1795
- if (fingerprintMatches) {
1796
- const relayPort = runningDaemon.status.relay.port ?? config.relayPort;
1797
- const clearedCount2 = staleCleared();
1798
- const staleNote2 = clearedCount2 > 0 ? `
1799
- Cleared ${clearedCount2} stale daemon process${clearedCount2 === 1 ? "" : "es"}.` : "";
1800
- return {
1801
- success: true,
1802
- message: `Daemon already running on 127.0.0.1:${requestedPort} (pid=${runningDaemon.status.pid}, relay ${relayPort}).${staleNote2}`,
1803
- data: {
1804
- port: requestedPort,
1805
- pid: runningDaemon.status.pid,
1806
- relayPort,
1807
- alreadyRunning: true,
1808
- staleDaemonsCleared: clearedCount2,
1809
- relay: runningDaemon.status.relay
1810
- },
1811
- exitCode: null
1812
- };
1873
+ const mismatchMessage = await prepareExistingDaemon(requestedPort, runningDaemon);
1874
+ if (mismatchMessage) {
1875
+ return { success: false, message: mismatchMessage, exitCode: EXIT_EXECUTION };
1813
1876
  }
1814
- await stopStaleDaemon(requestedPort, runningDaemon, staleDaemonPids);
1815
- replacedStaleFingerprint = true;
1816
- if (attempt === 0) {
1817
- continue;
1877
+ if (isCurrentDaemonFingerprint(runningDaemon.status.fingerprint)) {
1878
+ return buildAlreadyRunningResult(requestedPort, runningDaemon.status, config.relayPort, staleCleared());
1818
1879
  }
1819
1880
  }
1820
1881
  if (attempt === 0) {
1821
1882
  let clearedNewPid = false;
1822
- for (const pid of cleanupCompetingServeProcesses()) {
1883
+ for (const pid of cleanupCompetingServeProcesses(requestedPort)) {
1823
1884
  const previousSize = staleDaemonPids.size;
1824
1885
  staleDaemonPids.add(pid);
1825
1886
  if (staleDaemonPids.size > previousSize) {
@@ -1854,9 +1915,8 @@ Cleared ${clearedCount2} stale daemon process${clearedCount2 === 1 ? "" : "es"}.
1854
1915
  const clearedCount = staleCleared();
1855
1916
  const staleNote = clearedCount > 0 ? `
1856
1917
  Cleared ${clearedCount} stale daemon process${clearedCount === 1 ? "" : "es"}.` : "";
1857
- const fingerprintNote = replacedStaleFingerprint ? "\nReplaced stale daemon fingerprint." : "";
1858
1918
  const message = nativeMessage ? `${baseMessage}
1859
- ${nativeMessage}${fingerprintNote}${staleNote}` : `${baseMessage}${fingerprintNote}${staleNote}`;
1919
+ ${nativeMessage}${staleNote}` : `${baseMessage}${staleNote}`;
1860
1920
  return {
1861
1921
  success: true,
1862
1922
  message,
@@ -2424,17 +2484,40 @@ var parseDaemonArgs = (rawArgs) => {
2424
2484
  var stopDaemonIfRunning = async () => {
2425
2485
  const metadata = readDaemonMetadata();
2426
2486
  if (!metadata) {
2427
- return false;
2487
+ return { outcome: "not_running" };
2428
2488
  }
2429
2489
  try {
2430
2490
  const response = await fetchWithTimeout(`http://127.0.0.1:${metadata.port}/stop`, {
2431
2491
  method: "POST",
2432
- headers: { Authorization: `Bearer ${metadata.token}` }
2492
+ headers: createDaemonStopHeaders(metadata.token, "daemon.uninstall")
2433
2493
  });
2434
- return response.ok;
2435
- } catch {
2494
+ if (response.status === 409) {
2495
+ return { outcome: "fingerprint_rejected", pid: metadata.pid, port: metadata.port };
2496
+ }
2497
+ return response.ok ? { outcome: "stopped", pid: metadata.pid, port: metadata.port } : { outcome: "failed", pid: metadata.pid, port: metadata.port, status: response.status };
2498
+ } catch (error) {
2499
+ return {
2500
+ outcome: "failed",
2501
+ pid: metadata.pid,
2502
+ port: metadata.port,
2503
+ error: error instanceof Error ? error.message : String(error)
2504
+ };
2505
+ }
2506
+ };
2507
+ var buildStopFailureMessage = (stop) => {
2508
+ const target = stop.port ? `127.0.0.1:${stop.port}` : "recorded daemon";
2509
+ const pid = stop.pid ? ` pid=${stop.pid}` : "";
2510
+ if (stop.outcome === "fingerprint_rejected") {
2511
+ return `Daemon autostart removed, but the running daemon at ${target}${pid} rejected the stop request as stale. Run \`opendevbrowser status --daemon\` to inspect it and restart from the current install if needed.`;
2512
+ }
2513
+ const reason = stop.error ?? (stop.status ? `HTTP ${stop.status}` : "unknown error");
2514
+ return `Daemon autostart removed, but stopping ${target}${pid} failed (${reason}).`;
2515
+ };
2516
+ var shouldFailUninstallStop = (stop) => {
2517
+ if (stop.outcome === "stopped" || stop.outcome === "not_running") {
2436
2518
  return false;
2437
2519
  }
2520
+ return true;
2438
2521
  };
2439
2522
  var formatReason = (reason) => {
2440
2523
  return reason ? reason.replace(/_/g, " ") : "unknown reason";
@@ -2509,7 +2592,15 @@ async function runDaemonCommand(args) {
2509
2592
  exitCode: EXIT_EXECUTION
2510
2593
  };
2511
2594
  }
2512
- await stopDaemonIfRunning();
2595
+ const stop = await stopDaemonIfRunning();
2596
+ if (shouldFailUninstallStop(stop)) {
2597
+ return {
2598
+ success: false,
2599
+ message: buildStopFailureMessage(stop),
2600
+ data: { ...result, stop },
2601
+ exitCode: EXIT_EXECUTION
2602
+ };
2603
+ }
2513
2604
  return {
2514
2605
  success: true,
2515
2606
  message: `Daemon autostart removed (${result.platform}).`,
@@ -2517,7 +2608,7 @@ async function runDaemonCommand(args) {
2517
2608
  };
2518
2609
  }
2519
2610
  const autostart = getAutostartStatus();
2520
- const daemonStatus = await fetchDaemonStatusFromMetadata();
2611
+ const daemonStatus = await fetchDaemonStatusFromMetadata(void 0, DEFAULT_DAEMON_STATUS_FETCH_OPTIONS);
2521
2612
  const running = Boolean(daemonStatus);
2522
2613
  const message = buildStatusMessage(autostart, running);
2523
2614
  const data = {
@@ -3440,15 +3531,6 @@ async function runSessionInspector(args) {
3440
3531
  };
3441
3532
  }
3442
3533
 
3443
- // src/cli/transport-timeouts.ts
3444
- var DEFAULT_CLICK_TRANSPORT_TIMEOUT_MS = 6e4;
3445
- var DEFAULT_DIALOG_TRANSPORT_TIMEOUT_MS = 3e4;
3446
- var DEFAULT_TARGET_CREATION_TRANSPORT_TIMEOUT_MS = 3e4;
3447
- var DEFAULT_SNAPSHOT_TRANSPORT_TIMEOUT_MS = 3e4;
3448
- var DEFAULT_REVIEW_TRANSPORT_TIMEOUT_MS = 3e4;
3449
- var DEFAULT_SCREENSHOT_TRANSPORT_TIMEOUT_MS = 3e4;
3450
- var DEFAULT_WORKFLOW_TRANSPORT_TIMEOUT_MS = 12e4;
3451
-
3452
3534
  // src/cli/commands/challenge-automation-mode.ts
3453
3535
  function parseOptionalChallengeAutomationMode(rawArgs) {
3454
3536
  const value = parseOptionalStringFlag(rawArgs, "--challenge-automation-mode");
@@ -3574,11 +3656,6 @@ async function runSessionStatus(args) {
3574
3656
  }
3575
3657
 
3576
3658
  // src/cli/commands/status.ts
3577
- var DAEMON_STATUS_READ_OPTIONS = {
3578
- timeoutMs: 5e3,
3579
- retryAttempts: 5,
3580
- retryDelayMs: 250
3581
- };
3582
3659
  var parseStatusArgs2 = (rawArgs) => {
3583
3660
  const parsed = { daemon: false };
3584
3661
  for (let i = 0; i < rawArgs.length; i += 1) {
@@ -3619,21 +3696,23 @@ async function runStatus(args) {
3619
3696
  exitCode: assessment.exitCode ?? void 0
3620
3697
  };
3621
3698
  }
3622
- const daemonStatus = await fetchDaemonStatusFromMetadata(void 0, DAEMON_STATUS_READ_OPTIONS);
3699
+ const daemonStatus = await fetchDaemonStatusFromMetadata(void 0, DEFAULT_DAEMON_STATUS_FETCH_OPTIONS);
3623
3700
  if (!daemonStatus) {
3624
- throw createUsageError("Daemon not running. Start with `opendevbrowser serve`.");
3701
+ throw createDisconnectedError("Daemon not running. Start with `opendevbrowser serve`.");
3625
3702
  }
3626
3703
  const nativeStatus = getNativeStatusSnapshot();
3627
3704
  const nativeAssessment = assessNativeStatus(nativeStatus);
3705
+ const fingerprintLine = daemonStatus.fingerprintCurrent === false ? "Daemon fingerprint: mismatch with current build" : "Daemon fingerprint: current";
3628
3706
  const baseLines = [
3629
3707
  `Daemon OK (pid=${daemonStatus.pid})`,
3708
+ fingerprintLine,
3630
3709
  `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"} canvas=${daemonStatus.relay.canvasConnected ? "on" : "off"} pairing=${daemonStatus.relay.pairingRequired ? "on" : "off"} health=${daemonStatus.relay.health?.reason ?? "n/a"}`,
3631
3710
  `Native: ${nativeAssessment.summary}`,
3632
3711
  daemonStatus.relay.lastHandshakeError ? `Relay last handshake error: ${daemonStatus.relay.lastHandshakeError.code} (${daemonStatus.relay.lastHandshakeError.message})` : "Relay last handshake error: none",
3633
3712
  "Legend: ext=extension websocket, handshake=extension handshake, cdp=active /cdp client, annotate=annotation channel, ops=ops clients, canvas=canvas clients, pairing=token required, health=relay status"
3634
3713
  ];
3635
3714
  if (!nativeAssessment.success) {
3636
- baseLines.splice(3, 0, `Native detail: ${nativeAssessment.message}`);
3715
+ baseLines.splice(4, 0, `Native detail: ${nativeAssessment.message}`);
3637
3716
  }
3638
3717
  const baseMessage = baseLines.join("\n");
3639
3718
  const message = daemon || args.outputFormat !== "text" ? baseMessage : [
@@ -7391,10 +7470,11 @@ async function runInspiredesignCommand(args) {
7391
7470
  if (!parsed.brief?.trim()) {
7392
7471
  throw createUsageError("Missing --brief");
7393
7472
  }
7473
+ const captureMode = resolveInspiredesignCaptureMode(parsed.captureMode, parsed.urls);
7394
7474
  const data = await callDaemon("inspiredesign.run", {
7395
7475
  brief: parsed.brief,
7396
7476
  urls: parsed.urls,
7397
- captureMode: parsed.captureMode ?? "off",
7477
+ captureMode,
7398
7478
  includePrototypeGuidance: parsed.includePrototypeGuidance,
7399
7479
  mode: parsed.mode ?? "compact",
7400
7480
  timeoutMs: parsed.timeoutMs ?? DEFAULT_WORKFLOW_TRANSPORT_TIMEOUT_MS,
@@ -7414,7 +7494,7 @@ async function runInspiredesignCommand(args) {
7414
7494
  // package.json
7415
7495
  var package_default = {
7416
7496
  name: "opendevbrowser",
7417
- version: "0.0.24",
7497
+ version: "0.0.26",
7418
7498
  description: "Browser automation runtime with snapshot-refs-actions, browser replay screencasts, public read-only desktop observation, and browser-scoped computer-use orchestration",
7419
7499
  type: "module",
7420
7500
  main: "dist/index.js",