stagent 0.9.6 → 0.11.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 (396) hide show
  1. package/README.md +20 -44
  2. package/dist/cli.js +66 -18
  3. package/docs/.coverage-gaps.json +144 -56
  4. package/docs/.last-generated +1 -1
  5. package/docs/features/agent-intelligence.md +12 -2
  6. package/docs/features/chat.md +40 -5
  7. package/docs/features/cost-usage.md +1 -1
  8. package/docs/features/documents.md +5 -2
  9. package/docs/features/inbox-notifications.md +10 -2
  10. package/docs/features/keyboard-navigation.md +12 -3
  11. package/docs/features/provider-runtimes.md +20 -2
  12. package/docs/features/schedules.md +32 -4
  13. package/docs/features/settings.md +28 -5
  14. package/docs/features/shared-components.md +7 -3
  15. package/docs/features/tables.md +11 -2
  16. package/docs/features/tool-permissions.md +6 -2
  17. package/docs/features/workflows.md +14 -4
  18. package/docs/index.md +1 -1
  19. package/docs/journeys/developer.md +39 -2
  20. package/docs/journeys/personal-use.md +32 -8
  21. package/docs/journeys/power-user.md +45 -14
  22. package/docs/journeys/work-use.md +17 -8
  23. package/docs/manifest.json +15 -15
  24. package/docs/superpowers/plans/2026-04-07-instance-bootstrap.md +1691 -0
  25. package/docs/superpowers/plans/2026-04-08-schedule-orchestration.md +2983 -0
  26. package/docs/superpowers/plans/2026-04-11-schedule-maxturns-api-control.md +551 -0
  27. package/docs/superpowers/plans/2026-04-11-task-create-profile-validation.md +864 -0
  28. package/docs/superpowers/plans/2026-04-11-task-runtime-stagent-mcp-injection.md +739 -0
  29. package/docs/superpowers/plans/2026-04-14-chat-command-namespace-refactor.md +1390 -0
  30. package/docs/superpowers/plans/2026-04-14-chat-environment-integration.md +1561 -0
  31. package/docs/superpowers/plans/2026-04-14-chat-polish-bundle-v1.md +1219 -0
  32. package/docs/superpowers/plans/2026-04-14-chat-session-persistence-provider-closeout.md +399 -0
  33. package/docs/superpowers/specs/2026-04-08-chat-sse-resilience-hotfix-design.md +201 -0
  34. package/docs/superpowers/specs/2026-04-08-schedule-orchestration-design.md +371 -0
  35. package/docs/superpowers/specs/2026-04-08-swarm-visibility-design.md +213 -0
  36. package/next.config.mjs +1 -0
  37. package/package.json +3 -2
  38. package/src/__tests__/instrumentation-smoke.test.ts +15 -0
  39. package/src/app/analytics/page.tsx +1 -21
  40. package/src/app/api/chat/conversations/[id]/messages/route.ts +22 -1
  41. package/src/app/api/chat/conversations/[id]/skills/__tests__/activate.test.ts +141 -0
  42. package/src/app/api/chat/conversations/[id]/skills/activate/route.ts +74 -0
  43. package/src/app/api/chat/conversations/[id]/skills/deactivate/route.ts +33 -0
  44. package/src/app/api/chat/export/route.ts +52 -0
  45. package/src/app/api/chat/files/search/route.ts +50 -0
  46. package/src/app/api/diagnostics/chat-streams/route.ts +65 -0
  47. package/src/app/api/environment/rescan-if-stale/__tests__/route.test.ts +45 -0
  48. package/src/app/api/environment/rescan-if-stale/route.ts +23 -0
  49. package/src/app/api/environment/skills/route.ts +13 -0
  50. package/src/app/api/instance/config/route.ts +41 -0
  51. package/src/app/api/instance/init/route.ts +34 -0
  52. package/src/app/api/instance/upgrade/check/route.ts +26 -0
  53. package/src/app/api/instance/upgrade/route.ts +96 -0
  54. package/src/app/api/instance/upgrade/status/route.ts +35 -0
  55. package/src/app/api/memory/route.ts +0 -11
  56. package/src/app/api/notifications/route.ts +4 -2
  57. package/src/app/api/projects/[id]/route.ts +5 -155
  58. package/src/app/api/projects/__tests__/delete-project.test.ts +10 -19
  59. package/src/app/api/schedules/[id]/execute/route.ts +111 -0
  60. package/src/app/api/schedules/[id]/route.ts +9 -1
  61. package/src/app/api/schedules/__tests__/execute-route.test.ts +118 -0
  62. package/src/app/api/schedules/route.ts +3 -12
  63. package/src/app/api/settings/chat/pins/route.ts +94 -0
  64. package/src/app/api/settings/chat/saved-searches/__tests__/route.test.ts +119 -0
  65. package/src/app/api/settings/chat/saved-searches/route.ts +79 -0
  66. package/src/app/api/settings/environment/route.ts +26 -0
  67. package/src/app/api/settings/openai/login/route.ts +22 -0
  68. package/src/app/api/settings/openai/logout/route.ts +7 -0
  69. package/src/app/api/settings/openai/route.ts +21 -1
  70. package/src/app/api/settings/providers/route.ts +35 -8
  71. package/src/app/api/tables/[id]/enrich/__tests__/route.test.ts +153 -0
  72. package/src/app/api/tables/[id]/enrich/plan/route.ts +98 -0
  73. package/src/app/api/tables/[id]/enrich/route.ts +147 -0
  74. package/src/app/api/tables/[id]/enrich/runs/route.ts +25 -0
  75. package/src/app/api/tasks/[id]/execute/route.ts +52 -33
  76. package/src/app/api/tasks/[id]/respond/route.ts +31 -15
  77. package/src/app/api/tasks/[id]/resume/route.ts +24 -3
  78. package/src/app/api/workflows/[id]/resume/route.ts +59 -0
  79. package/src/app/api/workflows/[id]/status/route.ts +22 -8
  80. package/src/app/api/workspace/context/route.ts +2 -0
  81. package/src/app/api/workspace/fix-data-dir/route.ts +81 -0
  82. package/src/app/chat/page.tsx +11 -0
  83. package/src/app/documents/page.tsx +4 -1
  84. package/src/app/inbox/page.tsx +12 -5
  85. package/src/app/layout.tsx +42 -21
  86. package/src/app/page.tsx +0 -2
  87. package/src/app/settings/page.tsx +8 -9
  88. package/src/components/chat/__tests__/capability-banner.test.tsx +38 -0
  89. package/src/components/chat/__tests__/chat-session-provider.test.tsx +573 -0
  90. package/src/components/chat/__tests__/skill-row.test.tsx +91 -0
  91. package/src/components/chat/capability-banner.tsx +68 -0
  92. package/src/components/chat/chat-command-popover.tsx +670 -49
  93. package/src/components/chat/chat-input.tsx +104 -10
  94. package/src/components/chat/chat-message.tsx +12 -3
  95. package/src/components/chat/chat-session-provider.tsx +790 -0
  96. package/src/components/chat/chat-shell.tsx +151 -401
  97. package/src/components/chat/command-tab-bar.tsx +68 -0
  98. package/src/components/chat/conversation-template-picker.tsx +421 -0
  99. package/src/components/chat/help-dialog.tsx +39 -0
  100. package/src/components/chat/skill-composition-conflict-dialog.tsx +96 -0
  101. package/src/components/chat/skill-row.tsx +147 -0
  102. package/src/components/documents/document-browser.tsx +37 -19
  103. package/src/components/instance/__tests__/instance-section.test.tsx +125 -0
  104. package/src/components/instance/instance-section.tsx +382 -0
  105. package/src/components/instance/upgrade-badge.tsx +219 -0
  106. package/src/components/notifications/__tests__/batch-proposal-review.test.tsx +95 -0
  107. package/src/components/notifications/__tests__/notification-item.test.tsx +106 -0
  108. package/src/components/notifications/__tests__/permission-response-actions.test.tsx +70 -0
  109. package/src/components/notifications/batch-proposal-review.tsx +20 -5
  110. package/src/components/notifications/inbox-list.tsx +11 -2
  111. package/src/components/notifications/notification-item.tsx +56 -2
  112. package/src/components/notifications/pending-approval-host.tsx +56 -37
  113. package/src/components/notifications/permission-response-actions.tsx +155 -1
  114. package/src/components/schedules/schedule-create-sheet.tsx +19 -1
  115. package/src/components/schedules/schedule-edit-sheet.tsx +20 -1
  116. package/src/components/schedules/schedule-form.tsx +31 -0
  117. package/src/components/settings/__tests__/providers-runtimes-section.test.tsx +149 -0
  118. package/src/components/settings/auth-method-selector.tsx +19 -4
  119. package/src/components/settings/auth-status-badge.tsx +28 -3
  120. package/src/components/settings/environment-section.tsx +102 -0
  121. package/src/components/settings/openai-chatgpt-auth-control.tsx +278 -0
  122. package/src/components/settings/openai-runtime-section.tsx +7 -1
  123. package/src/components/settings/providers-runtimes-section.tsx +138 -19
  124. package/src/components/shared/__tests__/filter-hint.test.tsx +40 -0
  125. package/src/components/shared/__tests__/saved-searches-manager.test.tsx +147 -0
  126. package/src/components/shared/app-sidebar.tsx +4 -3
  127. package/src/components/shared/command-palette.tsx +266 -7
  128. package/src/components/shared/filter-hint.tsx +70 -0
  129. package/src/components/shared/filter-input.tsx +59 -0
  130. package/src/components/shared/saved-searches-manager.tsx +199 -0
  131. package/src/components/shared/theme-toggle.tsx +5 -24
  132. package/src/components/shared/workspace-indicator.tsx +61 -2
  133. package/src/components/tables/__tests__/table-enrichment-sheet.test.tsx +130 -0
  134. package/src/components/tables/table-create-sheet.tsx +4 -0
  135. package/src/components/tables/table-enrichment-runs.tsx +103 -0
  136. package/src/components/tables/table-enrichment-sheet.tsx +538 -0
  137. package/src/components/tables/table-spreadsheet.tsx +29 -5
  138. package/src/components/tables/table-toolbar.tsx +10 -1
  139. package/src/components/tasks/kanban-board.tsx +1 -0
  140. package/src/components/tasks/kanban-column.tsx +53 -14
  141. package/src/components/tasks/task-bento-grid.tsx +31 -2
  142. package/src/components/tasks/task-card.tsx +29 -3
  143. package/src/components/tasks/task-chip-bar.tsx +54 -1
  144. package/src/components/tasks/task-result-renderer.tsx +1 -1
  145. package/src/components/workflows/delay-step-body.tsx +109 -0
  146. package/src/components/workflows/hooks/use-workflow-status.ts +50 -0
  147. package/src/components/workflows/loop-status-view.tsx +1 -1
  148. package/src/components/workflows/shared/step-result.tsx +78 -0
  149. package/src/components/workflows/shared/workflow-header.tsx +141 -0
  150. package/src/components/workflows/shared/workflow-loading-skeleton.tsx +36 -0
  151. package/src/components/workflows/swarm-dashboard.tsx +2 -15
  152. package/src/components/workflows/views/loop-pattern-view.tsx +137 -0
  153. package/src/components/workflows/views/sequence-pattern-view.tsx +511 -0
  154. package/src/components/workflows/workflow-form-view.tsx +133 -16
  155. package/src/components/workflows/workflow-status-view.tsx +30 -740
  156. package/src/hooks/__tests__/use-chat-autocomplete-tabs.test.ts +47 -0
  157. package/src/hooks/__tests__/use-saved-searches.test.ts +70 -0
  158. package/src/hooks/use-active-skills.ts +110 -0
  159. package/src/hooks/use-chat-autocomplete.ts +120 -7
  160. package/src/hooks/use-enriched-skills.ts +19 -0
  161. package/src/hooks/use-pinned-entries.ts +104 -0
  162. package/src/hooks/use-recent-user-messages.ts +19 -0
  163. package/src/hooks/use-saved-searches.ts +142 -0
  164. package/src/instrumentation-node.ts +94 -0
  165. package/src/instrumentation.ts +4 -48
  166. package/src/lib/agents/__tests__/claude-agent-sdk-options.test.ts +56 -0
  167. package/src/lib/agents/__tests__/claude-agent.test.ts +212 -0
  168. package/src/lib/agents/__tests__/execution-manager.test.ts +1 -27
  169. package/src/lib/agents/__tests__/failure-reason.test.ts +68 -0
  170. package/src/lib/agents/__tests__/learned-context.test.ts +0 -11
  171. package/src/lib/agents/__tests__/learning-session.test.ts +158 -0
  172. package/src/lib/agents/__tests__/pattern-extractor.test.ts +48 -0
  173. package/src/lib/agents/__tests__/task-dispatch.test.ts +166 -0
  174. package/src/lib/agents/__tests__/tool-permissions.test.ts +60 -0
  175. package/src/lib/agents/claude-agent.ts +217 -21
  176. package/src/lib/agents/execution-manager.ts +0 -35
  177. package/src/lib/agents/handoff/bus.ts +2 -2
  178. package/src/lib/agents/learned-context.ts +0 -12
  179. package/src/lib/agents/learning-session.ts +18 -5
  180. package/src/lib/agents/profiles/__tests__/list-fused-profiles.test.ts +110 -0
  181. package/src/lib/agents/profiles/__tests__/registry.test.ts +53 -4
  182. package/src/lib/agents/profiles/builtins/upgrade-assistant/SKILL.md +97 -0
  183. package/src/lib/agents/profiles/builtins/upgrade-assistant/profile.yaml +36 -0
  184. package/src/lib/agents/profiles/list-fused-profiles.ts +104 -0
  185. package/src/lib/agents/profiles/registry.ts +18 -0
  186. package/src/lib/agents/profiles/types.ts +7 -1
  187. package/src/lib/agents/router.ts +3 -6
  188. package/src/lib/agents/runtime/__tests__/catalog.test.ts +130 -0
  189. package/src/lib/agents/runtime/__tests__/execution-target.test.ts +183 -0
  190. package/src/lib/agents/runtime/__tests__/openai-codex-auth.test.ts +118 -0
  191. package/src/lib/agents/runtime/anthropic-direct.ts +8 -0
  192. package/src/lib/agents/runtime/catalog.ts +121 -0
  193. package/src/lib/agents/runtime/claude-sdk.ts +32 -0
  194. package/src/lib/agents/runtime/codex-app-server-client.ts +11 -5
  195. package/src/lib/agents/runtime/execution-target.ts +456 -0
  196. package/src/lib/agents/runtime/index.ts +4 -0
  197. package/src/lib/agents/runtime/launch-failure.ts +101 -0
  198. package/src/lib/agents/runtime/openai-codex-auth.ts +389 -0
  199. package/src/lib/agents/runtime/openai-codex.ts +64 -60
  200. package/src/lib/agents/runtime/openai-direct.ts +8 -0
  201. package/src/lib/agents/runtime/types.ts +8 -0
  202. package/src/lib/agents/task-dispatch.ts +220 -0
  203. package/src/lib/agents/tool-permissions.ts +16 -1
  204. package/src/lib/book/chapter-mapping.ts +11 -0
  205. package/src/lib/book/content.ts +10 -0
  206. package/src/lib/chat/__tests__/active-skill-injection.test.ts +261 -0
  207. package/src/lib/chat/__tests__/active-streams.test.ts +49 -0
  208. package/src/lib/chat/__tests__/clean-filter-input.test.ts +68 -0
  209. package/src/lib/chat/__tests__/command-tabs.test.ts +68 -0
  210. package/src/lib/chat/__tests__/context-builder-files.test.ts +112 -0
  211. package/src/lib/chat/__tests__/dismissals.test.ts +65 -0
  212. package/src/lib/chat/__tests__/engine-sdk-options.test.ts +117 -0
  213. package/src/lib/chat/__tests__/finalize-safety-net.test.ts +139 -0
  214. package/src/lib/chat/__tests__/reconcile.test.ts +137 -0
  215. package/src/lib/chat/__tests__/skill-conflict.test.ts +35 -0
  216. package/src/lib/chat/__tests__/stream-telemetry.test.ts +151 -0
  217. package/src/lib/chat/__tests__/types.test.ts +28 -0
  218. package/src/lib/chat/active-skills.ts +31 -0
  219. package/src/lib/chat/active-streams.ts +27 -0
  220. package/src/lib/chat/clean-filter-input.ts +30 -0
  221. package/src/lib/chat/codex-engine.ts +46 -24
  222. package/src/lib/chat/command-tabs.ts +61 -0
  223. package/src/lib/chat/context-builder.ts +146 -4
  224. package/src/lib/chat/dismissals.ts +73 -0
  225. package/src/lib/chat/engine.ts +159 -18
  226. package/src/lib/chat/files/__tests__/search.test.ts +135 -0
  227. package/src/lib/chat/files/expand-mention.ts +76 -0
  228. package/src/lib/chat/files/search.ts +99 -0
  229. package/src/lib/chat/reconcile.ts +117 -0
  230. package/src/lib/chat/skill-composition.ts +210 -0
  231. package/src/lib/chat/skill-conflict.ts +105 -0
  232. package/src/lib/chat/stagent-tools.ts +7 -19
  233. package/src/lib/chat/stream-telemetry.ts +137 -0
  234. package/src/lib/chat/suggested-prompts.ts +28 -1
  235. package/src/lib/chat/system-prompt.ts +48 -1
  236. package/src/lib/chat/tool-catalog.ts +35 -4
  237. package/src/lib/chat/tools/__tests__/enrich-table-tool.test.ts +127 -0
  238. package/src/lib/chat/tools/__tests__/profile-tools.test.ts +51 -0
  239. package/src/lib/chat/tools/__tests__/schedule-tools.test.ts +261 -0
  240. package/src/lib/chat/tools/__tests__/settings-tools.test.ts +294 -0
  241. package/src/lib/chat/tools/__tests__/skill-tools.test.ts +474 -0
  242. package/src/lib/chat/tools/__tests__/task-tools.test.ts +399 -0
  243. package/src/lib/chat/tools/__tests__/workflow-tools-dedup.test.ts +351 -0
  244. package/src/lib/chat/tools/blueprint-tools.ts +190 -0
  245. package/src/lib/chat/tools/document-tools.ts +29 -13
  246. package/src/lib/chat/tools/helpers.ts +41 -0
  247. package/src/lib/chat/tools/notification-tools.ts +9 -5
  248. package/src/lib/chat/tools/profile-tools.ts +120 -23
  249. package/src/lib/chat/tools/project-tools.ts +33 -0
  250. package/src/lib/chat/tools/schedule-tools.ts +44 -11
  251. package/src/lib/chat/tools/skill-tools.ts +183 -0
  252. package/src/lib/chat/tools/table-tools.ts +71 -0
  253. package/src/lib/chat/tools/task-tools.ts +89 -21
  254. package/src/lib/chat/tools/workflow-tools.ts +275 -32
  255. package/src/lib/chat/types.ts +15 -0
  256. package/src/lib/constants/settings.ts +10 -18
  257. package/src/lib/data/__tests__/clear.test.ts +56 -2
  258. package/src/lib/data/clear.ts +17 -16
  259. package/src/lib/data/delete-project.ts +171 -0
  260. package/src/lib/db/__tests__/bootstrap.test.ts +1 -1
  261. package/src/lib/db/bootstrap.ts +62 -16
  262. package/src/lib/db/index.ts +5 -0
  263. package/src/lib/db/migrations/0009_add_app_instances.sql +25 -0
  264. package/src/lib/db/migrations/0024_add_workflow_resume_at.sql +10 -0
  265. package/src/lib/db/migrations/0025_drop_app_instances.sql +3 -0
  266. package/src/lib/db/migrations/0026_drop_license.sql +3 -0
  267. package/src/lib/db/migrations/meta/_journal.json +21 -0
  268. package/src/lib/db/schema.ts +94 -23
  269. package/src/lib/environment/__tests__/auto-promote.test.ts +132 -0
  270. package/src/lib/environment/__tests__/list-skills-enriched.test.ts +55 -0
  271. package/src/lib/environment/__tests__/skill-enrichment.test.ts +129 -0
  272. package/src/lib/environment/__tests__/skill-recommendations.test.ts +87 -0
  273. package/src/lib/environment/data.ts +9 -0
  274. package/src/lib/environment/list-skills.ts +176 -0
  275. package/src/lib/environment/parsers/__tests__/skill.test.ts +54 -0
  276. package/src/lib/environment/parsers/skill.ts +26 -5
  277. package/src/lib/environment/profile-generator.ts +54 -0
  278. package/src/lib/environment/skill-enrichment.ts +106 -0
  279. package/src/lib/environment/skill-recommendations.ts +66 -0
  280. package/src/lib/environment/workspace-context.ts +13 -1
  281. package/src/lib/filters/__tests__/parse.quoted.test.ts +40 -0
  282. package/src/lib/filters/__tests__/parse.test.ts +135 -0
  283. package/src/lib/filters/parse.ts +86 -0
  284. package/src/lib/import/dedup.ts +4 -54
  285. package/src/lib/instance/__tests__/bootstrap.test.ts +362 -0
  286. package/src/lib/instance/__tests__/detect.test.ts +115 -0
  287. package/src/lib/instance/__tests__/fingerprint.test.ts +48 -0
  288. package/src/lib/instance/__tests__/git-ops.test.ts +95 -0
  289. package/src/lib/instance/__tests__/settings.test.ts +83 -0
  290. package/src/lib/instance/__tests__/upgrade-poller.test.ts +181 -0
  291. package/src/lib/instance/bootstrap.ts +270 -0
  292. package/src/lib/instance/detect.ts +49 -0
  293. package/src/lib/instance/fingerprint.ts +76 -0
  294. package/src/lib/instance/git-ops.ts +95 -0
  295. package/src/lib/instance/settings.ts +61 -0
  296. package/src/lib/instance/types.ts +77 -0
  297. package/src/lib/instance/upgrade-poller.ts +205 -0
  298. package/src/lib/notifications/__tests__/visibility.test.ts +51 -0
  299. package/src/lib/notifications/visibility.ts +33 -0
  300. package/src/lib/schedules/__tests__/collision-check.test.ts +93 -0
  301. package/src/lib/schedules/__tests__/config.test.ts +62 -0
  302. package/src/lib/schedules/__tests__/firing-metrics.test.ts +99 -0
  303. package/src/lib/schedules/__tests__/integration.test.ts +82 -0
  304. package/src/lib/schedules/__tests__/slot-claim.test.ts +242 -0
  305. package/src/lib/schedules/__tests__/tick-scheduler.test.ts +102 -0
  306. package/src/lib/schedules/__tests__/turn-budget.test.ts +228 -0
  307. package/src/lib/schedules/collision-check.ts +105 -0
  308. package/src/lib/schedules/config.ts +53 -0
  309. package/src/lib/schedules/scheduler.ts +236 -17
  310. package/src/lib/schedules/slot-claim.ts +105 -0
  311. package/src/lib/settings/__tests__/openai-auth.test.ts +101 -0
  312. package/src/lib/settings/__tests__/openai-login-manager.test.ts +64 -0
  313. package/src/lib/settings/__tests__/runtime-setup.test.ts +33 -0
  314. package/src/lib/settings/openai-auth.ts +105 -10
  315. package/src/lib/settings/openai-login-manager.ts +260 -0
  316. package/src/lib/settings/runtime-setup.ts +14 -4
  317. package/src/lib/tables/__tests__/enrichment-planner.test.ts +124 -0
  318. package/src/lib/tables/__tests__/enrichment.test.ts +147 -0
  319. package/src/lib/tables/enrichment-planner.ts +454 -0
  320. package/src/lib/tables/enrichment.ts +328 -0
  321. package/src/lib/tables/query-builder.ts +5 -2
  322. package/src/lib/tables/trigger-evaluator.ts +3 -2
  323. package/src/lib/theme.ts +71 -0
  324. package/src/lib/usage/ledger.ts +2 -18
  325. package/src/lib/util/__tests__/similarity.test.ts +106 -0
  326. package/src/lib/util/similarity.ts +77 -0
  327. package/src/lib/utils/format-timestamp.ts +24 -0
  328. package/src/lib/utils/stagent-paths.ts +12 -0
  329. package/src/lib/validators/__tests__/blueprint.test.ts +172 -0
  330. package/src/lib/validators/__tests__/settings.test.ts +10 -0
  331. package/src/lib/validators/blueprint.ts +70 -9
  332. package/src/lib/validators/profile.ts +2 -2
  333. package/src/lib/validators/settings.ts +3 -1
  334. package/src/lib/workflows/__tests__/delay.test.ts +196 -0
  335. package/src/lib/workflows/__tests__/engine.test.ts +8 -0
  336. package/src/lib/workflows/__tests__/loop-executor.test.ts +54 -0
  337. package/src/lib/workflows/__tests__/post-action.test.ts +108 -0
  338. package/src/lib/workflows/blueprints/__tests__/render-prompt.test.ts +124 -0
  339. package/src/lib/workflows/blueprints/instantiator.ts +22 -1
  340. package/src/lib/workflows/blueprints/render-prompt.ts +71 -0
  341. package/src/lib/workflows/blueprints/types.ts +16 -2
  342. package/src/lib/workflows/delay.ts +106 -0
  343. package/src/lib/workflows/engine.ts +212 -7
  344. package/src/lib/workflows/loop-executor.ts +349 -24
  345. package/src/lib/workflows/post-action.ts +91 -0
  346. package/src/lib/workflows/types.ts +166 -1
  347. package/src/test/setup.ts +10 -0
  348. package/src/app/api/license/checkout/route.ts +0 -28
  349. package/src/app/api/license/portal/route.ts +0 -26
  350. package/src/app/api/license/route.ts +0 -89
  351. package/src/app/api/license/usage/route.ts +0 -63
  352. package/src/app/api/marketplace/browse/route.ts +0 -15
  353. package/src/app/api/marketplace/import/route.ts +0 -28
  354. package/src/app/api/marketplace/publish/route.ts +0 -40
  355. package/src/app/api/onboarding/email/route.ts +0 -53
  356. package/src/app/api/settings/telemetry/route.ts +0 -14
  357. package/src/app/api/sync/export/route.ts +0 -54
  358. package/src/app/api/sync/restore/route.ts +0 -37
  359. package/src/app/api/sync/sessions/route.ts +0 -24
  360. package/src/app/auth/callback/route.ts +0 -73
  361. package/src/app/marketplace/page.tsx +0 -19
  362. package/src/components/analytics/analytics-gate-card.tsx +0 -101
  363. package/src/components/marketplace/blueprint-card.tsx +0 -61
  364. package/src/components/marketplace/marketplace-browser.tsx +0 -131
  365. package/src/components/onboarding/email-capture-card.tsx +0 -104
  366. package/src/components/settings/activation-form.tsx +0 -95
  367. package/src/components/settings/cloud-account-section.tsx +0 -147
  368. package/src/components/settings/cloud-sync-section.tsx +0 -155
  369. package/src/components/settings/subscription-section.tsx +0 -410
  370. package/src/components/settings/telemetry-section.tsx +0 -80
  371. package/src/components/shared/premium-gate-overlay.tsx +0 -50
  372. package/src/components/shared/schedule-gate-dialog.tsx +0 -64
  373. package/src/components/shared/upgrade-banner.tsx +0 -112
  374. package/src/hooks/use-supabase-auth.ts +0 -79
  375. package/src/lib/billing/email.ts +0 -54
  376. package/src/lib/billing/products.ts +0 -80
  377. package/src/lib/billing/stripe.ts +0 -101
  378. package/src/lib/cloud/supabase-browser.ts +0 -32
  379. package/src/lib/cloud/supabase-client.ts +0 -56
  380. package/src/lib/license/__tests__/features.test.ts +0 -56
  381. package/src/lib/license/__tests__/key-format.test.ts +0 -88
  382. package/src/lib/license/__tests__/manager.test.ts +0 -64
  383. package/src/lib/license/__tests__/tier-limits.test.ts +0 -79
  384. package/src/lib/license/cloud-validation.ts +0 -60
  385. package/src/lib/license/features.ts +0 -44
  386. package/src/lib/license/key-format.ts +0 -101
  387. package/src/lib/license/limit-check.ts +0 -111
  388. package/src/lib/license/limit-queries.ts +0 -51
  389. package/src/lib/license/manager.ts +0 -345
  390. package/src/lib/license/notifications.ts +0 -59
  391. package/src/lib/license/tier-limits.ts +0 -71
  392. package/src/lib/marketplace/marketplace-client.ts +0 -107
  393. package/src/lib/sync/cloud-sync.ts +0 -235
  394. package/src/lib/telemetry/conversion-events.ts +0 -71
  395. package/src/lib/telemetry/queue.ts +0 -122
  396. package/src/lib/validators/license.ts +0 -33
