availsync 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 (460) hide show
  1. package/.adal/skills/stripe-best-practices/SKILL.md +42 -0
  2. package/.adal/skills/stripe-best-practices/references/billing.md +36 -0
  3. package/.adal/skills/stripe-best-practices/references/connect.md +48 -0
  4. package/.adal/skills/stripe-best-practices/references/payments.md +79 -0
  5. package/.adal/skills/stripe-best-practices/references/security.md +109 -0
  6. package/.adal/skills/stripe-best-practices/references/treasury.md +16 -0
  7. package/.adal/skills/stripe-projects/SKILL.md +139 -0
  8. package/.adal/skills/upgrade-stripe/SKILL.md +185 -0
  9. package/.agents/skills/stripe-best-practices/SKILL.md +42 -0
  10. package/.agents/skills/stripe-best-practices/references/billing.md +36 -0
  11. package/.agents/skills/stripe-best-practices/references/connect.md +48 -0
  12. package/.agents/skills/stripe-best-practices/references/payments.md +79 -0
  13. package/.agents/skills/stripe-best-practices/references/security.md +109 -0
  14. package/.agents/skills/stripe-best-practices/references/treasury.md +16 -0
  15. package/.agents/skills/stripe-projects/SKILL.md +139 -0
  16. package/.agents/skills/upgrade-stripe/SKILL.md +185 -0
  17. package/.augment/skills/stripe-best-practices/SKILL.md +42 -0
  18. package/.augment/skills/stripe-best-practices/references/billing.md +36 -0
  19. package/.augment/skills/stripe-best-practices/references/connect.md +48 -0
  20. package/.augment/skills/stripe-best-practices/references/payments.md +79 -0
  21. package/.augment/skills/stripe-best-practices/references/security.md +109 -0
  22. package/.augment/skills/stripe-best-practices/references/treasury.md +16 -0
  23. package/.augment/skills/stripe-projects/SKILL.md +139 -0
  24. package/.augment/skills/upgrade-stripe/SKILL.md +185 -0
  25. package/.bob/skills/stripe-best-practices/SKILL.md +42 -0
  26. package/.bob/skills/stripe-best-practices/references/billing.md +36 -0
  27. package/.bob/skills/stripe-best-practices/references/connect.md +48 -0
  28. package/.bob/skills/stripe-best-practices/references/payments.md +79 -0
  29. package/.bob/skills/stripe-best-practices/references/security.md +109 -0
  30. package/.bob/skills/stripe-best-practices/references/treasury.md +16 -0
  31. package/.bob/skills/stripe-projects/SKILL.md +139 -0
  32. package/.bob/skills/upgrade-stripe/SKILL.md +185 -0
  33. package/.claude/settings.local.json +7 -0
  34. package/.claude/skills/stripe-best-practices/SKILL.md +42 -0
  35. package/.claude/skills/stripe-best-practices/references/billing.md +36 -0
  36. package/.claude/skills/stripe-best-practices/references/connect.md +48 -0
  37. package/.claude/skills/stripe-best-practices/references/payments.md +79 -0
  38. package/.claude/skills/stripe-best-practices/references/security.md +109 -0
  39. package/.claude/skills/stripe-best-practices/references/treasury.md +16 -0
  40. package/.claude/skills/stripe-projects/SKILL.md +139 -0
  41. package/.claude/skills/upgrade-stripe/SKILL.md +185 -0
  42. package/.codebuddy/skills/stripe-best-practices/SKILL.md +42 -0
  43. package/.codebuddy/skills/stripe-best-practices/references/billing.md +36 -0
  44. package/.codebuddy/skills/stripe-best-practices/references/connect.md +48 -0
  45. package/.codebuddy/skills/stripe-best-practices/references/payments.md +79 -0
  46. package/.codebuddy/skills/stripe-best-practices/references/security.md +109 -0
  47. package/.codebuddy/skills/stripe-best-practices/references/treasury.md +16 -0
  48. package/.codebuddy/skills/stripe-projects/SKILL.md +139 -0
  49. package/.codebuddy/skills/upgrade-stripe/SKILL.md +185 -0
  50. package/.commandcode/skills/stripe-best-practices/SKILL.md +42 -0
  51. package/.commandcode/skills/stripe-best-practices/references/billing.md +36 -0
  52. package/.commandcode/skills/stripe-best-practices/references/connect.md +48 -0
  53. package/.commandcode/skills/stripe-best-practices/references/payments.md +79 -0
  54. package/.commandcode/skills/stripe-best-practices/references/security.md +109 -0
  55. package/.commandcode/skills/stripe-best-practices/references/treasury.md +16 -0
  56. package/.commandcode/skills/stripe-projects/SKILL.md +139 -0
  57. package/.commandcode/skills/upgrade-stripe/SKILL.md +185 -0
  58. package/.continue/skills/stripe-best-practices/SKILL.md +42 -0
  59. package/.continue/skills/stripe-best-practices/references/billing.md +36 -0
  60. package/.continue/skills/stripe-best-practices/references/connect.md +48 -0
  61. package/.continue/skills/stripe-best-practices/references/payments.md +79 -0
  62. package/.continue/skills/stripe-best-practices/references/security.md +109 -0
  63. package/.continue/skills/stripe-best-practices/references/treasury.md +16 -0
  64. package/.continue/skills/stripe-projects/SKILL.md +139 -0
  65. package/.continue/skills/upgrade-stripe/SKILL.md +185 -0
  66. package/.cortex/skills/stripe-best-practices/SKILL.md +42 -0
  67. package/.cortex/skills/stripe-best-practices/references/billing.md +36 -0
  68. package/.cortex/skills/stripe-best-practices/references/connect.md +48 -0
  69. package/.cortex/skills/stripe-best-practices/references/payments.md +79 -0
  70. package/.cortex/skills/stripe-best-practices/references/security.md +109 -0
  71. package/.cortex/skills/stripe-best-practices/references/treasury.md +16 -0
  72. package/.cortex/skills/stripe-projects/SKILL.md +139 -0
  73. package/.cortex/skills/upgrade-stripe/SKILL.md +185 -0
  74. package/.crush/skills/stripe-best-practices/SKILL.md +42 -0
  75. package/.crush/skills/stripe-best-practices/references/billing.md +36 -0
  76. package/.crush/skills/stripe-best-practices/references/connect.md +48 -0
  77. package/.crush/skills/stripe-best-practices/references/payments.md +79 -0
  78. package/.crush/skills/stripe-best-practices/references/security.md +109 -0
  79. package/.crush/skills/stripe-best-practices/references/treasury.md +16 -0
  80. package/.crush/skills/stripe-projects/SKILL.md +139 -0
  81. package/.crush/skills/upgrade-stripe/SKILL.md +185 -0
  82. package/.env.example +20 -0
  83. package/.factory/skills/stripe-best-practices/SKILL.md +42 -0
  84. package/.factory/skills/stripe-best-practices/references/billing.md +36 -0
  85. package/.factory/skills/stripe-best-practices/references/connect.md +48 -0
  86. package/.factory/skills/stripe-best-practices/references/payments.md +79 -0
  87. package/.factory/skills/stripe-best-practices/references/security.md +109 -0
  88. package/.factory/skills/stripe-best-practices/references/treasury.md +16 -0
  89. package/.factory/skills/stripe-projects/SKILL.md +139 -0
  90. package/.factory/skills/upgrade-stripe/SKILL.md +185 -0
  91. package/.goose/skills/stripe-best-practices/SKILL.md +42 -0
  92. package/.goose/skills/stripe-best-practices/references/billing.md +36 -0
  93. package/.goose/skills/stripe-best-practices/references/connect.md +48 -0
  94. package/.goose/skills/stripe-best-practices/references/payments.md +79 -0
  95. package/.goose/skills/stripe-best-practices/references/security.md +109 -0
  96. package/.goose/skills/stripe-best-practices/references/treasury.md +16 -0
  97. package/.goose/skills/stripe-projects/SKILL.md +139 -0
  98. package/.goose/skills/upgrade-stripe/SKILL.md +185 -0
  99. package/.iflow/skills/stripe-best-practices/SKILL.md +42 -0
  100. package/.iflow/skills/stripe-best-practices/references/billing.md +36 -0
  101. package/.iflow/skills/stripe-best-practices/references/connect.md +48 -0
  102. package/.iflow/skills/stripe-best-practices/references/payments.md +79 -0
  103. package/.iflow/skills/stripe-best-practices/references/security.md +109 -0
  104. package/.iflow/skills/stripe-best-practices/references/treasury.md +16 -0
  105. package/.iflow/skills/stripe-projects/SKILL.md +139 -0
  106. package/.iflow/skills/upgrade-stripe/SKILL.md +185 -0
  107. package/.junie/skills/stripe-best-practices/SKILL.md +42 -0
  108. package/.junie/skills/stripe-best-practices/references/billing.md +36 -0
  109. package/.junie/skills/stripe-best-practices/references/connect.md +48 -0
  110. package/.junie/skills/stripe-best-practices/references/payments.md +79 -0
  111. package/.junie/skills/stripe-best-practices/references/security.md +109 -0
  112. package/.junie/skills/stripe-best-practices/references/treasury.md +16 -0
  113. package/.junie/skills/stripe-projects/SKILL.md +139 -0
  114. package/.junie/skills/upgrade-stripe/SKILL.md +185 -0
  115. package/.kilocode/skills/stripe-best-practices/SKILL.md +42 -0
  116. package/.kilocode/skills/stripe-best-practices/references/billing.md +36 -0
  117. package/.kilocode/skills/stripe-best-practices/references/connect.md +48 -0
  118. package/.kilocode/skills/stripe-best-practices/references/payments.md +79 -0
  119. package/.kilocode/skills/stripe-best-practices/references/security.md +109 -0
  120. package/.kilocode/skills/stripe-best-practices/references/treasury.md +16 -0
  121. package/.kilocode/skills/stripe-projects/SKILL.md +139 -0
  122. package/.kilocode/skills/upgrade-stripe/SKILL.md +185 -0
  123. package/.kiro/skills/stripe-best-practices/SKILL.md +42 -0
  124. package/.kiro/skills/stripe-best-practices/references/billing.md +36 -0
  125. package/.kiro/skills/stripe-best-practices/references/connect.md +48 -0
  126. package/.kiro/skills/stripe-best-practices/references/payments.md +79 -0
  127. package/.kiro/skills/stripe-best-practices/references/security.md +109 -0
  128. package/.kiro/skills/stripe-best-practices/references/treasury.md +16 -0
  129. package/.kiro/skills/stripe-projects/SKILL.md +139 -0
  130. package/.kiro/skills/upgrade-stripe/SKILL.md +185 -0
  131. package/.kode/skills/stripe-best-practices/SKILL.md +42 -0
  132. package/.kode/skills/stripe-best-practices/references/billing.md +36 -0
  133. package/.kode/skills/stripe-best-practices/references/connect.md +48 -0
  134. package/.kode/skills/stripe-best-practices/references/payments.md +79 -0
  135. package/.kode/skills/stripe-best-practices/references/security.md +109 -0
  136. package/.kode/skills/stripe-best-practices/references/treasury.md +16 -0
  137. package/.kode/skills/stripe-projects/SKILL.md +139 -0
  138. package/.kode/skills/upgrade-stripe/SKILL.md +185 -0
  139. package/.mcpjam/skills/stripe-best-practices/SKILL.md +42 -0
  140. package/.mcpjam/skills/stripe-best-practices/references/billing.md +36 -0
  141. package/.mcpjam/skills/stripe-best-practices/references/connect.md +48 -0
  142. package/.mcpjam/skills/stripe-best-practices/references/payments.md +79 -0
  143. package/.mcpjam/skills/stripe-best-practices/references/security.md +109 -0
  144. package/.mcpjam/skills/stripe-best-practices/references/treasury.md +16 -0
  145. package/.mcpjam/skills/stripe-projects/SKILL.md +139 -0
  146. package/.mcpjam/skills/upgrade-stripe/SKILL.md +185 -0
  147. package/.mux/skills/stripe-best-practices/SKILL.md +42 -0
  148. package/.mux/skills/stripe-best-practices/references/billing.md +36 -0
  149. package/.mux/skills/stripe-best-practices/references/connect.md +48 -0
  150. package/.mux/skills/stripe-best-practices/references/payments.md +79 -0
  151. package/.mux/skills/stripe-best-practices/references/security.md +109 -0
  152. package/.mux/skills/stripe-best-practices/references/treasury.md +16 -0
  153. package/.mux/skills/stripe-projects/SKILL.md +139 -0
  154. package/.mux/skills/upgrade-stripe/SKILL.md +185 -0
  155. package/.neovate/skills/stripe-best-practices/SKILL.md +42 -0
  156. package/.neovate/skills/stripe-best-practices/references/billing.md +36 -0
  157. package/.neovate/skills/stripe-best-practices/references/connect.md +48 -0
  158. package/.neovate/skills/stripe-best-practices/references/payments.md +79 -0
  159. package/.neovate/skills/stripe-best-practices/references/security.md +109 -0
  160. package/.neovate/skills/stripe-best-practices/references/treasury.md +16 -0
  161. package/.neovate/skills/stripe-projects/SKILL.md +139 -0
  162. package/.neovate/skills/upgrade-stripe/SKILL.md +185 -0
  163. package/.nixpacksignore +14 -0
  164. package/.openhands/skills/stripe-best-practices/SKILL.md +42 -0
  165. package/.openhands/skills/stripe-best-practices/references/billing.md +36 -0
  166. package/.openhands/skills/stripe-best-practices/references/connect.md +48 -0
  167. package/.openhands/skills/stripe-best-practices/references/payments.md +79 -0
  168. package/.openhands/skills/stripe-best-practices/references/security.md +109 -0
  169. package/.openhands/skills/stripe-best-practices/references/treasury.md +16 -0
  170. package/.openhands/skills/stripe-projects/SKILL.md +139 -0
  171. package/.openhands/skills/upgrade-stripe/SKILL.md +185 -0
  172. package/.pi/skills/stripe-best-practices/SKILL.md +42 -0
  173. package/.pi/skills/stripe-best-practices/references/billing.md +36 -0
  174. package/.pi/skills/stripe-best-practices/references/connect.md +48 -0
  175. package/.pi/skills/stripe-best-practices/references/payments.md +79 -0
  176. package/.pi/skills/stripe-best-practices/references/security.md +109 -0
  177. package/.pi/skills/stripe-best-practices/references/treasury.md +16 -0
  178. package/.pi/skills/stripe-projects/SKILL.md +139 -0
  179. package/.pi/skills/upgrade-stripe/SKILL.md +185 -0
  180. package/.pochi/skills/stripe-best-practices/SKILL.md +42 -0
  181. package/.pochi/skills/stripe-best-practices/references/billing.md +36 -0
  182. package/.pochi/skills/stripe-best-practices/references/connect.md +48 -0
  183. package/.pochi/skills/stripe-best-practices/references/payments.md +79 -0
  184. package/.pochi/skills/stripe-best-practices/references/security.md +109 -0
  185. package/.pochi/skills/stripe-best-practices/references/treasury.md +16 -0
  186. package/.pochi/skills/stripe-projects/SKILL.md +139 -0
  187. package/.pochi/skills/upgrade-stripe/SKILL.md +185 -0
  188. package/.qoder/skills/stripe-best-practices/SKILL.md +42 -0
  189. package/.qoder/skills/stripe-best-practices/references/billing.md +36 -0
  190. package/.qoder/skills/stripe-best-practices/references/connect.md +48 -0
  191. package/.qoder/skills/stripe-best-practices/references/payments.md +79 -0
  192. package/.qoder/skills/stripe-best-practices/references/security.md +109 -0
  193. package/.qoder/skills/stripe-best-practices/references/treasury.md +16 -0
  194. package/.qoder/skills/stripe-projects/SKILL.md +139 -0
  195. package/.qoder/skills/upgrade-stripe/SKILL.md +185 -0
  196. package/.qwen/skills/stripe-best-practices/SKILL.md +42 -0
  197. package/.qwen/skills/stripe-best-practices/references/billing.md +36 -0
  198. package/.qwen/skills/stripe-best-practices/references/connect.md +48 -0
  199. package/.qwen/skills/stripe-best-practices/references/payments.md +79 -0
  200. package/.qwen/skills/stripe-best-practices/references/security.md +109 -0
  201. package/.qwen/skills/stripe-best-practices/references/treasury.md +16 -0
  202. package/.qwen/skills/stripe-projects/SKILL.md +139 -0
  203. package/.qwen/skills/upgrade-stripe/SKILL.md +185 -0
  204. package/.roo/skills/stripe-best-practices/SKILL.md +42 -0
  205. package/.roo/skills/stripe-best-practices/references/billing.md +36 -0
  206. package/.roo/skills/stripe-best-practices/references/connect.md +48 -0
  207. package/.roo/skills/stripe-best-practices/references/payments.md +79 -0
  208. package/.roo/skills/stripe-best-practices/references/security.md +109 -0
  209. package/.roo/skills/stripe-best-practices/references/treasury.md +16 -0
  210. package/.roo/skills/stripe-projects/SKILL.md +139 -0
  211. package/.roo/skills/upgrade-stripe/SKILL.md +185 -0
  212. package/.trae/skills/stripe-best-practices/SKILL.md +42 -0
  213. package/.trae/skills/stripe-best-practices/references/billing.md +36 -0
  214. package/.trae/skills/stripe-best-practices/references/connect.md +48 -0
  215. package/.trae/skills/stripe-best-practices/references/payments.md +79 -0
  216. package/.trae/skills/stripe-best-practices/references/security.md +109 -0
  217. package/.trae/skills/stripe-best-practices/references/treasury.md +16 -0
  218. package/.trae/skills/stripe-projects/SKILL.md +139 -0
  219. package/.trae/skills/upgrade-stripe/SKILL.md +185 -0
  220. package/.vibe/skills/stripe-best-practices/SKILL.md +42 -0
  221. package/.vibe/skills/stripe-best-practices/references/billing.md +36 -0
  222. package/.vibe/skills/stripe-best-practices/references/connect.md +48 -0
  223. package/.vibe/skills/stripe-best-practices/references/payments.md +79 -0
  224. package/.vibe/skills/stripe-best-practices/references/security.md +109 -0
  225. package/.vibe/skills/stripe-best-practices/references/treasury.md +16 -0
  226. package/.vibe/skills/stripe-projects/SKILL.md +139 -0
  227. package/.vibe/skills/upgrade-stripe/SKILL.md +185 -0
  228. package/.windsurf/skills/stripe-best-practices/SKILL.md +42 -0
  229. package/.windsurf/skills/stripe-best-practices/references/billing.md +36 -0
  230. package/.windsurf/skills/stripe-best-practices/references/connect.md +48 -0
  231. package/.windsurf/skills/stripe-best-practices/references/payments.md +79 -0
  232. package/.windsurf/skills/stripe-best-practices/references/security.md +109 -0
  233. package/.windsurf/skills/stripe-best-practices/references/treasury.md +16 -0
  234. package/.windsurf/skills/stripe-projects/SKILL.md +139 -0
  235. package/.windsurf/skills/upgrade-stripe/SKILL.md +185 -0
  236. package/.zencoder/skills/stripe-best-practices/SKILL.md +42 -0
  237. package/.zencoder/skills/stripe-best-practices/references/billing.md +36 -0
  238. package/.zencoder/skills/stripe-best-practices/references/connect.md +48 -0
  239. package/.zencoder/skills/stripe-best-practices/references/payments.md +79 -0
  240. package/.zencoder/skills/stripe-best-practices/references/security.md +109 -0
  241. package/.zencoder/skills/stripe-best-practices/references/treasury.md +16 -0
  242. package/.zencoder/skills/stripe-projects/SKILL.md +139 -0
  243. package/.zencoder/skills/upgrade-stripe/SKILL.md +185 -0
  244. package/AUDIT.md +95 -0
  245. package/BLOCKERS.md +0 -0
  246. package/COOLIFY.md +51 -0
  247. package/MCP_SETUP.md +23 -0
  248. package/PRODUCTION_CHECKLIST.md +246 -0
  249. package/README.md +47 -0
  250. package/ROADMAP.md +91 -0
  251. package/docs/superpowers/plans/2026-05-11-availsync-frontend-sales-flow.md +2445 -0
  252. package/frontend/.env.example +2 -0
  253. package/frontend/app/admin/layout.tsx +13 -0
  254. package/frontend/app/admin/page.tsx +747 -0
  255. package/frontend/app/app/activity/page.tsx +257 -0
  256. package/frontend/app/app/agents/[agentId]/page.tsx +21 -0
  257. package/frontend/app/app/agents/page.tsx +1155 -0
  258. package/frontend/app/app/audit/page.tsx +225 -0
  259. package/frontend/app/app/availability/page.tsx +840 -0
  260. package/frontend/app/app/holds/page.tsx +262 -0
  261. package/frontend/app/app/layout.tsx +19 -0
  262. package/frontend/app/app/onboarding/page.tsx +10 -0
  263. package/frontend/app/app/onboarding/verify/page.tsx +309 -0
  264. package/frontend/app/app/page.tsx +508 -0
  265. package/frontend/app/app/settings/page.tsx +399 -0
  266. package/frontend/app/app/work/page.tsx +426 -0
  267. package/frontend/app/changelog/page.tsx +93 -0
  268. package/frontend/app/checkout/page.tsx +25 -0
  269. package/frontend/app/docs/api/page.tsx +157 -0
  270. package/frontend/app/docs/page.tsx +296 -0
  271. package/frontend/app/docs/pilot/page.tsx +127 -0
  272. package/frontend/app/docs/quickstart/page.tsx +318 -0
  273. package/frontend/app/docs/reliability/page.tsx +78 -0
  274. package/frontend/app/docs/sdk/node/page.tsx +166 -0
  275. package/frontend/app/globals.css +57 -0
  276. package/frontend/app/icon.png +0 -0
  277. package/frontend/app/layout.tsx +87 -0
  278. package/frontend/app/login/page.tsx +14 -0
  279. package/frontend/app/page.tsx +47 -0
  280. package/frontend/app/pricing/page.tsx +66 -0
  281. package/frontend/app/privacy/page.tsx +52 -0
  282. package/frontend/app/robots.ts +26 -0
  283. package/frontend/app/security/page.tsx +74 -0
  284. package/frontend/app/signup/page.tsx +14 -0
  285. package/frontend/app/sitemap.ts +14 -0
  286. package/frontend/app/terms/page.tsx +51 -0
  287. package/frontend/components/brand/AvailsyncLogo.tsx +56 -0
  288. package/frontend/components/checkout/CheckoutClient.tsx +100 -0
  289. package/frontend/components/dashboard/AgentForm.tsx +59 -0
  290. package/frontend/components/dashboard/AppShell.tsx +291 -0
  291. package/frontend/components/dashboard/AvailabilityChecker.tsx +117 -0
  292. package/frontend/components/dashboard/AvailabilityWindowForm.tsx +40 -0
  293. package/frontend/components/dashboard/HoldForm.tsx +133 -0
  294. package/frontend/components/dashboard/MetricCard.tsx +10 -0
  295. package/frontend/components/login/LoginForm.tsx +95 -0
  296. package/frontend/components/marketing/AgentCoordinationStory.tsx +1530 -0
  297. package/frontend/components/marketing/Faq.tsx +41 -0
  298. package/frontend/components/marketing/Hero.tsx +73 -0
  299. package/frontend/components/marketing/HowItWorks.tsx +28 -0
  300. package/frontend/components/marketing/ObserveModeTeaser.tsx +41 -0
  301. package/frontend/components/marketing/PricingTeaser.tsx +23 -0
  302. package/frontend/components/marketing/ProblemSolution.tsx +36 -0
  303. package/frontend/components/marketing/SiteFooter.tsx +59 -0
  304. package/frontend/components/marketing/SiteHeader.tsx +45 -0
  305. package/frontend/components/marketing/UseCases.tsx +27 -0
  306. package/frontend/components/onboarding/OnboardingClient.tsx +278 -0
  307. package/frontend/components/pricing/PricingCards.tsx +65 -0
  308. package/frontend/components/privacy/CookieConsent.tsx +230 -0
  309. package/frontend/components/privacy/CookieSettingsButton.tsx +15 -0
  310. package/frontend/components/seo/JsonLd.tsx +10 -0
  311. package/frontend/components/signup/SignupForm.tsx +55 -0
  312. package/frontend/components/ui/Badge.tsx +23 -0
  313. package/frontend/components/ui/Button.tsx +37 -0
  314. package/frontend/components/ui/Card.tsx +11 -0
  315. package/frontend/components/ui/ConfirmDialog.tsx +77 -0
  316. package/frontend/components/ui/EmptyState.tsx +24 -0
  317. package/frontend/components/ui/Input.tsx +14 -0
  318. package/frontend/components/ui/KeyDisplay.tsx +49 -0
  319. package/frontend/components/ui/Select.tsx +14 -0
  320. package/frontend/components/ui/Skeleton.tsx +24 -0
  321. package/frontend/components/ui/Tabs.tsx +19 -0
  322. package/frontend/components/ui/Textarea.tsx +14 -0
  323. package/frontend/components/ui/Toast.tsx +78 -0
  324. package/frontend/components/waitlist/WaitlistDialog.tsx +128 -0
  325. package/frontend/lib/api.ts +1282 -0
  326. package/frontend/lib/billing.ts +6 -0
  327. package/frontend/lib/cookieConsent.ts +113 -0
  328. package/frontend/lib/format.ts +16 -0
  329. package/frontend/lib/plans.ts +62 -0
  330. package/frontend/lib/schemas.ts +108 -0
  331. package/frontend/lib/seo.ts +376 -0
  332. package/frontend/lib/setupGuides.ts +630 -0
  333. package/frontend/lib/storage.ts +30 -0
  334. package/frontend/next-env.d.ts +6 -0
  335. package/frontend/next.config.mjs +13 -0
  336. package/frontend/package-lock.json +14409 -0
  337. package/frontend/package.json +41 -0
  338. package/frontend/playwright.config.ts +20 -0
  339. package/frontend/postcss.config.mjs +8 -0
  340. package/frontend/public/.gitkeep +0 -0
  341. package/frontend/public/brand/availsync-logo-board.png +0 -0
  342. package/frontend/public/brand/availsync-logo-dark.png +0 -0
  343. package/frontend/public/brand/availsync-mark-dark.png +0 -0
  344. package/frontend/public/brand/availsync-wordmark-dark.png +0 -0
  345. package/frontend/public/marketing/hero-agent-coordination.png +0 -0
  346. package/frontend/tailwind.config.ts +53 -0
  347. package/frontend/tests/smoke.spec.ts +89 -0
  348. package/frontend/tsconfig.json +23 -0
  349. package/jest.config.js +7 -0
  350. package/nixpacks.toml +11 -0
  351. package/package.json +53 -0
  352. package/packages/mcp/LICENSE +21 -0
  353. package/packages/mcp/README.md +60 -0
  354. package/packages/mcp/jest.config.cjs +8 -0
  355. package/packages/mcp/package.json +54 -0
  356. package/packages/mcp/src/helpers.ts +38 -0
  357. package/packages/mcp/src/index.test.ts +60 -0
  358. package/packages/mcp/src/index.ts +387 -0
  359. package/packages/mcp/tsconfig.json +20 -0
  360. package/packages/mcp/tsconfig.test.json +12 -0
  361. package/packages/node/LICENSE +21 -0
  362. package/packages/node/README.md +120 -0
  363. package/packages/node/jest.config.cjs +8 -0
  364. package/packages/node/package.json +46 -0
  365. package/packages/node/src/index.test.ts +360 -0
  366. package/packages/node/src/index.ts +402 -0
  367. package/packages/node/tsconfig.json +20 -0
  368. package/packages/node/tsconfig.test.json +12 -0
  369. package/plan.md +923 -0
  370. package/skills/stripe-best-practices/SKILL.md +42 -0
  371. package/skills/stripe-best-practices/references/billing.md +36 -0
  372. package/skills/stripe-best-practices/references/connect.md +48 -0
  373. package/skills/stripe-best-practices/references/payments.md +79 -0
  374. package/skills/stripe-best-practices/references/security.md +109 -0
  375. package/skills/stripe-best-practices/references/treasury.md +16 -0
  376. package/skills/stripe-projects/SKILL.md +139 -0
  377. package/skills/upgrade-stripe/SKILL.md +185 -0
  378. package/skills-lock.json +20 -0
  379. package/src/core/availability.ts +178 -0
  380. package/src/core/conflict.ts +209 -0
  381. package/src/core/work.ts +490 -0
  382. package/src/db/client.ts +17 -0
  383. package/src/db/migrations/001_init.sql +88 -0
  384. package/src/db/migrations/002_stripe.sql +2 -0
  385. package/src/db/migrations/003_workspace_auth.sql +19 -0
  386. package/src/db/migrations/004_agent_mcp_status.sql +2 -0
  387. package/src/db/migrations/005_hold_event_actor.sql +4 -0
  388. package/src/db/migrations/006_agent_activity.sql +35 -0
  389. package/src/db/migrations/007_work_coordination.sql +60 -0
  390. package/src/db/migrations/008_work_claim_leases.sql +20 -0
  391. package/src/db/migrations/009_billing_subscription_state.sql +23 -0
  392. package/src/db/migrations/010_agent_api_key_prefix.sql +10 -0
  393. package/src/db/migrations/011_org_verified_and_work_event_retention.sql +11 -0
  394. package/src/db/migrations/012_agent_enforcement_mode.sql +12 -0
  395. package/src/db/migrations/013_support_tickets.sql +21 -0
  396. package/src/db/migrations/014_paid_plan_waitlist.sql +23 -0
  397. package/src/db/migrations/015_agent_last_seen.sql +2 -0
  398. package/src/db/migrations.ts +164 -0
  399. package/src/db/run-migrations.ts +13 -0
  400. package/src/index.ts +183 -0
  401. package/src/lib/activity.ts +137 -0
  402. package/src/lib/apiKeys.ts +32 -0
  403. package/src/lib/appInfo.ts +26 -0
  404. package/src/lib/billingConfig.ts +3 -0
  405. package/src/lib/env.ts +75 -0
  406. package/src/lib/logger.ts +8 -0
  407. package/src/lib/plans.ts +204 -0
  408. package/src/mcp/server.js +5 -0
  409. package/src/mcp/server.ts +350 -0
  410. package/src/middleware/auth.ts +342 -0
  411. package/src/middleware/requestId.ts +16 -0
  412. package/src/routes/account.ts +168 -0
  413. package/src/routes/activity.ts +126 -0
  414. package/src/routes/admin.ts +514 -0
  415. package/src/routes/audit.ts +68 -0
  416. package/src/routes/auth.ts +203 -0
  417. package/src/routes/availability.ts +325 -0
  418. package/src/routes/billing.ts +406 -0
  419. package/src/routes/conflicts.ts +131 -0
  420. package/src/routes/holds.ts +437 -0
  421. package/src/routes/mcp.ts +57 -0
  422. package/src/routes/metrics.ts +39 -0
  423. package/src/routes/onboarding.ts +273 -0
  424. package/src/routes/orgs.ts +981 -0
  425. package/src/routes/preferences.ts +132 -0
  426. package/src/routes/session.ts +16 -0
  427. package/src/routes/support.ts +77 -0
  428. package/src/routes/value.ts +186 -0
  429. package/src/routes/waitlist.ts +63 -0
  430. package/src/routes/work.ts +1578 -0
  431. package/src/server.ts +36 -0
  432. package/src/types/index.ts +109 -0
  433. package/tests/integration/activity.route.test.ts +103 -0
  434. package/tests/integration/admin.route.test.ts +143 -0
  435. package/tests/integration/agent-keys.route.test.ts +237 -0
  436. package/tests/integration/availability.route.test.ts +125 -0
  437. package/tests/integration/billing.route.test.ts +393 -0
  438. package/tests/integration/conflicts.route.test.ts +131 -0
  439. package/tests/integration/flows.test.ts +154 -0
  440. package/tests/integration/helpers.ts +134 -0
  441. package/tests/integration/holds.route.test.ts +185 -0
  442. package/tests/integration/metrics.route.test.ts +100 -0
  443. package/tests/integration/onboarding.verify.route.test.ts +163 -0
  444. package/tests/integration/preferences.route.test.ts +53 -0
  445. package/tests/integration/session.route.test.ts +97 -0
  446. package/tests/integration/system.route.test.ts +92 -0
  447. package/tests/integration/value.route.test.ts +235 -0
  448. package/tests/integration/work.route.test.ts +745 -0
  449. package/tests/setup.ts +4 -0
  450. package/tests/smoke.sh +62 -0
  451. package/tests/unit/auth.test.ts +114 -0
  452. package/tests/unit/availability.test.ts +149 -0
  453. package/tests/unit/conflict.test.ts +118 -0
  454. package/tests/unit/env.test.ts +69 -0
  455. package/tests/unit/migrations.test.ts +135 -0
  456. package/tests/unit/request-id.test.ts +37 -0
  457. package/tmp-mobile-agents.png +0 -0
  458. package/tmp-next-mobile.err.log +10 -0
  459. package/tmp-next-mobile.log +5 -0
  460. package/tsconfig.json +16 -0
