@pellux/goodvibes-tui 1.27.1 → 2.0.2

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 (405) hide show
  1. package/CHANGELOG.md +127 -2
  2. package/README.md +7 -9
  3. package/bin/goodvibes +2 -2
  4. package/bin/launcher-support.js +8 -4
  5. package/docs/README.md +11 -2
  6. package/docs/foundation-artifacts/operator-contract.json +6403 -2347
  7. package/package.json +14 -25
  8. package/scripts/postinstall.js +35 -35
  9. package/src/audio/spoken-turn-model-routing.ts +1 -1
  10. package/src/cli/bundle-command.ts +5 -5
  11. package/src/cli/completions/generate.ts +1 -1
  12. package/src/cli/doctor.ts +3 -3
  13. package/src/cli/entrypoint.ts +12 -7
  14. package/src/cli/help.ts +4 -5
  15. package/src/cli/hooks-command.ts +1 -1
  16. package/src/cli/index.ts +1 -4
  17. package/src/cli/launch-auto-update.ts +59 -7
  18. package/src/cli/management-commands.ts +6 -6
  19. package/src/cli/management-utils.ts +5 -5
  20. package/src/cli/management.ts +7 -7
  21. package/src/cli/network-posture.ts +1 -1
  22. package/src/cli/package-verification.ts +7 -6
  23. package/src/cli/plugin-bundles-command.ts +1 -1
  24. package/src/cli/plugin-command.ts +2 -2
  25. package/src/cli/service-command.ts +2 -2
  26. package/src/cli/service-posture.ts +4 -4
  27. package/src/cli/status.ts +9 -8
  28. package/src/cli/surface-command.ts +4 -4
  29. package/src/cli/tui-startup.ts +3 -3
  30. package/src/cli-flags.ts +3 -3
  31. package/src/config/goodvibes-home-audit.ts +0 -2
  32. package/src/config/secret-config.ts +37 -5
  33. package/src/config/tui-extension-settings.ts +1 -1
  34. package/src/core/approval-alert.ts +2 -2
  35. package/src/core/budget-breach-notifier.ts +3 -3
  36. package/src/core/conversation-compaction-render.ts +1 -1
  37. package/src/core/conversation-line-cache.ts +1 -1
  38. package/src/core/conversation-render-context.ts +1 -1
  39. package/src/core/conversation-rendering.ts +2 -2
  40. package/src/core/conversation.ts +10 -10
  41. package/src/core/long-task-notifier.ts +2 -2
  42. package/src/core/memory-diagnostics-gateway.ts +1 -1
  43. package/src/core/scriptable-statusline.ts +1 -1
  44. package/src/core/session-auto-titler.ts +1 -1
  45. package/src/core/session-resume-core.ts +2 -2
  46. package/src/core/stream-event-wiring.ts +24 -44
  47. package/src/core/terminal-notifier.ts +3 -3
  48. package/src/core/turn-cancellation.ts +1 -1
  49. package/src/core/turn-event-wiring.ts +5 -5
  50. package/src/input/command-registry.ts +6 -6
  51. package/src/input/commands/channel-pairing.ts +1 -1
  52. package/src/input/commands/cluster-runtime.ts +1 -1
  53. package/src/input/commands/codebase-runtime.ts +1 -1
  54. package/src/input/commands/devices-runtime.ts +1 -1
  55. package/src/input/commands/hosted-runtime.ts +330 -0
  56. package/src/input/commands/local-setup-review.ts +1 -1
  57. package/src/input/commands/local-setup-transfer.ts +2 -2
  58. package/src/input/commands/local-setup.ts +3 -3
  59. package/src/input/commands/managed-runtime.ts +2 -2
  60. package/src/input/commands/operator-rpc.ts +24 -110
  61. package/src/input/commands/payment-card-intake.ts +1 -1
  62. package/src/input/commands/permissions-provenance.ts +2 -2
  63. package/src/input/commands/plugin-runtime.ts +1 -1
  64. package/src/input/commands/provider.ts +1 -1
  65. package/src/input/commands/relay-runtime.ts +18 -9
  66. package/src/input/commands/rewind-runtime.ts +1 -1
  67. package/src/input/commands/runtime-services.ts +1 -1
  68. package/src/input/commands/services-runtime.ts +1 -1
  69. package/src/input/commands/session-workflow.ts +1 -1
  70. package/src/input/commands/settings-sync-runtime.ts +2 -2
  71. package/src/input/commands/share-runtime.ts +1 -1
  72. package/src/input/commands/tasks-runtime.ts +145 -55
  73. package/src/input/commands/update-runtime.ts +31 -28
  74. package/src/input/commands/work-plan-runtime.ts +2 -2
  75. package/src/input/commands/workstream-attempts.ts +1 -1
  76. package/src/input/commands/workstream-runtime.ts +2 -2
  77. package/src/input/commands.ts +2 -0
  78. package/src/input/feature-unit-layout.ts +1 -1
  79. package/src/input/feed-context-factory.ts +3 -3
  80. package/src/input/handler-content-actions.ts +2 -2
  81. package/src/input/handler-feed-routes.ts +1 -1
  82. package/src/input/handler-feed.ts +3 -3
  83. package/src/input/handler-modal-token-routes.ts +1 -1
  84. package/src/input/handler-onboarding-daemon-adopt.ts +3 -2
  85. package/src/input/handler-onboarding.ts +1 -1
  86. package/src/input/handler-types.ts +2 -2
  87. package/src/input/handler-ui-state.ts +1 -1
  88. package/src/input/handler.ts +4 -4
  89. package/src/input/input-history.ts +1 -1
  90. package/src/input/onboarding/onboarding-feature-units.ts +2 -2
  91. package/src/input/onboarding/onboarding-runtime-status.ts +15 -1
  92. package/src/input/onboarding/onboarding-wizard-apply.ts +17 -8
  93. package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +5 -3
  94. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +1 -1
  95. package/src/input/onboarding/onboarding-wizard-types.ts +1 -1
  96. package/src/input/payments-config.ts +6 -6
  97. package/src/input/search.ts +1 -1
  98. package/src/input/session-picker-modal.ts +46 -0
  99. package/src/input/settings-modal-activation.ts +1 -1
  100. package/src/input/settings-modal-behavior.ts +4 -4
  101. package/src/input/settings-modal-commit.ts +11 -0
  102. package/src/input/settings-modal-data.ts +9 -122
  103. package/src/input/settings-modal-mutations.ts +33 -0
  104. package/src/input/settings-modal-secrets.ts +34 -0
  105. package/src/input/settings-modal-subscriptions.ts +1 -1
  106. package/src/input/settings-modal-types.ts +15 -1
  107. package/src/input/settings-modal.ts +28 -3
  108. package/src/input/worktree-setup-config.ts +1 -1
  109. package/src/main.ts +11 -9
  110. package/src/panels/agent-inspector-shared.ts +2 -2
  111. package/src/panels/base-panel.ts +2 -2
  112. package/src/panels/builtin/operations.ts +6 -8
  113. package/src/panels/builtin/session.ts +17 -0
  114. package/src/panels/builtin/shared.ts +5 -5
  115. package/src/panels/builtin-modals.ts +3 -3
  116. package/src/panels/confirm-state.ts +1 -1
  117. package/src/panels/cost-tracker-panel.ts +4 -4
  118. package/src/panels/diff-panel.ts +2 -2
  119. package/src/panels/diff-review-panel.ts +2 -2
  120. package/src/panels/expandable-list-panel.ts +2 -2
  121. package/src/panels/fleet-acts.ts +1 -1
  122. package/src/panels/fleet-observed-render.ts +1 -1
  123. package/src/panels/fleet-panel-format.ts +1 -1
  124. package/src/panels/fleet-panel.ts +1 -1
  125. package/src/panels/fleet-spawn.ts +1 -1
  126. package/src/panels/fleet-steer.ts +1 -1
  127. package/src/panels/fleet-transcript.ts +1 -1
  128. package/src/panels/git-panel.ts +1 -1
  129. package/src/panels/hosted-session-feed.ts +352 -0
  130. package/src/panels/hosted-session-panel.ts +221 -0
  131. package/src/panels/local-auth-panel.ts +3 -3
  132. package/src/panels/modals/devices-modal.ts +1 -1
  133. package/src/panels/modals/local-auth-modal.ts +1 -1
  134. package/src/panels/modals/pairing-modal.ts +1 -1
  135. package/src/panels/modals/services-modal.ts +1 -1
  136. package/src/panels/modals/settings-sync-modal.ts +1 -1
  137. package/src/panels/modals/subscription-modal.ts +1 -1
  138. package/src/panels/modals/work-plan-modal.ts +1 -1
  139. package/src/panels/notifications-panel.ts +1 -1
  140. package/src/panels/panel-confirm-overlay.ts +1 -1
  141. package/src/panels/plugins-panel.ts +2 -2
  142. package/src/panels/polish-core.ts +2 -2
  143. package/src/panels/polish-tables.ts +1 -1
  144. package/src/panels/polish.ts +2 -2
  145. package/src/panels/scrollable-list-panel.ts +3 -3
  146. package/src/panels/skills-panel.ts +3 -3
  147. package/src/panels/token-budget-panel.ts +5 -5
  148. package/src/panels/types.ts +1 -2
  149. package/src/permissions/prompt.ts +1 -1
  150. package/src/renderer/autocomplete-overlay.ts +1 -1
  151. package/src/renderer/block-actions-overlay.ts +1 -1
  152. package/src/renderer/bookmark-modal.ts +1 -1
  153. package/src/renderer/buffer.ts +1 -1
  154. package/src/renderer/code-block.ts +1 -1
  155. package/src/renderer/composer-fragments.ts +1 -1
  156. package/src/renderer/compositor.ts +2 -2
  157. package/src/renderer/config-modal.ts +1 -1
  158. package/src/renderer/context-inspector.ts +1 -1
  159. package/src/renderer/conversation-layout.ts +2 -2
  160. package/src/renderer/conversation-overlays.ts +1 -1
  161. package/src/renderer/conversation-surface.ts +2 -2
  162. package/src/renderer/diff-view.ts +1 -1
  163. package/src/renderer/diff.ts +1 -1
  164. package/src/renderer/file-picker-overlay.ts +1 -1
  165. package/src/renderer/fleet-tab-strip.ts +1 -1
  166. package/src/renderer/fullscreen-primitives.ts +2 -2
  167. package/src/renderer/fullscreen-workspace.ts +1 -1
  168. package/src/renderer/header-line.ts +1 -1
  169. package/src/renderer/help-overlay.ts +1 -1
  170. package/src/renderer/history-search-overlay.ts +2 -2
  171. package/src/renderer/layout.ts +5 -8
  172. package/src/renderer/markdown-inline.ts +1 -1
  173. package/src/renderer/markdown-table.ts +1 -1
  174. package/src/renderer/markdown.ts +1 -1
  175. package/src/renderer/mcp-workspace.ts +1 -1
  176. package/src/renderer/modal-factory.ts +2 -2
  177. package/src/renderer/model-workspace.ts +2 -2
  178. package/src/renderer/onboarding/onboarding-wizard.ts +1 -1
  179. package/src/renderer/overlay-box.ts +1 -1
  180. package/src/renderer/panel-composite.ts +1 -1
  181. package/src/renderer/panel-workspace-bar.ts +1 -1
  182. package/src/renderer/process-indicator.ts +1 -1
  183. package/src/renderer/profile-picker-modal.ts +1 -1
  184. package/src/renderer/search-overlay.ts +2 -2
  185. package/src/renderer/selection-modal-overlay.ts +1 -1
  186. package/src/renderer/session-picker-modal.ts +124 -3
  187. package/src/renderer/settings-modal-helpers.ts +2 -0
  188. package/src/renderer/settings-modal.ts +2 -2
  189. package/src/renderer/shell-surface.ts +2 -2
  190. package/src/renderer/system-message.ts +1 -1
  191. package/src/renderer/tab-strip.ts +1 -1
  192. package/src/renderer/theme-mode-config.ts +21 -33
  193. package/src/renderer/theme.ts +11 -12
  194. package/src/renderer/thinking.ts +1 -1
  195. package/src/renderer/tool-call.ts +2 -2
  196. package/src/renderer/tool-result-expanded-lines.ts +1 -1
  197. package/src/renderer/turn-injection.ts +7 -8
  198. package/src/renderer/ui-factory.ts +3 -3
  199. package/src/runtime/bootstrap-command-context.ts +4 -4
  200. package/src/runtime/bootstrap-command-parts.ts +5 -5
  201. package/src/runtime/bootstrap-core.ts +30 -30
  202. package/src/runtime/bootstrap-shell.ts +4 -4
  203. package/src/runtime/bootstrap.ts +128 -157
  204. package/src/runtime/client/approval-updates.ts +82 -0
  205. package/src/runtime/client/build-floors.ts +167 -0
  206. package/src/runtime/client/daemon-attach-handshake.ts +115 -0
  207. package/src/runtime/client/fleet-union.ts +147 -0
  208. package/src/runtime/client/host-service-status.ts +71 -0
  209. package/src/runtime/client/hosted-exit.ts +86 -0
  210. package/src/runtime/client/hosted-roster.ts +110 -0
  211. package/src/runtime/client/hosted-session-stream.ts +178 -0
  212. package/src/runtime/client/hosted-sessions.ts +187 -0
  213. package/src/runtime/client/operator-endpoint.ts +213 -0
  214. package/src/runtime/client/spine-adoption.ts +99 -0
  215. package/src/runtime/cloudflare-control-plane.ts +1 -1
  216. package/src/runtime/context-accounting-source.ts +1 -1
  217. package/src/runtime/context.ts +2 -2
  218. package/src/runtime/conversation-rewind-port.ts +42 -14
  219. package/src/runtime/daemon-attach-notices.ts +55 -19
  220. package/src/runtime/diagnostics/panels/ops.ts +1 -1
  221. package/src/runtime/disposal-wiring.ts +4 -24
  222. package/src/runtime/index.ts +119 -105
  223. package/src/runtime/install-self-check-startup.ts +2 -2
  224. package/src/runtime/interaction-seams.ts +1 -1
  225. package/src/runtime/knowledge-services.ts +8 -2
  226. package/src/runtime/legacy-daemon-migration.ts +4 -3
  227. package/src/runtime/legacy-daemon-reconcile.ts +3 -2
  228. package/src/runtime/memory-spine-transport.ts +37 -267
  229. package/src/runtime/notification-dispatch.ts +2 -2
  230. package/src/runtime/onboarding/apply.ts +2 -2
  231. package/src/runtime/onboarding/derivation.ts +2 -2
  232. package/src/runtime/onboarding/markers.ts +2 -2
  233. package/src/runtime/onboarding/progress.ts +2 -2
  234. package/src/runtime/onboarding/snapshot.ts +1 -1
  235. package/src/runtime/onboarding/state.ts +2 -2
  236. package/src/runtime/onboarding/types.ts +2 -2
  237. package/src/runtime/orchestrator-core-services.ts +1 -1
  238. package/src/runtime/path-shadow-startup.ts +28 -221
  239. package/src/runtime/power-keepawake-remote.ts +3 -2
  240. package/src/runtime/process-lifecycle.ts +2 -2
  241. package/src/runtime/provider-fallback.ts +10 -66
  242. package/src/runtime/recovery-autosave.ts +1 -1
  243. package/src/runtime/recovery-offer-wiring.ts +39 -23
  244. package/src/runtime/recovery-prompt.ts +31 -17
  245. package/src/runtime/relay-reachability-bridge.ts +53 -42
  246. package/src/runtime/release-artifacts.ts +2 -1
  247. package/src/runtime/runtime-services-types.ts +82 -35
  248. package/src/runtime/services.ts +305 -300
  249. package/src/runtime/session-resume-liveness-confirm.ts +1 -1
  250. package/src/runtime/ui-services.ts +7 -6
  251. package/src/runtime/update-check.ts +23 -43
  252. package/src/runtime/workspace-checkpointing.ts +1 -1
  253. package/src/runtime/wrfc-persistence.ts +2 -2
  254. package/src/shell/ui-openers.ts +30 -7
  255. package/src/utils/clipboard.ts +1 -1
  256. package/src/verification/verification-ledger.ts +10 -8
  257. package/src/version.ts +1 -1
  258. package/bin/goodvibes-daemon +0 -90
  259. package/src/cli/config-overrides.ts +0 -267
  260. package/src/cli/endpoints.ts +0 -109
  261. package/src/cli/parser.ts +0 -395
  262. package/src/cli/redaction.ts +0 -170
  263. package/src/cli/types.ts +0 -84
  264. package/src/cluster/commands.ts +0 -382
  265. package/src/cluster/remote-daemon-target.ts +0 -225
  266. package/src/cluster/render.ts +0 -268
  267. package/src/config/atomic-write.ts +0 -70
  268. package/src/config/credential-availability.ts +0 -122
  269. package/src/config/goodvibes-home.ts +0 -149
  270. package/src/config/index.ts +0 -88
  271. package/src/config/provider-model.ts +0 -23
  272. package/src/config/read-versioned.ts +0 -228
  273. package/src/core/alert-gating.ts +0 -67
  274. package/src/core/consolidation-receipt.ts +0 -41
  275. package/src/core/focus-tracker.ts +0 -41
  276. package/src/core/history.ts +0 -45
  277. package/src/core/memory-status.ts +0 -118
  278. package/src/core/orchestrator.ts +0 -7
  279. package/src/core/pairing-devices.ts +0 -49
  280. package/src/core/pairing-handoff.ts +0 -101
  281. package/src/core/pairing-offers.ts +0 -83
  282. package/src/core/pairing-origin.ts +0 -78
  283. package/src/core/rewind-turn-anchors.ts +0 -311
  284. package/src/core/session-recovery.ts +0 -210
  285. package/src/core/stable-host.ts +0 -171
  286. package/src/core/transcript-journal.ts +0 -503
  287. package/src/daemon/cli.ts +0 -693
  288. package/src/daemon/fatal-boot-report.ts +0 -111
  289. package/src/daemon/handlers/context.ts +0 -29
  290. package/src/daemon/handlers/contracts.ts +0 -77
  291. package/src/daemon/handlers/credentials.ts +0 -139
  292. package/src/daemon/handlers/drafts/draft-store.ts +0 -427
  293. package/src/daemon/handlers/drafts/index.ts +0 -17
  294. package/src/daemon/handlers/drafts/register.ts +0 -331
  295. package/src/daemon/handlers/errors.ts +0 -18
  296. package/src/daemon/handlers/inbox/cursor-store.ts +0 -457
  297. package/src/daemon/handlers/inbox/index.ts +0 -284
  298. package/src/daemon/handlers/inbox/mapping.ts +0 -192
  299. package/src/daemon/handlers/inbox/poller.ts +0 -212
  300. package/src/daemon/handlers/inbox/provider-adapter.ts +0 -156
  301. package/src/daemon/handlers/inbox/providers/discord.ts +0 -252
  302. package/src/daemon/handlers/inbox/providers/email.ts +0 -152
  303. package/src/daemon/handlers/inbox/providers/imap-client.ts +0 -300
  304. package/src/daemon/handlers/inbox/providers/route-util.ts +0 -24
  305. package/src/daemon/handlers/inbox/providers/slack.ts +0 -263
  306. package/src/daemon/handlers/index.ts +0 -117
  307. package/src/daemon/handlers/register.ts +0 -168
  308. package/src/daemon/handlers/remote/backends/cloud-terminal.ts +0 -142
  309. package/src/daemon/handlers/remote/backends/docker.ts +0 -79
  310. package/src/daemon/handlers/remote/backends/index.ts +0 -40
  311. package/src/daemon/handlers/remote/backends/local-process.ts +0 -113
  312. package/src/daemon/handlers/remote/backends/process-runner.ts +0 -127
  313. package/src/daemon/handlers/remote/backends/ssh.ts +0 -125
  314. package/src/daemon/handlers/remote/backends/types.ts +0 -97
  315. package/src/daemon/handlers/remote/dispatcher.ts +0 -181
  316. package/src/daemon/handlers/remote/index.ts +0 -119
  317. package/src/daemon/handlers/remote/peer-registry.ts +0 -357
  318. package/src/daemon/handlers/remote/service.ts +0 -191
  319. package/src/daemon/handlers/routing/inbox-bridge.ts +0 -71
  320. package/src/daemon/handlers/routing/index.ts +0 -261
  321. package/src/daemon/handlers/routing/route-store.ts +0 -319
  322. package/src/daemon/handlers/routing/routing-resolver.ts +0 -75
  323. package/src/daemon/handlers/sqlite-store.ts +0 -278
  324. package/src/daemon/handlers/triage/index.ts +0 -57
  325. package/src/daemon/handlers/triage/integration.ts +0 -213
  326. package/src/daemon/handlers/triage/pipeline.ts +0 -274
  327. package/src/daemon/handlers/triage/scorer.ts +0 -287
  328. package/src/daemon/handlers/triage/tagger/discord.ts +0 -187
  329. package/src/daemon/handlers/triage/tagger/imap.ts +0 -384
  330. package/src/daemon/handlers/triage/tagger/index.ts +0 -184
  331. package/src/daemon/handlers/triage/tagger/shared.ts +0 -70
  332. package/src/daemon/handlers/triage/tagger/slack.ts +0 -69
  333. package/src/daemon/handlers/triage/types.ts +0 -50
  334. package/src/daemon/lifecycle.ts +0 -41
  335. package/src/daemon/provision-wake-model.ts +0 -81
  336. package/src/daemon/safe-serve.ts +0 -61
  337. package/src/daemon/send/channels.ts +0 -200
  338. package/src/daemon/send/command.ts +0 -332
  339. package/src/daemon/send/composition.ts +0 -99
  340. package/src/daemon/send/failure-text.ts +0 -93
  341. package/src/daemon/send/inert-text.ts +0 -225
  342. package/src/daemon/send/stdin.ts +0 -24
  343. package/src/daemon/service-commands.ts +0 -329
  344. package/src/export/cost-utils.ts +0 -319
  345. package/src/input/selection.ts +0 -135
  346. package/src/plugins/loader.ts +0 -15
  347. package/src/renderer/bottom-bar.ts +0 -58
  348. package/src/renderer/conversation-tree.ts +0 -228
  349. package/src/runtime/agent-graph-composition.ts +0 -78
  350. package/src/runtime/channel-composition.ts +0 -46
  351. package/src/runtime/cluster-composition.ts +0 -124
  352. package/src/runtime/cluster-group-composition.ts +0 -284
  353. package/src/runtime/code-index-services.ts +0 -167
  354. package/src/runtime/daemon-handler-composition.ts +0 -76
  355. package/src/runtime/device-posture-composition.ts +0 -110
  356. package/src/runtime/durability-housekeeping.ts +0 -183
  357. package/src/runtime/durability-services.ts +0 -155
  358. package/src/runtime/embedded-service-factories.ts +0 -74
  359. package/src/runtime/feature-settings.ts +0 -154
  360. package/src/runtime/fleet-needs-input-push.ts +0 -70
  361. package/src/runtime/fleet-services.ts +0 -41
  362. package/src/runtime/idle-power-services.ts +0 -85
  363. package/src/runtime/mail-composition.ts +0 -65
  364. package/src/runtime/memory-fold.ts +0 -55
  365. package/src/runtime/operator-token-cleanup.ts +0 -109
  366. package/src/runtime/path-shadow.ts +0 -418
  367. package/src/runtime/perf/panel-health-monitor.ts +0 -18
  368. package/src/runtime/provider-optimizer-wiring.ts +0 -55
  369. package/src/runtime/reachability-notice.ts +0 -100
  370. package/src/runtime/remote-execution-composition.ts +0 -62
  371. package/src/runtime/session-liveness-marker.ts +0 -257
  372. package/src/runtime/session-pointer-surface.ts +0 -22
  373. package/src/runtime/session-storage-services.ts +0 -41
  374. package/src/runtime/surface-feature-flags.ts +0 -96
  375. package/src/runtime/terminal-output-guard.ts +0 -235
  376. package/src/runtime/trust/workspace-registration.ts +0 -192
  377. package/src/runtime/trust/workspace-trust.ts +0 -229
  378. package/src/runtime/ui/index.ts +0 -39
  379. package/src/runtime/ui/model-picker/data-provider.ts +0 -182
  380. package/src/runtime/ui/model-picker/health-enrichment.ts +0 -231
  381. package/src/runtime/ui/model-picker/index.ts +0 -59
  382. package/src/runtime/ui/model-picker/types.ts +0 -153
  383. package/src/runtime/ui/provider-health/data-provider.ts +0 -244
  384. package/src/runtime/ui/provider-health/fallback-visualizer.ts +0 -71
  385. package/src/runtime/ui/provider-health/index.ts +0 -46
  386. package/src/runtime/ui/provider-health/types.ts +0 -146
  387. package/src/runtime/ui-events.ts +0 -1
  388. package/src/runtime/ui-read-model-helpers.ts +0 -1
  389. package/src/runtime/ui-read-models-observability-maintenance.ts +0 -1
  390. package/src/runtime/ui-read-models-observability-options.ts +0 -1
  391. package/src/runtime/ui-read-models-observability-remote.ts +0 -1
  392. package/src/runtime/ui-read-models-observability-security.ts +0 -1
  393. package/src/runtime/ui-read-models-observability-system.ts +0 -1
  394. package/src/runtime/ui-read-models-observability.ts +0 -1
  395. package/src/runtime/ui-service-queries.ts +0 -1
  396. package/src/runtime/voice-setup-services.ts +0 -98
  397. package/src/runtime/workstream-attempts-validation.ts +0 -77
  398. package/src/runtime/workstream-draft-edits.ts +0 -95
  399. package/src/runtime/workstream-draft-store.ts +0 -274
  400. package/src/runtime/workstream-draft-types.ts +0 -42
  401. package/src/runtime/workstream-services.ts +0 -373
  402. package/src/tools/index.ts +0 -1
  403. package/src/types/grid.ts +0 -48
  404. package/src/types/sql-js.d.ts +0 -15
  405. package/src/work-plans/work-plan-store.ts +0 -702