@@ -20,8 +20,37 @@ export interface WorkflowStep {
20
20
  budgetUsd?: number;
21
21
  /** Per-step runtime override — takes precedence over workflow.runtimeId and global settings */
22
22
  runtimeId?: string;
23
+ /**
24
+ * If set, this step is a pure time delay (not a task). Format: Nm|Nh|Nd|Nw
25
+ * (1 minute to 30 days). When the engine reaches a delay step, the workflow
26
+ * is marked paused with resume_at = now + delayDuration. The scheduler tick
27
+ * resumes the workflow when resume_at is reached. Delay steps must NOT have
28
+ * a prompt/profile/runtime. See features/workflow-step-delays.md.
29
+ */
30
+ delayDuration?: string;
31
+ /**
32
+ * Optional declarative side-effect to apply after the step's task completes
33
+ * successfully. Used by bulk row enrichment to write the agent's result back
34
+ * into a user table cell. Discriminated union — `type` selects the variant.
35
+ * See features/bulk-row-enrichment.md.
36
+ */
37
+ postAction?: StepPostAction;
23
38
  }
24
39
 
40
+ /**
41
+ * Declarative post-step side effect. Currently only `update_row` is supported;
42
+ * adding new variants is purely additive (extend the union, add a dispatcher
43
+ * branch). The `tableId` is informational/audit-only — `updateRow` finds the
44
+ * row by `rowId`. The `rowId` field may contain `{{itemVariable.field}}`
45
+ * placeholders that are resolved against the current loop iteration's row.
46
+ */
47
+ export type StepPostAction = {
48
+ type: "update_row";
49
+ tableId: string;
50
+ rowId: string;
51
+ column: string;
52
+ };
53
+
25
54
  /** Selector for auto-discovering documents from the project pool */