@@ -0,0 +1,350 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { z } from 'zod';
4
+ import { createHash } from 'crypto';
5
+
6
+ type JsonObject = Record<string, unknown>;
7
+
8
+ function printHelp(): void {
9
+ console.log(`Availsync MCP server
10
+
11
+ Environment:
12
+ AVAILSYNC_API_URL Base URL for Availsync, default http://localhost:3000
13
+ AVAILSYNC_API_KEY Bearer token for the scheduling agent
14
+
15
+ Tools:
16
+ check_availability Check available slots before booking
17
+ preview_conflict Preview who would win before booking
18
+ book_slot Reserve a slot
19
+ release_slot Release a booked hold
20
+ check_work_slot Check whether a coding agent can work on a repo/project
21
+ claim_work_slot Claim a repo/project before a coding agent starts
22
+ extend_work_slot Extend a live work claim while the agent is still working
23
+ release_work_slot Release a repo/project work claim
24
+
25
+ Status:
26
+ The server sends a heartbeat to Availsync on startup and then every 60 seconds.
27
+
28
+ Production flow:
29
+ heartbeat -> check_work_slot -> claim_work_slot -> extend_work_slot while working -> release_work_slot
30
+ `);
31
+ }
32
+
33
+ function getConfig(): { apiUrl: string; apiKey: string } {
34
+ const apiUrl = process.env.AVAILSYNC_API_URL || 'http://localhost:3000';
35
+ const apiKey = process.env.AVAILSYNC_API_KEY;
36
+
37
+ if (!apiKey) {
38
+ throw new Error('AVAILSYNC_API_KEY is required');
39
+ }
40
+
41
+ return { apiUrl: apiUrl.replace(/\/$/, ''), apiKey };
42
+ }
43
+
44
+ async function requestAvailsync(
45
+ method: string,
46
+ path: string,
47
+ body?: JsonObject,
48
+ extraHeaders: Record<string, string> = {},
49
+ ): Promise<unknown> {
50
+ const { apiUrl, apiKey } = getConfig();
51
+ const response = await fetch(`${apiUrl}${path}`, {
52
+ method,
53
+ headers: {
54
+ Authorization: `Bearer ${apiKey}`,
55
+ 'Content-Type': 'application/json',
56
+ ...extraHeaders,
57
+ },
58
+ body: body ? JSON.stringify(body) : undefined,
59
+ });
60
+
61
+ const text = await response.text();
62
+ const payload = text ? (JSON.parse(text) as unknown) : {};
63
+
64
+ if (!response.ok) {
65
+ return {
66
+ status: response.status,
67
+ error: payload,
68
+ };
69
+ }
70
+
71
+ return payload;
72
+ }
73
+
74
+ function deterministicWorkKey(input: {
75
+ agent_id: string;
76
+ resource_type: string;
77
+ resource_key: string;
78
+ start_at?: string;
79
+ reason?: string;
80
+ }): string {
81
+ return createHash('sha256')
82
+ .update(`${input.agent_id}:${input.resource_type}:${input.resource_key}:${input.start_at ?? 'now'}:${input.reason ?? ''}`)
83
+ .digest('hex');
84
+ }
85
+
86
+ async function sendHeartbeat(): Promise<void> {
87
+ await requestAvailsync('POST', '/v1/mcp/heartbeat', {
88
+ client_name: 'availsync-mcp',
89
+ });
90
+ }
91
+
92
+ function jsonContent(payload: unknown) {
93
+ return {
94
+ content: [
95
+ {
96
+ type: 'text' as const,
97
+ text: JSON.stringify(payload, null, 2),
98
+ },
99
+ ],
100
+ };
101
+ }
102
+
103
+ const usageSchema = z
104
+ .object({
105
+ provider: z.string().optional(),
106
+ model: z.string().optional(),
107
+ input_tokens: z.number().int().min(0).optional(),
108
+ output_tokens: z.number().int().min(0).optional(),
109
+ total_tokens: z.number().int().min(0).optional(),
110
+ estimated_cost_usd: z.number().min(0).optional(),
111
+ })
112
+ .optional();
113
+
114
+ async function reportUsage(activityType: string, usage?: z.infer<typeof usageSchema>): Promise<void> {
115
+ if (!usage) return;
116
+ await requestAvailsync('POST', '/v1/activity/usage', {
117
+ activity_type: activityType,
118
+ client_name: 'availsync-mcp',
119
+ ...usage,
120
+ });
121
+ }
122
+
123
+ function createServer(): McpServer {
124
+ const server = new McpServer({
125
+ name: 'availsync',
126
+ version: '0.1.0',
127
+ });
128
+
129
+ server.registerTool(
130
+ 'check_availability',
131
+ {
132
+ description:
133
+ 'Check available time slots for an AI scheduling agent before booking. Always call this before book_slot.',
134
+ inputSchema: {
135
+ agent_id: z.string().uuid().describe('UUID of the agent to check availability for'),
136
+ from: z.string().datetime().describe('Start of search window (ISO 8601 UTC)'),
137
+ to: z.string().datetime().describe('End of search window (ISO 8601 UTC)'),
138
+ duration_minutes: z.number().describe('Required meeting duration in minutes'),
139
+ usage: usageSchema,
140
+ },
141
+ },
142
+ async ({ agent_id, from, to, duration_minutes, usage }) => {
143
+ const query = new URLSearchParams({
144
+ agent_id,
145
+ from,
146
+ to,
147
+ duration_minutes: String(duration_minutes),
148
+ });
149
+ const result = await requestAvailsync('GET', `/v1/availability?${query.toString()}`);
150
+ await reportUsage('mcp_check_availability', usage);
151
+ return jsonContent(result);
152
+ },
153
+ );
154
+
155
+ server.registerTool(
156
+ 'preview_conflict',
157
+ {
158
+ description:
159
+ 'Preview whether a proposed slot would be available, win, or lose before booking. Call this after check_availability and before book_slot.',
160
+ inputSchema: {
161
+ agent_id: z.string().uuid(),
162
+ start_at: z.string().datetime(),
163
+ end_at: z.string().datetime(),
164
+ reason: z.string().max(500).optional(),
165
+ usage: usageSchema,
166
+ },
167
+ },
168
+ async ({ agent_id, start_at, end_at, reason, usage }) => {
169
+ const result = await requestAvailsync('POST', '/v1/conflicts/check', {
170
+ agent_id,
171
+ start_at,
172
+ end_at,
173
+ ...(reason ? { reason } : {}),
174
+ });
175
+ await reportUsage('mcp_preview_conflict', usage);
176
+ return jsonContent(result);
177
+ },
178
+ );
179
+
180
+ server.registerTool(
181
+ 'book_slot',
182
+ {
183
+ description:
184
+ 'Reserve a time slot. Returns confirmed hold or 409 with alternative suggestions if slot is taken by higher-priority agent.',
185
+ inputSchema: {
186
+ agent_id: z.string().uuid(),
187
+ start_at: z.string().datetime(),
188
+ end_at: z.string().datetime(),
189
+ reason: z.string().max(500).optional(),
190
+ usage: usageSchema,
191
+ },
192
+ },
193
+ async ({ agent_id, start_at, end_at, reason, usage }) => {
194
+ const result = await requestAvailsync('POST', '/v1/holds', {
195
+ agent_id,
196
+ start_at,
197
+ end_at,
198
+ ...(reason ? { reason } : {}),
199
+ });
200
+ await reportUsage('mcp_book_slot', usage);
201
+ return jsonContent(result);
202
+ },
203
+ );
204
+
205
+ server.registerTool(
206
+ 'release_slot',
207
+ {
208
+ description: 'Release a previously booked hold so other agents can use the time.',
209
+ inputSchema: {
210
+ hold_id: z.string().uuid(),
211
+ usage: usageSchema,
212
+ },
213
+ },
214
+ async ({ hold_id, usage }) => {
215
+ const result = await requestAvailsync('DELETE', `/v1/holds/${hold_id}`);
216
+ await reportUsage('mcp_release_slot', usage);
217
+ return jsonContent(result);
218
+ },
219
+ );
220
+
221
+ server.registerTool(
222
+ 'check_work_slot',
223
+ {
224
+ description:
225
+ 'Check whether a coding agent can work on a repo or project before it starts. If status is would_lose, skip the run.',
226
+ inputSchema: {
227
+ agent_id: z.string().uuid(),
228
+ resource_type: z.enum(['repo', 'project']),
229
+ resource_key: z.string().min(1),
230
+ label: z.string().optional(),
231
+ start_at: z.string().datetime().optional(),
232
+ duration_minutes: z.number().int().min(1).max(240).optional(),
233
+ reason: z.string().max(500).optional(),
234
+ usage: usageSchema,
235
+ },
236
+ },
237
+ async ({ agent_id, resource_type, resource_key, label, start_at, duration_minutes, reason, usage }) => {
238
+ const result = await requestAvailsync('POST', '/v1/work/check', {
239
+ agent_id,
240
+ resource_type,
241
+ resource_key,
242
+ ...(label ? { label } : {}),
243
+ ...(start_at ? { start_at } : {}),
244
+ ...(duration_minutes ? { duration_minutes } : {}),
245
+ ...(reason ? { reason } : {}),
246
+ });
247
+ await reportUsage('mcp_check_work_slot', usage);
248
+ return jsonContent(result);
249
+ },
250
+ );
251
+
252
+ server.registerTool(
253
+ 'claim_work_slot',
254
+ {
255
+ description:
256
+ 'Claim a repo or project before a coding agent starts. Use the returned claim id and release it when done.',
257
+ inputSchema: {
258
+ agent_id: z.string().uuid(),
259
+ resource_type: z.enum(['repo', 'project']),
260
+ resource_key: z.string().min(1),
261
+ label: z.string().optional(),
262
+ start_at: z.string().datetime().optional(),
263
+ duration_minutes: z.number().int().min(1).max(240).optional(),
264
+ reason: z.string().max(500).optional(),
265
+ idempotency_key: z.string().min(1).max(200).optional(),
266
+ usage: usageSchema,
267
+ },
268
+ },
269
+ async ({ agent_id, resource_type, resource_key, label, start_at, duration_minutes, reason, idempotency_key, usage }) => {
270
+ const key = idempotency_key ?? deterministicWorkKey({ agent_id, resource_type, resource_key, start_at, reason });
271
+ const result = await requestAvailsync(
272
+ 'POST',
273
+ '/v1/work/claim',
274
+ {
275
+ agent_id,
276
+ resource_type,
277
+ resource_key,
278
+ ...(label ? { label } : {}),
279
+ ...(start_at ? { start_at } : {}),
280
+ ...(duration_minutes ? { duration_minutes } : {}),
281
+ ...(reason ? { reason } : {}),
282
+ },
283
+ { 'Idempotency-Key': key },
284
+ );
285
+ await reportUsage('mcp_claim_work_slot', usage);
286
+ return jsonContent(result);
287
+ },
288
+ );
289
+
290
+ server.registerTool(
291
+ 'extend_work_slot',
292
+ {
293
+ description:
294
+ 'Extend an active work claim while a coding agent is still working. Call periodically before expires_at.',
295
+ inputSchema: {
296
+ claim_id: z.string().uuid(),
297
+ duration_minutes: z.number().int().min(1).max(120).optional(),
298
+ usage: usageSchema,
299
+ },
300
+ },
301
+ async ({ claim_id, duration_minutes, usage }) => {
302
+ const result = await requestAvailsync('POST', `/v1/work/claims/${claim_id}/extend`, {
303
+ ...(duration_minutes ? { duration_minutes } : {}),
304
+ });
305
+ await reportUsage('mcp_extend_work_slot', usage);
306
+ return jsonContent(result);
307
+ },
308
+ );
309
+
310
+ server.registerTool(
311
+ 'release_work_slot',
312
+ {
313
+ description: 'Release a repo or project work claim so another coding agent can work.',
314
+ inputSchema: {
315
+ claim_id: z.string().uuid(),
316
+ usage: usageSchema,
317
+ },
318
+ },
319
+ async ({ claim_id, usage }) => {
320
+ const result = await requestAvailsync('DELETE', `/v1/work/claims/${claim_id}`);
321
+ await reportUsage('mcp_release_work_slot', usage);
322
+ return jsonContent(result);
323
+ },
324
+ );
325
+
326
+ return server;
327
+ }
328
+
329
+ export async function main(): Promise<void> {
330
+ if (process.argv.includes('--help') || process.argv.includes('-h')) {
331
+ printHelp();
332
+ return;
333
+ }
334
+
335
+ const server = createServer();
336
+ const transport = new StdioServerTransport();
337
+ void sendHeartbeat().catch(() => {});
338
+ const heartbeat = setInterval(() => {
339
+ void sendHeartbeat().catch(() => {});
340
+ }, 60_000);
341
+ heartbeat.unref?.();
342
+ await server.connect(transport);
343
+ }
344
+
345
+ if (require.main === module) {
346
+ main().catch((error) => {
347
+ console.error('Server error:', error);
348
+ process.exit(1);
349
+ });
350
+ }
@@ -0,0 +1,342 @@
1
+ import crypto from 'crypto';
2
+ import type { NextFunction, Request, Response } from 'express';
3
+ import bcrypt from 'bcrypt';
4
+ import pool from '../db/client';
5
+ import { log } from '../lib/logger';
6
+ import { parseAgentApiKey } from '../lib/apiKeys';
7
+ import type { Agent, Org, WorkspaceUser } from '../types';
8
+
9
+ export const WORKSPACE_SESSION_COOKIE = 'availsync_session';
10
+ export const WORKSPACE_SESSION_DAYS = 30;
11
+
12
+ export interface AgentAuthenticatedRequest extends Request {
13
+ auth: { kind: 'agent' };
14
+ agent: Agent;
15
+ org: Org;
16
+ }
17
+
18
+ export interface WorkspaceAuthenticatedRequest extends Request {
19
+ auth: { kind: 'workspace' };
20
+ user: WorkspaceUser;
21
+ org: Org;
22
+ }
23
+
24
+ export type AuthenticatedRequest = AgentAuthenticatedRequest;
25
+ export type AnyAuthenticatedRequest = AgentAuthenticatedRequest | WorkspaceAuthenticatedRequest;
26
+ export type AdminAuthenticatedRequest = WorkspaceAuthenticatedRequest;
27
+
28
+ type AgentAuthRow = {
29
+ agent_id: string;
30
+ agent_name: string;
31
+ api_key_hash: string;
32
+ agent_type: Agent['agent_type'];
33
+ priority: number;
34
+ enforcement_mode: Agent['enforcement_mode'];
35
+ last_seen_at?: Date | null;
36
+ mcp_last_seen_at?: Date | null;
37
+ mcp_client_name?: string | null;
38
+ agent_created_at: Date;
39
+ org_id: string;
40
+ org_name: string;
41
+ plan: Org['plan'];
42
+ agent_limit: number;
43
+ org_verified_at?: Date | null;
44
+ org_created_at: Date;
45
+ };
46
+
47
+ const DUMMY_AGENT_API_KEY_HASH = '$2b$10$uqpuuBbmijjr1bD0L6pBDezy0ACGhT0ZaLkUXAvWSA4LgyaviWC9q';
48
+
49
+ type WorkspaceAuthRow = {
50
+ user_id: string;
51
+ email: string;
52
+ password_hash: string;
53
+ role: WorkspaceUser['role'];
54
+ user_created_at: Date;
55
+ org_id: string;
56
+ org_name: string;
57
+ plan: Org['plan'];
58
+ agent_limit: number;
59
+ org_verified_at?: Date | null;
60
+ org_created_at: Date;
61
+ };
62
+
63
+ function sessionSecret() {
64
+ return process.env.SESSION_SECRET || 'dev-session-secret';
65
+ }
66
+
67
+ export function hashSessionToken(token: string): string {
68
+ return crypto.createHmac('sha256', sessionSecret()).update(token).digest('hex');
69
+ }
70
+
71
+ export function getCookie(req: Request, name: string): string | null {
72
+ const header = req.header('cookie');
73
+ if (!header) return null;
74
+
75
+ const cookies = header.split(';').map((cookie) => cookie.trim());
76
+ for (const cookie of cookies) {
77
+ const separator = cookie.indexOf('=');
78
+ if (separator === -1) continue;
79
+ const key = cookie.slice(0, separator);
80
+ if (key === name) {
81
+ return decodeURIComponent(cookie.slice(separator + 1));
82
+ }
83
+ }
84
+ return null;
85
+ }
86
+
87
+ export function setWorkspaceSessionCookie(res: Response, token: string) {
88
+ res.cookie(WORKSPACE_SESSION_COOKIE, token, {
89
+ httpOnly: true,
90
+ secure: process.env.NODE_ENV === 'production',
91
+ sameSite: 'lax',
92
+ path: '/',
93
+ maxAge: WORKSPACE_SESSION_DAYS * 24 * 60 * 60 * 1000,
94
+ });
95
+ }
96
+
97
+ export function clearWorkspaceSessionCookie(res: Response) {
98
+ res.clearCookie(WORKSPACE_SESSION_COOKIE, {
99
+ httpOnly: true,
100
+ secure: process.env.NODE_ENV === 'production',
101
+ sameSite: 'lax',
102
+ path: '/',
103
+ });
104
+ }
105
+
106
+ function buildAgent(row: AgentAuthRow): Agent {
107
+ return {
108
+ id: row.agent_id,
109
+ org_id: row.org_id,
110
+ name: row.agent_name,
111
+ api_key_hash: row.api_key_hash,
112
+ agent_type: row.agent_type,
113
+ priority: row.priority,
114
+ enforcement_mode: row.enforcement_mode,
115
+ last_seen_at: row.last_seen_at ?? null,
116
+ mcp_last_seen_at: row.mcp_last_seen_at ?? null,
117
+ mcp_client_name: row.mcp_client_name ?? null,
118
+ created_at: row.agent_created_at,
119
+ };
120
+ }
121
+
122
+ function buildOrg(row: AgentAuthRow | WorkspaceAuthRow): Org {
123
+ return {
124
+ id: row.org_id,
125
+ name: row.org_name,
126
+ plan: row.plan,
127
+ agent_limit: row.agent_limit,
128
+ verified_at: row.org_verified_at ?? null,
129
+ created_at: row.org_created_at,
130
+ };
131
+ }
132
+
133
+ function buildUser(row: WorkspaceAuthRow): WorkspaceUser {
134
+ return {
135
+ id: row.user_id,
136
+ org_id: row.org_id,
137
+ email: row.email,
138
+ password_hash: row.password_hash,
139
+ role: row.role,
140
+ created_at: row.user_created_at,
141
+ };
142
+ }
143
+
144
+ async function authenticateAgentKey(req: Request): Promise<{ agent: Agent; org: Org } | null> {
145
+ const header = req.header('Authorization');
146
+ if (!header?.startsWith('Bearer ')) return null;
147
+
148
+ const token = header.slice('Bearer '.length).trim();
149
+
150
+ const parsedKey = parseAgentApiKey(token);
151
+ if (!parsedKey) {
152
+ return null;
153
+ }
154
+
155
+ const result = await pool.query<AgentAuthRow>(
156
+ `SELECT
157
+ agents.id AS agent_id,
158
+ agents.name AS agent_name,
159
+ agents.api_key_hash,
160
+ agents.agent_type,
161
+ agents.priority,
162
+ agents.enforcement_mode,
163
+ agents.last_seen_at,
164
+ agents.mcp_last_seen_at,
165
+ agents.mcp_client_name,
166
+ agents.created_at AS agent_created_at,
167
+ orgs.id AS org_id,
168
+ orgs.name AS org_name,
169
+ orgs.plan,
170
+ orgs.agent_limit,
171
+ orgs.verified_at AS org_verified_at,
172
+ orgs.created_at AS org_created_at
173
+ FROM agents
174
+ JOIN orgs ON agents.org_id = orgs.id
175
+ WHERE agents.api_key_prefix = $1`,
176
+ [parsedKey.prefix],
177
+ );
178
+
179
+ const row = result.rows[0];
180
+ const hashToCompare = row?.api_key_hash ?? DUMMY_AGENT_API_KEY_HASH;
181
+ const ok = await bcrypt.compare(token, hashToCompare);
182
+ if (!row || !ok) {
183
+ return null;
184
+ }
185
+ Promise.resolve(pool.query('UPDATE agents SET last_seen_at = NOW() WHERE id = $1', [row.agent_id])).catch((err) => {
186
+ log.warn('[auth] failed to update agent last_seen_at', {
187
+ agent_id: row.agent_id,
188
+ error: (err as Error).message,
189
+ });
190
+ });
191
+ return { agent: buildAgent(row), org: buildOrg(row) };
192
+ }
193
+
194
+ export async function authenticateWorkspaceSession(req: Request): Promise<{ user: WorkspaceUser; org: Org } | null> {
195
+ const token = getCookie(req, WORKSPACE_SESSION_COOKIE);
196
+ if (!token) return null;
197
+
198
+ const result = await pool.query<WorkspaceAuthRow>(
199
+ `SELECT
200
+ workspace_users.id AS user_id,
201
+ workspace_users.email,
202
+ workspace_users.password_hash,
203
+ workspace_users.role,
204
+ workspace_users.created_at AS user_created_at,
205
+ orgs.id AS org_id,
206
+ orgs.name AS org_name,
207
+ orgs.plan,
208
+ orgs.agent_limit,
209
+ orgs.verified_at AS org_verified_at,
210
+ orgs.created_at AS org_created_at
211
+ FROM workspace_sessions
212
+ JOIN workspace_users ON workspace_users.id = workspace_sessions.user_id
213
+ JOIN orgs ON orgs.id = workspace_users.org_id
214
+ WHERE workspace_sessions.token_hash = $1
215
+ AND workspace_sessions.expires_at > NOW()`,
216
+ [hashSessionToken(token)],
217
+ );
218
+
219
+ if (result.rows.length === 0) return null;
220
+ return { user: buildUser(result.rows[0]), org: buildOrg(result.rows[0]) };
221
+ }
222
+
223
+ export function adminEmails(): string[] {
224
+ return (process.env.ADMIN_EMAILS || '')
225
+ .split(',')
226
+ .map((email) => email.trim().toLowerCase())
227
+ .filter(Boolean);
228
+ }
229
+
230
+ export function isAdminEmail(email: string): boolean {
231
+ return adminEmails().includes(email.toLowerCase());
232
+ }
233
+
234
+ export async function requireAgentKey(
235
+ req: Request,
236
+ res: Response,
237
+ next: NextFunction,
238
+ ): Promise<Response | void> {
239
+ try {
240
+ const authenticated = await authenticateAgentKey(req);
241
+ if (!authenticated) {
242
+ return res.status(401).json({ error: req.header('Authorization') ? 'invalid_key' : 'missing_auth' });
243
+ }
244
+
245
+ const authReq = req as AgentAuthenticatedRequest;
246
+ authReq.auth = { kind: 'agent' };
247
+ authReq.agent = authenticated.agent;
248
+ authReq.org = authenticated.org;
249
+ return next();
250
+ } catch (err) {
251
+ log.error('[auth] agent auth error', { error: (err as Error).message });
252
+ return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
253
+ }
254
+ }
255
+
256
+ export async function requireWorkspaceSession(
257
+ req: Request,
258
+ res: Response,
259
+ next: NextFunction,
260
+ ): Promise<Response | void> {
261
+ try {
262
+ const authenticated = await authenticateWorkspaceSession(req);
263
+ if (!authenticated) {
264
+ return res.status(401).json({ error: 'missing_workspace_session' });
265
+ }
266
+
267
+ const authReq = req as WorkspaceAuthenticatedRequest;
268
+ authReq.auth = { kind: 'workspace' };
269
+ authReq.user = authenticated.user;
270
+ authReq.org = authenticated.org;
271
+ return next();
272
+ } catch (err) {
273
+ log.error('[auth] workspace auth error', { error: (err as Error).message });
274
+ return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
275
+ }
276
+ }
277
+
278
+ export async function requireAdminSession(
279
+ req: Request,
280
+ res: Response,
281
+ next: NextFunction,
282
+ ): Promise<Response | void> {
283
+ try {
284
+ const authenticated = await authenticateWorkspaceSession(req);
285
+ if (!authenticated) {
286
+ return res.status(401).json({ error: 'missing_workspace_session' });
287
+ }
288
+ if (!isAdminEmail(authenticated.user.email)) {
289
+ return res.status(403).json({ error: 'forbidden', message: 'Admin access required' });
290
+ }
291
+
292
+ const authReq = req as AdminAuthenticatedRequest;
293
+ authReq.auth = { kind: 'workspace' };
294
+ authReq.user = authenticated.user;
295
+ authReq.org = authenticated.org;
296
+ return next();
297
+ } catch (err) {
298
+ log.error('[auth] admin auth error', { error: (err as Error).message });
299
+ return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
300
+ }
301
+ }
302
+
303
+ export async function requireWorkspaceOrAgent(
304
+ req: Request,
305
+ res: Response,
306
+ next: NextFunction,
307
+ ): Promise<Response | void> {
308
+ try {
309
+ const workspace = await authenticateWorkspaceSession(req);
310
+ if (workspace) {
311
+ const authReq = req as WorkspaceAuthenticatedRequest;
312
+ authReq.auth = { kind: 'workspace' };
313
+ authReq.user = workspace.user;
314
+ authReq.org = workspace.org;
315
+ return next();
316
+ }
317
+
318
+ const agent = await authenticateAgentKey(req);
319
+ if (agent) {
320
+ const authReq = req as AgentAuthenticatedRequest;
321
+ authReq.auth = { kind: 'agent' };
322
+ authReq.agent = agent.agent;
323
+ authReq.org = agent.org;
324
+ return next();
325
+ }
326
+
327
+ return res.status(401).json({ error: 'missing_auth' });
328
+ } catch (err) {
329
+ log.error('[auth] mixed auth error', { error: (err as Error).message });
330
+ return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
331
+ }
332
+ }
333
+
334
+ export function canAccessAgent(req: AnyAuthenticatedRequest, agentId: string): boolean {
335
+ return !isAgentAuth(req) || req.agent.id === agentId;
336
+ }
337
+
338
+ export function isAgentAuth(req: AnyAuthenticatedRequest): req is AgentAuthenticatedRequest {
339
+ return req.auth.kind === 'agent';
340
+ }
341
+
342
+ export const authMiddleware = requireAgentKey;
@@ -0,0 +1,16 @@
1
+ import { randomUUID } from 'crypto';
2
+ import type { NextFunction, Request, Response } from 'express';
3
+
4
+ export const REQUEST_ID_HEADER = 'X-Request-Id';
5
+
6
+ export function requestIdMiddleware(req: Request, res: Response, next: NextFunction) {
7
+ const incoming = req.header(REQUEST_ID_HEADER);
8
+ const requestId = incoming && incoming.length <= 128 ? incoming : randomUUID();
9
+ res.locals.requestId = requestId;
10
+ res.setHeader(REQUEST_ID_HEADER, requestId);
11
+ next();
12
+ }
13
+
14
+ export function requestIdFrom(res: Response) {
15
+ return typeof res.locals.requestId === 'string' ? res.locals.requestId : undefined;
16
+ }