@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,363 @@
1
+ /**
2
+ * /provider command handler.
3
+ *
4
+ * Implements the Provider Optimizer panel commands:
5
+ *
6
+ * /provider route auto|manual — Set optimizer routing mode
7
+ * /provider explain-route — Print current route explanation
8
+ * /provider pin <provider:model> — Pin routing to a specific provider/model
9
+ * /provider fallback test — Simulate the fallback chain
10
+ *
11
+ * When the optimizer is disabled, commands report its status and
12
+ * explain-route still works (reads current model capabilities).
13
+ */
14
+
15
+ import type { SlashCommand, CommandContext } from '../command-registry.ts';
16
+ import type { RouteExplanation } from '@pellux/goodvibes-sdk/platform/providers';
17
+ import type { FallbackTestResult, FallbackTransition } from '@pellux/goodvibes-sdk/platform/providers';
18
+ import type { ProviderApiModelRecord } from '@pellux/goodvibes-sdk/platform/providers';
19
+ import { requireProviderApi } from './runtime-services.ts';
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Formatting helpers
23
+ // ---------------------------------------------------------------------------
24
+
25
+ function fmtBool(value: boolean): string {
26
+ return value ? 'yes' : 'no';
27
+ }
28
+
29
+ function fmtTs(epochMs: number): string {
30
+ return new Date(epochMs).toISOString().replace('T', ' ').slice(0, 19);
31
+ }
32
+
33
+ function requireProviderOptimizer(context: CommandContext) {
34
+ if (!context.provider.providerOptimizer) {
35
+ context.print('[provider] Provider optimizer is not wired into this runtime.');
36
+ return null;
37
+ }
38
+ return context.provider.providerOptimizer;
39
+ }
40
+
41
+ function fmtExplanation(expl: RouteExplanation, context: CommandContext): void {
42
+ const status = expl.accepted ? '[accepted]' : '[rejected]';
43
+ context.print(` ${status} ${expl.providerId}/${expl.modelId}`);
44
+ context.print(` ${expl.summary}`);
45
+ if (!expl.accepted && expl.rejections.length > 0) {
46
+ context.print(' Unmet requirements:');
47
+ for (const r of expl.rejections) {
48
+ context.print(
49
+ ` - ${r.code}: ${r.reason} (actual=${r.actual}, required=${r.required})`,
50
+ );
51
+ }
52
+ }
53
+ if (expl.accepted) {
54
+ const c = expl.capability;
55
+ context.print(
56
+ ` Capabilities: streaming=${fmtBool(c.streaming)}, tools=${fmtBool(c.toolCalling)}, ` +
57
+ `parallel=${fmtBool(c.parallelTools)}, json=${fmtBool(c.jsonMode)}, ` +
58
+ `reasoning=${fmtBool(c.reasoningControls)}`,
59
+ );
60
+ context.print(
61
+ ` Context=${c.maxContextTokens.toLocaleString()} tokens, ` +
62
+ `output=${c.maxOutputTokens.toLocaleString()} tokens, ` +
63
+ `timeout=${c.timeoutMs}ms, caching=${c.caching}`,
64
+ );
65
+ }
66
+ }
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // /provider route auto|manual
70
+ // ---------------------------------------------------------------------------
71
+
72
+ function handleRoute(
73
+ args: string[],
74
+ context: CommandContext,
75
+ ): void {
76
+ const optimizer = requireProviderOptimizer(context);
77
+ if (!optimizer) return;
78
+ const sub = args[0];
79
+
80
+ if (sub !== 'auto' && sub !== 'manual') {
81
+ context.print('[provider] Usage: /provider route auto|manual');
82
+ context.print(` Current mode: ${optimizer.mode} (optimizer ${optimizer.enabled ? 'on' : 'off'})`);
83
+ return;
84
+ }
85
+
86
+ if (!optimizer.enabled) {
87
+ context.print(
88
+ '[provider] Optimizer is currently disabled. Enable it with the provider-optimizer feature flag.',
89
+ );
90
+ context.print(` Routing mode set to: ${sub} (no-op until optimizer is enabled)`);
91
+ }
92
+
93
+ optimizer.setMode(sub);
94
+ context.print(`[provider] Routing mode → ${sub}`);
95
+
96
+ if (sub === 'auto') {
97
+ context.print(
98
+ ' Auto mode: optimizer selects the best capable provider for each request profile.',
99
+ );
100
+ } else {
101
+ context.print(
102
+ ' Manual mode: optimizer is advisory only; provider selection is caller-driven.',
103
+ );
104
+ }
105
+ }
106
+
107
+ // ---------------------------------------------------------------------------
108
+ // /provider explain-route
109
+ // ---------------------------------------------------------------------------
110
+
111
+ async function handleExplainRoute(
112
+ _args: string[],
113
+ context: CommandContext,
114
+ ): Promise<void> {
115
+ const optimizer = requireProviderOptimizer(context);
116
+ if (!optimizer) return;
117
+ const providerApi = requireProviderApi(context);
118
+
119
+ let currentModel: ProviderApiModelRecord;
120
+ try {
121
+ currentModel = await providerApi.getCurrentModel();
122
+ } catch {
123
+ context.print('[provider] No current model selected.');
124
+ return;
125
+ }
126
+
127
+ context.print(
128
+ `[provider] Route explanation for current model: ${currentModel.providerId}/${currentModel.modelId}`,
129
+ );
130
+
131
+ // Always explain current route regardless of optimizer enabled state
132
+ const expl = optimizer.explainCurrentRoute();
133
+ fmtExplanation(expl, context);
134
+
135
+ // Show optimizer status
136
+ context.print(`
137
+ Optimizer: ${optimizer.enabled ? 'enabled' : 'disabled'}, mode=${optimizer.mode}`);
138
+ if (optimizer.pinnedTarget) {
139
+ context.print(
140
+ ` Pinned to: ${optimizer.pinnedTarget.providerId}/${optimizer.pinnedTarget.modelId}`,
141
+ );
142
+ }
143
+
144
+ // Show recent fallback transitions
145
+ const log = optimizer.fallbackLog;
146
+ if (log.length > 0) {
147
+ const recent = log.slice(-5); // last 5 transitions
148
+ context.print(` Fallback log (last ${recent.length} of ${log.length}):`);
149
+ for (const t of recent) {
150
+ context.print(
151
+ ` ${fmtTs(t.ts)} ${t.from} → ${t.to} (${t.reason})`,
152
+ );
153
+ }
154
+ } else {
155
+ context.print(' Fallback log: empty');
156
+ }
157
+ }
158
+
159
+ // ---------------------------------------------------------------------------
160
+ // /provider pin <provider:model>
161
+ // ---------------------------------------------------------------------------
162
+
163
+ async function handlePin(
164
+ args: string[],
165
+ context: CommandContext,
166
+ ): Promise<void> {
167
+ const optimizer = requireProviderOptimizer(context);
168
+ if (!optimizer) return;
169
+ const target = args[0];
170
+
171
+ if (!target) {
172
+ // Show current pin status
173
+ if (optimizer.pinnedTarget) {
174
+ context.print(
175
+ `[provider] Currently pinned to: ${optimizer.pinnedTarget.providerId}/${optimizer.pinnedTarget.modelId}`,
176
+ );
177
+ context.print(' Use "/provider pin <provider:model>" to change, or "/provider route manual" to unpin.');
178
+ } else {
179
+ context.print('[provider] No pin active. Usage: /provider pin <provider:model>');
180
+ context.print(' Example: /provider pin anthropic:claude-opus-4-5');
181
+ }
182
+ return;
183
+ }
184
+
185
+ // Parse provider:model format
186
+ const colonIdx = target.indexOf(':');
187
+ if (colonIdx === -1) {
188
+ context.print(`[provider] Invalid format "${target}". Expected: <provider>:<model>`);
189
+ context.print(' Example: /provider pin anthropic:claude-opus-4-5');
190
+ return;
191
+ }
192
+
193
+ const providerId = target.slice(0, colonIdx);
194
+ const modelId = target.slice(colonIdx + 1);
195
+
196
+ if (!providerId || !modelId) {
197
+ context.print(`[provider] Invalid format "${target}". Both provider and model must be non-empty.`);
198
+ return;
199
+ }
200
+
201
+ // Validate that the model exists in registry
202
+ const providerApi = requireProviderApi(context);
203
+ const models = await providerApi.listModels();
204
+ const currentModel = await providerApi.getCurrentModel();
205
+ const match = models.find(
206
+ (m) => m.providerId === providerId && (m.modelId === modelId || m.registryKey === target),
207
+ ) ?? (
208
+ currentModel.providerId === providerId
209
+ && (currentModel.modelId === modelId || currentModel.registryKey === target)
210
+ ? currentModel
211
+ : undefined
212
+ );
213
+
214
+ if (!match) {
215
+ context.print(
216
+ `[provider] Model not found in registry: ${providerId}/${modelId}`,
217
+ );
218
+ context.print(' Use "/model" to see available models, or check the provider and model ID.');
219
+ return;
220
+ }
221
+
222
+ // Enable optimizer if it's off
223
+ if (!optimizer.enabled) {
224
+ optimizer.setEnabled(true);
225
+ context.print('⚠ Optimizer was disabled — enabling it for pin to take effect.');
226
+ }
227
+
228
+ optimizer.pin(match.providerId, match.modelId);
229
+ context.print(`[provider] Pinned → ${match.providerId}/${match.modelId}`);
230
+ context.print(' All routed requests will target this provider/model.');
231
+ context.print(' Unpin with: /provider route manual');
232
+ }
233
+
234
+ // ---------------------------------------------------------------------------
235
+ // /provider fallback test
236
+ // ---------------------------------------------------------------------------
237
+
238
+ function handleFallbackTest(
239
+ args: string[],
240
+ context: CommandContext,
241
+ ): void {
242
+ const optimizer = requireProviderOptimizer(context);
243
+ if (!optimizer) return;
244
+ const sub = args[0];
245
+
246
+ if (sub !== 'test') {
247
+ context.print('[provider] Usage: /provider fallback test');
248
+ return;
249
+ }
250
+
251
+ context.print('[provider] Simulating fallback chain (empty request profile — no requirements)...');
252
+
253
+ const result: FallbackTestResult = optimizer.testFallback();
254
+
255
+ context.print(
256
+ `[provider] Fallback chain: ${result.viableCount} capable / ${result.totalCount} total providers`,
257
+ );
258
+
259
+ // Show first capable then rejected
260
+ const capable = result.chain.filter((n) => n.capable);
261
+ const rejected = result.chain.filter((n) => !n.capable);
262
+
263
+ if (capable.length > 0) {
264
+ context.print(' Capable providers (would succeed):');
265
+ for (const node of capable.slice(0, 10)) {
266
+ context.print(` [${node.position}] ${node.providerId}/${node.modelId}`);
267
+ }
268
+ if (capable.length > 10) {
269
+ context.print(` ... and ${capable.length - 10} more`);
270
+ }
271
+ } else {
272
+ context.print(' No capable providers found for this profile.');
273
+ }
274
+
275
+ if (rejected.length > 0) {
276
+ context.print(` Rejected providers (${rejected.length}):`);
277
+ for (const node of rejected.slice(0, 5)) {
278
+ const reasons = !node.explanation.accepted
279
+ ? node.explanation.rejections.map((r) => r.code).join(', ')
280
+ : '';
281
+ context.print(
282
+ ` [${node.position}] ${node.providerId}/${node.modelId} — ${reasons || 'unknown'}`,
283
+ );
284
+ }
285
+ if (rejected.length > 5) {
286
+ context.print(` ... and ${rejected.length - 5} more`);
287
+ }
288
+ }
289
+
290
+ // Show fallback log
291
+ const log = optimizer.fallbackLog;
292
+ if (log.length > 0) {
293
+ context.print(` Logged transitions (${log.length} total):`);
294
+ const recent: readonly FallbackTransition[] = log.slice(-5);
295
+ for (const t of recent) {
296
+ context.print(
297
+ ` ${fmtTs(t.ts)} ${t.from} → ${t.to} (${t.reason})`,
298
+ );
299
+ }
300
+ } else {
301
+ context.print(' No fallback transitions logged this session.');
302
+ }
303
+
304
+ context.print(` Test completed at: ${fmtTs(result.testedAt)}`);
305
+ }
306
+
307
+ // ---------------------------------------------------------------------------
308
+ // Top-level command definition
309
+ // ---------------------------------------------------------------------------
310
+
311
+ /**
312
+ * providerCommand — The `/provider` slash command.
313
+ *
314
+ * Routes to subcommand handlers based on args[0].
315
+ */
316
+ export const providerCommand: SlashCommand = {
317
+ name: 'provider-opt',
318
+ aliases: ['prov-opt'],
319
+ description: 'Manage provider routing optimizer (route, pin, explain, fallback).',
320
+ usage: '<subcommand> [args]',
321
+ argsHint: 'route|explain-route|pin|fallback',
322
+ handler: async (args: string[], context: CommandContext): Promise<void> => {
323
+ const [sub, ...rest] = args;
324
+
325
+ switch (sub) {
326
+ case 'route':
327
+ handleRoute(rest, context);
328
+ break;
329
+
330
+ case 'explain-route':
331
+ case 'explain':
332
+ await handleExplainRoute(rest, context);
333
+ break;
334
+
335
+ case 'pin':
336
+ await handlePin(rest, context);
337
+ break;
338
+
339
+ case 'fallback':
340
+ handleFallbackTest(rest, context);
341
+ break;
342
+
343
+ default: {
344
+ const optimizer = requireProviderOptimizer(context);
345
+ if (!optimizer) return;
346
+ const lines = [
347
+ 'Usage: /provider <subcommand>',
348
+ ' route auto|manual — Set optimizer routing mode',
349
+ ' explain-route — Show current route explanation',
350
+ ' pin <provider:model> — Pin routing to specific provider/model',
351
+ ' fallback test — Simulate the full fallback chain',
352
+ '',
353
+ ` Optimizer: ${optimizer.enabled ? 'enabled' : 'disabled'} mode=${optimizer.mode}`,
354
+ ];
355
+ if (optimizer.pinnedTarget) {
356
+ lines.push(` Pinned: ${optimizer.pinnedTarget.providerId}/${optimizer.pinnedTarget.modelId}`);
357
+ }
358
+ context.print(lines.join('\n'));
359
+ break;
360
+ }
361
+ }
362
+ },
363
+ };
@@ -0,0 +1,20 @@
1
+ import type { CommandRegistry } from '../command-registry.ts';
2
+ import { openCommandPanel } from './runtime-services.ts';
3
+
4
+ /**
5
+ * Register the /qrcode command.
6
+ *
7
+ * Opens the QR Code panel which displays a scannable QR code for
8
+ * companion app pairing, along with connection URL, token, and username.
9
+ */
10
+ export function registerQrcodeRuntimeCommands(registry: CommandRegistry): void {
11
+ registry.register({
12
+ name: 'qrcode',
13
+ aliases: ['qr', 'pair'],
14
+ description: 'Open the QR code panel for companion app pairing',
15
+ usage: '',
16
+ handler(_args, ctx) {
17
+ openCommandPanel(ctx, 'qr-code');
18
+ },
19
+ });
20
+ }
@@ -0,0 +1,162 @@
1
+ import type { StatusResult } from 'simple-git';
2
+ import { basename } from 'path';
3
+ import type { CommandContext } from '../command-registry.ts';
4
+ import { GitService } from '@pellux/goodvibes-sdk/platform/git';
5
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
6
+
7
+ type GitLike = Pick<GitService, 'addAll' | 'status' | 'commit'>;
8
+
9
+ export type GitChange = {
10
+ action: 'add' | 'update' | 'delete' | 'rename';
11
+ path: string;
12
+ from?: string;
13
+ to?: string;
14
+ };
15
+
16
+ type GitStatusLike = Pick<StatusResult, 'staged' | 'modified' | 'not_added' | 'deleted' | 'created' | 'renamed'> & {
17
+ isClean?: () => boolean;
18
+ };
19
+
20
+ export function collectGitChanges(status: GitStatusLike): GitChange[] {
21
+ const changes = new Map<string, GitChange>();
22
+
23
+ for (const rename of status.renamed ?? []) {
24
+ const to = rename.to || rename.from;
25
+ if (!to) continue;
26
+ changes.set(to, { action: 'rename', path: to, from: rename.from, to: rename.to });
27
+ }
28
+
29
+ for (const path of status.created ?? []) {
30
+ if (!changes.has(path)) changes.set(path, { action: 'add', path });
31
+ }
32
+
33
+ for (const path of status.not_added ?? []) {
34
+ if (!changes.has(path)) changes.set(path, { action: 'add', path });
35
+ }
36
+
37
+ for (const path of status.deleted ?? []) {
38
+ if (!changes.has(path)) changes.set(path, { action: 'delete', path });
39
+ }
40
+
41
+ for (const path of status.modified ?? []) {
42
+ if (!changes.has(path)) changes.set(path, { action: 'update', path });
43
+ }
44
+
45
+ for (const path of status.staged ?? []) {
46
+ if (!changes.has(path)) changes.set(path, { action: 'update', path });
47
+ }
48
+
49
+ return Array.from(changes.values()).sort((a, b) => a.path.localeCompare(b.path));
50
+ }
51
+
52
+ function formatList(items: string[]): string {
53
+ if (items.length === 0) return '';
54
+ if (items.length === 1) return items[0]!;
55
+ if (items.length === 2) return `${items[0]} and ${items[1]}`;
56
+ return `${items.slice(0, -1).join(', ')}, and ${items[items.length - 1]}`;
57
+ }
58
+
59
+ function topLevelScope(path: string): string {
60
+ const normalized = path.replace(/\\/g, '/').replace(/^\.\/+/, '');
61
+ if (!normalized) return 'root';
62
+ const [first] = normalized.split('/');
63
+ return first && first.length > 0 ? first : 'root';
64
+ }
65
+
66
+ function summarizeScopes(changes: GitChange[]): string | null {
67
+ const scopes = Array.from(new Set(changes.map((change) => topLevelScope(change.path)).filter(Boolean)));
68
+ if (scopes.length === 0) return null;
69
+ if (scopes.length <= 3) {
70
+ return scopes.length === 1 ? `${scopes[0]} files` : `${formatList(scopes)} files`;
71
+ }
72
+ return `${changes.length} files`;
73
+ }
74
+
75
+ function shortPath(path: string): string {
76
+ return path.length > 42 ? basename(path) : path;
77
+ }
78
+
79
+ export function buildWriteQuitCommitMessage(changes: GitChange[]): string {
80
+ if (changes.length === 0) return 'Update working tree';
81
+
82
+ if (changes.length === 1) {
83
+ const [change] = changes;
84
+ if (!change) return 'Update working tree';
85
+ if (change.action === 'rename') {
86
+ return `Rename ${shortPath(change.from ?? change.path)} to ${shortPath(change.to ?? change.path)}`;
87
+ }
88
+ const verb = change.action === 'add'
89
+ ? 'Add'
90
+ : change.action === 'delete'
91
+ ? 'Delete'
92
+ : 'Update';
93
+ return `${verb} ${shortPath(change.path)}`;
94
+ }
95
+
96
+ const uniqueActions = Array.from(new Set(changes.map((change) => change.action)));
97
+ if (uniqueActions.length === 1) {
98
+ const [action] = uniqueActions;
99
+ const verb = action === 'add'
100
+ ? 'Add'
101
+ : action === 'delete'
102
+ ? 'Delete'
103
+ : action === 'rename'
104
+ ? 'Rename'
105
+ : 'Update';
106
+ const scopeLabel = summarizeScopes(changes);
107
+ if (scopeLabel) return `${verb} ${scopeLabel}`;
108
+ return `${verb} ${changes.length} files`;
109
+ }
110
+
111
+ const scopeLabel = summarizeScopes(changes);
112
+ if (scopeLabel) return `Update ${scopeLabel}`;
113
+ return `Update ${changes.length} files`;
114
+ }
115
+
116
+ export type ExecuteWriteQuitOptions = {
117
+ cwd?: string;
118
+ isGitRepo?: (cwd: string) => boolean;
119
+ getRepoRoot?: (cwd: string) => string | null;
120
+ gitFactory?: (cwd: string) => GitLike;
121
+ };
122
+
123
+ export async function executeWriteQuit(
124
+ ctx: Pick<CommandContext, 'print' | 'exit'> & {
125
+ workspace?: CommandContext['workspace'];
126
+ },
127
+ options: ExecuteWriteQuitOptions = {},
128
+ ): Promise<void> {
129
+ const cwd = options.cwd ?? ctx.workspace?.shellPaths?.workingDirectory;
130
+ if (!cwd) {
131
+ throw new Error('commandContext.workspace.shellPaths is required when executeWriteQuit() is called without an explicit cwd');
132
+ }
133
+ const isGitRepo = options.isGitRepo ?? ((dir: string) => GitService.isGitRepo(dir));
134
+ if (!isGitRepo(cwd)) {
135
+ ctx.exit();
136
+ return;
137
+ }
138
+
139
+ const repoRoot = options.getRepoRoot?.(cwd) ?? GitService.getRepoRoot(cwd) ?? cwd;
140
+ const git = options.gitFactory?.(repoRoot) ?? new GitService(repoRoot);
141
+
142
+ try {
143
+ ctx.print(`[wq] Staging changes in ${repoRoot}...`);
144
+ await git.addAll();
145
+ const status = await git.status();
146
+ if (status.isClean()) {
147
+ ctx.print('[wq] Working tree clean. Exiting without creating a commit.');
148
+ ctx.exit();
149
+ return;
150
+ }
151
+
152
+ const changes = collectGitChanges(status);
153
+ const message = buildWriteQuitCommitMessage(changes);
154
+ ctx.print(`[wq] Committing ${changes.length} change${changes.length === 1 ? '' : 's'}: ${message}`);
155
+ const result = await git.commit(message);
156
+ const shortHash = result.hash ? result.hash.slice(0, 7) : 'unknown';
157
+ ctx.print(`[wq] Commit complete: ${shortHash} ${message}`);
158
+ ctx.exit();
159
+ } catch (error) {
160
+ ctx.print(`[wq] Commit failed: ${summarizeError(error)}`);
161
+ }
162
+ }
@@ -0,0 +1,132 @@
1
+ import { dirname } from 'node:path';
2
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import type { CommandContext } from '../command-registry.ts';
4
+ import type { MemoryBundle, MemorySearchFilter } from '@pellux/goodvibes-sdk/platform/state';
5
+ import { VALID_CLASSES, VALID_SCOPES, isValidClass, isValidScope, resolveBundlePath } from './recall-shared.ts';
6
+ import { requireShellPaths } from './runtime-services.ts';
7
+ import { getMemoryApi } from './recall-query.ts';
8
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
9
+
10
+ export function handleRecallExport(args: string[], context: CommandContext): void {
11
+ const memory = getMemoryApi(context);
12
+ if (!memory) {
13
+ return;
14
+ }
15
+
16
+ const pathArg = args[0];
17
+ if (!pathArg) {
18
+ context.print('[recall] Usage: /recall export <path> [--scope <scope>] [--cls <class>]');
19
+ return;
20
+ }
21
+
22
+ const filter: MemorySearchFilter = {};
23
+ const scopeIdx = args.indexOf('--scope');
24
+ if (scopeIdx !== -1 && args[scopeIdx + 1]) {
25
+ const scope = args[scopeIdx + 1];
26
+ if (!isValidScope(scope)) {
27
+ context.print(`[recall] Unknown scope "${scope}". Valid: ${VALID_SCOPES.join(', ')}`);
28
+ return;
29
+ }
30
+ filter.scope = scope;
31
+ }
32
+
33
+ const clsIdx = args.indexOf('--cls');
34
+ if (clsIdx !== -1 && args[clsIdx + 1]) {
35
+ const cls = args[clsIdx + 1];
36
+ if (!isValidClass(cls)) {
37
+ context.print(`[recall] Unknown class "${cls}". Valid: ${VALID_CLASSES.join(', ')}`);
38
+ return;
39
+ }
40
+ filter.cls = cls;
41
+ }
42
+
43
+ const bundle = memory.exportBundle(filter);
44
+ const targetPath = resolveBundlePath(pathArg, requireShellPaths(context));
45
+ mkdirSync(dirname(targetPath), { recursive: true });
46
+ writeFileSync(targetPath, JSON.stringify(bundle, null, 2) + '\n', 'utf-8');
47
+ context.print(`[recall] Exported ${bundle.recordCount} record(s) and ${bundle.linkCount} link(s) to ${targetPath}`);
48
+ }
49
+
50
+ export async function handleRecallImport(args: string[], context: CommandContext): Promise<void> {
51
+ const memory = getMemoryApi(context);
52
+ if (!memory) {
53
+ return;
54
+ }
55
+
56
+ const pathArg = args[0];
57
+ if (!pathArg) {
58
+ context.print('[recall] Usage: /recall import <path>');
59
+ return;
60
+ }
61
+
62
+ const targetPath = resolveBundlePath(pathArg, requireShellPaths(context));
63
+ let bundle: MemoryBundle;
64
+ try {
65
+ bundle = JSON.parse(readFileSync(targetPath, 'utf-8')) as MemoryBundle;
66
+ } catch (error) {
67
+ context.print(`[recall] Failed to read memory bundle: ${summarizeError(error)}`);
68
+ return;
69
+ }
70
+
71
+ const result = await memory.importBundle(bundle);
72
+ context.print(`[recall] Imported bundle from ${targetPath}`);
73
+ context.print(` Records: imported=${result.importedRecords} skipped=${result.skippedRecords}`);
74
+ context.print(` Links: imported=${result.importedLinks}`);
75
+ }
76
+
77
+ function inspectBundle(bundle: MemoryBundle): string {
78
+ return [
79
+ 'Memory Handoff Review',
80
+ ` scope: ${bundle.scope}`,
81
+ ` records: ${bundle.recordCount}`,
82
+ ` links: ${bundle.linkCount}`,
83
+ ` exportedAt: ${new Date(bundle.exportedAt).toISOString()}`,
84
+ ].join('\n');
85
+ }
86
+
87
+ export function handleRecallHandoffExport(args: string[], context: CommandContext): void {
88
+ const memory = getMemoryApi(context);
89
+ if (!memory) {
90
+ return;
91
+ }
92
+ const pathArg = args[0];
93
+ if (!pathArg) {
94
+ context.print('[recall] Usage: /recall handoff-export <path> [--scope <scope>]');
95
+ return;
96
+ }
97
+ const scopeIdx = args.indexOf('--scope');
98
+ const scopeRaw = scopeIdx !== -1 ? args[scopeIdx + 1] : 'team';
99
+ if (!scopeRaw || !isValidScope(scopeRaw)) {
100
+ context.print(`[recall] Unknown scope "${scopeRaw ?? ''}". Valid: ${VALID_SCOPES.join(', ')}`);
101
+ return;
102
+ }
103
+ const bundle = memory.exportBundle({ scope: scopeRaw });
104
+ const targetPath = resolveBundlePath(pathArg, requireShellPaths(context));
105
+ mkdirSync(dirname(targetPath), { recursive: true });
106
+ writeFileSync(targetPath, JSON.stringify(bundle, null, 2) + '\n', 'utf-8');
107
+ context.print(`[recall] Exported ${scopeRaw} handoff bundle to ${targetPath}`);
108
+ }
109
+
110
+ export function handleRecallHandoffInspect(args: string[], context: CommandContext): void {
111
+ const pathArg = args[0];
112
+ if (!pathArg) {
113
+ context.print('[recall] Usage: /recall handoff-inspect <path>');
114
+ return;
115
+ }
116
+ const targetPath = resolveBundlePath(pathArg, requireShellPaths(context));
117
+ try {
118
+ const bundle = JSON.parse(readFileSync(targetPath, 'utf-8')) as MemoryBundle;
119
+ context.print(inspectBundle(bundle));
120
+ } catch (error) {
121
+ context.print(`[recall] Failed to inspect handoff bundle: ${summarizeError(error)}`);
122
+ }
123
+ }
124
+
125
+ export async function handleRecallHandoffImport(args: string[], context: CommandContext): Promise<void> {
126
+ const pathArg = args[0];
127
+ if (!pathArg) {
128
+ context.print('[recall] Usage: /recall handoff-import <path>');
129
+ return;
130
+ }
131
+ await handleRecallImport([pathArg], context);
132
+ }