@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,534 @@
1
+ import { DEFAULT_CONFIG } from '../../config/index.ts';
2
+ import { getProviderIdFromModel } from '../../config/provider-model.ts';
3
+ import type {
4
+ OnboardingAcknowledgementState,
5
+ OnboardingAcknowledgementTarget,
6
+ OnboardingNetworkMode,
7
+ OnboardingReopenEditAcknowledgementState,
8
+ OnboardingSnapshotState,
9
+ OnboardingStep1CapabilityItem,
10
+ OnboardingStepDerivationState,
11
+ } from './types.ts';
12
+
13
+ const PROVIDER_SECRET_ENV_ALIASES = {
14
+ openai: ['OPENAI_API_KEY', 'OPENAI_KEY'],
15
+ anthropic: ['ANTHROPIC_API_KEY', 'CLAUDE_API_KEY'],
16
+ gemini: ['GEMINI_API_KEY', 'GOOGLE_API_KEY', 'GOOGLE_GEMINI_API_KEY'],
17
+ inceptionlabs: ['INCEPTION_API_KEY'],
18
+ openrouter: ['OPENROUTER_API_KEY'],
19
+ aihubmix: ['AIHUBMIX_API_KEY'],
20
+ groq: ['GROQ_API_KEY'],
21
+ cerebras: ['CEREBRAS_API_KEY'],
22
+ mistral: ['MISTRAL_API_KEY'],
23
+ 'ollama-cloud': ['OLLAMA_CLOUD_API_KEY', 'OLLAMA_API_KEY'],
24
+ huggingface: ['HF_API_KEY', 'HUGGINGFACE_API_KEY', 'HF_TOKEN'],
25
+ nvidia: ['NVIDIA_API_KEY'],
26
+ llm7: ['LLM7_API_KEY'],
27
+ deepseek: ['DEEPSEEK_API_KEY'],
28
+ fireworks: ['FIREWORKS_API_KEY'],
29
+ 'github-copilot': ['COPILOT_GITHUB_TOKEN', 'GH_TOKEN', 'GITHUB_TOKEN'],
30
+ 'microsoft-foundry': ['AZURE_OPENAI_API_KEY'],
31
+ minimax: ['MINIMAX_API_KEY'],
32
+ moonshot: ['MOONSHOT_API_KEY'],
33
+ qianfan: ['QIANFAN_API_KEY'],
34
+ qwen: ['QWEN_API_KEY', 'DASHSCOPE_API_KEY', 'MODELSTUDIO_API_KEY'],
35
+ sglang: ['SGLANG_API_KEY'],
36
+ stepfun: ['STEPFUN_API_KEY'],
37
+ together: ['TOGETHER_API_KEY'],
38
+ venice: ['VENICE_API_KEY'],
39
+ volcengine: ['VOLCANO_ENGINE_API_KEY'],
40
+ xai: ['XAI_API_KEY'],
41
+ xiaomi: ['XIAOMI_API_KEY'],
42
+ zai: ['ZAI_API_KEY', 'Z_AI_API_KEY'],
43
+ 'cloudflare-ai-gateway': ['CLOUDFLARE_AI_GATEWAY_API_KEY'],
44
+ 'vercel-ai-gateway': ['AI_GATEWAY_API_KEY'],
45
+ litellm: ['LITELLM_API_KEY'],
46
+ 'copilot-proxy': ['COPILOT_PROXY_API_KEY'],
47
+ } as const satisfies Record<string, readonly string[]>;
48
+
49
+ const SECRET_KEY_TO_PROVIDER_IDS = new Map<string, readonly string[]>(
50
+ Object.entries(PROVIDER_SECRET_ENV_ALIASES).flatMap(([providerId, aliases]) => aliases.map((alias) => [alias, [providerId] as const])),
51
+ );
52
+
53
+ const INBOUND_EVENT_SURFACE_KINDS = new Set<string>([
54
+ 'bluebubbles',
55
+ 'discord',
56
+ 'google-chat',
57
+ 'googleChat',
58
+ 'homeassistant',
59
+ 'imessage',
60
+ 'mattermost',
61
+ 'matrix',
62
+ 'msteams',
63
+ 'ntfy',
64
+ 'signal',
65
+ 'slack',
66
+ 'telegram',
67
+ 'webhook',
68
+ 'whatsapp',
69
+ ]);
70
+
71
+ function isDeepEqual(left: unknown, right: unknown): boolean {
72
+ if (Object.is(left, right)) return true;
73
+ if (Array.isArray(left) && Array.isArray(right)) {
74
+ return left.length === right.length && left.every((value, index) => isDeepEqual(value, right[index]));
75
+ }
76
+
77
+ if (
78
+ typeof left === 'object' && left !== null
79
+ && typeof right === 'object' && right !== null
80
+ && !Array.isArray(left)
81
+ && !Array.isArray(right)
82
+ ) {
83
+ const leftEntries = Object.entries(left);
84
+ const rightEntries = Object.entries(right);
85
+ if (leftEntries.length !== rightEntries.length) return false;
86
+
87
+ return leftEntries.every(([key, value]) => isDeepEqual(value, (right as Record<string, unknown>)[key]));
88
+ }
89
+
90
+ return false;
91
+ }
92
+
93
+ function countPermissionToolOverrides(snapshot: OnboardingSnapshotState): number {
94
+ return Object.entries(snapshot.config.permissions.tools).filter(([key, value]) => {
95
+ if (value === undefined) return false;
96
+ return value !== DEFAULT_CONFIG.permissions.tools[key as keyof typeof DEFAULT_CONFIG.permissions.tools];
97
+ }).length;
98
+ }
99
+
100
+ function hasCustomizedProviderRouting(snapshot: OnboardingSnapshotState): boolean {
101
+ return snapshot.providerRouting.primaryProviderId !== getProviderIdFromModel(DEFAULT_CONFIG.provider.model)
102
+ || snapshot.providerRouting.primaryModelId !== DEFAULT_CONFIG.provider.model
103
+ || snapshot.providerRouting.primaryReasoningEffort !== DEFAULT_CONFIG.provider.reasoningEffort
104
+ || snapshot.providerRouting.embeddingProviderId !== DEFAULT_CONFIG.provider.embeddingProvider
105
+ || snapshot.providerRouting.systemPromptFile.trim() !== DEFAULT_CONFIG.provider.systemPromptFile.trim()
106
+ || snapshot.providerRouting.helperEnabled !== DEFAULT_CONFIG.helper.enabled
107
+ || snapshot.providerRouting.helperProviderId !== DEFAULT_CONFIG.helper.globalProvider
108
+ || snapshot.providerRouting.helperModelId !== DEFAULT_CONFIG.helper.globalModel
109
+ || snapshot.providerRouting.toolLlmEnabled !== DEFAULT_CONFIG.tools.llmEnabled
110
+ || snapshot.providerRouting.toolProviderId !== DEFAULT_CONFIG.tools.llmProvider
111
+ || snapshot.providerRouting.toolModelId !== DEFAULT_CONFIG.tools.llmModel;
112
+ }
113
+
114
+ function getProviderAccountSignalIds(snapshot: OnboardingSnapshotState): string[] {
115
+ return (snapshot.providerAccounts?.providers ?? [])
116
+ .filter((provider) => provider.activeRoute !== 'unconfigured' || provider.pendingLogin || provider.oauthReady)
117
+ .map((provider) => provider.providerId);
118
+ }
119
+
120
+ function getServiceCredentialProviderIds(snapshot: OnboardingSnapshotState): string[] {
121
+ return snapshot.services.services
122
+ .filter((service) => service.hasPrimaryCredential || service.hasPasswordCredential)
123
+ .map((service) => service.providerId);
124
+ }
125
+
126
+ function getSecretBackedProviderIds(snapshot: OnboardingSnapshotState): string[] {
127
+ const providerIds = new Set<string>();
128
+
129
+ for (const record of snapshot.secrets.records) {
130
+ const matches = SECRET_KEY_TO_PROVIDER_IDS.get(record.key);
131
+ if (!matches) continue;
132
+ for (const providerId of matches) providerIds.add(providerId);
133
+ }
134
+
135
+ return [...providerIds].sort((left, right) => left.localeCompare(right));
136
+ }
137
+
138
+ function getConfiguredProviderSignalIds(snapshot: OnboardingSnapshotState): string[] {
139
+ return [...new Set<string>([
140
+ ...getProviderAccountSignalIds(snapshot),
141
+ ...snapshot.services.oauthProviderIds,
142
+ ...getServiceCredentialProviderIds(snapshot),
143
+ ...snapshot.subscriptions.activeProviderIds,
144
+ ...snapshot.subscriptions.pendingProviderIds,
145
+ ...getSecretBackedProviderIds(snapshot),
146
+ ])].sort((left, right) => left.localeCompare(right));
147
+ }
148
+
149
+ function hasConfiguredProviderState(snapshot: OnboardingSnapshotState): boolean {
150
+ return getConfiguredProviderSignalIds(snapshot).length > 0;
151
+ }
152
+
153
+ function getConfiguredSurfaceKinds(snapshot: OnboardingSnapshotState): string[] {
154
+ const kinds = new Set<string>([
155
+ ...snapshot.surfaces.configuredEnabledKinds,
156
+ ...snapshot.surfaces.records.filter((surface) => surface.enabled).map((surface) => surface.kind),
157
+ ]);
158
+
159
+ for (const [kind, value] of Object.entries(snapshot.config.surfaces)) {
160
+ if (!value || typeof value !== 'object') continue;
161
+ const defaults = DEFAULT_CONFIG.surfaces[kind as keyof typeof DEFAULT_CONFIG.surfaces];
162
+ if (!defaults || typeof defaults !== 'object') continue;
163
+ if (!isDeepEqual(value, defaults)) kinds.add(kind);
164
+ }
165
+
166
+ return [...kinds].sort((left, right) => left.localeCompare(right));
167
+ }
168
+
169
+ function countConfiguredSurfaceKinds(snapshot: OnboardingSnapshotState): number {
170
+ return getConfiguredSurfaceKinds(snapshot).length;
171
+ }
172
+
173
+ function hasInboundEventSurface(snapshot: OnboardingSnapshotState): boolean {
174
+ return snapshot.surfaces.configuredEnabledKinds.some((kind) => INBOUND_EVENT_SURFACE_KINDS.has(kind))
175
+ || snapshot.surfaces.records.some((surface) => surface.enabled && INBOUND_EVENT_SURFACE_KINDS.has(surface.kind));
176
+ }
177
+
178
+ function hasCustomizedWorkspaceDefaults(snapshot: OnboardingSnapshotState): boolean {
179
+ return !isDeepEqual(snapshot.config.behavior, DEFAULT_CONFIG.behavior)
180
+ || !isDeepEqual(snapshot.config.display, DEFAULT_CONFIG.display);
181
+ }
182
+
183
+ function hasAnyServerEnabled(snapshot: OnboardingSnapshotState): boolean {
184
+ return snapshot.bindSettings.daemonEnabled
185
+ || snapshot.bindSettings.controlPlane.enabled
186
+ || snapshot.bindSettings.httpListenerEnabled
187
+ || snapshot.bindSettings.web.enabled;
188
+ }
189
+
190
+ function hasBrowserAccess(snapshot: OnboardingSnapshotState): boolean {
191
+ return snapshot.bindSettings.web.enabled;
192
+ }
193
+
194
+ function isLoopbackHost(host: string | null | undefined): boolean {
195
+ const normalized = (host ?? '').trim().toLowerCase();
196
+ if (normalized.length === 0) return false;
197
+ return normalized === 'localhost'
198
+ || normalized === '::1'
199
+ || normalized === '[::1]'
200
+ || normalized === '0:0:0:0:0:0:0:1'
201
+ || /^127(?:\.\d{1,3}){3}$/.test(normalized);
202
+ }
203
+
204
+ function isRemoteBind(hostMode: string, host: string | null | undefined, allowRemote = false): boolean {
205
+ if (hostMode === 'network') return true;
206
+ if (hostMode === 'local') return allowRemote;
207
+ if (hostMode === 'custom') return !isLoopbackHost(host);
208
+ return false;
209
+ }
210
+
211
+ function hasRemoteDeviceAccess(snapshot: OnboardingSnapshotState): boolean {
212
+ return (
213
+ ((snapshot.bindSettings.daemonEnabled || snapshot.bindSettings.controlPlane.enabled)
214
+ && isRemoteBind(
215
+ snapshot.bindSettings.controlPlane.hostMode,
216
+ snapshot.bindSettings.controlPlane.host,
217
+ snapshot.bindSettings.controlPlane.allowRemote,
218
+ ))
219
+ || (snapshot.bindSettings.web.enabled && isRemoteBind(
220
+ snapshot.bindSettings.web.hostMode,
221
+ snapshot.bindSettings.web.host,
222
+ ))
223
+ );
224
+ }
225
+
226
+ function hasWebhookOrEventIngress(snapshot: OnboardingSnapshotState): boolean {
227
+ return snapshot.bindSettings.httpListenerEnabled
228
+ || hasInboundEventSurface(snapshot)
229
+ || snapshot.services.services.some((service) => service.hasWebhookUrl || service.hasSigningSecret || service.hasPublicKey || service.hasAppToken);
230
+ }
231
+
232
+ function getProviderIdentityIds(snapshot: OnboardingSnapshotState): Set<string> {
233
+ return new Set<string>([
234
+ ...Object.keys(PROVIDER_SECRET_ENV_ALIASES),
235
+ ...getConfiguredProviderSignalIds(snapshot),
236
+ snapshot.providerRouting.primaryProviderId,
237
+ snapshot.providerRouting.embeddingProviderId,
238
+ snapshot.providerRouting.helperProviderId,
239
+ snapshot.providerRouting.toolProviderId,
240
+ ].filter((value): value is string => typeof value === 'string' && value.trim().length > 0));
241
+ }
242
+
243
+ function getExternalIntegrationServiceIds(snapshot: OnboardingSnapshotState): string[] {
244
+ const providerIdentityIds = getProviderIdentityIds(snapshot);
245
+
246
+ return snapshot.services.services
247
+ .filter((service) => !providerIdentityIds.has(service.providerId) && !providerIdentityIds.has(service.name))
248
+ .map((service) => service.name);
249
+ }
250
+
251
+ function hasExternalIntegrations(snapshot: OnboardingSnapshotState): boolean {
252
+ return getExternalIntegrationServiceIds(snapshot).length > 0
253
+ || countConfiguredSurfaceKinds(snapshot) > 0;
254
+ }
255
+
256
+ function hasCloudflareBatch(snapshot: OnboardingSnapshotState): boolean {
257
+ return snapshot.config.cloudflare.enabled
258
+ || snapshot.config.batch.queueBackend === 'cloudflare'
259
+ || snapshot.config.batch.mode !== 'off'
260
+ || snapshot.config.cloudflare.accountId.trim().length > 0
261
+ || snapshot.config.cloudflare.apiTokenRef.trim().length > 0
262
+ || snapshot.config.cloudflare.workerBaseUrl.trim().length > 0;
263
+ }
264
+
265
+ function describeLocalTuiOnly(snapshot: OnboardingSnapshotState): string {
266
+ if (!hasAnyServerEnabled(snapshot)) {
267
+ return 'Use GoodVibes Agent in this terminal while connecting only to an externally managed daemon. Agent does not enable service mode, HTTP listeners, external app surfaces, or network setup.';
268
+ }
269
+
270
+ return 'Keep Agent local-only by not enabling browser access, background services, HTTP listeners, external app surfaces, or network setup.';
271
+ }
272
+
273
+ function describeBrowserAccess(snapshot: OnboardingSnapshotState): string {
274
+ return snapshot.bindSettings.web.enabled
275
+ ? 'Review the externally managed daemon web UI posture. Network reachability is controlled by the daemon owner.'
276
+ : 'Review browser access requirements for the externally managed daemon. Agent records intent but does not start web services.';
277
+ }
278
+
279
+ function describeRemoteDeviceAccess(snapshot: OnboardingSnapshotState): string {
280
+ return hasRemoteDeviceAccess(snapshot)
281
+ ? 'Keep enabled GoodVibes services reachable from other devices on your LAN. Local authentication is required.'
282
+ : 'Make enabled GoodVibes services reachable from other devices on your LAN. Local authentication is required.';
283
+ }
284
+
285
+ function describeWebhookIngress(snapshot: OnboardingSnapshotState): string {
286
+ return hasWebhookOrEventIngress(snapshot)
287
+ ? 'Keep the HTTP listener available for incoming webhooks, callbacks, and automation events.'
288
+ : 'Turn on the HTTP listener for incoming webhooks, callbacks, and automation events.';
289
+ }
290
+
291
+ function describeExternalIntegrations(snapshot: OnboardingSnapshotState): string {
292
+ const integrationCount = new Set<string>([
293
+ ...getExternalIntegrationServiceIds(snapshot),
294
+ ...getConfiguredSurfaceKinds(snapshot),
295
+ ]).size;
296
+
297
+ if (integrationCount === 0) {
298
+ return 'Enable setup screens for Slack, Discord, Telegram, Home Assistant, Teams, Matrix, and other app surfaces you choose.';
299
+ }
300
+
301
+ return `Review and configure ${integrationCount} detected external app, service, or surface integration signal(s).`;
302
+ }
303
+
304
+ function describeCloudflareBatch(snapshot: OnboardingSnapshotState): string {
305
+ if (hasCloudflareBatch(snapshot)) {
306
+ return 'Review Cloudflare Workers/Queues batch processing, token storage, and optional remote daemon provisioning settings.';
307
+ }
308
+
309
+ return 'Optionally configure Cloudflare Workers and Queues for explicit or eligible background batch jobs. Immediate local daemon behavior stays the default unless enabled.';
310
+ }
311
+
312
+ function getAcknowledgementAccepted(
313
+ snapshot: OnboardingSnapshotState,
314
+ target: OnboardingAcknowledgementTarget,
315
+ ): boolean {
316
+ return snapshot.acknowledgements.accepted[target] === true;
317
+ }
318
+
319
+ function buildNotNeededAcknowledgement(
320
+ snapshot: OnboardingSnapshotState,
321
+ target: OnboardingAcknowledgementTarget,
322
+ detail: string,
323
+ ): OnboardingAcknowledgementState {
324
+ return {
325
+ required: false,
326
+ accepted: getAcknowledgementAccepted(snapshot, target),
327
+ reason: 'not-needed',
328
+ detail,
329
+ };
330
+ }
331
+
332
+ function buildRequiredAcknowledgement(
333
+ snapshot: OnboardingSnapshotState,
334
+ target: OnboardingAcknowledgementTarget,
335
+ reason: Exclude<OnboardingAcknowledgementState['reason'], 'not-needed'>,
336
+ detail: string,
337
+ ): OnboardingAcknowledgementState {
338
+ return {
339
+ required: true,
340
+ accepted: getAcknowledgementAccepted(snapshot, target),
341
+ reason,
342
+ detail,
343
+ };
344
+ }
345
+
346
+ export function deriveStep1Capabilities(
347
+ snapshot: OnboardingSnapshotState,
348
+ ): readonly OnboardingStep1CapabilityItem[] {
349
+ return [
350
+ {
351
+ id: 'local-tui-only',
352
+ label: 'Agent Local Only (External Daemon)',
353
+ selected: !hasAnyServerEnabled(snapshot),
354
+ detail: describeLocalTuiOnly(snapshot),
355
+ },
356
+ {
357
+ id: 'browser-access',
358
+ label: 'Open GoodVibes in a Browser',
359
+ selected: hasBrowserAccess(snapshot),
360
+ detail: describeBrowserAccess(snapshot),
361
+ },
362
+ {
363
+ id: 'network-access',
364
+ label: 'Let other devices use GoodVibes',
365
+ selected: hasRemoteDeviceAccess(snapshot),
366
+ detail: describeRemoteDeviceAccess(snapshot),
367
+ },
368
+ {
369
+ id: 'webhook-events',
370
+ label: 'Receive webhooks or events from other tools',
371
+ selected: hasWebhookOrEventIngress(snapshot),
372
+ detail: describeWebhookIngress(snapshot),
373
+ },
374
+ {
375
+ id: 'external-integrations',
376
+ label: 'Connect GoodVibes to external apps and services',
377
+ selected: hasExternalIntegrations(snapshot),
378
+ detail: describeExternalIntegrations(snapshot),
379
+ },
380
+ {
381
+ id: 'cloudflare-batch',
382
+ label: 'Use Cloudflare for batch or remote daemon work',
383
+ selected: hasCloudflareBatch(snapshot),
384
+ detail: describeCloudflareBatch(snapshot),
385
+ },
386
+ ];
387
+ }
388
+
389
+ export function deriveStep1CapabilityFlags(
390
+ snapshot: OnboardingSnapshotState,
391
+ ): {
392
+ readonly providers: boolean;
393
+ readonly services: boolean;
394
+ readonly subscriptions: boolean;
395
+ readonly auth: boolean;
396
+ readonly controlPlane: boolean;
397
+ readonly httpListener: boolean;
398
+ readonly web: boolean;
399
+ readonly surfaces: boolean;
400
+ readonly cloudflare: boolean;
401
+ } {
402
+ return {
403
+ providers: hasConfiguredProviderState(snapshot) || hasCustomizedProviderRouting(snapshot),
404
+ services: snapshot.services.total > 0,
405
+ subscriptions: snapshot.subscriptions.active.length > 0 || snapshot.subscriptions.pending.length > 0,
406
+ auth: snapshot.auth.snapshot.userCount > 0
407
+ || snapshot.auth.snapshot.sessionCount > 0
408
+ || snapshot.auth.snapshot.bootstrapCredentialPresent,
409
+ controlPlane: snapshot.bindSettings.daemonEnabled || snapshot.bindSettings.controlPlane.enabled,
410
+ httpListener: snapshot.bindSettings.httpListenerEnabled,
411
+ web: snapshot.bindSettings.web.enabled,
412
+ surfaces: countConfiguredSurfaceKinds(snapshot) > 0,
413
+ cloudflare: hasCloudflareBatch(snapshot),
414
+ };
415
+ }
416
+
417
+ export function deriveStep1_5NetworkMode(
418
+ bindSettings: Pick<OnboardingSnapshotState, 'bindSettings'>['bindSettings'],
419
+ ): OnboardingNetworkMode {
420
+ const activeModes: string[] = [];
421
+ const hasNetworkFacingSurface = bindSettings.httpListenerEnabled || bindSettings.web.enabled;
422
+
423
+ if (
424
+ (bindSettings.daemonEnabled || bindSettings.controlPlane.enabled)
425
+ && (!hasNetworkFacingSurface || bindSettings.controlPlane.hostMode !== 'local')
426
+ ) {
427
+ activeModes.push(bindSettings.controlPlane.hostMode);
428
+ }
429
+
430
+ if (bindSettings.httpListenerEnabled) {
431
+ activeModes.push(bindSettings.httpListener.hostMode);
432
+ }
433
+
434
+ if (bindSettings.web.enabled) {
435
+ activeModes.push(bindSettings.web.hostMode);
436
+ }
437
+
438
+ return activeModes.some((mode) => mode !== 'network') ? 'custom' : 'local-network-default';
439
+ }
440
+
441
+ export function deriveReopenEditAcknowledgementState(
442
+ snapshot: OnboardingSnapshotState,
443
+ ): OnboardingReopenEditAcknowledgementState {
444
+ const providerAccounts = snapshot.providerAccounts?.providers ?? [];
445
+ const providerPendingCount = providerAccounts.filter((provider) => provider.pendingLogin).length;
446
+ const providerConfiguredCount = providerAccounts.filter((provider) => provider.activeRoute !== 'unconfigured' || provider.oauthReady).length;
447
+ const providerRoutingCustomized = hasCustomizedProviderRouting(snapshot);
448
+ const providerSignalCount = getConfiguredProviderSignalIds(snapshot).length;
449
+
450
+ const subscriptionsPendingCount = snapshot.subscriptions.pending.length;
451
+ const subscriptionsActiveCount = snapshot.subscriptions.active.length;
452
+
453
+ const authUserCount = snapshot.auth.snapshot.userCount;
454
+ const authSessionCount = snapshot.auth.snapshot.sessionCount;
455
+ const bootstrapCredentialPresent = snapshot.auth.snapshot.bootstrapCredentialPresent;
456
+
457
+ const providers = providerPendingCount > 0
458
+ ? buildRequiredAcknowledgement(
459
+ snapshot,
460
+ 'providers',
461
+ 'pending-login',
462
+ `${providerPendingCount} provider login(s) are still pending completion.`,
463
+ )
464
+ : providerConfiguredCount > 0 || providerSignalCount > 0
465
+ ? buildRequiredAcknowledgement(
466
+ snapshot,
467
+ 'providers',
468
+ 'configured-routing',
469
+ `${Math.max(providerConfiguredCount, providerSignalCount, 1)} provider auth path(s) are already configured.`,
470
+ )
471
+ : providerRoutingCustomized
472
+ ? buildRequiredAcknowledgement(
473
+ snapshot,
474
+ 'providers',
475
+ 'customized-config',
476
+ 'Provider routing already differs from the default shell configuration.',
477
+ )
478
+ : buildNotNeededAcknowledgement(snapshot, 'providers', 'No existing provider routing needs confirmation.');
479
+
480
+ const subscriptions = subscriptionsPendingCount > 0
481
+ ? buildRequiredAcknowledgement(
482
+ snapshot,
483
+ 'subscriptions',
484
+ 'pending-login',
485
+ `${subscriptionsPendingCount} subscription login(s) are pending completion.`,
486
+ )
487
+ : subscriptionsActiveCount > 0
488
+ ? buildRequiredAcknowledgement(
489
+ snapshot,
490
+ 'subscriptions',
491
+ 'subscription-state',
492
+ `${subscriptionsActiveCount} stored subscription session(s) already exist.`,
493
+ )
494
+ : buildNotNeededAcknowledgement(snapshot, 'subscriptions', 'No stored subscription sessions need confirmation.');
495
+
496
+ const auth = bootstrapCredentialPresent
497
+ ? buildRequiredAcknowledgement(
498
+ snapshot,
499
+ 'auth',
500
+ 'bootstrap-credential',
501
+ 'The local auth bootstrap credential file is still present.',
502
+ )
503
+ : authSessionCount > 0
504
+ ? buildRequiredAcknowledgement(
505
+ snapshot,
506
+ 'auth',
507
+ 'active-sessions',
508
+ `${authSessionCount} local auth session(s) are currently active.`,
509
+ )
510
+ : authUserCount > 0
511
+ ? buildRequiredAcknowledgement(
512
+ snapshot,
513
+ 'auth',
514
+ 'auth-state',
515
+ `${authUserCount} local auth user(s) are already configured.`,
516
+ )
517
+ : buildNotNeededAcknowledgement(snapshot, 'auth', 'No local auth state needs confirmation.');
518
+
519
+ return {
520
+ providers,
521
+ subscriptions,
522
+ auth,
523
+ };
524
+ }
525
+
526
+ export function deriveOnboardingStepState(
527
+ snapshot: OnboardingSnapshotState,
528
+ ): OnboardingStepDerivationState {
529
+ return {
530
+ step1Capabilities: deriveStep1Capabilities(snapshot),
531
+ step1_5NetworkMode: deriveStep1_5NetworkMode(snapshot.bindSettings),
532
+ reopenEditAcknowledgements: deriveReopenEditAcknowledgementState(snapshot),
533
+ };
534
+ }
@@ -0,0 +1,7 @@
1
+ export * from './types.ts';
2
+ export * from './snapshot.ts';
3
+ export * from './derivation.ts';
4
+ export * from './apply.ts';
5
+ export * from './verify.ts';
6
+ export * from './markers.ts';
7
+ export * from './state.ts';
@@ -0,0 +1,148 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import type { ShellPathService } from '@/runtime/index.ts';
4
+ import type {
5
+ OnboardingCheckMarkerPayload,
6
+ OnboardingCheckMarkerState,
7
+ OnboardingCheckMarkersState,
8
+ OnboardingStateScope,
9
+ WriteOnboardingCheckMarkerOptions,
10
+ } from './types.ts';
11
+ import { GOODVIBES_AGENT_SURFACE_ROOT } from '../../config/surface.ts';
12
+
13
+ const ONBOARDING_CHECK_MARKER_FILE = 'onboarding-checked.json';
14
+
15
+ type OnboardingShellPaths = Pick<
16
+ ShellPathService,
17
+ 'workingDirectory' | 'resolveProjectPath' | 'resolveUserPath'
18
+ >;
19
+
20
+ function resolveMarkerPath(
21
+ shellPaths: OnboardingShellPaths,
22
+ scope: OnboardingStateScope,
23
+ ): string {
24
+ return scope === 'project'
25
+ ? shellPaths.resolveProjectPath(GOODVIBES_AGENT_SURFACE_ROOT, ONBOARDING_CHECK_MARKER_FILE)
26
+ : shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, ONBOARDING_CHECK_MARKER_FILE);
27
+ }
28
+
29
+ function isObject(value: unknown): value is Record<string, unknown> {
30
+ return typeof value === 'object' && value !== null;
31
+ }
32
+
33
+ function isOnboardingMode(value: unknown): value is OnboardingCheckMarkerPayload['mode'] {
34
+ return value === 'new' || value === 'edit' || value === 'reopen';
35
+ }
36
+
37
+ function isCheckMarkerPayload(value: unknown): value is OnboardingCheckMarkerPayload {
38
+ return isObject(value)
39
+ && value.version === 1
40
+ && typeof value.checkedAt === 'number'
41
+ && Number.isFinite(value.checkedAt)
42
+ && typeof value.updatedAt === 'number'
43
+ && Number.isFinite(value.updatedAt)
44
+ && typeof value.source === 'string'
45
+ && (value.mode === undefined || isOnboardingMode(value.mode))
46
+ && (value.workspaceRoot === undefined || typeof value.workspaceRoot === 'string');
47
+ }
48
+
49
+ function buildMissingMarkerState(
50
+ scope: OnboardingStateScope,
51
+ path: string,
52
+ ): OnboardingCheckMarkerState {
53
+ return {
54
+ scope,
55
+ path,
56
+ exists: false,
57
+ payload: null,
58
+ };
59
+ }
60
+
61
+ function buildParseErrorState(
62
+ scope: OnboardingStateScope,
63
+ path: string,
64
+ parseError: string,
65
+ ): OnboardingCheckMarkerState {
66
+ return {
67
+ scope,
68
+ path,
69
+ exists: true,
70
+ payload: null,
71
+ parseError,
72
+ };
73
+ }
74
+
75
+ function pickEffectiveMarker(
76
+ user: OnboardingCheckMarkerState,
77
+ ): OnboardingCheckMarkerState | null {
78
+ if (user.payload) return user;
79
+ if (user.exists) return user;
80
+ return null;
81
+ }
82
+
83
+ export function getOnboardingCheckMarkerPath(
84
+ shellPaths: OnboardingShellPaths,
85
+ scope: OnboardingStateScope = 'user',
86
+ ): string {
87
+ return resolveMarkerPath(shellPaths, scope);
88
+ }
89
+
90
+ export function readOnboardingCheckMarker(
91
+ shellPaths: OnboardingShellPaths,
92
+ scope: OnboardingStateScope = 'user',
93
+ ): OnboardingCheckMarkerState {
94
+ const path = resolveMarkerPath(shellPaths, scope);
95
+ if (!existsSync(path)) return buildMissingMarkerState(scope, path);
96
+
97
+ try {
98
+ const parsed = JSON.parse(readFileSync(path, 'utf-8')) as unknown;
99
+ if (!isCheckMarkerPayload(parsed)) {
100
+ return buildParseErrorState(scope, path, 'Invalid onboarding check marker payload.');
101
+ }
102
+
103
+ return {
104
+ scope,
105
+ path,
106
+ exists: true,
107
+ payload: parsed,
108
+ };
109
+ } catch (error) {
110
+ const parseError = error instanceof Error ? error.message : String(error);
111
+ return buildParseErrorState(scope, path, parseError);
112
+ }
113
+ }
114
+
115
+ export function readOnboardingCheckMarkers(
116
+ shellPaths: OnboardingShellPaths,
117
+ ): OnboardingCheckMarkersState {
118
+ const user = readOnboardingCheckMarker(shellPaths, 'user');
119
+ const project = readOnboardingCheckMarker(shellPaths, 'project');
120
+
121
+ return {
122
+ user,
123
+ project,
124
+ effective: pickEffectiveMarker(user),
125
+ };
126
+ }
127
+
128
+ export function writeOnboardingCheckMarker(
129
+ shellPaths: OnboardingShellPaths,
130
+ options: WriteOnboardingCheckMarkerOptions = {},
131
+ ): OnboardingCheckMarkerState {
132
+ const scope = options.scope ?? 'user';
133
+ const path = resolveMarkerPath(shellPaths, scope);
134
+ const checkedAt = options.checkedAt ?? Date.now();
135
+ const payload: OnboardingCheckMarkerPayload = {
136
+ version: 1,
137
+ checkedAt,
138
+ updatedAt: options.updatedAt ?? checkedAt,
139
+ source: options.source ?? 'wizard',
140
+ ...(options.mode ? { mode: options.mode } : {}),
141
+ ...(options.workspaceRoot ? { workspaceRoot: options.workspaceRoot } : {}),
142
+ };
143
+
144
+ mkdirSync(dirname(path), { recursive: true });
145
+ writeFileSync(path, `${JSON.stringify(payload, null, 2)}\n`, 'utf-8');
146
+
147
+ return readOnboardingCheckMarker(shellPaths, scope);
148
+ }