@pellux/goodvibes-agent 0.1.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 (398) hide show
  1. package/.goodvibes/GOODVIBES.md +35 -0
  2. package/.goodvibes/agents/reviewer.md +48 -0
  3. package/.goodvibes/skills/add-provider/SKILL.md +199 -0
  4. package/CHANGELOG.md +25 -0
  5. package/README.md +74 -0
  6. package/bin/goodvibes-agent.ts +2 -0
  7. package/docs/README.md +23 -0
  8. package/docs/deployment-and-services.md +57 -0
  9. package/docs/getting-started.md +53 -0
  10. package/docs/release-and-publishing.md +46 -0
  11. package/package.json +134 -0
  12. package/scripts/check-bun.sh +20 -0
  13. package/src/audio/player.ts +156 -0
  14. package/src/audio/spoken-turn-controller.ts +203 -0
  15. package/src/audio/spoken-turn-model-routing.ts +117 -0
  16. package/src/audio/spoken-turn-wiring.ts +44 -0
  17. package/src/audio/text-chunker.ts +110 -0
  18. package/src/cli/bundle-command.ts +227 -0
  19. package/src/cli/completion.ts +90 -0
  20. package/src/cli/config-overrides.ts +159 -0
  21. package/src/cli/endpoints.ts +63 -0
  22. package/src/cli/entrypoint.ts +172 -0
  23. package/src/cli/help.ts +299 -0
  24. package/src/cli/index.ts +11 -0
  25. package/src/cli/management-commands.ts +426 -0
  26. package/src/cli/management.ts +744 -0
  27. package/src/cli/network-posture.ts +46 -0
  28. package/src/cli/package-verification.ts +123 -0
  29. package/src/cli/parser.ts +369 -0
  30. package/src/cli/provider-auth-routes.ts +22 -0
  31. package/src/cli/provider-classification.ts +107 -0
  32. package/src/cli/redaction.ts +105 -0
  33. package/src/cli/service-command.ts +26 -0
  34. package/src/cli/service-posture.ts +482 -0
  35. package/src/cli/status.ts +383 -0
  36. package/src/cli/surface-command.ts +247 -0
  37. package/src/cli/tui-startup.ts +32 -0
  38. package/src/cli/types.ts +69 -0
  39. package/src/cli-flags.ts +21 -0
  40. package/src/config/goodvibes-home-audit.ts +465 -0
  41. package/src/config/index.ts +57 -0
  42. package/src/config/provider-model.ts +23 -0
  43. package/src/config/secret-config.ts +119 -0
  44. package/src/config/secrets.ts +71 -0
  45. package/src/config/surface.ts +1 -0
  46. package/src/core/composer-state.ts +61 -0
  47. package/src/core/conversation-rendering.ts +359 -0
  48. package/src/core/conversation.ts +551 -0
  49. package/src/core/history.ts +45 -0
  50. package/src/core/orchestrator.ts +7 -0
  51. package/src/core/system-message-router.ts +171 -0
  52. package/src/daemon/cli.ts +55 -0
  53. package/src/daemon/safe-serve.ts +61 -0
  54. package/src/input/agent-workspace.ts +428 -0
  55. package/src/input/autocomplete.ts +96 -0
  56. package/src/input/bookmark-modal.ts +115 -0
  57. package/src/input/command-args-hint.ts +36 -0
  58. package/src/input/command-registry.ts +329 -0
  59. package/src/input/commands/agent-externalized-tui.ts +73 -0
  60. package/src/input/commands/agent-workspace-runtime.ts +17 -0
  61. package/src/input/commands/branch-runtime.ts +72 -0
  62. package/src/input/commands/cloudflare-runtime.ts +370 -0
  63. package/src/input/commands/config.ts +18 -0
  64. package/src/input/commands/control-room-runtime.ts +255 -0
  65. package/src/input/commands/conversation-runtime.ts +207 -0
  66. package/src/input/commands/discovery-runtime.ts +52 -0
  67. package/src/input/commands/eval.ts +204 -0
  68. package/src/input/commands/experience-runtime.ts +278 -0
  69. package/src/input/commands/guidance-runtime.ts +106 -0
  70. package/src/input/commands/health-runtime.ts +434 -0
  71. package/src/input/commands/hooks-runtime.ts +148 -0
  72. package/src/input/commands/incident-runtime.ts +95 -0
  73. package/src/input/commands/integration-runtime.ts +394 -0
  74. package/src/input/commands/intelligence-runtime.ts +223 -0
  75. package/src/input/commands/knowledge.ts +531 -0
  76. package/src/input/commands/local-auth-runtime.ts +105 -0
  77. package/src/input/commands/local-provider-runtime.ts +170 -0
  78. package/src/input/commands/local-runtime.ts +392 -0
  79. package/src/input/commands/local-setup-review.ts +199 -0
  80. package/src/input/commands/local-setup-transfer.ts +135 -0
  81. package/src/input/commands/local-setup.ts +282 -0
  82. package/src/input/commands/managed-runtime.ts +209 -0
  83. package/src/input/commands/marketplace-runtime.ts +290 -0
  84. package/src/input/commands/mcp-runtime.ts +432 -0
  85. package/src/input/commands/memory-product-runtime.ts +111 -0
  86. package/src/input/commands/memory.ts +151 -0
  87. package/src/input/commands/notify-runtime.ts +83 -0
  88. package/src/input/commands/onboarding-runtime.ts +14 -0
  89. package/src/input/commands/operator-panel-runtime.ts +146 -0
  90. package/src/input/commands/operator-runtime.ts +392 -0
  91. package/src/input/commands/planning-runtime.ts +205 -0
  92. package/src/input/commands/platform-access-runtime.ts +422 -0
  93. package/src/input/commands/platform-services-runtime.ts +246 -0
  94. package/src/input/commands/policy-dispatch.ts +339 -0
  95. package/src/input/commands/policy.ts +17 -0
  96. package/src/input/commands/product-runtime.ts +351 -0
  97. package/src/input/commands/profile-sync-runtime.ts +99 -0
  98. package/src/input/commands/provider-accounts-runtime.ts +113 -0
  99. package/src/input/commands/provider.ts +363 -0
  100. package/src/input/commands/qrcode-runtime.ts +20 -0
  101. package/src/input/commands/quit-shared.ts +162 -0
  102. package/src/input/commands/recall-bundle.ts +132 -0
  103. package/src/input/commands/recall-capture.ts +152 -0
  104. package/src/input/commands/recall-query.ts +229 -0
  105. package/src/input/commands/recall-review.ts +98 -0
  106. package/src/input/commands/recall-shared.ts +22 -0
  107. package/src/input/commands/remote-runtime-pool.ts +106 -0
  108. package/src/input/commands/remote-runtime-setup.ts +199 -0
  109. package/src/input/commands/remote-runtime.ts +431 -0
  110. package/src/input/commands/replay-runtime.ts +18 -0
  111. package/src/input/commands/runtime-services.ts +291 -0
  112. package/src/input/commands/schedule-runtime.ts +91 -0
  113. package/src/input/commands/services-runtime.ts +209 -0
  114. package/src/input/commands/session-content.ts +408 -0
  115. package/src/input/commands/session-workflow.ts +464 -0
  116. package/src/input/commands/session.ts +375 -0
  117. package/src/input/commands/settings-sync-runtime.ts +174 -0
  118. package/src/input/commands/share-runtime.ts +119 -0
  119. package/src/input/commands/shell-core.ts +307 -0
  120. package/src/input/commands/skills-runtime.ts +221 -0
  121. package/src/input/commands/subscription-runtime.ts +434 -0
  122. package/src/input/commands/tasks-runtime.ts +230 -0
  123. package/src/input/commands/teamwork-runtime.ts +339 -0
  124. package/src/input/commands/teleport-runtime.ts +57 -0
  125. package/src/input/commands/tts-runtime.ts +29 -0
  126. package/src/input/commands/work-plan-runtime.ts +169 -0
  127. package/src/input/commands.ts +131 -0
  128. package/src/input/feed-context-factory.ts +254 -0
  129. package/src/input/file-picker.ts +192 -0
  130. package/src/input/handler-command-route.ts +180 -0
  131. package/src/input/handler-content-actions.ts +497 -0
  132. package/src/input/handler-feed-routes.ts +648 -0
  133. package/src/input/handler-feed.ts +452 -0
  134. package/src/input/handler-interactions.ts +281 -0
  135. package/src/input/handler-modal-routes.ts +418 -0
  136. package/src/input/handler-modal-stack.ts +263 -0
  137. package/src/input/handler-modal-token-routes.ts +329 -0
  138. package/src/input/handler-onboarding-cloudflare.ts +391 -0
  139. package/src/input/handler-onboarding.ts +620 -0
  140. package/src/input/handler-picker-routes.ts +472 -0
  141. package/src/input/handler-prompt-buffer.ts +320 -0
  142. package/src/input/handler-shortcuts.ts +213 -0
  143. package/src/input/handler-ui-state.ts +372 -0
  144. package/src/input/handler.ts +729 -0
  145. package/src/input/input-history.ts +297 -0
  146. package/src/input/keybindings.ts +292 -0
  147. package/src/input/mcp-workspace.ts +554 -0
  148. package/src/input/model-picker-provider-filter.ts +28 -0
  149. package/src/input/model-picker-types.ts +137 -0
  150. package/src/input/model-picker.ts +797 -0
  151. package/src/input/onboarding/handler-onboarding-routes.ts +125 -0
  152. package/src/input/onboarding/onboarding-runtime-status.ts +87 -0
  153. package/src/input/onboarding/onboarding-wizard-apply.ts +277 -0
  154. package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +494 -0
  155. package/src/input/onboarding/onboarding-wizard-cloudflare.ts +204 -0
  156. package/src/input/onboarding/onboarding-wizard-constants.ts +158 -0
  157. package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +130 -0
  158. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +762 -0
  159. package/src/input/onboarding/onboarding-wizard-helpers.ts +167 -0
  160. package/src/input/onboarding/onboarding-wizard-rules.ts +256 -0
  161. package/src/input/onboarding/onboarding-wizard-state.ts +365 -0
  162. package/src/input/onboarding/onboarding-wizard-steps.ts +798 -0
  163. package/src/input/onboarding/onboarding-wizard-types.ts +195 -0
  164. package/src/input/onboarding/onboarding-wizard.ts +711 -0
  165. package/src/input/panel-integration-actions.ts +78 -0
  166. package/src/input/profile-picker-modal.ts +222 -0
  167. package/src/input/search.ts +100 -0
  168. package/src/input/selection-modal.ts +163 -0
  169. package/src/input/selection.ts +135 -0
  170. package/src/input/session-picker-modal.ts +136 -0
  171. package/src/input/settings-modal-behavior.ts +37 -0
  172. package/src/input/settings-modal-secrets.ts +41 -0
  173. package/src/input/settings-modal-subscriptions.ts +95 -0
  174. package/src/input/settings-modal-types.ts +91 -0
  175. package/src/input/settings-modal.ts +793 -0
  176. package/src/input/submission-intent.ts +17 -0
  177. package/src/input/submission-router.ts +59 -0
  178. package/src/input/tts-settings-actions.ts +100 -0
  179. package/src/main.ts +792 -0
  180. package/src/mcp/runtime-reload.ts +81 -0
  181. package/src/panels/agent-inspector-panel.ts +521 -0
  182. package/src/panels/agent-inspector-shared.ts +94 -0
  183. package/src/panels/agent-logs-panel.ts +559 -0
  184. package/src/panels/agent-logs-shared.ts +129 -0
  185. package/src/panels/approval-panel.ts +150 -0
  186. package/src/panels/automation-control-panel.ts +212 -0
  187. package/src/panels/base-panel.ts +254 -0
  188. package/src/panels/builtin/agent.ts +117 -0
  189. package/src/panels/builtin/development.ts +31 -0
  190. package/src/panels/builtin/knowledge.ts +26 -0
  191. package/src/panels/builtin/operations.ts +349 -0
  192. package/src/panels/builtin/session.ts +129 -0
  193. package/src/panels/builtin/shared.ts +274 -0
  194. package/src/panels/builtin-panels.ts +23 -0
  195. package/src/panels/cockpit-panel.ts +183 -0
  196. package/src/panels/communication-panel.ts +153 -0
  197. package/src/panels/confirm-state.ts +61 -0
  198. package/src/panels/context-visualizer-panel.ts +204 -0
  199. package/src/panels/control-plane-panel.ts +211 -0
  200. package/src/panels/cost-tracker-panel.ts +444 -0
  201. package/src/panels/debug-panel.ts +432 -0
  202. package/src/panels/diff-panel.ts +520 -0
  203. package/src/panels/docs-panel.ts +283 -0
  204. package/src/panels/eval-panel.ts +399 -0
  205. package/src/panels/file-explorer-panel.ts +584 -0
  206. package/src/panels/file-preview-panel.ts +434 -0
  207. package/src/panels/forensics-panel.ts +364 -0
  208. package/src/panels/git-panel.ts +638 -0
  209. package/src/panels/hooks-panel.ts +239 -0
  210. package/src/panels/incident-review-panel.ts +197 -0
  211. package/src/panels/index.ts +46 -0
  212. package/src/panels/intelligence-panel.ts +176 -0
  213. package/src/panels/knowledge-panel.ts +345 -0
  214. package/src/panels/local-auth-panel.ts +130 -0
  215. package/src/panels/marketplace-panel.ts +212 -0
  216. package/src/panels/memory-panel.ts +225 -0
  217. package/src/panels/ops-control-panel.ts +150 -0
  218. package/src/panels/ops-strategy-panel.ts +235 -0
  219. package/src/panels/orchestration-panel.ts +273 -0
  220. package/src/panels/panel-list-panel.ts +509 -0
  221. package/src/panels/panel-manager.ts +570 -0
  222. package/src/panels/panel-picker.ts +106 -0
  223. package/src/panels/plan-dashboard-panel.ts +274 -0
  224. package/src/panels/plugins-panel.ts +178 -0
  225. package/src/panels/policy-panel.ts +308 -0
  226. package/src/panels/polish.ts +717 -0
  227. package/src/panels/project-planning-panel.ts +711 -0
  228. package/src/panels/provider-account-snapshot.ts +259 -0
  229. package/src/panels/provider-accounts-panel.ts +218 -0
  230. package/src/panels/provider-health-domains.ts +215 -0
  231. package/src/panels/provider-health-panel.ts +727 -0
  232. package/src/panels/provider-health-tracker.ts +115 -0
  233. package/src/panels/provider-stats-panel.ts +366 -0
  234. package/src/panels/qr-panel.ts +182 -0
  235. package/src/panels/remote-panel.ts +449 -0
  236. package/src/panels/routes-panel.ts +178 -0
  237. package/src/panels/sandbox-panel.ts +283 -0
  238. package/src/panels/schedule-panel.ts +329 -0
  239. package/src/panels/scrollable-list-panel.ts +491 -0
  240. package/src/panels/search-focus.ts +32 -0
  241. package/src/panels/security-panel.ts +295 -0
  242. package/src/panels/services-panel.ts +231 -0
  243. package/src/panels/session-browser-panel.ts +400 -0
  244. package/src/panels/session-maintenance.ts +125 -0
  245. package/src/panels/settings-sync-panel.ts +120 -0
  246. package/src/panels/skills-panel.ts +431 -0
  247. package/src/panels/subscription-panel.ts +263 -0
  248. package/src/panels/symbol-outline-panel.ts +486 -0
  249. package/src/panels/system-messages-panel.ts +230 -0
  250. package/src/panels/tasks-panel.ts +399 -0
  251. package/src/panels/thinking-panel.ts +304 -0
  252. package/src/panels/token-budget-panel.ts +475 -0
  253. package/src/panels/tool-inspector-panel.ts +429 -0
  254. package/src/panels/types.ts +54 -0
  255. package/src/panels/watchers-panel.ts +193 -0
  256. package/src/panels/work-plan-panel.ts +175 -0
  257. package/src/panels/worktree-panel.ts +182 -0
  258. package/src/panels/wrfc-panel.ts +609 -0
  259. package/src/permissions/prompt.ts +165 -0
  260. package/src/planning/project-planning-coordinator.ts +543 -0
  261. package/src/plugins/loader.ts +15 -0
  262. package/src/renderer/agent-detail-modal.ts +331 -0
  263. package/src/renderer/agent-workspace.ts +238 -0
  264. package/src/renderer/ansi-sanitize.ts +76 -0
  265. package/src/renderer/autocomplete-overlay.ts +154 -0
  266. package/src/renderer/block-actions.ts +76 -0
  267. package/src/renderer/bookmark-modal.ts +101 -0
  268. package/src/renderer/bottom-bar.ts +58 -0
  269. package/src/renderer/buffer.ts +113 -0
  270. package/src/renderer/code-block.ts +373 -0
  271. package/src/renderer/compositor.ts +283 -0
  272. package/src/renderer/context-inspector.ts +219 -0
  273. package/src/renderer/conversation-layout.ts +67 -0
  274. package/src/renderer/conversation-overlays.ts +140 -0
  275. package/src/renderer/conversation-surface.ts +260 -0
  276. package/src/renderer/diff-view.ts +132 -0
  277. package/src/renderer/diff.ts +130 -0
  278. package/src/renderer/file-picker-overlay.ts +101 -0
  279. package/src/renderer/file-tree.ts +153 -0
  280. package/src/renderer/fullscreen-primitives.ts +130 -0
  281. package/src/renderer/fullscreen-workspace.ts +199 -0
  282. package/src/renderer/git-status.ts +89 -0
  283. package/src/renderer/help-overlay.ts +267 -0
  284. package/src/renderer/history-search-overlay.ts +73 -0
  285. package/src/renderer/layout-engine.ts +97 -0
  286. package/src/renderer/layout.ts +32 -0
  287. package/src/renderer/live-tail-modal.ts +156 -0
  288. package/src/renderer/markdown.ts +635 -0
  289. package/src/renderer/mcp-workspace.ts +237 -0
  290. package/src/renderer/modal-factory.ts +467 -0
  291. package/src/renderer/modal-utils.ts +24 -0
  292. package/src/renderer/model-picker-overlay.ts +473 -0
  293. package/src/renderer/model-workspace.ts +488 -0
  294. package/src/renderer/onboarding/onboarding-wizard.ts +615 -0
  295. package/src/renderer/overlay-box.ts +146 -0
  296. package/src/renderer/overlay-viewport.ts +104 -0
  297. package/src/renderer/panel-composite.ts +158 -0
  298. package/src/renderer/panel-picker-overlay.ts +202 -0
  299. package/src/renderer/panel-tab-bar.ts +69 -0
  300. package/src/renderer/panel-workspace-bar.ts +42 -0
  301. package/src/renderer/process-indicator.ts +96 -0
  302. package/src/renderer/process-modal.ts +656 -0
  303. package/src/renderer/process-summary.ts +67 -0
  304. package/src/renderer/profile-picker-modal.ts +129 -0
  305. package/src/renderer/progress.ts +98 -0
  306. package/src/renderer/qr-renderer.ts +120 -0
  307. package/src/renderer/search-overlay.ts +54 -0
  308. package/src/renderer/selection-modal-overlay.ts +214 -0
  309. package/src/renderer/semantic-diff.ts +369 -0
  310. package/src/renderer/session-picker-modal.ts +127 -0
  311. package/src/renderer/settings-modal-helpers.ts +193 -0
  312. package/src/renderer/settings-modal.ts +537 -0
  313. package/src/renderer/shell-surface.ts +88 -0
  314. package/src/renderer/status-glyphs.ts +21 -0
  315. package/src/renderer/status-token.ts +67 -0
  316. package/src/renderer/surface-layout.ts +101 -0
  317. package/src/renderer/syntax-highlighter.ts +542 -0
  318. package/src/renderer/system-message.ts +83 -0
  319. package/src/renderer/tab-strip.ts +108 -0
  320. package/src/renderer/text-layout.ts +31 -0
  321. package/src/renderer/thinking.ts +17 -0
  322. package/src/renderer/tool-call.ts +234 -0
  323. package/src/renderer/ui-factory.ts +524 -0
  324. package/src/renderer/ui-primitives.ts +96 -0
  325. package/src/runtime/bootstrap-command-context.ts +278 -0
  326. package/src/runtime/bootstrap-command-parts.ts +386 -0
  327. package/src/runtime/bootstrap-core.ts +540 -0
  328. package/src/runtime/bootstrap-hook-bridge.ts +112 -0
  329. package/src/runtime/bootstrap-shell.ts +283 -0
  330. package/src/runtime/bootstrap.ts +575 -0
  331. package/src/runtime/cloudflare-control-plane.ts +349 -0
  332. package/src/runtime/context.ts +142 -0
  333. package/src/runtime/diagnostics/panels/index.ts +24 -0
  334. package/src/runtime/diagnostics/panels/ops.ts +156 -0
  335. package/src/runtime/diagnostics/panels/panel-resources.ts +118 -0
  336. package/src/runtime/diagnostics/panels/policy.ts +177 -0
  337. package/src/runtime/index.ts +662 -0
  338. package/src/runtime/onboarding/apply.ts +642 -0
  339. package/src/runtime/onboarding/derivation.ts +534 -0
  340. package/src/runtime/onboarding/index.ts +7 -0
  341. package/src/runtime/onboarding/markers.ts +148 -0
  342. package/src/runtime/onboarding/snapshot.ts +406 -0
  343. package/src/runtime/onboarding/state.ts +141 -0
  344. package/src/runtime/onboarding/types.ts +404 -0
  345. package/src/runtime/onboarding/verify.ts +171 -0
  346. package/src/runtime/operator-token-cleanup.ts +27 -0
  347. package/src/runtime/perf/panel-contracts.ts +32 -0
  348. package/src/runtime/perf/panel-health-monitor.ts +18 -0
  349. package/src/runtime/sandbox-public-gaps.ts +358 -0
  350. package/src/runtime/services.ts +670 -0
  351. package/src/runtime/store/domains/domain-read-matrix.ts +15 -0
  352. package/src/runtime/store/domains/index.ts +222 -0
  353. package/src/runtime/store/domains/panels.ts +117 -0
  354. package/src/runtime/store/domains/ui-perf.ts +103 -0
  355. package/src/runtime/store/index.ts +305 -0
  356. package/src/runtime/store/selectors/index.ts +359 -0
  357. package/src/runtime/store/state.ts +145 -0
  358. package/src/runtime/surface-feature-flags.ts +65 -0
  359. package/src/runtime/terminal-output-guard.ts +228 -0
  360. package/src/runtime/ui/index.ts +39 -0
  361. package/src/runtime/ui/model-picker/data-provider.ts +182 -0
  362. package/src/runtime/ui/model-picker/health-enrichment.ts +228 -0
  363. package/src/runtime/ui/model-picker/index.ts +59 -0
  364. package/src/runtime/ui/model-picker/types.ts +149 -0
  365. package/src/runtime/ui/provider-health/data-provider.ts +244 -0
  366. package/src/runtime/ui/provider-health/fallback-visualizer.ts +71 -0
  367. package/src/runtime/ui/provider-health/index.ts +46 -0
  368. package/src/runtime/ui/provider-health/types.ts +146 -0
  369. package/src/runtime/ui-events.ts +1 -0
  370. package/src/runtime/ui-read-model-helpers.ts +1 -0
  371. package/src/runtime/ui-read-models-observability-maintenance.ts +1 -0
  372. package/src/runtime/ui-read-models-observability-options.ts +1 -0
  373. package/src/runtime/ui-read-models-observability-remote.ts +1 -0
  374. package/src/runtime/ui-read-models-observability-security.ts +1 -0
  375. package/src/runtime/ui-read-models-observability-system.ts +1 -0
  376. package/src/runtime/ui-read-models-observability.ts +1 -0
  377. package/src/runtime/ui-read-models.ts +61 -0
  378. package/src/runtime/ui-service-queries.ts +1 -0
  379. package/src/runtime/ui-services.ts +190 -0
  380. package/src/scripts/process-messages.ts +42 -0
  381. package/src/shell/blocking-input.ts +98 -0
  382. package/src/shell/service-settings-sync.ts +273 -0
  383. package/src/shell/ui-openers.ts +352 -0
  384. package/src/tools/index.ts +1 -0
  385. package/src/tools/wrfc-agent-guard.ts +49 -0
  386. package/src/types/grid.ts +48 -0
  387. package/src/types/sql-js.d.ts +15 -0
  388. package/src/utils/clipboard.ts +22 -0
  389. package/src/utils/splash-lines.ts +46 -0
  390. package/src/utils/terminal-width.ts +185 -0
  391. package/src/verification/live-verifier.ts +430 -0
  392. package/src/verification/verification-ledger.ts +242 -0
  393. package/src/version.ts +17 -0
  394. package/src/widget/index.ts +2 -0
  395. package/src/widget/types.ts +9 -0
  396. package/src/widget/widget.ts +8 -0
  397. package/src/work-plans/work-plan-store.ts +374 -0
  398. package/tsconfig.json +18 -0
