@reticlehq/server 2.12.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (386) hide show
  1. package/SKILL.md +67 -109
  2. package/dist/bridge/auth-failure-reason.d.ts +6 -2
  3. package/dist/bridge/auth-failure-reason.js +15 -8
  4. package/dist/bridge/bridge.d.ts +18 -1
  5. package/dist/bridge/bridge.js +247 -164
  6. package/dist/bridge/pairing-token.d.ts +5 -0
  7. package/dist/bridge/pairing-token.js +28 -0
  8. package/dist/bridge/protocol-skew.d.ts +37 -0
  9. package/dist/bridge/protocol-skew.js +45 -0
  10. package/dist/capsule/capsule-store.d.ts +6 -6
  11. package/dist/capsule/causal-summary.js +7 -2
  12. package/dist/cli/cli-doctor.js +31 -1
  13. package/dist/cli/cli-launch.d.ts +11 -1
  14. package/dist/cli/cli-launch.js +59 -10
  15. package/dist/cli/cli-parse.d.ts +15 -1
  16. package/dist/cli/cli-parse.js +212 -8
  17. package/dist/cli/cli-verify.d.ts +1 -0
  18. package/dist/cli/cli-verify.js +3 -2
  19. package/dist/cli/cloud-cli.js +428 -259
  20. package/dist/cli/cloud-keystore.d.ts +37 -0
  21. package/dist/cli/cloud-keystore.js +97 -0
  22. package/dist/cli/cloud-kit.d.ts +37 -0
  23. package/dist/cli/cloud-kit.js +121 -0
  24. package/dist/cli/cloud-login.d.ts +17 -0
  25. package/dist/cli/cloud-login.js +183 -0
  26. package/dist/cli/cloud-session.d.ts +48 -0
  27. package/dist/cli/cloud-session.js +53 -0
  28. package/dist/cli/config-discovery.js +1 -0
  29. package/dist/cli/daemon-start-options.d.ts +32 -0
  30. package/dist/cli/daemon-start-options.js +38 -0
  31. package/dist/cli/device-flow.d.ts +76 -0
  32. package/dist/cli/device-flow.js +56 -0
  33. package/dist/cli/doctor-rows.d.ts +9 -0
  34. package/dist/cli/doctor-rows.js +9 -0
  35. package/dist/cli/drive-attach.d.ts +13 -0
  36. package/dist/cli/drive-attach.js +18 -0
  37. package/dist/cli/drive-command.js +7 -3
  38. package/dist/cli/mcp-command.js +51 -20
  39. package/dist/cli/project-name.d.ts +30 -0
  40. package/dist/cli/project-name.js +44 -0
  41. package/dist/cli/status-next-action.d.ts +14 -0
  42. package/dist/cli/status-next-action.js +4 -4
  43. package/dist/cli.d.ts +1 -0
  44. package/dist/cli.js +134 -80
  45. package/dist/cloud/cloud-config.d.ts +22 -1
  46. package/dist/cloud/cloud-config.js +64 -5
  47. package/dist/cloud/cloud-sync.d.ts +5 -5
  48. package/dist/cloud/cloud-sync.js +33 -2
  49. package/dist/cloud/sync-cycle.d.ts +110 -0
  50. package/dist/cloud/sync-cycle.js +270 -0
  51. package/dist/cloud/sync-daemon.d.ts +69 -0
  52. package/dist/cloud/sync-daemon.js +260 -0
  53. package/dist/cloud/sync-disk.d.ts +9 -0
  54. package/dist/cloud/sync-disk.js +191 -0
  55. package/dist/cloud/sync-hash.d.ts +2 -0
  56. package/dist/cloud/sync-hash.js +35 -0
  57. package/dist/crawl/crawl-tools.js +5 -3
  58. package/dist/crawl/crawl.d.ts +3 -0
  59. package/dist/crawl/crawl.js +5 -0
  60. package/dist/daemon/daemon-resolve.d.ts +43 -0
  61. package/dist/daemon/daemon-resolve.js +69 -0
  62. package/dist/daemon/dev-servers.d.ts +15 -0
  63. package/dist/daemon/dev-servers.js +68 -0
  64. package/dist/daemon/wake-decision.d.ts +32 -0
  65. package/dist/daemon/wake-decision.js +38 -0
  66. package/dist/domain/domain-tools.js +2 -2
  67. package/dist/events/contradictions.d.ts +18 -0
  68. package/dist/events/contradictions.js +134 -21
  69. package/dist/events/declared.d.ts +7 -4
  70. package/dist/events/declared.js +15 -6
  71. package/dist/events/echo-mismatch.js +64 -14
  72. package/dist/events/event-filters.d.ts +8 -0
  73. package/dist/events/event-filters.js +19 -2
  74. package/dist/events/predicate-eval.js +53 -5
  75. package/dist/events/predicate-precheck.d.ts +8 -0
  76. package/dist/events/predicate-precheck.js +41 -0
  77. package/dist/events/predicate-route.d.ts +14 -0
  78. package/dist/events/predicate-route.js +36 -8
  79. package/dist/events/predicate.d.ts +5 -0
  80. package/dist/events/predicate.js +37 -5
  81. package/dist/events/split-text-miss.d.ts +30 -0
  82. package/dist/events/split-text-miss.js +44 -0
  83. package/dist/flows/flake.d.ts +6 -6
  84. package/dist/flows/flow-memory-consult.d.ts +55 -0
  85. package/dist/flows/flow-memory-consult.js +38 -0
  86. package/dist/flows/flow-replay-run.d.ts +18 -7
  87. package/dist/flows/flow-replay-run.js +239 -22
  88. package/dist/flows/flow-replay.js +11 -2
  89. package/dist/flows/flow-step-runners.d.ts +8 -0
  90. package/dist/flows/flow-step-runners.js +8 -2
  91. package/dist/flows/flow-store-for-session.d.ts +31 -0
  92. package/dist/flows/flow-store-for-session.js +33 -0
  93. package/dist/flows/flow-tools.js +58 -32
  94. package/dist/flows/flows.d.ts +8 -0
  95. package/dist/flows/flows.js +468 -2
  96. package/dist/flows/heal-run.js +5 -2
  97. package/dist/flows/predicate-to-expect.d.ts +17 -12
  98. package/dist/flows/predicate-to-expect.js +30 -12
  99. package/dist/flows/recordings.d.ts +13 -1
  100. package/dist/flows/recordings.js +17 -2
  101. package/dist/flows/replay.d.ts +22 -2
  102. package/dist/flows/replay.js +28 -2
  103. package/dist/flows/verify-change-tools.js +5 -2
  104. package/dist/honesty/blind-spots.d.ts +55 -2
  105. package/dist/honesty/blind-spots.js +63 -3
  106. package/dist/honesty/honesty.d.ts +24 -0
  107. package/dist/honesty/honesty.js +25 -1
  108. package/dist/honesty/instrumentation-gaps.d.ts +9 -0
  109. package/dist/honesty/instrumentation-gaps.js +29 -2
  110. package/dist/honesty/verified.d.ts +2 -1
  111. package/dist/honesty/verified.js +16 -1
  112. package/dist/http-server.js +12 -0
  113. package/dist/impact/impact-recorder.d.ts +31 -8
  114. package/dist/impact/impact-recorder.js +104 -21
  115. package/dist/impact/impact-store.d.ts +15 -1
  116. package/dist/impact/impact-store.js +43 -2
  117. package/dist/index.js +113 -7
  118. package/dist/init/agent-rules.d.ts +13 -6
  119. package/dist/init/agent-rules.js +23 -12
  120. package/dist/init/astro-patch.d.ts +16 -0
  121. package/dist/init/astro-patch.js +28 -0
  122. package/dist/init/capabilities.d.ts +4 -1
  123. package/dist/init/capabilities.js +22 -2
  124. package/dist/init/confirm.d.ts +31 -7
  125. package/dist/init/confirm.js +48 -8
  126. package/dist/init/cra.d.ts +20 -4
  127. package/dist/init/cra.js +57 -17
  128. package/dist/init/csp-check.js +17 -2
  129. package/dist/init/csp-doctor.d.ts +8 -1
  130. package/dist/init/csp-doctor.js +8 -3
  131. package/dist/init/dev-script.d.ts +3 -1
  132. package/dist/init/dev-script.js +8 -5
  133. package/dist/init/format-generated.d.ts +18 -0
  134. package/dist/init/format-generated.js +39 -0
  135. package/dist/init/install-hint.d.ts +9 -0
  136. package/dist/init/install-hint.js +29 -0
  137. package/dist/init/mcp-clients.d.ts +7 -0
  138. package/dist/init/mcp-clients.js +7 -0
  139. package/dist/init/mcp.d.ts +1 -1
  140. package/dist/init/mcp.js +1 -1
  141. package/dist/init/node-io.js +11 -1
  142. package/dist/init/plan-framework.js +81 -45
  143. package/dist/init/plan.d.ts +20 -7
  144. package/dist/init/plan.js +20 -45
  145. package/dist/init/preflight.d.ts +28 -0
  146. package/dist/init/preflight.js +38 -0
  147. package/dist/init/run.d.ts +32 -9
  148. package/dist/init/run.js +141 -119
  149. package/dist/init/slash-command.d.ts +1 -1
  150. package/dist/init/snippets.js +41 -25
  151. package/dist/init/vite-config.d.ts +1 -1
  152. package/dist/init/vite-config.js +39 -25
  153. package/dist/init/workspace-apps.d.ts +17 -10
  154. package/dist/init/workspace-apps.js +110 -0
  155. package/dist/input/network-mock-tools.js +3 -6
  156. package/dist/input/real-input.js +25 -5
  157. package/dist/input/scroll-tools.js +4 -6
  158. package/dist/input/viewport-tools.js +5 -4
  159. package/dist/intent/inline-intent.d.ts +10 -19
  160. package/dist/intent/inline-intent.js +91 -1
  161. package/dist/intent/intent-shard-store.d.ts +72 -0
  162. package/dist/intent/intent-shard-store.js +203 -0
  163. package/dist/intent/intent-shard.d.ts +411 -0
  164. package/dist/intent/intent-shard.js +176 -0
  165. package/dist/intent/intent-store.d.ts +12 -0
  166. package/dist/intent/intent-store.js +21 -0
  167. package/dist/intent/intent-subject.d.ts +45 -0
  168. package/dist/intent/intent-subject.js +177 -0
  169. package/dist/intent/intent-tools.js +65 -2
  170. package/dist/intent/open-intents.d.ts +21 -0
  171. package/dist/intent/open-intents.js +15 -0
  172. package/dist/license/license-env.d.ts +29 -0
  173. package/dist/license/license-env.js +148 -0
  174. package/dist/loopback-agent.d.ts +31 -0
  175. package/dist/loopback-agent.js +57 -0
  176. package/dist/mcp/attach-memory.d.ts +18 -2
  177. package/dist/mcp/attach-memory.js +50 -7
  178. package/dist/mcp/client-identity.d.ts +31 -0
  179. package/dist/mcp/client-identity.js +51 -0
  180. package/dist/mcp/mcp-post-transport.d.ts +26 -0
  181. package/dist/mcp/mcp-post-transport.js +109 -0
  182. package/dist/mcp/mcp-proxy.d.ts +2 -1
  183. package/dist/mcp/mcp-proxy.js +52 -62
  184. package/dist/mcp/mcp.js +7 -1
  185. package/dist/mcp/proxy-backoff.d.ts +11 -0
  186. package/dist/mcp/proxy-backoff.js +13 -0
  187. package/dist/mcp/proxy-daemon-probe.d.ts +1 -0
  188. package/dist/mcp/proxy-daemon-probe.js +7 -1
  189. package/dist/mcp/proxy-handshake.d.ts +23 -0
  190. package/dist/mcp/proxy-handshake.js +31 -0
  191. package/dist/mcp/tool-catalog-cache.d.ts +8 -0
  192. package/dist/mcp/tool-catalog-cache.js +12 -0
  193. package/dist/memory/memory-tools.d.ts +2 -0
  194. package/dist/memory/memory-tools.js +82 -0
  195. package/dist/memory/project-memory.d.ts +34 -0
  196. package/dist/memory/project-memory.js +96 -0
  197. package/dist/oracles/propose-consequences.js +12 -3
  198. package/dist/pool/browser-pool.d.ts +63 -4
  199. package/dist/pool/browser-pool.js +113 -4
  200. package/dist/pool/playwright-launcher.js +9 -0
  201. package/dist/project/project-tools.js +3 -3
  202. package/dist/project/reticle-dir.d.ts +15 -2
  203. package/dist/project/reticle-dir.js +21 -4
  204. package/dist/runs/run-store.d.ts +10 -0
  205. package/dist/runs/run-store.js +10 -0
  206. package/dist/runs/verification-sync.d.ts +0 -11
  207. package/dist/runs/verification-sync.js +22 -1
  208. package/dist/runs/verify-server.d.ts +8 -1
  209. package/dist/runs/verify-server.js +14 -2
  210. package/dist/session/command-timeout.d.ts +5 -2
  211. package/dist/session/command-timeout.js +5 -4
  212. package/dist/session/lease-visibility.d.ts +67 -0
  213. package/dist/session/lease-visibility.js +76 -0
  214. package/dist/session/no-session-diagnosis.d.ts +23 -0
  215. package/dist/session/no-session-diagnosis.js +58 -17
  216. package/dist/session/no-session-next-action.d.ts +7 -0
  217. package/dist/session/no-session-next-action.js +42 -3
  218. package/dist/session/no-session-watch.d.ts +10 -6
  219. package/dist/session/no-session-watch.js +51 -19
  220. package/dist/session/pending-commands.d.ts +11 -1
  221. package/dist/session/pending-commands.js +12 -2
  222. package/dist/session/resolve-within.d.ts +34 -0
  223. package/dist/session/resolve-within.js +53 -0
  224. package/dist/session/session-info.d.ts +18 -0
  225. package/dist/session/session-info.js +11 -0
  226. package/dist/session/session-manager.d.ts +14 -0
  227. package/dist/session/session-manager.js +61 -3
  228. package/dist/session/session-mirror.d.ts +16 -0
  229. package/dist/session/session-mirror.js +22 -0
  230. package/dist/session/session-recommendation.d.ts +19 -5
  231. package/dist/session/session-recommendation.js +25 -9
  232. package/dist/session/session-successor.js +36 -1
  233. package/dist/session/session-tools.js +2 -2
  234. package/dist/session/session.d.ts +22 -2
  235. package/dist/session/session.js +87 -7
  236. package/dist/session/wall-clock.d.ts +12 -0
  237. package/dist/session/wall-clock.js +14 -0
  238. package/dist/setup/agent-configs.d.ts +96 -0
  239. package/dist/setup/agent-configs.js +333 -0
  240. package/dist/setup/agent-io.d.ts +9 -0
  241. package/dist/setup/agent-io.js +16 -0
  242. package/dist/setup/agent-writer.d.ts +51 -0
  243. package/dist/setup/agent-writer.js +115 -0
  244. package/dist/setup/approval-migration.d.ts +47 -0
  245. package/dist/setup/approval-migration.js +68 -0
  246. package/dist/setup/auto-approve.d.ts +85 -0
  247. package/dist/setup/auto-approve.js +150 -0
  248. package/dist/setup/bridge-port.d.ts +15 -0
  249. package/dist/setup/bridge-port.js +24 -0
  250. package/dist/setup/desktop-shape.d.ts +57 -0
  251. package/dist/setup/desktop-shape.js +63 -0
  252. package/dist/setup/dev-server-wait.d.ts +60 -0
  253. package/dist/setup/dev-server-wait.js +65 -0
  254. package/dist/setup/drive-agent.d.ts +71 -0
  255. package/dist/setup/drive-agent.js +171 -0
  256. package/dist/setup/drive-plan.d.ts +68 -0
  257. package/dist/setup/drive-plan.js +130 -0
  258. package/dist/setup/ensure-daemon.d.ts +29 -0
  259. package/dist/setup/ensure-daemon.js +58 -0
  260. package/dist/setup/init-runtime.d.ts +47 -0
  261. package/dist/setup/init-runtime.js +160 -0
  262. package/dist/setup/interrupt.d.ts +25 -0
  263. package/dist/setup/interrupt.js +40 -0
  264. package/dist/setup/license-key.d.ts +39 -0
  265. package/dist/setup/license-key.js +72 -0
  266. package/dist/setup/listeners.d.ts +37 -0
  267. package/dist/setup/listeners.js +85 -0
  268. package/dist/setup/node-effects.d.ts +60 -0
  269. package/dist/setup/node-effects.js +210 -0
  270. package/dist/setup/page-probe.d.ts +39 -0
  271. package/dist/setup/page-probe.js +49 -0
  272. package/dist/setup/relaunch.d.ts +36 -0
  273. package/dist/setup/relaunch.js +57 -0
  274. package/dist/setup/remaining-steps.d.ts +19 -0
  275. package/dist/setup/remaining-steps.js +31 -0
  276. package/dist/setup/run-setup.d.ts +99 -0
  277. package/dist/setup/run-setup.js +187 -0
  278. package/dist/setup/session-pick.d.ts +47 -0
  279. package/dist/setup/session-pick.js +51 -0
  280. package/dist/setup/setup-command.d.ts +45 -0
  281. package/dist/setup/setup-command.js +199 -0
  282. package/dist/setup/setup-options.d.ts +74 -0
  283. package/dist/setup/setup-options.js +67 -0
  284. package/dist/setup/transcripts.d.ts +16 -0
  285. package/dist/setup/transcripts.js +43 -0
  286. package/dist/status-payload.d.ts +14 -1
  287. package/dist/status-payload.js +23 -1
  288. package/dist/telemetry/daemon-telemetry.d.ts +8 -1
  289. package/dist/telemetry/daemon-telemetry.js +52 -5
  290. package/dist/telemetry/error-class.js +1 -1
  291. package/dist/telemetry/feedback-context.d.ts +9 -1
  292. package/dist/telemetry/feedback-context.js +60 -29
  293. package/dist/telemetry/feedback-local.d.ts +15 -0
  294. package/dist/telemetry/feedback-local.js +56 -0
  295. package/dist/telemetry/feedback.d.ts +11 -0
  296. package/dist/telemetry/feedback.js +19 -6
  297. package/dist/telemetry/init-telemetry.d.ts +2 -0
  298. package/dist/telemetry/init-telemetry.js +2 -0
  299. package/dist/telemetry/install-source.d.ts +14 -0
  300. package/dist/telemetry/install-source.js +30 -0
  301. package/dist/telemetry/license-activation.d.ts +7 -1
  302. package/dist/telemetry/license-activation.js +9 -5
  303. package/dist/telemetry/mcp-connection.js +7 -0
  304. package/dist/telemetry/project-profile.d.ts +12 -1
  305. package/dist/telemetry/project-profile.js +11 -6
  306. package/dist/telemetry/proxy-telemetry.d.ts +3 -0
  307. package/dist/telemetry/proxy-telemetry.js +21 -0
  308. package/dist/telemetry/session-metrics.d.ts +12 -0
  309. package/dist/telemetry/session-metrics.js +35 -1
  310. package/dist/telemetry/telemetry.js +17 -2
  311. package/dist/telemetry/tool-refused.d.ts +3 -1
  312. package/dist/telemetry/tool-refused.js +34 -2
  313. package/dist/tools/act-capsule.d.ts +10 -0
  314. package/dist/tools/act-capsule.js +8 -2
  315. package/dist/tools/act-danger.d.ts +2 -1
  316. package/dist/tools/act-danger.js +11 -3
  317. package/dist/tools/act-observation.d.ts +41 -0
  318. package/dist/tools/act-observation.js +36 -0
  319. package/dist/tools/act-preflight.d.ts +20 -0
  320. package/dist/tools/act-preflight.js +98 -0
  321. package/dist/tools/act-sequence-retry.d.ts +45 -0
  322. package/dist/tools/act-sequence-retry.js +58 -0
  323. package/dist/tools/act-sequence-tool.d.ts +13 -0
  324. package/dist/tools/act-sequence-tool.js +135 -0
  325. package/dist/tools/act-target.d.ts +14 -0
  326. package/dist/tools/act-target.js +47 -0
  327. package/dist/tools/act-tools.d.ts +21 -1
  328. package/dist/tools/act-tools.js +100 -213
  329. package/dist/tools/assert-verdict.js +22 -16
  330. package/dist/tools/browser-tools.js +6 -2
  331. package/dist/tools/dynamic-tools.js +24 -5
  332. package/dist/tools/error-recovery.d.ts +1 -0
  333. package/dist/tools/error-recovery.js +8 -0
  334. package/dist/tools/invoke-tool.js +59 -9
  335. package/dist/tools/lease-tools.d.ts +30 -4
  336. package/dist/tools/lease-tools.js +169 -5
  337. package/dist/tools/merged-name-redirect.d.ts +13 -0
  338. package/dist/tools/merged-name-redirect.js +22 -0
  339. package/dist/tools/numeric-bounds.d.ts +100 -0
  340. package/dist/tools/numeric-bounds.js +125 -0
  341. package/dist/tools/observe-tools.js +47 -40
  342. package/dist/tools/read-tools.js +28 -7
  343. package/dist/tools/real-input-attempt.d.ts +10 -0
  344. package/dist/tools/real-input-attempt.js +59 -1
  345. package/dist/tools/reconcile-tools.js +4 -4
  346. package/dist/tools/tool-kit.d.ts +12 -0
  347. package/dist/tools/tool-kit.js +6 -1
  348. package/dist/tools/tool-names.d.ts +2 -0
  349. package/dist/tools/tool-names.js +2 -0
  350. package/dist/tools/tools-helpers.d.ts +19 -0
  351. package/dist/tools/tools-helpers.js +46 -0
  352. package/dist/tools/tools.d.ts +2 -0
  353. package/dist/tools/tools.js +56 -8
  354. package/dist/update/reticle-deps.d.ts +9 -0
  355. package/dist/update/reticle-deps.js +27 -0
  356. package/dist/update/sdk-sync.d.ts +1 -2
  357. package/dist/update/sdk-sync.js +1 -21
  358. package/dist/update/update-nudge.d.ts +18 -0
  359. package/dist/update/update-nudge.js +20 -0
  360. package/dist/version/peer-announce.js +4 -2
  361. package/dist/version/sdk-fix.d.ts +47 -0
  362. package/dist/version/sdk-fix.js +127 -0
  363. package/dist/version/version-nudge.d.ts +14 -0
  364. package/dist/version/version-nudge.js +21 -0
  365. package/dist/version/version-skew.d.ts +38 -3
  366. package/dist/version/version-skew.js +83 -7
  367. package/dist/visual/visual-store.d.ts +6 -6
  368. package/dist/visual/visual-store.js +18 -16
  369. package/dist/visual/visual-tools.js +57 -12
  370. package/docs/cli/doctor.mdx +1 -0
  371. package/docs/cli/serve.mdx +4 -2
  372. package/docs/cli.mdx +7 -3
  373. package/docs/desktop.mdx +1 -1
  374. package/docs/docs.json +1 -1
  375. package/docs/getting-started.md +19 -3
  376. package/docs/index.mdx +9 -6
  377. package/docs/install-agentic.mdx +38 -12
  378. package/docs/install-manual.mdx +1 -1
  379. package/docs/packages.mdx +1 -1
  380. package/docs/skill-file.mdx +3 -3
  381. package/docs/telemetry-contract.md +108 -4
  382. package/docs/tools-act-sequence.mdx +16 -0
  383. package/docs/usage.md +1 -1
  384. package/package.json +4 -3
  385. package/dist/init/cursor.d.ts +0 -24
  386. package/dist/init/cursor.js +0 -80