26
55
  export interface DocumentSelector {
27
56
  fromWorkflowId?: string;
@@ -40,6 +69,16 @@ export interface LoopConfig {
40
69
  assignedAgent?: string;
41
70
  agentProfile?: string;
42
71
  completionSignals?: string[];
72
+ /**
73
+ * Row-driven loop: when set, the loop iterates once per item instead of
74
+ * looping autonomously until completionSignals fire. Each item is bound
75
+ * into the prompt template under the name in `itemVariable` (default
76
+ * "item"). Used by bulk row enrichment workflows. Iteration count is
77
+ * still capped by `maxIterations`. See features/bulk-row-enrichment.md.
78
+ */
79
+ items?: unknown[];
80
+ /** Variable name the current item is bound to (default "item"). */
81
+ itemVariable?: string;
43
82
  }
44
83
 
45
84
  export interface SwarmConfig {
@@ -51,10 +90,37 @@ export interface WorkflowDefinition {
51
90
  steps: WorkflowStep[];
52
91
  loopConfig?: LoopConfig;
53
92
  swarmConfig?: SwarmConfig;
93
+ metadata?: WorkflowMetadata;
54
94
  /** Parent task ID — set when workflow is created from AI assist, used to propagate document context */
55
95
  sourceTaskId?: string;
56
96
  }
57
97
 
98
+ export interface WorkflowMetadata {
99
+ enrichment?: WorkflowEnrichmentMetadata;
100
+ }
101
+
102
+ export interface WorkflowEnrichmentTargetContract {
103
+ columnName: string;
104
+ columnLabel: string;
105
+ dataType: "text" | "number" | "boolean" | "select" | "url" | "email";
106
+ allowedOptions?: string[];
107
+ }
108
+
109
+ export interface WorkflowEnrichmentMetadata {
110
+ tableId: string;
111
+ tableName: string;
112
+ targetColumn: string;
113
+ targetColumnLabel: string;
114
+ promptMode: "auto" | "custom";
115
+ strategy:
116
+ | "single-pass-lookup"
117
+ | "single-pass-classify"
118
+ | "research-and-synthesize";
119
+ agentProfile: string;
120
+ eligibleRowCount: number;
121
+ targetContract: WorkflowEnrichmentTargetContract;
122
+ }
123
+
58
124
  export type LoopStopReason =
59
125
  | "max_iterations"
60
126
  | "time_budget"
@@ -99,7 +165,9 @@ export type WorkflowStepStatus =
99
165
  | "completed"
100
166
  | "failed"
101
167
  | "waiting_approval"
102
- | "waiting_dependencies";
168
+ | "waiting_dependencies"
169
+ /** Step is a time delay and the workflow is paused waiting for resume_at. */
170
+ | "delayed";
103
171
 
104
172
  export interface StepState {
105
173
  stepId: string;
@@ -132,3 +200,100 @@ export function createInitialState(definition: WorkflowDefinition): WorkflowStat
132
200
  startedAt: new Date().toISOString(),
133
201
  };
134
202
  }
203
+
204
+ /**
205
+ * Document reference returned by the workflow status API alongside each step
206
+ * (output) or parent task (input). The shape matches what the API route
207
+ * actually selects from the documents table.
208
+ */
209
+ export interface WorkflowStatusDocument {
210
+ id: string;
211
+ originalName: string;
212
+ mimeType: string;
213
+ storagePath: string;
214
+ direction: string;
215
+ }
216
+
217
+ /**
218
+ * Step with computed state — the shape returned by the status API for every
219
+ * non-loop pattern (sequence, parallel, swarm, planner-executor, checkpoint).
220
+ * `state` is always present because the route synthesizes a pending placeholder
221
+ * when the real stepState hasn't been created yet.
222
+ */
223
+ export interface StepWithState extends WorkflowStep {
224
+ state: StepState;
225
+ }
226
+
227
+ /**
228
+ * Run-history summary row returned alongside every status response — counts
229
+ * of tasks per workflow run number.
230
+ */
231
+ export interface WorkflowRunHistoryEntry {
232
+ runNumber: number | null;
233
+ taskCount: number;
234
+ completedCount: number;
235
+ failedCount: number;
236
+ }
237
+
238
+ /**
239
+ * All non-loop workflow patterns share one response shape. Alias exists so
240
+ * the union arm and the new-pattern checklist (TDR-031) can both reference
241
+ * it — when a new pattern is added to WorkflowPattern, it automatically
242
+ * joins this arm unless the author explicitly adds a new arm with different
243
+ * fields.
244
+ */
245
+ export type NonLoopPattern = Exclude<WorkflowPattern, "loop">;
246
+
247
+ /**
248
+ * Discriminated union shape for `GET /api/workflows/[id]/status` responses.
249
+ * Consumers MUST narrow on `pattern` before reading pattern-specific fields.
250
+ * See TDR-031: Workflow status API is a pattern-discriminated union.
251
+ *
252
+ * - Loop arm: raw step definitions (no `.state`), plus `loopState` carrying
253
+ * the real iteration progress in `loopState.iterations[]`. Consumers that
254
+ * need completed outputs for a loop workflow should read from
255
+ * `loopState.iterations[].result`, not `steps[].state.result` (which does
256
+ * not exist on this arm — it's a compile error).
257
+ *
258
+ * - Non-loop arm: each step wrapped with `.state` synthesized from
259
+ * `workflowState.stepStates[i]`, plus `resumeAt` for delay pauses. The
260
+ * non-loop arm covers sequence, parallel, swarm, planner-executor, and
261
+ * checkpoint patterns — they all share the step-state rendering path.
262
+ */
263
+ export type WorkflowStatusResponse =
264
+ | {
265
+ pattern: "loop";
266
+ id: string;
267
+ name: string;
268
+ status: string;
269
+ projectId?: string | null;
270
+ definition?: string;
271
+ loopConfig?: LoopConfig;
272
+ loopState: LoopState | null;
273
+ swarmConfig?: SwarmConfig;
274
+ /** Raw step definitions — no `.state` on this arm. Reading from the
275
+ * iteration stream (`loopState.iterations[]`) is the correct path. */
276
+ steps: WorkflowStep[];
277
+ stepDocuments?: Record<string, WorkflowStatusDocument[]>;
278
+ parentDocuments?: WorkflowStatusDocument[];
279
+ runNumber?: number;
280
+ runHistory?: WorkflowRunHistoryEntry[];
281
+ }
282
+ | {
283
+ pattern: NonLoopPattern;
284
+ id: string;
285
+ name: string;
286
+ status: string;
287
+ /** Epoch ms for a paused (delay-step) workflow's scheduled resume. */
288
+ resumeAt: number | null;
289
+ projectId?: string | null;
290
+ definition?: string;
291
+ swarmConfig?: SwarmConfig;
292
+ /** Each step wrapped with `.state` — always present on this arm. */
293
+ steps: StepWithState[];
294
+ workflowState: WorkflowState | null;
295
+ stepDocuments?: Record<string, WorkflowStatusDocument[]>;
296
+ parentDocuments?: WorkflowStatusDocument[];
297
+ runNumber?: number;
298
+ runHistory?: WorkflowRunHistoryEntry[];
299
+ };
package/src/test/setup.ts CHANGED
@@ -3,6 +3,16 @@ import { mkdtempSync, mkdirSync } from "fs";
3
3
  import { join } from "path";
4
4
  import { tmpdir } from "os";
5
5
 
6
+ // Mock ResizeObserver for cmdk
7
+ global.ResizeObserver = class ResizeObserver {
8
+ observe() {}
9
+ unobserve() {}
10
+ disconnect() {}
11
+ };
12
+
13
+ // Mock scrollIntoView for cmdk
14
+ HTMLElement.prototype.scrollIntoView = () => {};
15
+
6
16
  if (!process.env.STAGENT_DATA_DIR) {
7
17
  const tempDataDir = mkdtempSync(join(tmpdir(), "stagent-vitest-"));
8
18
  mkdirSync(tempDataDir, { recursive: true });
@@ -1,28 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { createCheckoutSession } from "@/lib/billing/stripe";
3
- import { TIERS } from "@/lib/license/tier-limits";
4
-
5
- /**
6
- * POST /api/license/checkout
7
- * Creates a Stripe Checkout Session and returns the URL.
8
- * Body: { tier: "solo"|"operator"|"scale", billingPeriod?: "monthly"|"annual" }
9
- */
10
- export async function POST(req: NextRequest) {
11
- const body = await req.json();
12
- const { tier, billingPeriod } = body;
13
-
14
- if (!tier || tier === "community" || !TIERS.includes(tier)) {
15
- return NextResponse.json(
16
- { error: "Valid paid tier required (solo, operator, scale)" },
17
- { status: 400 }
18
- );
19
- }
20
-
21
- const result = await createCheckoutSession(tier, billingPeriod ?? "monthly");
22
-
23
- if ("error" in result) {
24
- return NextResponse.json({ error: result.error }, { status: 502 });
25
- }
26
-
27
- return NextResponse.json({ url: result.url });
28
- }
@@ -1,26 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { createPortalSession } from "@/lib/billing/stripe";
3
-
4
- /**
5
- * GET /api/license/portal
6
- * Creates a Stripe Customer Portal session and redirects.
7
- * Query: ?email=user@example.com
8
- */
9
- export async function GET(req: NextRequest) {
10
- const email = req.nextUrl.searchParams.get("email");
11
-
12
- if (!email) {
13
- return NextResponse.json(
14
- { error: "email query parameter required" },
15
- { status: 400 }
16
- );
17
- }
18
-
19
- const result = await createPortalSession(email);
20
-
21
- if ("error" in result) {
22
- return NextResponse.json({ error: result.error }, { status: 502 });
23
- }
24
-
25
- return NextResponse.redirect(result.url);
26
- }
@@ -1,89 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { licenseManager } from "@/lib/license/manager";
3
- import { TIER_LIMITS, type LimitResource } from "@/lib/license/tier-limits";
4
- import { LICENSE_FEATURES, canAccessFeature } from "@/lib/license/features";
5
- import { activateLicenseSchema } from "@/lib/validators/license";
6
- import type { LicenseTier } from "@/lib/license/tier-limits";
7
-
8
- /**
9
- * GET /api/license — current license status, feature flags, and limits.
10
- */
11
- export async function GET() {
12
- // Read from DB directly — avoids stale singleton cache in Turbopack dev mode
13
- const status = licenseManager.getStatusFromDb();
14
- const tier = status.tier;
15
-
16
- // Build feature access map
17
- const features: Record<string, boolean> = {};
18
- for (const feature of LICENSE_FEATURES) {
19
- features[feature] = canAccessFeature(tier, feature);
20
- }
21
-
22
- // Build limit map
23
- const limits: Record<string, number> = {};
24
- for (const key of Object.keys(TIER_LIMITS[tier]) as LimitResource[]) {
25
- const val = TIER_LIMITS[tier][key];
26
- limits[key] = val === Infinity ? -1 : val; // -1 = unlimited for JSON
27
- }
28
-
29
- return NextResponse.json({
30
- tier: status.tier,
31
- status: status.status,
32
- email: status.email,
33
- activatedAt: status.activatedAt?.toISOString() ?? null,
34
- expiresAt: status.expiresAt?.toISOString() ?? null,
35
- lastValidatedAt: status.lastValidatedAt?.toISOString() ?? null,
36
- gracePeriodExpiresAt: status.gracePeriodExpiresAt?.toISOString() ?? null,
37
- isPremium: tier !== "community",
38
- features,
39
- limits,
40
- });
41
- }
42
-
43
- /**
44
- * POST /api/license — activate a license.
45
- * Accepts either a license key or direct tier/email/token (from Stripe webhook flow).
46
- */
47
- export async function POST(req: NextRequest) {
48
- const body = await req.json();
49
- const parsed = activateLicenseSchema.safeParse(body);
50
-
51
- if (!parsed.success) {
52
- return NextResponse.json(
53
- { error: "Invalid request", details: parsed.error.flatten() },
54
- { status: 400 }
55
- );
56
- }
57
-
58
- const { tier, email, token } = parsed.data;
59
-
60
- if (!tier || !email) {
61
- return NextResponse.json(
62
- { error: "tier and email are required for activation" },
63
- { status: 400 }
64
- );
65
- }
66
-
67
- licenseManager.activate({
68
- tier: tier as LicenseTier,
69
- email,
70
- encryptedToken: token,
71
- });
72
-
73
- return NextResponse.json({
74
- success: true,
75
- tier: licenseManager.getTier(),
76
- status: licenseManager.getStatus().status,
77
- });
78
- }
79
-
80
- /**
81
- * DELETE /api/license — deactivate and revert to community.
82
- */
83
- export async function DELETE() {
84
- licenseManager.deactivate();
85
- return NextResponse.json({
86
- success: true,
87
- tier: "community",
88
- });
89
- }
@@ -1,63 +0,0 @@
1
- import { NextResponse } from "next/server";
2
- import { licenseManager } from "@/lib/license/manager";
3
- import { getContextVersionCount, getActiveScheduleCount } from "@/lib/license/limit-queries";
4
- import { getAllExecutions } from "@/lib/agents/execution-manager";
5
- import { db } from "@/lib/db";
6
- import { agentMemory } from "@/lib/db/schema";
7
- import { eq, sql, desc } from "drizzle-orm";
8
-
9
- /**
10
- * GET /api/license/usage
11
- * Returns current usage counts and limits for the subscription UI.
12
- */
13
- export async function GET() {
14
- // Find the profile with the most memories for the usage display
15
- const topProfile = db
16
- .select({
17
- profileId: agentMemory.profileId,
18
- count: sql<number>`count(*)`,
19
- })
20
- .from(agentMemory)
21
- .where(eq(agentMemory.status, "active"))
22
- .groupBy(agentMemory.profileId)
23
- .orderBy(desc(sql`count(*)`))
24
- .limit(1)
25
- .get();
26
-
27
- const memoryCount = topProfile?.count ?? 0;
28
-
29
- // Find the profile with the most context versions
30
- // Use the same top profile for consistency
31
- const contextCount = topProfile?.profileId
32
- ? getContextVersionCount(topProfile.profileId)
33
- : 0;
34
-
35
- const scheduleCount = getActiveScheduleCount();
36
- const parallelCount = getAllExecutions().size;
37
-
38
- const limits = {
39
- agentMemories: licenseManager.getLimit("agentMemories"),
40
- contextVersions: licenseManager.getLimit("contextVersions"),
41
- activeSchedules: licenseManager.getLimit("activeSchedules"),
42
- parallelWorkflows: licenseManager.getLimit("parallelWorkflows"),
43
- };
44
-
45
- return NextResponse.json({
46
- agentMemories: {
47
- current: memoryCount,
48
- limit: limits.agentMemories === Infinity ? -1 : limits.agentMemories,
49
- },
50
- contextVersions: {
51
- current: contextCount,
52
- limit: limits.contextVersions === Infinity ? -1 : limits.contextVersions,
53
- },
54
- activeSchedules: {
55
- current: scheduleCount,
56
- limit: limits.activeSchedules === Infinity ? -1 : limits.activeSchedules,
57
- },
58
- parallelWorkflows: {
59
- current: parallelCount,
60
- limit: limits.parallelWorkflows === Infinity ? -1 : limits.parallelWorkflows,
61
- },
62
- });
63
- }
@@ -1,15 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { browseBlueprints } from "@/lib/marketplace/marketplace-client";
3
-
4
- /**
5
- * GET /api/marketplace/browse?page=1&category=general
6
- * Browse published marketplace blueprints. Available to all tiers.
7
- */
8
- export async function GET(req: NextRequest) {
9
- const { searchParams } = req.nextUrl;
10
- const page = parseInt(searchParams.get("page") ?? "1", 10);
11
- const category = searchParams.get("category") ?? undefined;
12
-
13
- const result = await browseBlueprints(page, category);
14
- return NextResponse.json(result);
15
- }
@@ -1,28 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { licenseManager } from "@/lib/license/manager";
3
- import { importBlueprint } from "@/lib/marketplace/marketplace-client";
4
-
5
- /**
6
- * POST /api/marketplace/import
7
- * Import a blueprint from the marketplace. Requires Solo+ tier.
8
- */
9
- export async function POST(req: NextRequest) {
10
- if (!licenseManager.isFeatureAllowed("marketplace-import")) {
11
- return NextResponse.json(
12
- { error: "Blueprint import requires Solo tier or above" },
13
- { status: 402 }
14
- );
15
- }
16
-
17
- const { blueprintId } = await req.json();
18
- if (!blueprintId) {
19
- return NextResponse.json({ error: "blueprintId required" }, { status: 400 });
20
- }
21
-
22
- const result = await importBlueprint(blueprintId);
23
- if (!result.success) {
24
- return NextResponse.json({ error: result.error }, { status: 500 });
25
- }
26
-
27
- return NextResponse.json({ content: result.content });
28
- }
@@ -1,40 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { licenseManager } from "@/lib/license/manager";
3
- import { publishBlueprint } from "@/lib/marketplace/marketplace-client";
4
-
5
- /**
6
- * POST /api/marketplace/publish
7
- * Publish a workflow blueprint to the marketplace. Requires Operator+ tier.
8
- */
9
- export async function POST(req: NextRequest) {
10
- if (!licenseManager.isFeatureAllowed("marketplace-publish")) {
11
- return NextResponse.json(
12
- { error: "Blueprint publishing requires Operator tier or above" },
13
- { status: 402 }
14
- );
15
- }
16
-
17
- const body = await req.json();
18
- const { title, description, category, content, tags } = body;
19
-
20
- if (!title || !content) {
21
- return NextResponse.json(
22
- { error: "title and content are required" },
23
- { status: 400 }
24
- );
25
- }
26
-
27
- const result = await publishBlueprint({
28
- title,
29
- description: description ?? "",
30
- category: category ?? "general",
31
- content,
32
- tags: tags ?? [],
33
- });
34
-
35
- if (!result.success) {
36
- return NextResponse.json({ error: result.error }, { status: 500 });
37
- }
38
-
39
- return NextResponse.json({ id: result.id }, { status: 201 });
40
- }
@@ -1,53 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { z } from "zod";
3
- import { getSupabaseClient } from "@/lib/cloud/supabase-client";
4
-
5
- const emailSchema = z.object({
6
- email: z.string().email(),
7
- });
8
-
9
- /**
10
- * POST /api/onboarding/email
11
- * Creates a Supabase Auth user via magic link.
12
- * Fire-and-forget — does not block any local functionality.
13
- */
14
- export async function POST(req: NextRequest) {
15
- const body = await req.json();
16
- const parsed = emailSchema.safeParse(body);
17
-
18
- if (!parsed.success) {
19
- return NextResponse.json(
20
- { error: "Valid email required" },
21
- { status: 400 }
22
- );
23
- }
24
-
25
- const supabase = getSupabaseClient();
26
- if (!supabase) {
27
- // Cloud not configured — silently succeed
28
- return NextResponse.json({ ok: true });
29
- }
30
-
31
- try {
32
- const { error } = await supabase.auth.signInWithOtp({
33
- email: parsed.data.email,
34
- options: {
35
- emailRedirectTo: `${process.env.NEXT_PUBLIC_SUPABASE_URL ?? "http://localhost:3000"}/auth/callback`,
36
- },
37
- });
38
-
39
- if (error) {
40
- return NextResponse.json(
41
- { error: error.message },
42
- { status: 502 }
43
- );
44
- }
45
-
46
- return NextResponse.json({ ok: true });
47
- } catch {
48
- return NextResponse.json(
49
- { error: "Failed to connect to cloud service" },
50
- { status: 502 }
51
- );
52
- }
53
- }
@@ -1,14 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { getSetting, setSetting } from "@/lib/settings/helpers";
3
- import { SETTINGS_KEYS } from "@/lib/constants/settings";
4
-
5
- export async function GET() {
6
- const enabled = await getSetting(SETTINGS_KEYS.TELEMETRY_ENABLED);
7
- return NextResponse.json({ enabled: enabled === "true" });
8
- }
9
-
10
- export async function POST(req: NextRequest) {
11
- const { enabled } = await req.json();
12
- await setSetting(SETTINGS_KEYS.TELEMETRY_ENABLED, String(!!enabled));
13
- return NextResponse.json({ enabled: !!enabled });
14
- }
@@ -1,54 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { licenseManager } from "@/lib/license/manager";
3
- import { exportAndUpload } from "@/lib/sync/cloud-sync";
4
- import { getSettingSync } from "@/lib/settings/helpers";
5
- import { SETTINGS_KEYS } from "@/lib/constants/settings";
6
-
7
- /**
8
- * POST /api/sync/export
9
- * Export and encrypt the database, upload to Supabase Storage.
10
- * Requires Operator+ tier and an authenticated Supabase session.
11
- * Body: { accessToken: string } — the user's Supabase JWT
12
- */
13
- export async function POST(req: NextRequest) {
14
- if (!licenseManager.isFeatureAllowed("cloud-sync")) {
15
- return NextResponse.json(
16
- { error: "Cloud sync requires Operator tier or above" },
17
- { status: 402 }
18
- );
19
- }
20
-
21
- const email = licenseManager.getStatus().email;
22
- if (!email) {
23
- return NextResponse.json(
24
- { error: "Sign in with your email first (Settings → Cloud Account)" },
25
- { status: 400 }
26
- );
27
- }
28
-
29
- // Get the user's access token for authenticated Storage uploads
30
- const body = await req.json().catch(() => ({}));
31
- const accessToken = body.accessToken as string | undefined;
32
-
33
- let deviceId = getSettingSync(SETTINGS_KEYS.DEVICE_ID);
34
- if (!deviceId) {
35
- deviceId = crypto.randomUUID();
36
- const { setSetting } = await import("@/lib/settings/helpers");
37
- await setSetting(SETTINGS_KEYS.DEVICE_ID, deviceId);
38
- }
39
-
40
- const result = await exportAndUpload(email, deviceId, accessToken);
41
-
42
- if (!result.success) {
43
- return NextResponse.json({ error: result.error }, { status: 500 });
44
- }
45
-
46
- const { setSetting } = await import("@/lib/settings/helpers");
47
- await setSetting(SETTINGS_KEYS.LAST_SYNC_AT, new Date().toISOString());
48
-
49
- return NextResponse.json({
50
- success: true,
51
- blobPath: result.blobPath,
52
- sizeBytes: result.sizeBytes,
53
- });
54
- }
@@ -1,37 +0,0 @@
1
- import { NextResponse } from "next/server";
2
- import { licenseManager } from "@/lib/license/manager";
3
- import { downloadAndRestore } from "@/lib/sync/cloud-sync";
4
-
5
- /**
6
- * POST /api/sync/restore
7
- * Download the latest snapshot, decrypt, and restore.
8
- * Requires Operator+ tier. Creates a safety backup first.
9
- */
10
- export async function POST() {
11
- if (!licenseManager.isFeatureAllowed("cloud-sync")) {
12
- return NextResponse.json(
13
- { error: "Cloud sync requires Operator tier or above" },
14
- { status: 402 }
15
- );
16
- }
17
-
18
- const email = licenseManager.getStatus().email;
19
- if (!email) {
20
- return NextResponse.json(
21
- { error: "No license email — activate your license first" },
22
- { status: 400 }
23
- );
24
- }
25
-
26
- const result = await downloadAndRestore(email);
27
-
28
- if (!result.success) {
29
- return NextResponse.json({ error: result.error }, { status: 500 });
30
- }
31
-
32
- return NextResponse.json({
33
- success: true,
34
- sizeBytes: result.sizeBytes,
35
- message: "Database restored. Restart the app to apply changes.",
36
- });
37
- }