@@ -1,93 +0,0 @@
1
- /**
2
- * failure-text.ts — what the operator is told when a send does not happen.
3
- *
4
- * ## Why this is not `summarizeError`
5
- *
6
- * The SDK's `summarizeError` is the right tool nearly everywhere: it classifies
7
- * an error, adds a hint, and deliberately drops detail so a transcript stays
8
- * readable. That is the wrong trade here, and measurably so. Given the real
9
- * Telegram failure
10
- *
11
- * Telegram delivery failed HTTP 401: {"ok":false,"description":"Unauthorized"}
12
- *
13
- * it returns `Telegram delivery failed HTTP 401` — its `stripJson` pass removes
14
- * the `{...}`, which is exactly the part naming what went wrong. Given
15
- * `connect ECONNREFUSED 149.154.167.220:443` it returns "Cannot connect to the
16
- * provider. Check whether the service is reachable." — advice in place of the
17
- * address that was refused.
18
- *
19
- * A person debugging a message that did not arrive needs the provider's own
20
- * words: "Unauthorized", "chat not found", and "Bad Request: message is too
21
- * long" have three different fixes, and all three flatten to the same summary.
22
- *
23
- * ## What is removed, and why that is not the same thing
24
- *
25
- * Detail is kept; CREDENTIALS are not. This matters more here than in most
26
- * places because Telegram puts the bot token in the URL PATH
27
- * (`api.telegram.org/bot<token>/sendMessage`), so a transport error that echoes
28
- * the request URL would print the owner's bot token to a terminal and into
29
- * whatever log or pasted report that terminal output ends up in.
30
- *
31
- * `redactSensitiveData` from the SDK covers the shapes it knows (bearer tokens,
32
- * `sk-`/`xoxb-`/`ghp_` keys, home directories). It has NO pattern for a
33
- * URL-embedded credential, so the two passes below add them. They run BEFORE
34
- * the SDK pass, because a redacted string must not then be re-scanned in a way
35
- * that could reveal structure.
36
- */
37
-
38
- import { redactSensitiveData } from '@pellux/goodvibes-sdk/platform/utils';
39
-
40
- /**
41
- * A generous cap. The point of this text is diagnosis, so it is far larger than
42
- * the 240 characters `summarizeError` allows — but an HTML error page or a
43
- * multi-megabyte body still must not flood the terminal.
44
- */
45
- const MAX_FAILURE_TEXT = 2_000;
46
-
47
- const URL_CREDENTIAL_PATTERNS: ReadonlyArray<{ readonly pattern: RegExp; readonly replacement: string }> = [
48
- // Telegram: the bot token IS the path segment.
49
- { pattern: /\/bot\d{5,}:[A-Za-z0-9_-]{10,}/g, replacement: '/bot[REDACTED_BOT_TOKEN]' },
50
- // Any URL carrying `user:password@host` — BlueBubbles, Mattermost and Matrix
51
- // base URLs are all operator-supplied and can be written this way.
52
- { pattern: /(\b[a-z][a-z0-9+.-]*:\/\/)[^/\s:@]+:[^/\s@]+@/gi, replacement: '$1[REDACTED_CREDENTIALS]@' },
53
- // A credential passed as a query parameter — BlueBubbles sends `?password=`.
54
- {
55
- pattern: /([?&](?:password|token|secret|access_token|api_?key|auth)=)[^&\s"']+/gi,
56
- replacement: '$1[REDACTED]',
57
- },
58
- ];
59
-
60
- /** Strip credentials this command can put on the wire but the SDK pass does not know. */
61
- export function redactUrlCredentials(text: string): string {
62
- let result = text;
63
- for (const { pattern, replacement } of URL_CREDENTIAL_PATTERNS) {
64
- result = result.replace(pattern, replacement);
65
- }
66
- return result;
67
- }
68
-
69
- /**
70
- * The provider's own failure text, with credentials removed and nothing else
71
- * rewritten.
72
- *
73
- * Never returns an empty string: an error whose message is blank still has to
74
- * produce something an operator can act on, because the alternative is an exit
75
- * code with no explanation beside it.
76
- */
77
- export function describeSendFailure(error: unknown): string {
78
- const raw = error instanceof Error
79
- ? (error.message.trim().length > 0 ? error.message : error.name)
80
- : typeof error === 'string'
81
- ? error
82
- : (() => { try { return JSON.stringify(error); } catch { return String(error); } })();
83
- const message = typeof raw === 'string' && raw.trim().length > 0 ? raw.trim() : 'the provider failed without saying why';
84
- // `cause` is where Node and Bun put the real reason for `fetch failed`, and
85
- // dropping it is how a genuinely diagnosable DNS or TLS failure becomes two
86
- // useless words.
87
- const cause = error instanceof Error && error.cause instanceof Error && error.cause.message.trim().length > 0
88
- ? ` (${error.cause.message.trim()})`
89
- : '';
90
- const full = `${message}${cause}`;
91
- const redacted = redactSensitiveData(redactUrlCredentials(full));
92
- return redacted.length <= MAX_FAILURE_TEXT ? redacted : `${redacted.slice(0, MAX_FAILURE_TEXT)}…`;
93
- }
@@ -1,225 +0,0 @@
1
- /**
2
- * inert-text.ts — make a message arrive as LITERAL TEXT on whichever channel
3
- * `goodvibes-daemon send` delivers it to.
4
- *
5
- * ## What "inert" means here, and why it is per (surface × transport), not
6
- * per surface
7
- *
8
- * The transform a surface needs depends on what the delivery strategy in
9
- * `platform/channels/delivery/strategies-core.ts` actually does with the body,
10
- * not on what the surface is capable of parsing. Two examples that pull in
11
- * opposite directions:
12
- *
13
- * - Telegram's `sendMessage` CAN parse MarkdownV2, but the strategy sends with
14
- * NO `parse_mode`, which is Telegram's plain-text mode. Running a MarkdownV2
15
- * escaper over the body on this path would not protect anything — it would
16
- * put visible backslashes in front of every `.` `-` `!` `(` in the owner's
17
- * message. The correct transform is identity, and the property that makes it
18
- * correct is asserted by a test against the real wire payload rather than
19
- * asserted here in prose (see src/test/daemon/send-command-wire.test.ts).
20
- *
21
- * - Discord's `postMessage` renders markdown unconditionally, INCLUDING masked
22
- * links, so the same body needs real escaping.
23
- *
24
- * A single "escape for channel X" table that ignored the transport would get
25
- * Telegram wrong in the direction that mangles the owner's text, and would get
26
- * Discord wrong in the direction that renders a link. Hence the split below.
27
- *
28
- * ## Provenance of the escaper bodies
29
- *
30
- * `escapeDiscordMarkdown` and `escapeSlackMrkdwn` are the escapers written for
31
- * the inbound-mail structured notices, in the SDK at
32
- * `packages/sdk/src/platform/email/inbound-notice.ts` — same character classes,
33
- * same zero-width-space mention break, same rationale. They are reproduced here
34
- * rather than imported for one reason: that module lives on the unmerged
35
- * `inbound-email-*` branches and is absent from the published
36
- * `@pellux/goodvibes-sdk@1.18.1` this product consumes, so there is nothing to
37
- * import yet.
38
- *
39
- * This is a second copy and that is a defect, not a design. When the
40
- * inbound-mail round merges and an SDK release publishes `platform/email`'s
41
- * notice escapers, THIS FILE'S `escapeDiscordMarkdown` and `escapeSlackMrkdwn`
42
- * are to be deleted and the SDK's imported in their place; the surface-kind
43
- * table below is the only part that should survive that change. The behaviour
44
- * is pinned by tests either way, so the swap is verifiable rather than hopeful.
45
- */
46
-
47
- import type { ChannelDeliverySurfaceKind } from '@pellux/goodvibes-sdk/platform/channels';
48
-
49
- /**
50
- * Zero-width space. Used to break a token that a surface would otherwise read
51
- * as syntax, on surfaces that offer no backslash escape (Slack) or where
52
- * backslash-escaping demonstrably does not defeat the token (Discord mentions).
53
- */
54
- const ZERO_WIDTH_SPACE = '​';
55
-
56
- /**
57
- * `@everyone`, `@here`, and raw role/channel/user mentions. Backslash-escaping
58
- * the `@` does NOT reliably suppress these in every Discord client, so the
59
- * zero-width break is used instead.
60
- */
61
- function breakMentionForms(text: string): string {
62
- return text
63
- .replace(/@(everyone|here)/g, (_match, word: string) => `@${ZERO_WIDTH_SPACE}${word}`)
64
- .replace(/<@[!&]?(\d+)>/g, (_match, id: string) => `<@${ZERO_WIDTH_SPACE}${id}>`)
65
- .replace(/<#(\d+)>/g, (_match, id: string) => `<#${ZERO_WIDTH_SPACE}${id}>`);
66
- }
67
-
68
- /**
69
- * Discord markdown. Backslash-escaping `* _ ~ \` |` and `>` renders each as its
70
- * literal character rather than triggering bold/italic/strikethrough/code/
71
- * spoiler/quote.
72
- *
73
- * `[` `]` `(` `)` are escaped too, and this is REQUIRED rather than
74
- * precautionary: masked links (`[text](url)`) DO render as clickable in
75
- * bot-sent and webhook messages — which is exactly how this product delivers to
76
- * Discord — even though they do not render for text a human typed into the
77
- * client.
78
- * https://github.com/discord/discord-api-docs/issues/6096
79
- * https://gist.github.com/matthewzring/9f7bbfd102003963f9be7dbcf7d40e51
80
- *
81
- * Without this, a message body assembled from anything the owner did not type
82
- * arrives in his Discord as a clickable link reading whatever the body said.
83
- * An escaper that looks optional gets tidied away by the next reader: it is not
84
- * optional, and it stays.
85
- *
86
- * `\` is inside the character class, so a backslash in the input is escaped
87
- * first and cannot un-escape what follows it.
88
- */
89
- function escapeDiscordMarkdown(text: string): string {
90
- return breakMentionForms(text.replace(/[*_~`|>[\]()\\]/g, (ch) => `\\${ch}`));
91
- }
92
-
93
- /**
94
- * Slack mrkdwn. `&`, `<`, `>` MUST be HTML-entity-escaped per Slack's own
95
- * formatting reference. That is also what defeats `<url|text>` link syntax and
96
- * `<!channel>` / `<@id>` mention syntax outright, since both require a literal
97
- * unescaped `<`.
98
- *
99
- * Slack has no backslash escape for `* _ ~ \``, so the zero-width break is
100
- * applied to those as the best available mitigation — stated as a mitigation,
101
- * not a guarantee, because Slack's whitespace-adjacency rule for what breaks a
102
- * delimiter pair is not publicly specified to that precision. The residual risk
103
- * is cosmetic (accidental bold/italic); the injection class this exists to
104
- * close — a clickable link or a real mention — is closed by the entity escaping
105
- * above.
106
- */
107
- function escapeSlackMrkdwn(text: string): string {
108
- const entityEscaped = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
109
- return entityEscaped.replace(/[*_~`]/g, (ch) => `${ch}${ZERO_WIDTH_SPACE}`);
110
- }
111
-
112
- /**
113
- * Google Chat's `text` field renders `*bold*`, `_italic_`, `~strike~`,
114
- * `` `code` `` and `<url|text>` links. The `<` entity escape is what defeats
115
- * the link and `<users/all>` mention forms; the delimiters get the zero-width
116
- * break, as Google Chat documents no backslash escape either.
117
- */
118
- function escapeGoogleChatMarkup(text: string): string {
119
- const entityEscaped = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
120
- return entityEscaped.replace(/[*_~`]/g, (ch) => `${ch}${ZERO_WIDTH_SPACE}`);
121
- }
122
-
123
- /**
124
- * WhatsApp Cloud API text messages render `*bold*`, `_italic_`, `~strike~` and
125
- * ```` ```mono``` ````. There is no masked-link syntax — a URL in the body is
126
- * auto-linked showing its real address — so the injection class Discord has
127
- * does not exist here and only the delimiters need neutralizing. WhatsApp
128
- * documents no backslash escape, so the zero-width break is used.
129
- */
130
- function escapeWhatsAppMarkup(text: string): string {
131
- return text.replace(/[*_~`]/g, (ch) => `${ch}${ZERO_WIDTH_SPACE}`);
132
- }
133
-
134
- /**
135
- * The surfaces whose delivery strategy hands the body to a renderer that parses
136
- * markup. Every other routable surface is absent DELIBERATELY, and each absence
137
- * is a checked claim about the strategy, not an oversight:
138
- *
139
- * - `telegram` — `sendMessage` without `parse_mode`; Telegram's plain-text
140
- * mode. Escaping here would corrupt, not protect.
141
- * - `ntfy` — `publish` sends the body as `text/plain` and never sets the
142
- * `Markdown` header, so ntfy renders it literally. The title
143
- * is derived by `titleFromBody` (first non-empty LINE) and
144
- * passed through `toHeaderSafeTitle`, so no line break or
145
- * non-ASCII byte from the body can reach an HTTP header.
146
- * - `webhook` — the body is a JSON string field; the receiver decides what
147
- * to do with it and there is no markup layer to neutralize.
148
- *
149
- * - `signal` — the strategy posts `text` to a signal bridge. Signal renders
150
- * no markup in a plain message body; styling travels as
151
- * explicit range metadata the strategy never sends.
152
- * - `imessage`,
153
- * `bluebubbles` — both end at iMessage, which renders no markup at all.
154
- * - `msteams` — the strategy sends `textFormat: 'plain'` alongside the text,
155
- * which is Teams' own instruction not to parse it.
156
- * - `matrix` — `msgtype: 'm.text'` with no `format`/`formatted_body`. A
157
- * Matrix event without the HTML format field is rendered
158
- * literally by clients, per the spec.
159
- *
160
- * `web` is absent for a different reason: its strategy needs a live
161
- * `ControlPlaneGateway` in the same process, which a short-lived CLI does not
162
- * have, so the command does not offer it as a channel at all. `telephony` is
163
- * absent because a CLI `send` to it would place a phone call or an SMS through
164
- * a paid carrier — a different act from messaging a channel — and because the
165
- * strategy already XML-escapes the voice path itself via `escapeTwiml`.
166
- */
167
- const INERT_TRANSFORMS: Partial<Record<ChannelDeliverySurfaceKind, (text: string) => string>> = {
168
- // Renders markup, including a masked link: full escaping.
169
- discord: escapeDiscordMarkdown,
170
- mattermost: escapeDiscordMarkdown,
171
- // Renders markup, but has no masked-link syntax: delimiters only.
172
- slack: escapeSlackMrkdwn,
173
- 'google-chat': escapeGoogleChatMarkup,
174
- whatsapp: escapeWhatsAppMarkup,
175
- // Delivered as plain text by the strategy: transforming would corrupt, not
176
- // protect. Each of these is a checked claim about the strategy, not a guess —
177
- // see the list above.
178
- telegram: (text) => text,
179
- ntfy: (text) => text,
180
- webhook: (text) => text,
181
- signal: (text) => text,
182
- imessage: (text) => text,
183
- bluebubbles: (text) => text,
184
- msteams: (text) => text,
185
- matrix: (text) => text,
186
- };
187
-
188
- /** Surfaces `goodvibes-daemon send` will deliver to, in a stable display order. */
189
- export const INERT_RENDERABLE_SURFACE_KINDS: readonly ChannelDeliverySurfaceKind[] = [
190
- 'telegram', 'ntfy', 'discord', 'slack', 'google-chat', 'webhook',
191
- 'signal', 'whatsapp', 'imessage', 'msteams', 'bluebubbles', 'mattermost', 'matrix',
192
- ];
193
-
194
- /**
195
- * Whether this surface has a verified inert transform.
196
- *
197
- * A surface with no entry gets a refusal from the send command, never an
198
- * untransformed body. Returning the body unchanged for an unknown surface is
199
- * precisely how a markup-rendering channel acquires a live link: the default
200
- * has to be "refuse", and it has to be checkable from the caller.
201
- */
202
- export function canRenderInert(surfaceKind: ChannelDeliverySurfaceKind): boolean {
203
- return Object.prototype.hasOwnProperty.call(INERT_TRANSFORMS, surfaceKind);
204
- }
205
-
206
- /**
207
- * Render `text` so it arrives on `surfaceKind` as the literal characters the
208
- * caller supplied.
209
- *
210
- * Throws for a surface with no verified transform. This function has no
211
- * "pass it through" branch and must never grow one: every caller of the send
212
- * command reaches the world through here, so a permissive fallback would make
213
- * the command a way to put live markup on the owner's phone.
214
- */
215
- export function inertBodyFor(surfaceKind: ChannelDeliverySurfaceKind, text: string): string {
216
- const transform = INERT_TRANSFORMS[surfaceKind];
217
- if (!transform) {
218
- throw new Error(
219
- `No verified inert-text transform for surface '${surfaceKind}'. `
220
- + 'Sending would risk the message being rendered as markup rather than as text, '
221
- + `so nothing was sent. Supported: ${INERT_RENDERABLE_SURFACE_KINDS.join(', ')}.`,
222
- );
223
- }
224
- return transform(text);
225
- }
@@ -1,24 +0,0 @@
1
- /**
2
- * stdin.ts — read the whole of stdin as the message body.
3
- *
4
- * Kept out of command.ts so the command stays a pure function of its arguments
5
- * and its injected dependencies: every test drives it with a stub rather than
6
- * having to arrange a real pipe on the process running the suite.
7
- */
8
-
9
- /**
10
- * Read stdin to end, as UTF-8.
11
- *
12
- * There is deliberately no timeout. `send` reads stdin only when the operator
13
- * gave no message argument AND stdin is not a terminal — meaning something is
14
- * piping into it — so waiting for that producer to finish is the correct
15
- * behaviour, and cutting it off at an arbitrary deadline would silently
16
- * truncate a long message.
17
- */
18
- export async function readAllStdin(): Promise<string> {
19
- const chunks: Buffer[] = [];
20
- for await (const chunk of process.stdin) {
21
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as string));
22
- }
23
- return Buffer.concat(chunks).toString('utf-8');
24
- }
@@ -1,329 +0,0 @@
1
- /**
2
- * TUI wiring — `goodvibes-daemon install-service | uninstall-service
3
- * | service-status`.
4
- *
5
- * The daemon is a SYSTEM SERVICE. These subcommands install it as a durable host
6
- * service (systemd user unit on Linux, launchd agent on macOS, a Scheduled Task
7
- * on Windows) so N surfaces share ONE daemon that survives reboots, instead of
8
- * every surface spawning a session-scoped daemon.
9
- *
10
- * Drift note: the SDK's older `systemd-user-service.ts` (a Linux-only,
11
- * bespoke systemd shim) was dead code and got deleted from the SDK. The SDK's
12
- * REAL wired service machinery — reached in production by the daemon's own HTTP
13
- * `/api/service/*` routes via facade-composition.ts — is
14
- * `PlatformServiceManager` (`@pellux/goodvibes-sdk/platform/daemon`): a single
15
- * systemd/launchd/windows-aware manager with install/uninstall/status/start/
16
- * stop/restart and a `suggestedCommands` hint list. This module now rewires the
17
- * three CLI subcommands onto that manager instead of the deleted shim.
18
- *
19
- * Two real behavioral differences from the old shim, called out honestly:
20
- * - `install()` only WRITES the unit/plist/task; it does not enable/start it.
21
- * So `install-service` here calls `install()` then `start()` to preserve the
22
- * old "install implies enabled + running" behavior.
23
- * - `uninstall()` only REMOVES the unit file; there is no manager-level
24
- * `disable` verb (only start/stop/restart exist). So `uninstall-service`
25
- * calls `stop()` then `uninstall()`, and honestly tells the caller that a
26
- * stray "enabled" symlink may remain until `systemctl --user daemon-reload`
27
- * (offered back as a suggested follow-up) or the next login cleans it up.
28
- *
29
- * A fourth subcommand, `migrate-service`, closes the earlier "detect and
30
- * disclose only" inheritance (W3 Finding 4, below) with a GUIDED, CONSENTED
31
- * takeover of the legacy `goodvibes-daemon.service` unit. Design constraints,
32
- * all load-bearing:
33
- * - NEVER auto-migrate. Without explicit consent (`confirmMigration`, wired
34
- * from the CLI's existing `-y`/`--yes` flag) this prints the exact plan
35
- * and touches nothing.
36
- * - NEW-UP-THEN-OLD-DOWN. The new `goodvibes.service` unit is installed,
37
- * started, and verified healthy (a fresh `status().running` read, which
38
- * honestly queries systemd via the injected actionRunner) BEFORE the
39
- * legacy unit is stopped, disabled, or removed. A failed or unhealthy new
40
- * unit rolls itself back (uninstalled) and never touches the legacy one
41
- * — a botched takeover must never cost the user their working daemon.
42
- * - ADOPT-OR-WARN, NEVER KILL. If the legacy unit file is simply absent but
43
- * something is already listening on the configured host:port (Mike's real
44
- * dev-host case: a manual `nohup`'d daemon with no unit at all), this is
45
- * an unidentified process, not a managed unit — there is nothing to stop
46
- * or disable, and this module will not attempt to kill it. It warns and
47
- * leaves the decision to the operator.
48
- * - Every action (legacy stop/disable, unit-file removal, daemon-reload)
49
- * goes through the SAME injectable `actionRunner`/`legacyUnitFileRemove`
50
- * seams tests already use — this module never has a code path that bypasses
51
- * them, so the migration is exercised deterministically via fakes and never
52
- * touches a real running service in tests.
53
- */
54
-
55
- import { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
56
- import { PlatformServiceManager, type ManagedServiceStatus } from '@pellux/goodvibes-sdk/platform/daemon';
57
- import {
58
- buildManagedDaemonServiceManager,
59
- detectLegacyUnit,
60
- legacyUnitNote,
61
- resolveManagedUnitName,
62
- runLegacyDaemonMigration,
63
- LEGACY_SERVICE_UNIT_NAME,
64
- MANAGED_SERVICE_NAME as SERVICE_NAME,
65
- MANAGED_SERVICE_DESCRIPTION as SERVICE_DESCRIPTION,
66
- type LegacyUnitInfo,
67
- type ManagedServiceActionRunner,
68
- } from '../runtime/legacy-daemon-migration.ts';
69
-
70
- // `resolveInstalledDaemonBinary` lives in the runtime module (shared with the
71
- // onboarding guided UX) — re-exported here so this module stays the CLI's
72
- // stable public surface (and so existing test imports keep working).
73
- export {
74
- resolveInstalledDaemonBinary,
75
- type ResolveDaemonBinaryOptions,
76
- } from '../runtime/legacy-daemon-migration.ts';
77
- export type { ManagedServiceActionRunner } from '../runtime/legacy-daemon-migration.ts';
78
-
79
- // ---------------------------------------------------------------------------
80
- // Legacy-unit detection, plus guided migration.
81
- //
82
- // The prior command installed the daemon's systemd unit under the
83
- // literal name `goodvibes-daemon.service`. This module (rewired onto
84
- // PlatformServiceManager, see the file banner above) manages a DIFFERENT
85
- // unit name (`goodvibes`, SERVICE_NAME). A host that still has the legacy
86
- // unit installed — this dev machine included — would otherwise see
87
- // service-status honestly report installed:false/running:false for the
88
- // tracked name while the legacy unit keeps running untouched underneath it,
89
- // uninstall-service would silently leave the legacy unit orphaned with no
90
- // mention, and install-service could start a SECOND daemon competing for the
91
- // same port.
92
- //
93
- // The detection (`detectLegacyUnit`/`legacyUnitNote`) and the guided
94
- // migration engine (`runLegacyDaemonMigration`) both live in
95
- // `../runtime/legacy-daemon-migration.ts` rather than here, so the TUI's
96
- // onboarding UX (`src/input/handler-onboarding-daemon-adopt.ts`) can reuse
97
- // them directly — the architecture gate forbids `src/input/**` from
98
- // importing `src/daemon/**` (input must stay entrypoint-agnostic), so the
99
- // shared engine lives in the entrypoint-agnostic `runtime` layer instead and
100
- // this CLI module is just one of its two consumers.
101
- //
102
- // This detection is entirely independent of PlatformServiceManager's own
103
- // status() — it does not rely on (or get invalidated by) the parallel SDK
104
- // fix that makes status().running itself query systemd honestly for the
105
- // TRACKED unit name.
106
- // ---------------------------------------------------------------------------
107
-
108
- export type DaemonServiceSubcommand = 'install-service' | 'uninstall-service' | 'service-status' | 'migrate-service';
109
-
110
- export function isDaemonServiceSubcommand(value: string | undefined): value is DaemonServiceSubcommand {
111
- return value === 'install-service' || value === 'uninstall-service' || value === 'service-status' || value === 'migrate-service';
112
- }
113
-
114
- export interface DaemonServiceCliInput {
115
- readonly subcommand: DaemonServiceSubcommand;
116
- readonly binaryPath: string;
117
- readonly homeDir: string;
118
- readonly host: string;
119
- readonly port: number;
120
- /** Defaults to `homeDir` — overridable so tests can scope both to one tempdir. */
121
- readonly workingDirectory?: string | undefined;
122
- /** Injected in tests; a real `ConfigManager` rooted at `homeDir` otherwise. */
123
- readonly configManager?: ConfigManager | undefined;
124
- /** Injectable systemctl/launchctl/schtasks runner so tests never touch the host. */
125
- readonly actionRunner?: ManagedServiceActionRunner | undefined;
126
- /** Injectable existsSync for the legacy-unit file check (W3 Finding 4) so tests never touch the host filesystem. */
127
- readonly legacyUnitFileExists?: ((path: string) => boolean) | undefined;
128
- /**
129
- * `migrate-service` only: explicit consent to actually execute the
130
- * migration (wired from the CLI's `-y`/`--yes` flag). Without it, the
131
- * subcommand prints the exact plan and changes nothing — never auto-migrate.
132
- */
133
- readonly confirmMigration?: boolean | undefined;
134
- /**
135
- * `migrate-service` only: injectable port-liveness check for the
136
- * legacy-absent branch (never a raw network call in tests). Defaults to a
137
- * real, read-only TCP connect attempt.
138
- */
139
- readonly portProbe?: ((host: string, port: number) => boolean | Promise<boolean>) | undefined;
140
- /**
141
- * `migrate-service` only: injectable removal of the legacy unit file, so
142
- * tests never call a raw fs op against a real path. Defaults to a real
143
- * `rmSync`.
144
- */
145
- readonly legacyUnitFileRemove?: ((path: string) => void) | undefined;
146
- }
147
-
148
- export interface DaemonServiceCliResult {
149
- readonly ok: boolean;
150
- readonly exitCode: number;
151
- /** Honest, human-readable stdout lines describing exactly what happened. */
152
- readonly lines: readonly string[];
153
- readonly status: ManagedServiceStatus;
154
- }
155
-
156
- /**
157
- * The manager's definition (`ExecStart` command/args, name, description) is
158
- * built once, in `../runtime/legacy-daemon-migration.ts`, and shared with the
159
- * onboarding guided UX — see that module's doc comment for why.
160
- */
161
- function buildManager(input: DaemonServiceCliInput): PlatformServiceManager {
162
- return buildManagedDaemonServiceManager({
163
- binaryPath: input.binaryPath,
164
- homeDir: input.homeDir,
165
- host: input.host,
166
- port: input.port,
167
- workingDirectory: input.workingDirectory,
168
- configManager: input.configManager,
169
- actionRunner: input.actionRunner,
170
- });
171
- }
172
-
173
- function statusLines(status: ManagedServiceStatus): string[] {
174
- const lines = [
175
- `platform: ${status.platform}`,
176
- `installed: ${status.installed}`,
177
- `running: ${status.running}`,
178
- `unit path: ${status.path}`,
179
- ];
180
- if (status.pid !== undefined) lines.push(`pid: ${status.pid}`);
181
- if (status.platform !== 'manual' && status.installed && !status.running) {
182
- // W3 Finding 4: this used to assert "'running' here only reflects
183
- // processes this tool started directly" — true for the pid-file-only
184
- // check the (currently linked) SDK still uses, but the parallel SDK
185
- // batch is making status().running query systemd honestly via
186
- // `is-active`, which would make that specific claim stale. Drop the
187
- // claim about HOW running was computed and just offer the escape
188
- // hatch — true and useful under either SDK version.
189
- lines.push(
190
- `note: if this looks wrong, verify directly: ${status.suggestedCommands[status.suggestedCommands.length - 1] ?? 'the platform service-status command'}`,
191
- );
192
- }
193
- return lines;
194
- }
195
-
196
- /**
197
- * Build the install-branch's result lines. Exported for direct unit
198
- * coverage: the "suggested follow-ups" block is gated on the actual
199
- * not-started case (W3 Finding 4 friction fix) rather than printed
200
- * unconditionally, and driving that through the full
201
- * PlatformServiceManager/systemd integration can't reliably produce
202
- * `running: true` under the (currently linked) SDK build, whose systemd
203
- * status check is still pid-file-only (see the honesty note above).
204
- */
205
- export function buildInstallResultLines(status: ManagedServiceStatus): string[] {
206
- const lines = [`installed the ${status.platform} service at ${status.path}`];
207
- if (status.running) {
208
- lines.push('service is enabled and running');
209
- } else {
210
- lines.push('suggested follow-ups if it did not start automatically:');
211
- for (const cmd of status.suggestedCommands) lines.push(` ${cmd}`);
212
- }
213
- return lines;
214
- }
215
-
216
- function ok(action: 'install' | 'uninstall' | 'status', status: ManagedServiceStatus, extra: string[] = []): DaemonServiceCliResult {
217
- const lines: string[] = [];
218
- if (action === 'install') {
219
- lines.push(...buildInstallResultLines(status));
220
- } else if (action === 'uninstall') {
221
- lines.push(`removed the ${status.platform} service at ${status.path}`);
222
- if (status.platform === 'systemd') {
223
- lines.push(
224
- "note: this removes the unit file but does not run `disable` — run " +
225
- "`systemctl --user daemon-reload` to clear any stale enablement symlink.",
226
- );
227
- }
228
- } else {
229
- lines.push(...statusLines(status));
230
- }
231
- lines.push(...extra);
232
- return { ok: true, exitCode: 0, lines, status };
233
- }
234
-
235
- function failed(action: 'install' | 'uninstall' | 'status', status: ManagedServiceStatus): DaemonServiceCliResult {
236
- return {
237
- ok: false,
238
- exitCode: 1,
239
- lines: [`service ${action} failed: ${status.actionError ?? 'unknown error'}`],
240
- status,
241
- };
242
- }
243
-
244
- /**
245
- * `migrate-service`: the guided, consented takeover of the legacy
246
- * `goodvibes-daemon.service` unit. Thin wrapper over
247
- * `runLegacyDaemonMigration` (`../runtime/legacy-daemon-migration.ts`) — see
248
- * that module for the design constraints (never auto-migrate,
249
- * new-up-then-old-down, adopt-or-warn/never kill an unrecognized process,
250
- * every action through an injectable seam) and the onboarding UX consumer.
251
- */
252
- function runMigrateService(
253
- input: DaemonServiceCliInput,
254
- manager: PlatformServiceManager,
255
- legacy: LegacyUnitInfo,
256
- ): Promise<DaemonServiceCliResult> {
257
- return runLegacyDaemonMigration(
258
- {
259
- host: input.host,
260
- port: input.port,
261
- trackedServiceName: SERVICE_NAME,
262
- confirmMigration: input.confirmMigration,
263
- portProbe: input.portProbe,
264
- legacyUnitFileRemove: input.legacyUnitFileRemove,
265
- actionRunner: input.actionRunner,
266
- },
267
- manager,
268
- legacy,
269
- );
270
- }
271
-
272
- /** Dispatch a daemon service subcommand to the SDK's `PlatformServiceManager`. */
273
- export async function runDaemonServiceCli(input: DaemonServiceCliInput): Promise<DaemonServiceCliResult> {
274
- const manager = buildManager(input);
275
- const legacy = detectLegacyUnit(input);
276
- switch (input.subcommand) {
277
- case 'migrate-service':
278
- return runMigrateService(input, manager, legacy);
279
- case 'install-service': {
280
- // W3 Finding 4: refuse rather than risk starting a second daemon
281
- // alongside an already-installed legacy unit. Refuses whenever the
282
- // legacy unit is present at all (not just when currently active) —
283
- // an installed-but-inactive legacy unit can still be enabled and
284
- // start competing for the same port later, and "never silently
285
- // start a second daemon" is the bar here, not "never right now."
286
- if (legacy.present) {
287
- const status = manager.status();
288
- const resolvedName = resolveManagedUnitName(status);
289
- return {
290
- ok: false,
291
- exitCode: 1,
292
- lines: [
293
- `service install refused: a service is already installed as ${LEGACY_SERVICE_UNIT_NAME}.service (the unit name the goodvibes install script manages).`,
294
- legacyUnitNote(legacy, resolvedName),
295
- `Installing this tool's ${resolvedName}.service alongside it risks two daemons competing for the same port.`,
296
- ],
297
- status,
298
- };
299
- }
300
- const installed = manager.install();
301
- if (installed.actionError) return failed('install', installed);
302
- const started = manager.start();
303
- // start()'s actionError (e.g. a platform this manager can't dispatch
304
- // actions for) doesn't undo the write — report install as ok, but surface
305
- // the follow-up problem honestly instead of claiming it is running.
306
- return started.actionError
307
- ? ok('install', { ...started, running: false }, [`could not start it automatically: ${started.actionError}`])
308
- : ok('install', started);
309
- }
310
- case 'uninstall-service': {
311
- const stopped = manager.stop();
312
- const uninstalled = manager.uninstall();
313
- if (uninstalled.actionError) return failed('uninstall', uninstalled);
314
- const extra: string[] = [];
315
- if (stopped.actionError) extra.push(`(it may not have been running: ${stopped.actionError})`);
316
- // This command only ever touches the TRACKED unit (whatever name/path
317
- // actually resolved — not necessarily the SERVICE_NAME constant) above —
318
- // say so explicitly when an install-script unit also exists, so its
319
- // continued presence is never a silent surprise.
320
- if (legacy.present) extra.push(legacyUnitNote(legacy, resolveManagedUnitName(uninstalled)));
321
- return ok('uninstall', uninstalled, extra);
322
- }
323
- case 'service-status': {
324
- const status = manager.status();
325
- if (status.actionError) return failed('status', status);
326
- return ok('status', status, legacy.present ? [legacyUnitNote(legacy, resolveManagedUnitName(status))] : []);
327
- }
328
- }
329
- }