@@ -0,0 +1,110 @@
1
+ export interface TtsTextChunkerOptions {
2
+ readonly minBoundaryChars?: number;
3
+ readonly maxChunkChars?: number;
4
+ readonly maxLatencyMs?: number;
5
+ readonly now?: () => number;
6
+ }
7
+
8
+ export class TtsTextChunker {
9
+ private buffer = '';
10
+ private firstBufferedAt: number | null = null;
11
+ private readonly minBoundaryChars: number;
12
+ private readonly maxChunkChars: number;
13
+ private readonly maxLatencyMs: number;
14
+ private readonly now: () => number;
15
+
16
+ constructor(options: TtsTextChunkerOptions = {}) {
17
+ this.minBoundaryChars = options.minBoundaryChars ?? 24;
18
+ this.maxChunkChars = options.maxChunkChars ?? 320;
19
+ this.maxLatencyMs = options.maxLatencyMs ?? 1_000;
20
+ this.now = options.now ?? (() => Date.now());
21
+ }
22
+
23
+ push(delta: string): string[] {
24
+ if (!delta) return [];
25
+ if (this.firstBufferedAt === null) this.firstBufferedAt = this.now();
26
+ this.buffer += delta;
27
+ return this.drainReady(false);
28
+ }
29
+
30
+ flushDue(): string[] {
31
+ if (!this.buffer.trim() || this.firstBufferedAt === null) return [];
32
+ if (this.now() - this.firstBufferedAt < this.maxLatencyMs) return [];
33
+ return this.drainReady(true);
34
+ }
35
+
36
+ flushAll(): string[] {
37
+ if (!this.buffer.trim()) {
38
+ this.buffer = '';
39
+ this.firstBufferedAt = null;
40
+ return [];
41
+ }
42
+ return [this.takeChunk(this.buffer.length)].filter(Boolean);
43
+ }
44
+
45
+ reset(): void {
46
+ this.buffer = '';
47
+ this.firstBufferedAt = null;
48
+ }
49
+
50
+ private drainReady(forceLatencyFlush: boolean): string[] {
51
+ const chunks: string[] = [];
52
+ while (this.buffer.trim()) {
53
+ const boundary = this.findBoundary(forceLatencyFlush);
54
+ if (boundary <= 0) break;
55
+ const chunk = this.takeChunk(boundary);
56
+ if (chunk) chunks.push(chunk);
57
+ forceLatencyFlush = false;
58
+ }
59
+ return chunks;
60
+ }
61
+
62
+ private findBoundary(forceLatencyFlush: boolean): number {
63
+ const latestSentence = this.findLatestSentenceBoundary();
64
+ if (latestSentence >= this.minBoundaryChars) return latestSentence;
65
+
66
+ if (this.buffer.length >= this.maxChunkChars) {
67
+ return this.findWordBoundaryBefore(this.maxChunkChars) || this.maxChunkChars;
68
+ }
69
+
70
+ if (forceLatencyFlush) {
71
+ return this.buffer.length;
72
+ }
73
+
74
+ return -1;
75
+ }
76
+
77
+ private findLatestSentenceBoundary(): number {
78
+ let best = -1;
79
+ for (let i = 0; i < this.buffer.length; i++) {
80
+ const char = this.buffer[i];
81
+ if (char !== '.' && char !== '!' && char !== '?' && char !== ';' && char !== ':' && char !== '\n') {
82
+ continue;
83
+ }
84
+ const next = this.buffer[i + 1];
85
+ if (i === this.buffer.length - 1 || next === undefined || /\s/.test(next)) {
86
+ best = i + 1;
87
+ }
88
+ }
89
+ return best;
90
+ }
91
+
92
+ private findWordBoundaryBefore(index: number): number {
93
+ const max = Math.min(index, this.buffer.length);
94
+ for (let i = max; i > 0; i--) {
95
+ if (/\s/.test(this.buffer[i - 1] ?? '')) return i;
96
+ }
97
+ return -1;
98
+ }
99
+
100
+ private takeChunk(end: number): string {
101
+ const raw = this.buffer.slice(0, end);
102
+ this.buffer = this.buffer.slice(end);
103
+ this.firstBufferedAt = this.buffer.trim() ? this.now() : null;
104
+ return normalizeSpeechText(raw);
105
+ }
106
+ }
107
+
108
+ export function normalizeSpeechText(text: string): string {
109
+ return text.replace(/\s+/g, ' ').trim();
110
+ }
@@ -0,0 +1,227 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { RuntimeEventBus } from '@/runtime/index.ts';
4
+ import { createShellPathService } from '@/runtime/index.ts';
5
+ import { listProviderRuntimeSnapshots } from '@pellux/goodvibes-sdk/platform/providers';
6
+ import { createRuntimeServices } from '../runtime/services.ts';
7
+ import { createRuntimeStore } from '../runtime/store/index.ts';
8
+ import { getOnboardingCheckMarkerPath } from '../runtime/onboarding/index.ts';
9
+ import { CONFIG_SCHEMA } from '../config/index.ts';
10
+ import { SecretsManager } from '../config/secrets.ts';
11
+ import type { ConfigKey } from '../config/index.ts';
12
+ import type { CliCommandRuntime } from './management.ts';
13
+ import type { CliCommandOutput } from './types.ts';
14
+ import { getPackageVersion } from './help.ts';
15
+ import { classifyProviderSetup } from './provider-classification.ts';
16
+ import { buildCliServicePosture } from './service-posture.ts';
17
+ import { REDACTED_VALUE, collectSensitiveConfigValues, isRedactedValue, redactConfig, redactSerializedSecrets } from './redaction.ts';
18
+ import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
19
+
20
+ interface BundleInspectSummary {
21
+ readonly type: string;
22
+ readonly version: string;
23
+ readonly path: string;
24
+ readonly capturedAt: number | null;
25
+ readonly configKeys: number;
26
+ readonly redactedConfigPaths: readonly string[];
27
+ readonly hasDiagnostics: boolean;
28
+ }
29
+
30
+ function formatJsonOrText(runtime: CliCommandRuntime, value: unknown, text: string): string {
31
+ return runtime.cli.flags.outputFormat === 'json' ? JSON.stringify(value, null, 2) : text;
32
+ }
33
+
34
+ function getNestedValue(source: unknown, key: string): unknown {
35
+ let cursor = source;
36
+ for (const part of key.split('.')) {
37
+ if (cursor == null || typeof cursor !== 'object') return undefined;
38
+ cursor = (cursor as Record<string, unknown>)[part];
39
+ }
40
+ return cursor;
41
+ }
42
+
43
+ function readJsonFile(path: string): { readonly ok: true; readonly value: Record<string, unknown> } | { readonly ok: false; readonly error: string } {
44
+ try {
45
+ return { ok: true, value: JSON.parse(readFileSync(path, 'utf-8')) as Record<string, unknown> };
46
+ } catch (error) {
47
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
48
+ }
49
+ }
50
+
51
+ function inspectBundle(path: string, parsed: Record<string, unknown>): BundleInspectSummary {
52
+ return {
53
+ type: String(parsed['type'] ?? 'unknown'),
54
+ version: String(parsed['version'] ?? 'unknown'),
55
+ path,
56
+ capturedAt: typeof parsed['capturedAt'] === 'number' ? parsed['capturedAt'] : null,
57
+ configKeys: parsed['config'] && typeof parsed['config'] === 'object'
58
+ ? CONFIG_SCHEMA.filter((setting) => getNestedValue(parsed['config'], setting.key) !== undefined).length
59
+ : 0,
60
+ redactedConfigPaths: Array.isArray((parsed['redaction'] as { redactedConfigPaths?: unknown } | undefined)?.redactedConfigPaths)
61
+ ? (parsed['redaction'] as { redactedConfigPaths: string[] }).redactedConfigPaths
62
+ : [],
63
+ hasDiagnostics: Boolean(parsed['diagnostics'] && typeof parsed['diagnostics'] === 'object'),
64
+ };
65
+ }
66
+
67
+ function readAuthPosture(runtime: CliCommandRuntime) {
68
+ const shellPaths = createShellPathService({
69
+ workingDirectory: runtime.workingDirectory,
70
+ homeDirectory: runtime.homeDirectory,
71
+ });
72
+ const userStorePath = shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, 'auth-users.json');
73
+ const bootstrapCredentialPath = shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, 'auth-bootstrap.txt');
74
+ const operatorTokenPath = join(runtime.homeDirectory, '.goodvibes', 'daemon', 'operator-tokens.json');
75
+ return {
76
+ userStorePath,
77
+ userStorePresent: existsSync(userStorePath),
78
+ bootstrapCredentialPath,
79
+ bootstrapCredentialPresent: existsSync(bootstrapCredentialPath),
80
+ operatorTokenPath,
81
+ operatorTokenPresent: existsSync(operatorTokenPath),
82
+ };
83
+ }
84
+
85
+ async function buildProviderReadiness(runtime: CliCommandRuntime) {
86
+ const runtimeBus = new RuntimeEventBus();
87
+ const runtimeStore = createRuntimeStore();
88
+ const services = createRuntimeServices({
89
+ configManager: runtime.configManager,
90
+ runtimeBus,
91
+ runtimeStore,
92
+ workingDir: runtime.workingDirectory,
93
+ homeDirectory: runtime.homeDirectory,
94
+ });
95
+ services.providerRegistry.initModelLimits();
96
+ services.benchmarkStore.initBenchmarks();
97
+ services.providerRegistry.initCatalog();
98
+ try {
99
+ await services.providerRegistry.ready();
100
+ const snapshots = await listProviderRuntimeSnapshots(services.providerRegistry);
101
+ return snapshots.map((snapshot) => ({
102
+ provider: snapshot.providerId,
103
+ active: snapshot.active,
104
+ configured: snapshot.runtime.auth?.configured ?? true,
105
+ configuredVia: snapshot.runtime.auth?.mode ?? 'unknown',
106
+ models: snapshot.modelCount,
107
+ setup: classifyProviderSetup({
108
+ providerId: snapshot.providerId,
109
+ authMode: snapshot.runtime.auth?.mode,
110
+ configured: snapshot.runtime.auth?.configured ?? true,
111
+ modelCount: snapshot.modelCount,
112
+ }),
113
+ }));
114
+ } finally {
115
+ services.providerRegistry.stopWatching();
116
+ }
117
+ }
118
+
119
+ export async function handleBundleCommand(runtime: CliCommandRuntime): Promise<CliCommandOutput> {
120
+ const [sub = 'inspect', ...rest] = runtime.cli.commandArgs;
121
+ const shellPaths = createShellPathService({
122
+ workingDirectory: runtime.workingDirectory,
123
+ homeDirectory: runtime.homeDirectory,
124
+ });
125
+
126
+ if (sub === 'inspect') {
127
+ const path = rest[0];
128
+ if (!path) return { output: 'Usage: goodvibes bundle inspect <path>', exitCode: 2 };
129
+ const sourcePath = shellPaths.resolveWorkspacePath(path);
130
+ const parsed = readJsonFile(sourcePath);
131
+ if (!parsed.ok) return { output: `Invalid bundle JSON: ${parsed.error}`, exitCode: 1 };
132
+ const summary = inspectBundle(sourcePath, parsed.value);
133
+ return {
134
+ output: formatJsonOrText(runtime, summary, [
135
+ 'GoodVibes bundle',
136
+ ` type: ${summary.type}`,
137
+ ` version: ${summary.version}`,
138
+ ` path: ${summary.path}`,
139
+ ` capturedAt: ${summary.capturedAt === null ? 'n/a' : new Date(summary.capturedAt).toISOString()}`,
140
+ ` configKeys: ${summary.configKeys}`,
141
+ ` redactedConfigKeys: ${summary.redactedConfigPaths.length}`,
142
+ ` diagnostics: ${summary.hasDiagnostics ? 'present' : 'missing'}`,
143
+ ].join('\n')),
144
+ exitCode: 0,
145
+ };
146
+ }
147
+
148
+ if (sub === 'export') {
149
+ const outputPath = rest[0] ?? 'goodvibes-agent-bundle.json';
150
+ const secrets = new SecretsManager({
151
+ projectRoot: runtime.workingDirectory,
152
+ globalHome: runtime.homeDirectory,
153
+ configManager: runtime.configManager,
154
+ });
155
+ const rawConfig = runtime.configManager.getRaw();
156
+ const sensitiveValues = collectSensitiveConfigValues(rawConfig);
157
+ const redactedConfig = redactConfig(rawConfig);
158
+ const service = await buildCliServicePosture(runtime, { logTailBytes: 8192 });
159
+ const bundle = {
160
+ version: 2,
161
+ type: 'goodvibes-agent.support',
162
+ capturedAt: Date.now(),
163
+ package: {
164
+ name: '@pellux/goodvibes-agent',
165
+ version: getPackageVersion(),
166
+ },
167
+ roots: {
168
+ workingDirectory: runtime.workingDirectory,
169
+ homeDirectory: runtime.homeDirectory,
170
+ },
171
+ config: redactedConfig.value,
172
+ redaction: {
173
+ sentinel: REDACTED_VALUE,
174
+ redactedConfigPaths: redactedConfig.redactedPaths,
175
+ },
176
+ diagnostics: {
177
+ service,
178
+ auth: readAuthPosture(runtime),
179
+ providers: await buildProviderReadiness(runtime),
180
+ },
181
+ secrets: await secrets.inspect(),
182
+ onboarding: {
183
+ userMarker: existsSync(getOnboardingCheckMarkerPath(shellPaths, 'user')),
184
+ projectMarker: existsSync(getOnboardingCheckMarkerPath(shellPaths, 'project')),
185
+ },
186
+ };
187
+ const targetPath = shellPaths.resolveWorkspacePath(outputPath);
188
+ mkdirSync(dirname(targetPath), { recursive: true });
189
+ writeFileSync(targetPath, redactSerializedSecrets(JSON.stringify(bundle, null, 2), sensitiveValues) + '\n', 'utf-8');
190
+ return {
191
+ output: formatJsonOrText(runtime, {
192
+ path: targetPath,
193
+ redactedConfigPaths: redactedConfig.redactedPaths,
194
+ serviceIssues: service.issues,
195
+ }, `Bundle exported: ${targetPath}\n redactedConfigKeys: ${redactedConfig.redactedPaths.length}\n serviceIssues: ${service.issues.length}`),
196
+ exitCode: 0,
197
+ };
198
+ }
199
+
200
+ if (sub === 'import') {
201
+ const path = rest[0];
202
+ if (!path) return { output: 'Usage: goodvibes bundle import <path>', exitCode: 2 };
203
+ const sourcePath = shellPaths.resolveWorkspacePath(path);
204
+ const parsed = readJsonFile(sourcePath);
205
+ if (!parsed.ok) return { output: `Invalid bundle JSON: ${parsed.error}`, exitCode: 1 };
206
+ const config = parsed.value['config'];
207
+ if (!config || typeof config !== 'object') return { output: 'Bundle has no config object to import.', exitCode: 1 };
208
+ let count = 0;
209
+ let skippedRedacted = 0;
210
+ for (const setting of CONFIG_SCHEMA) {
211
+ const value = getNestedValue(config, setting.key);
212
+ if (value === undefined) continue;
213
+ if (isRedactedValue(value)) {
214
+ skippedRedacted++;
215
+ continue;
216
+ }
217
+ runtime.configManager.setDynamic(setting.key as ConfigKey, value as never);
218
+ count++;
219
+ }
220
+ return {
221
+ output: `Bundle imported: ${count} config value${count === 1 ? '' : 's'} applied.${skippedRedacted ? ` ${skippedRedacted} redacted value${skippedRedacted === 1 ? '' : 's'} skipped.` : ''}`,
222
+ exitCode: 0,
223
+ };
224
+ }
225
+
226
+ return { output: 'Usage: goodvibes bundle export [path]|inspect <path>|import <path>', exitCode: 2 };
227
+ }
@@ -0,0 +1,90 @@
1
+ const COMMANDS = [
2
+ 'tui',
3
+ 'run',
4
+ 'exec',
5
+ 'serve',
6
+ 'web',
7
+ 'service',
8
+ 'onboarding',
9
+ 'setup',
10
+ 'doctor',
11
+ 'status',
12
+ 'models',
13
+ 'providers',
14
+ 'auth',
15
+ 'subscription',
16
+ 'secrets',
17
+ 'sessions',
18
+ 'tasks',
19
+ 'pair',
20
+ 'qrcode',
21
+ 'surfaces',
22
+ 'listener',
23
+ 'control-plane',
24
+ 'bundle',
25
+ 'remote',
26
+ 'bridge',
27
+ 'completion',
28
+ 'version',
29
+ 'help',
30
+ ] as const;
31
+
32
+ const OPTIONS = [
33
+ '--help',
34
+ '--version',
35
+ '--model',
36
+ '--provider',
37
+ '--cd',
38
+ '--working-dir',
39
+ '--daemon-home',
40
+ '--config',
41
+ '--enable',
42
+ '--disable',
43
+ '--prompt',
44
+ '--print',
45
+ '--output',
46
+ '--output-format',
47
+ '--json',
48
+ '--no-alt-screen',
49
+ '--port',
50
+ '--hostname',
51
+ '--open',
52
+ '--resume',
53
+ '--session',
54
+ '--continue',
55
+ '--fork',
56
+ '--password',
57
+ '--password-stdin',
58
+ '--role',
59
+ '--manual',
60
+ ] as const;
61
+
62
+ export function renderCompletion(shell: string | undefined, binary = 'goodvibes'): string {
63
+ const normalized = (shell ?? 'bash').toLowerCase();
64
+ const words = [...COMMANDS, ...OPTIONS].join(' ');
65
+
66
+ if (normalized === 'zsh') {
67
+ return [
68
+ `#compdef ${binary}`,
69
+ `_${binary}() {`,
70
+ ` compadd -- ${words}`,
71
+ '}',
72
+ `_${binary} "$@"`,
73
+ ].join('\n');
74
+ }
75
+
76
+ if (normalized === 'fish') {
77
+ return [...COMMANDS, ...OPTIONS]
78
+ .map((word) => `complete -c ${binary} -a ${JSON.stringify(word)}`)
79
+ .join('\n');
80
+ }
81
+
82
+ return [
83
+ `_${binary}() {`,
84
+ ' local cur',
85
+ ' cur="${COMP_WORDS[COMP_CWORD]}"',
86
+ ` COMPREPLY=( $(compgen -W "${words}" -- "$cur") )`,
87
+ '}',
88
+ `complete -F _${binary} ${binary}`,
89
+ ].join('\n');
90
+ }
@@ -0,0 +1,159 @@
1
+ import type { ConfigKey, ConfigManager, ConfigSetting, GoodVibesConfig, PersistedFlagState } from '../config/index.ts';
2
+ import { CONFIG_SCHEMA, ConfigError } from '../config/index.ts';
3
+ import type { GoodVibesCliCommand, GoodVibesCliFlags } from './types.ts';
4
+ import { RUNTIME_ENDPOINT_CONFIG_KEYS, hostModeForHostname } from './endpoints.ts';
5
+ import type { RuntimeEndpointId } from './endpoints.ts';
6
+
7
+ const CONFIG_SCHEMA_BY_KEY = new Map<string, ConfigSetting>(
8
+ CONFIG_SCHEMA.map((setting) => [setting.key, setting]),
9
+ );
10
+
11
+ function parseConfigOverrideValue(value: string): unknown {
12
+ const trimmed = value.trim();
13
+ if (trimmed.length === 0) return '';
14
+ try {
15
+ return JSON.parse(trimmed) as unknown;
16
+ } catch {
17
+ if (trimmed === 'true') return true;
18
+ if (trimmed === 'false') return false;
19
+ if (/^-?\d+(?:\.\d+)?$/.test(trimmed)) return Number(trimmed);
20
+ return value;
21
+ }
22
+ }
23
+
24
+ function getRuntimeConfig(configManager: ConfigManager): GoodVibesConfig {
25
+ const mutable = configManager as unknown as { config?: GoodVibesConfig };
26
+ if (!mutable.config || typeof mutable.config !== 'object') {
27
+ throw new ConfigError('ConfigManager runtime config is not available for CLI overrides.');
28
+ }
29
+ return mutable.config;
30
+ }
31
+
32
+ function validateConfigValue(setting: ConfigSetting, value: unknown): void {
33
+ if (setting.type === 'boolean' && typeof value !== 'boolean') {
34
+ throw new ConfigError(`Invalid value for ${setting.key}: expected boolean.`);
35
+ }
36
+ if (setting.type === 'number' && (typeof value !== 'number' || !Number.isFinite(value))) {
37
+ throw new ConfigError(`Invalid value for ${setting.key}: expected number.`);
38
+ }
39
+ if (setting.type === 'string' && typeof value !== 'string') {
40
+ throw new ConfigError(`Invalid value for ${setting.key}: expected string.`);
41
+ }
42
+ if (setting.type === 'enum' && setting.enumValues && !setting.enumValues.includes(String(value))) {
43
+ throw new ConfigError(`Invalid value for ${setting.key}: "${String(value)}". Allowed: ${setting.enumValues.join(', ')}`);
44
+ }
45
+ if (setting.validate && !setting.validate(value)) {
46
+ throw new ConfigError(`Invalid value for ${setting.key}: ${String(value)}`);
47
+ }
48
+ }
49
+
50
+ function setNestedConfigValue(config: GoodVibesConfig, key: ConfigKey, value: unknown): void {
51
+ const parts = key.split('.');
52
+ let cursor: unknown = config;
53
+ for (const part of parts.slice(0, -1)) {
54
+ if (cursor == null || typeof cursor !== 'object' || !(part in cursor)) {
55
+ throw new ConfigError(`Invalid config path: section '${part}' does not exist`);
56
+ }
57
+ cursor = (cursor as Record<string, unknown>)[part];
58
+ }
59
+ if (cursor == null || typeof cursor !== 'object') {
60
+ throw new ConfigError(`Invalid config path: section '${parts.slice(0, -1).join('.')}' does not exist`);
61
+ }
62
+ (cursor as Record<string, unknown>)[parts[parts.length - 1]!] = value;
63
+ }
64
+
65
+ export function applyRuntimeConfigValue(configManager: ConfigManager, key: ConfigKey, value: unknown): void {
66
+ const setting = CONFIG_SCHEMA_BY_KEY.get(key);
67
+ if (!setting) {
68
+ throw new ConfigError(`Unknown config key: ${key}`);
69
+ }
70
+ validateConfigValue(setting, value);
71
+ setNestedConfigValue(getRuntimeConfig(configManager), key, value);
72
+ }
73
+
74
+ export function applyRuntimeConfigOverrides(
75
+ configManager: ConfigManager,
76
+ overrides: readonly string[],
77
+ ): readonly string[] {
78
+ const errors: string[] = [];
79
+ for (const override of overrides) {
80
+ const index = override.indexOf('=');
81
+ if (index <= 0) {
82
+ errors.push(`Invalid --config override "${override}". Expected key=value.`);
83
+ continue;
84
+ }
85
+ const key = override.slice(0, index) as ConfigKey;
86
+ const rawValue = override.slice(index + 1);
87
+ try {
88
+ applyRuntimeConfigValue(configManager, key, parseConfigOverrideValue(rawValue));
89
+ } catch (error) {
90
+ errors.push(error instanceof Error ? `Invalid --config ${override}: ${error.message}` : `Invalid --config ${override}`);
91
+ }
92
+ }
93
+ return errors;
94
+ }
95
+
96
+ export function applyRuntimeFeatureFlagOverrides(
97
+ configManager: ConfigManager,
98
+ options: {
99
+ readonly enableFeatures: readonly string[];
100
+ readonly disableFeatures: readonly string[];
101
+ },
102
+ ): void {
103
+ if (options.enableFeatures.length === 0 && options.disableFeatures.length === 0) return;
104
+ const config = getRuntimeConfig(configManager);
105
+ const flags = { ...config.featureFlags };
106
+ for (const feature of options.enableFeatures) {
107
+ flags[feature] = 'enabled' satisfies PersistedFlagState;
108
+ }
109
+ for (const feature of options.disableFeatures) {
110
+ flags[feature] = 'disabled' satisfies PersistedFlagState;
111
+ }
112
+ config.featureFlags = flags;
113
+ }
114
+
115
+ export function applyRuntimeEndpointFlagOverrides(
116
+ configManager: ConfigManager,
117
+ endpoint: RuntimeEndpointId,
118
+ flags: Pick<GoodVibesCliFlags, 'hostname' | 'port'>,
119
+ ): readonly string[] {
120
+ const keys = RUNTIME_ENDPOINT_CONFIG_KEYS[endpoint];
121
+ const errors: string[] = [];
122
+
123
+ if (flags.hostname !== undefined) {
124
+ try {
125
+ applyRuntimeConfigValue(configManager, keys.hostMode, hostModeForHostname(flags.hostname));
126
+ applyRuntimeConfigValue(configManager, keys.host, flags.hostname);
127
+ } catch (error) {
128
+ errors.push(error instanceof Error
129
+ ? `Invalid --hostname ${flags.hostname}: ${error.message}`
130
+ : `Invalid --hostname ${flags.hostname}`);
131
+ }
132
+ }
133
+
134
+ if (flags.port !== undefined) {
135
+ try {
136
+ applyRuntimeConfigValue(configManager, keys.port, flags.port);
137
+ } catch (error) {
138
+ errors.push(error instanceof Error
139
+ ? `Invalid --port ${flags.port}: ${error.message}`
140
+ : `Invalid --port ${flags.port}`);
141
+ }
142
+ }
143
+
144
+ return errors;
145
+ }
146
+
147
+ export function applyRuntimeCommandEndpointFlagOverrides(
148
+ configManager: ConfigManager,
149
+ command: GoodVibesCliCommand,
150
+ flags: Pick<GoodVibesCliFlags, 'hostname' | 'port'>,
151
+ ): readonly string[] {
152
+ if (flags.hostname === undefined && flags.port === undefined) return [];
153
+ if (command === 'web') return applyRuntimeEndpointFlagOverrides(configManager, 'web', flags);
154
+ if (command === 'listener') return applyRuntimeEndpointFlagOverrides(configManager, 'httpListener', flags);
155
+ if (command === 'control-plane' || command === 'pair' || command === 'serve') {
156
+ return applyRuntimeEndpointFlagOverrides(configManager, 'controlPlane', flags);
157
+ }
158
+ return [];
159
+ }
@@ -0,0 +1,63 @@
1
+ import type { ConfigKey, ConfigManager } from '../config/index.ts';
2
+
3
+ export type RuntimeEndpointId = 'controlPlane' | 'httpListener' | 'web';
4
+ export type RuntimeHostMode = 'local' | 'network' | 'custom';
5
+
6
+ export const RUNTIME_ENDPOINT_DEFAULT_PORTS: Record<RuntimeEndpointId, number> = {
7
+ controlPlane: 3421,
8
+ httpListener: 3422,
9
+ web: 3423,
10
+ };
11
+
12
+ export const RUNTIME_ENDPOINT_CONFIG_KEYS: Record<RuntimeEndpointId, {
13
+ readonly hostMode: ConfigKey;
14
+ readonly host: ConfigKey;
15
+ readonly port: ConfigKey;
16
+ }> = {
17
+ controlPlane: {
18
+ hostMode: 'controlPlane.hostMode',
19
+ host: 'controlPlane.host',
20
+ port: 'controlPlane.port',
21
+ },
22
+ httpListener: {
23
+ hostMode: 'httpListener.hostMode',
24
+ host: 'httpListener.host',
25
+ port: 'httpListener.port',
26
+ },
27
+ web: {
28
+ hostMode: 'web.hostMode',
29
+ host: 'web.host',
30
+ port: 'web.port',
31
+ },
32
+ };
33
+
34
+ export interface RuntimeEndpointBinding {
35
+ readonly hostMode: string;
36
+ readonly configuredHost: string;
37
+ readonly host: string;
38
+ readonly port: number;
39
+ }
40
+
41
+ export function hostModeForHostname(hostname: string): RuntimeHostMode {
42
+ const normalized = hostname.toLowerCase();
43
+ if (normalized === '0.0.0.0' || normalized === '::') return 'network';
44
+ if (normalized === '127.0.0.1' || normalized === 'localhost' || normalized === '::1') return 'local';
45
+ return 'custom';
46
+ }
47
+
48
+ export function resolveRuntimeEndpointBinding(
49
+ config: Pick<ConfigManager, 'get'>,
50
+ endpoint: RuntimeEndpointId,
51
+ ): RuntimeEndpointBinding {
52
+ const keys = RUNTIME_ENDPOINT_CONFIG_KEYS[endpoint];
53
+ const hostMode = String(config.get(keys.hostMode) ?? 'local');
54
+ const configuredHost = String(config.get(keys.host) ?? '127.0.0.1');
55
+ const port = Number(config.get(keys.port) || RUNTIME_ENDPOINT_DEFAULT_PORTS[endpoint]);
56
+ if (hostMode === 'network') {
57
+ return { hostMode, configuredHost, host: '0.0.0.0', port };
58
+ }
59
+ if (hostMode === 'custom') {
60
+ return { hostMode, configuredHost, host: configuredHost || '127.0.0.1', port };
61
+ }
62
+ return { hostMode, configuredHost, host: '127.0.0.1', port };
63
+ }