@@ -0,0 +1,38 @@
1
+ import { DAEMON_INNER_COMMAND, DRIVE_FLAG, HEADED_FLAG, HTTP_FLAG, HTTP_PORT_FLAG, HTTP_TOKEN_FLAG, PORT_FLAG, } from './cli-parse.js';
2
+ export function daemonStartOptions(parsed) {
3
+ return {
4
+ port: parsed.port,
5
+ headless: parsed.headless,
6
+ ...(parsed.driveUrl !== undefined ? { driveUrl: parsed.driveUrl } : {}),
7
+ ...(parsed.http
8
+ ? {
9
+ httpVerify: true,
10
+ ...(parsed.httpPort !== undefined ? { httpVerifyPort: parsed.httpPort } : {}),
11
+ ...(parsed.httpToken !== undefined ? { httpVerifyToken: parsed.httpToken } : {}),
12
+ }
13
+ : {}),
14
+ };
15
+ }
16
+ /**
17
+ * The argv a parent hands its detached `_daemon` child.
18
+ *
19
+ * `serve` and `mcp` each built this list by hand and each made the SAME mistake as the options
20
+ * builder above — pushing `--headed` only inside the `driveUrl` branch — so the flag survived
21
+ * parsing, was dropped at the spawn, and the child launched a hidden pool. Three sites, one wrong
22
+ * idea: that headless belongs to the drive URL. It belongs to the pool, which exists either way.
23
+ */
24
+ export function daemonSpawnArgs(parsed) {
25
+ const args = [DAEMON_INNER_COMMAND, PORT_FLAG, String(parsed.port)];
26
+ if (parsed.driveUrl !== undefined)
27
+ args.push(DRIVE_FLAG, parsed.driveUrl);
28
+ if (!parsed.headless)
29
+ args.push(HEADED_FLAG);
30
+ if (parsed.http) {
31
+ args.push(HTTP_FLAG);
32
+ if (parsed.httpPort !== undefined)
33
+ args.push(HTTP_PORT_FLAG, String(parsed.httpPort));
34
+ if (parsed.httpToken !== undefined)
35
+ args.push(HTTP_TOKEN_FLAG, parsed.httpToken);
36
+ }
37
+ return args;
38
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The browser device flow's wire shapes and its hand-off to a real browser.
3
+ *
4
+ * `reticle login` works like `gh auth login`: the CLI asks the cloud for a device code, opens the
5
+ * approval page, and polls until a human confirms it in a session that is already signed in. That
6
+ * confirmation is the whole security of the flow — only somebody already authenticated in the
7
+ * dashboard can turn a device code into a token for their org.
8
+ *
9
+ * Split from `cloud-cli` because it is self-contained: the schemas describe two endpoints and the
10
+ * opener knows only about platforms. Nothing here reads a session, a credential, or a project.
11
+ */
12
+ import { z } from 'zod';
13
+ /** What `/v1/auth/device/start` answers: the codes, where to approve, and how fast to poll. */
14
+ export declare const DeviceStartSchema: z.ZodObject<{
15
+ deviceCode: z.ZodString;
16
+ userCode: z.ZodString;
17
+ verificationUri: z.ZodString;
18
+ verificationUriComplete: z.ZodString;
19
+ interval: z.ZodNumber;
20
+ expiresAt: z.ZodNumber;
21
+ }, "strip", z.ZodTypeAny, {
22
+ deviceCode: string;
23
+ userCode: string;
24
+ verificationUri: string;
25
+ verificationUriComplete: string;
26
+ interval: number;
27
+ expiresAt: number;
28
+ }, {
29
+ deviceCode: string;
30
+ userCode: string;
31
+ verificationUri: string;
32
+ verificationUriComplete: string;
33
+ interval: number;
34
+ expiresAt: number;
35
+ }>;
36
+ /**
37
+ * What `/v1/auth/device/token` answers while polling. Everything past `status` is optional because
38
+ * only an APPROVED poll carries a token, and `org.id` is absent on a cloud older than org-scoped
39
+ * credentials.
40
+ */
41
+ export declare const DevicePollSchema: z.ZodObject<{
42
+ status: z.ZodString;
43
+ token: z.ZodOptional<z.ZodString>;
44
+ org: z.ZodOptional<z.ZodObject<{
45
+ id: z.ZodOptional<z.ZodString>;
46
+ name: z.ZodString;
47
+ }, "strip", z.ZodTypeAny, {
48
+ name: string;
49
+ id?: string | undefined;
50
+ }, {
51
+ name: string;
52
+ id?: string | undefined;
53
+ }>>;
54
+ }, "strip", z.ZodTypeAny, {
55
+ status: string;
56
+ org?: {
57
+ name: string;
58
+ id?: string | undefined;
59
+ } | undefined;
60
+ token?: string | undefined;
61
+ }, {
62
+ status: string;
63
+ org?: {
64
+ name: string;
65
+ id?: string | undefined;
66
+ } | undefined;
67
+ token?: string | undefined;
68
+ }>;
69
+ /**
70
+ * Best-effort open the approval page in the default browser.
71
+ *
72
+ * Every failure is swallowed on purpose: the URL is printed either way, and a headless box, a
73
+ * container, or an SSH session has no opener at all. Refusing to log in because a convenience did
74
+ * not work would break the one environment that most needs the printed fallback.
75
+ */
76
+ export declare const openBrowser: (target: string) => void;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The browser device flow's wire shapes and its hand-off to a real browser.
3
+ *
4
+ * `reticle login` works like `gh auth login`: the CLI asks the cloud for a device code, opens the
5
+ * approval page, and polls until a human confirms it in a session that is already signed in. That
6
+ * confirmation is the whole security of the flow — only somebody already authenticated in the
7
+ * dashboard can turn a device code into a token for their org.
8
+ *
9
+ * Split from `cloud-cli` because it is self-contained: the schemas describe two endpoints and the
10
+ * opener knows only about platforms. Nothing here reads a session, a credential, or a project.
11
+ */
12
+ import { z } from 'zod';
13
+ import { spawn } from 'node:child_process';
14
+ import { NodePlatform } from '../platform.js';
15
+ /** What `/v1/auth/device/start` answers: the codes, where to approve, and how fast to poll. */
16
+ export const DeviceStartSchema = z.object({
17
+ deviceCode: z.string(),
18
+ userCode: z.string(),
19
+ verificationUri: z.string(),
20
+ verificationUriComplete: z.string(),
21
+ interval: z.number(),
22
+ expiresAt: z.number(),
23
+ });
24
+ /**
25
+ * What `/v1/auth/device/token` answers while polling. Everything past `status` is optional because
26
+ * only an APPROVED poll carries a token, and `org.id` is absent on a cloud older than org-scoped
27
+ * credentials.
28
+ */
29
+ export const DevicePollSchema = z.object({
30
+ status: z.string(),
31
+ token: z.string().optional(),
32
+ org: z.object({ id: z.string().optional(), name: z.string() }).optional(),
33
+ });
34
+ /**
35
+ * Best-effort open the approval page in the default browser.
36
+ *
37
+ * Every failure is swallowed on purpose: the URL is printed either way, and a headless box, a
38
+ * container, or an SSH session has no opener at all. Refusing to log in because a convenience did
39
+ * not work would break the one environment that most needs the printed fallback.
40
+ */
41
+ export const openBrowser = (target) => {
42
+ const cmd = NodePlatform.MACOS === process.platform
43
+ ? 'open'
44
+ : NodePlatform.WINDOWS === process.platform
45
+ ? 'cmd'
46
+ : 'xdg-open';
47
+ const args = NodePlatform.WINDOWS === process.platform ? ['/c', 'start', '', target] : [target];
48
+ try {
49
+ const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
50
+ child.on('error', () => undefined);
51
+ child.unref();
52
+ }
53
+ catch {
54
+ /* no opener available — the user opens the printed URL manually */
55
+ }
56
+ };
@@ -31,6 +31,15 @@ export declare const DoctorRow: {
31
31
  readonly DAEMON: "daemon";
32
32
  readonly VERSION: "version";
33
33
  readonly SESSIONS: "sessions";
34
+ /**
35
+ * The agent-to-daemon hop, which every other row here is blind to.
36
+ *
37
+ * Reported from the field: a user with the SDK injected, the overlay visible, a live session on
38
+ * the daemon and every tool listed in their client was still told by three separate agents that
39
+ * Reticle was not present. Their words: "four green checkmarks that don't add up to a working
40
+ * verification." Every row in this list checks a COMPONENT; none checked a LINK.
41
+ */
42
+ readonly AGENT_LINK: "agent link";
34
43
  readonly BRIDGE_PORT: "bridge port";
35
44
  readonly PORT_CHECK: "port check";
36
45
  readonly SIBLING: "sibling";
@@ -31,6 +31,15 @@ export const DoctorRow = {
31
31
  DAEMON: 'daemon',
32
32
  VERSION: 'version',
33
33
  SESSIONS: 'sessions',
34
+ /**
35
+ * The agent-to-daemon hop, which every other row here is blind to.
36
+ *
37
+ * Reported from the field: a user with the SDK injected, the overlay visible, a live session on
38
+ * the daemon and every tool listed in their client was still told by three separate agents that
39
+ * Reticle was not present. Their words: "four green checkmarks that don't add up to a working
40
+ * verification." Every row in this list checks a COMPONENT; none checked a LINK.
41
+ */
42
+ AGENT_LINK: 'agent link',
34
43
  BRIDGE_PORT: 'bridge port',
35
44
  PORT_CHECK: 'port check',
36
45
  SIBLING: 'sibling',
@@ -78,6 +78,19 @@ export declare function readAttachResponse(port: number, url: string, res: {
78
78
  export declare function requestDriveSession(port: number, url: string, post?: DrivePost): Promise<DriveAttachResult>;
79
79
  /** The success line. Names the session, because that is the handle everything downstream needs. */
80
80
  export declare function describeAttached(port: number, url: string, session: DriveSession): string;
81
+ /**
82
+ * What an attach cannot honour: the window.
83
+ *
84
+ * `drive` documents a HEADED browser and parses `--headless` as the opt-out, but the attach path
85
+ * POSTs a url and nothing else — the daemon answers from a pool it launched headless at boot, and no
86
+ * per-request flag can change a browser that is already running. So the run is invisible, which is
87
+ * the half of the field report that reads "the app starts to run headlessly".
88
+ *
89
+ * Saying so is the honest half. The other half is that the run is no longer unwatchable: the HUD
90
+ * feed is mirrored to every other tab of the same app (see Session.setViewers), so a tab the human
91
+ * already has open shows the narration and the counters as the leased context is driven.
92
+ */
93
+ export declare function driveHeadlessOnAttach(port: number, url: string): string;
81
94
  /**
82
95
  * A stranger holds the port, in words `drive` can honour.
83
96
  *
@@ -147,6 +147,24 @@ export function describeAttached(port, url, session) {
147
147
  return (`attached to the Reticle daemon on :${String(port)} — it is driving ${url} as session ` +
148
148
  `${session.sessionId}, which every Reticle tool can address.${life}`);
149
149
  }
150
+ /**
151
+ * What an attach cannot honour: the window.
152
+ *
153
+ * `drive` documents a HEADED browser and parses `--headless` as the opt-out, but the attach path
154
+ * POSTs a url and nothing else — the daemon answers from a pool it launched headless at boot, and no
155
+ * per-request flag can change a browser that is already running. So the run is invisible, which is
156
+ * the half of the field report that reads "the app starts to run headlessly".
157
+ *
158
+ * Saying so is the honest half. The other half is that the run is no longer unwatchable: the HUD
159
+ * feed is mirrored to every other tab of the same app (see Session.setViewers), so a tab the human
160
+ * already has open shows the narration and the counters as the leased context is driven.
161
+ */
162
+ export function driveHeadlessOnAttach(port, url) {
163
+ return (`note: this session runs inside the daemon on :${String(port)}, whose browser was launched ` +
164
+ 'headless — there is no window to show, and `--headless` is not a per-drive choice. Open ' +
165
+ `${url} in your own browser to watch: any tab of that app mirrors this session's HUD. To get a ` +
166
+ 'window instead, stop the daemon and run `reticle drive` again with the port free.');
167
+ }
150
168
  /**
151
169
  * A stranger holds the port, in words `drive` can honour.
152
170
  *
@@ -13,7 +13,7 @@ import { probeDaemon } from '../mcp/mcp-proxy.js';
13
13
  import { readPid } from '../daemon/daemon.js';
14
14
  import { fetchStatus } from './cli-launch.js';
15
15
  import { captureLookup, findPortHolder } from './port-holder.js';
16
- import { DriveMode, decideDriveMode, describeAttached, driveForeignHolder, driveRaceLost, requestDriveSession, } from './drive-attach.js';
16
+ import { DriveMode, decideDriveMode, describeAttached, driveForeignHolder, driveHeadlessOnAttach, driveRaceLost, requestDriveSession, } from './drive-attach.js';
17
17
  export function handleDrive(parsed) {
18
18
  void driveWithHonestConflict(parsed);
19
19
  }
@@ -43,7 +43,7 @@ async function driveWithHonestConflict(parsed) {
43
43
  return;
44
44
  }
45
45
  if (DriveMode.ATTACH === mode) {
46
- await attachToRunningDaemon(parsed.port, parsed.driveUrl);
46
+ await attachToRunningDaemon(parsed.port, parsed.driveUrl, parsed.headless);
47
47
  return;
48
48
  }
49
49
  // The probe cannot close the race: something can take the port between here and the bind, and the
@@ -81,7 +81,7 @@ async function driveWithHonestConflict(parsed) {
81
81
  * against it renews its lease), and a foreground hold here could only keep it alive by faking tool
82
82
  * calls at it.
83
83
  */
84
- async function attachToRunningDaemon(port, url) {
84
+ async function attachToRunningDaemon(port, url, headless) {
85
85
  const result = await requestDriveSession(port, url);
86
86
  if (!result.ok) {
87
87
  log('reticle_drive_attach_failed', { port, url, reason: result.reason });
@@ -96,6 +96,10 @@ async function attachToRunningDaemon(port, url) {
96
96
  ready: result.session.ready,
97
97
  });
98
98
  process.stderr.write(`${describeAttached(port, url, result.session)}\n`);
99
+ // The command's own default is headed, and this path cannot deliver that. Said out loud rather
100
+ // than left for the user to discover by watching a window that never opens.
101
+ if (!headless)
102
+ process.stderr.write(`${driveHeadlessOnAttach(port, url)}\n`);
99
103
  // A tab that never connected is not a drive session, and reporting one is the false green this
100
104
  // command exists to avoid.
101
105
  if (!result.session.ready)
@@ -13,10 +13,16 @@ import { PortPresence, probePresence, presenceIsUsable, describePresence, } from
13
13
  import { waitForDaemon, startMcpProxy, probeDaemon, proxyLog, setProxyLogPort, } from '../mcp/mcp-proxy.js';
14
14
  import { installProxyResilience } from '../daemon/daemon-resilience.js';
15
15
  import { readProjectId } from './cli-port.js';
16
- import { DAEMON_INNER_COMMAND, PORT_FLAG, DRIVE_FLAG, HEADED_FLAG, HTTP_FLAG, HTTP_PORT_FLAG, HTTP_TOKEN_FLAG, } from './cli-parse.js';
17
- import { resolveMcpPort } from '../daemon/daemon-resolve.js';
16
+ import { resolveMcpPort, daemonProjectAt } from '../daemon/daemon-resolve.js';
17
+ import { daemonSpawnArgs } from './daemon-start-options.js';
18
+ import { WakeAction, decideWake } from '../daemon/wake-decision.js';
18
19
  import { pickDaemonPortToBind } from '../daemon/free-port.js';
19
20
  import { fetchStatus } from './cli-launch.js';
21
+ import { migrateApprovals } from '../setup/approval-migration.js';
22
+ import { ReticleEnv } from '@reticlehq/core';
23
+ import { agentIo } from '../setup/agent-io.js';
24
+ import { SERVER_VERSION } from '../version/server-version.js';
25
+ import { homedir } from 'node:os';
20
26
  /**
21
27
  * MCP proxy mode: ensures the daemon is running, then bridges Claude Code's
22
28
  * stdin/stdout to the daemon's SSE endpoint. This is the recommended way to
@@ -36,7 +42,25 @@ export async function handleMcp(opts) {
36
42
  // proxy's transport built around a single fixed number. Making the proxy chase a moving port would
37
43
  // mean reworking the reconnect path, which is the one piece whose failure reads to a user as
38
44
  // "the MCP server disappeared mid-session".
39
- const port = await resolveMcpPort(opts.port, readProjectId(process.cwd()), reticleStateHome(), {
45
+ // Read once and reused by the wake path below: the boot resolution and every later wake must be
46
+ // defending the SAME identity, and computing it twice is how they drift apart.
47
+ // Before the port work, because it is the only moment we are guaranteed to get on a machine that
48
+ // installed Reticle before the pre-approval rules existed. Once per version, never a create that
49
+ // supersedes an allowlist we cannot read, and it cannot throw: see approval-migration.
50
+ // A sandboxed state dir means a test, a gate or a fixture, none of which are asking us to rewrite
51
+ // the real user's editor configuration.
52
+ if (undefined === process.env[ReticleEnv.STATE_DIR]) {
53
+ migrateApprovals({
54
+ io: agentIo,
55
+ home: homedir(),
56
+ platform: process.platform,
57
+ stateHome: reticleStateHome(),
58
+ version: SERVER_VERSION,
59
+ log,
60
+ });
61
+ }
62
+ const projectId = readProjectId(process.cwd());
63
+ const port = await resolveMcpPort(opts.port, projectId, reticleStateHome(), {
40
64
  alive: isAlive,
41
65
  daemonPresent: async (p) => presenceIsUsable(await probePresence(p, { tcpOpen: probeDaemon, status: fetchStatus })),
42
66
  pickPort: (p) => pickDaemonPortToBind(p),
@@ -71,29 +95,36 @@ export async function handleMcp(opts) {
71
95
  // client request woke into the identical non-answer. Rejecting here is what puts the proxy
72
96
  // dormant with a reason, instead of pretending the wake succeeded.
73
97
  const presence = await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus });
74
- if (presenceIsUsable(presence))
98
+ // Identity, not just presence. `resolveMcpPort` above already refuses to adopt a stranger's
99
+ // daemon at boot; this is the same question asked on every WAKE, which is where it was missing.
100
+ //
101
+ // Reachable by ordinary use, with no error anywhere: a daemon retires on its idle timer and
102
+ // frees the port, another project's daemon binds it, and this project's dormant proxy wakes
103
+ // straight onto it. The agent then drives, asserts and reports a verdict about a DIFFERENT
104
+ // APPLICATION. That is worse than the disconnect it was reintroduced under the cover of, because
105
+ // a disconnect is visible and this is a false green wearing a valid session.
106
+ const wake = decideWake(presence, daemonProjectAt(port, reticleStateHome()), projectId);
107
+ if (WakeAction.USE === wake)
75
108
  return;
76
- if (PortPresence.FREE !== presence)
77
- throw new Error(describePresence(presence, port));
109
+ if (WakeAction.REFUSE === wake) {
110
+ throw new Error(PortPresence.DAEMON === presence
111
+ ? `the daemon on port ${String(port)} belongs to a different project, so this one will ` +
112
+ 'not adopt it. Stop it, or start this project on its own port.'
113
+ : describePresence(presence, port));
114
+ }
78
115
  const scriptPath = process.argv[1];
79
116
  if (scriptPath === undefined) {
80
117
  log('reticle_mcp_no_script', {});
81
118
  process.exit(1);
82
119
  }
83
- const daemonArgs = [DAEMON_INNER_COMMAND, PORT_FLAG, String(port)];
84
- if (driveUrl !== undefined) {
85
- daemonArgs.push(DRIVE_FLAG, driveUrl);
86
- if (!headless)
87
- daemonArgs.push(HEADED_FLAG);
88
- }
89
- // Forward the HTTP-verify flags too (previously silently dropped for `reticle mcp`).
90
- if (http) {
91
- daemonArgs.push(HTTP_FLAG);
92
- if (httpPort !== undefined)
93
- daemonArgs.push(HTTP_PORT_FLAG, String(httpPort));
94
- if (httpToken !== undefined)
95
- daemonArgs.push(HTTP_TOKEN_FLAG, httpToken);
96
- }
120
+ const daemonArgs = daemonSpawnArgs({
121
+ port,
122
+ headless,
123
+ http,
124
+ ...(driveUrl !== undefined ? { driveUrl } : {}),
125
+ ...(httpPort !== undefined ? { httpPort } : {}),
126
+ ...(httpToken !== undefined ? { httpToken } : {}),
127
+ });
97
128
  spawnDaemon(process.execPath, scriptPath, daemonArgs, port);
98
129
  // Announce the daemon only once the PORT ACCEPTS. This line used to be written the instant the
99
130
  // child was spawned, which on a Windows first bootstrap meant `reticle_mcp_daemon_started`
@@ -0,0 +1,30 @@
1
+ /**
2
+ * What project a repo binds to when nobody says.
3
+ *
4
+ * `link` used to answer "default" for every repo on every machine, which is wrong in two directions
5
+ * at once. Measured: two unrelated checkouts linked on one account — a storefront and a billing
6
+ * service — both bound to a single project called "Default" and shared one key, so their runs,
7
+ * issues and impact merged into one bucket and the dashboard's per-project view described nothing.
8
+ * It is also what made two TENANTS collide, because a credential slot built from a project id is
9
+ * only as distinct as the ids are.
10
+ *
11
+ * A repo already knows its name: the directory it lives in. That is not a guess, it is the name its
12
+ * owner chose and the one they will look for in a project list.
13
+ */
14
+ /** The id used when a directory name yields nothing usable — the historical behaviour, unchanged. */
15
+ export declare const DEFAULT_PROJECT_ID = "default";
16
+ /**
17
+ * Turn a directory name into a project id: lowercase, and anything that is not a letter or digit
18
+ * becomes a single hyphen. Conservative on purpose — this id ends up in URLs, in credential slot
19
+ * keys and in a project list somebody has to read.
20
+ */
21
+ export declare const slugifyProjectName: (raw: string) => string;
22
+ /**
23
+ * The project a `link` with no `--project` should target.
24
+ *
25
+ * The EXISTING binding wins over the directory name, and that ordering is the whole safety of this
26
+ * change. A repo that is already linked has history in a project; re-running `link` — which people
27
+ * do to rotate a key or repoint an environment — must land in that same project or the history
28
+ * silently splits in two, which is worse than the shared-bucket problem this fixes.
29
+ */
30
+ export declare const defaultProjectFor: (cwdBasename: string, existingProjectId: string | undefined) => string;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * What project a repo binds to when nobody says.
3
+ *
4
+ * `link` used to answer "default" for every repo on every machine, which is wrong in two directions
5
+ * at once. Measured: two unrelated checkouts linked on one account — a storefront and a billing
6
+ * service — both bound to a single project called "Default" and shared one key, so their runs,
7
+ * issues and impact merged into one bucket and the dashboard's per-project view described nothing.
8
+ * It is also what made two TENANTS collide, because a credential slot built from a project id is
9
+ * only as distinct as the ids are.
10
+ *
11
+ * A repo already knows its name: the directory it lives in. That is not a guess, it is the name its
12
+ * owner chose and the one they will look for in a project list.
13
+ */
14
+ /** Cap the slug so a deep or oddly-named checkout cannot produce an unreadable project id. */
15
+ const MAX_SLUG = 48;
16
+ /** The id used when a directory name yields nothing usable — the historical behaviour, unchanged. */
17
+ export const DEFAULT_PROJECT_ID = 'default';
18
+ /**
19
+ * Turn a directory name into a project id: lowercase, and anything that is not a letter or digit
20
+ * becomes a single hyphen. Conservative on purpose — this id ends up in URLs, in credential slot
21
+ * keys and in a project list somebody has to read.
22
+ */
23
+ export const slugifyProjectName = (raw) => {
24
+ const slug = raw
25
+ .toLowerCase()
26
+ .replace(/[^a-z0-9]+/g, '-')
27
+ .replace(/^-+|-+$/g, '')
28
+ .slice(0, MAX_SLUG)
29
+ .replace(/-+$/g, '');
30
+ return 0 === slug.length ? DEFAULT_PROJECT_ID : slug;
31
+ };
32
+ /**
33
+ * The project a `link` with no `--project` should target.
34
+ *
35
+ * The EXISTING binding wins over the directory name, and that ordering is the whole safety of this
36
+ * change. A repo that is already linked has history in a project; re-running `link` — which people
37
+ * do to rotate a key or repoint an environment — must land in that same project or the history
38
+ * silently splits in two, which is worse than the shared-bucket problem this fixes.
39
+ */
40
+ export const defaultProjectFor = (cwdBasename, existingProjectId) => {
41
+ if (existingProjectId !== undefined && existingProjectId.length > 0)
42
+ return existingProjectId;
43
+ return slugifyProjectName(cwdBasename);
44
+ };
@@ -27,6 +27,20 @@ export interface StatusFacts {
27
27
  * paths do the first without the second.
28
28
  */
29
29
  initialized: boolean;
30
+ /**
31
+ * Ports of dev servers that ANNOUNCED themselves — i.e. that have Reticle loaded in the process
32
+ * actually running.
33
+ *
34
+ * `nextActionFor` already ranks on a listening-port list; it just never had one from here, so this
35
+ * command passed `[]` and every wired project with a live dev server was told "the app is probably
36
+ * not running". That is advice contradicting the terminal the reader is looking at, and it sends
37
+ * them to restart something already up.
38
+ *
39
+ * Stronger than the port scan the field was designed for: a scanned port proves something is
40
+ * listening, an announced one proves it is listening AND instrumented. Optional so every existing
41
+ * caller keeps today's behaviour.
42
+ */
43
+ devServerPorts?: readonly number[];
30
44
  }
31
45
  /**
32
46
  * What to do next, or `undefined` when a session is connected and there is nothing to fix.
@@ -43,9 +43,9 @@ export function statusNextAction(facts) {
43
43
  `Reticle tool, and it exits again when idle. ${wiring} Ask your agent to verify something, then ` +
44
44
  `run status again.`).replace(/\s+/g, ' ');
45
45
  }
46
- // The daemon is up and no page has connected. `nextActionFor` owns the ranking; the CLI cannot see
47
- // listening ports or a dev command from here, so it passes what it has and lets that function say
48
- // what is missing rather than guessing at it.
46
+ // The daemon is up and no page has connected. `nextActionFor` owns the ranking; this passes what
47
+ // it has and lets that function say what is missing rather than guessing at it. It CAN see
48
+ // listening ports now — the announced ones — which is what the empty list here used to cost.
49
49
  const next = nextActionFor({
50
50
  // The durable bit IS this side's `everConnected`. In-process the flag means "this daemon has served
51
51
  // a session", which a daemon seconds old cannot know; from the CLI the honest equivalent is "an app
@@ -53,7 +53,7 @@ export function statusNextAction(facts) {
53
53
  // is in question at all.
54
54
  everConnected: facts.previouslyConnected,
55
55
  initialized: facts.initialized,
56
- listening: [],
56
+ listening: facts.devServerPorts ?? [],
57
57
  dev: undefined,
58
58
  previouslyConnected: facts.previouslyConnected,
59
59
  });
package/dist/cli.d.ts CHANGED
@@ -2,3 +2,4 @@
2
2
  import { parseCliArgs, CLI_USAGE } from './cli/cli-parse.js';
3
3
  export { parseCliArgs, CLI_USAGE };
4
4
  export type { CliResult } from './cli/cli-parse.js';
5
+ export declare function handleStatus(port: number): Promise<void>;