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
package/plan.md ADDED
@@ -0,0 +1,923 @@
1
+ # PLAN.md — Availsync MVP
2
+
3
+ ## Goal contract (læs dette først)
4
+
5
+ Du bygger Availsync: en REST API der fungerer som scheduling source-of-truth for AI-agenter.
6
+ Kerneproblemet: flere AI-agenter (salgs-bot, rekrutteringsbot, kalenderagent) booker møder
7
+ uafhængigt og laver double-bookings. Availsync løser det ved at alle agenter tjekker ét
8
+ fælles lag af regler og holds, inden de booker.
9
+
10
+ **Done-betingelse:**
11
+ `npm test` kører grønt (alle test passes), `npm run build` producerer fejlfri TypeScript-build,
12
+ og `docker compose up` starter systemet lokalt. Alle tre endpoints svarer korrekt på
13
+ de smoke tests beskrevet i `/tests/smoke.sh`.
14
+
15
+ **Stop hvis:**
16
+ - En migration fejler og kan ikke rettes automatisk — log fejlen og stop
17
+ - En afhængighed ikke kan installeres — prøv alternativ, ellers stop og skriv til BLOCKERS.md
18
+ - Du er i tvivl om forretningslogik — se kommentarer i denne fil, lav ikke antagelser
19
+
20
+ **Rør ikke:**
21
+ - Denne PLAN.md fil
22
+ - .env.example (kun tilføj, aldrig slet eksisterende variabler)
23
+ - Eventuelle filer brugeren har lagt i /existing/ mappen
24
+
25
+ ---
26
+
27
+ ## Stack
28
+
29
+ | Lag | Teknologi | Begrundelse |
30
+ |---|---|---|
31
+ | Runtime | Node.js 20 + TypeScript 5 | Async-first, god type-støtte til DB |
32
+ | Web framework | Express.js 4 | Simpelt, veldokumenteret |
33
+ | Database | PostgreSQL 16 via `pg` driver | Row-level locking til konfliktresolution |
34
+ | Validation | Zod | Skema-validation + TypeScript-inferens |
35
+ | Testing | Jest + Supertest | Enkel setup, god Express-integration |
36
+ | Containerisering | Docker + docker-compose | Reproducerbar lokal kørsel |
37
+ | Linting | ESLint + Prettier | Konsistent kode |
38
+
39
+ **Brug ikke** Prisma, TypeORM, Sequelize eller andre ORMs — brug rå `pg`-queries.
40
+ Det giver fuld kontrol over `SELECT FOR UPDATE SKIP LOCKED` som konfliktmotoren kræver.
41
+
42
+ ---
43
+
44
+ ## Mappestruktur (opret præcis dette)
45
+
46
+ ```
47
+ availsync/
48
+ ├── PLAN.md ← denne fil
49
+ ├── BLOCKERS.md ← opret som tom fil, log blokeringer her
50
+ ├── package.json
51
+ ├── tsconfig.json
52
+ ├── .env.example
53
+ ├── docker-compose.yml
54
+ ├── Dockerfile
55
+ ├── jest.config.js
56
+ ├── src/
57
+ │ ├── index.ts ← Express app entry point
58
+ │ ├── db/
59
+ │ │ ├── client.ts ← pg Pool singleton
60
+ │ │ └── migrations/
61
+ │ │ └── 001_init.sql ← hele databaseskemaet
62
+ │ ├── core/
63
+ │ │ ├── availability.ts ← getAvailableSlots()
64
+ │ │ └── conflict.ts ← resolveConflict()
65
+ │ ├── middleware/
66
+ │ │ └── auth.ts ← Bearer token validering
67
+ │ ├── routes/
68
+ │ │ ├── availability.ts ← GET /v1/availability
69
+ │ │ ├── holds.ts ← POST /v1/holds, DELETE /v1/holds/:id
70
+ │ │ ├── preferences.ts ← GET + PUT /v1/preferences/:agent_id
71
+ │ │ └── orgs.ts ← POST /v1/orgs, POST /v1/orgs/:id/agents
72
+ │ └── types/
73
+ │ └── index.ts ← delte TypeScript-typer
74
+ └── tests/
75
+ ├── unit/
76
+ │ ├── conflict.test.ts
77
+ │ └── availability.test.ts
78
+ ├── integration/
79
+ │ ├── availability.route.test.ts
80
+ │ ├── holds.route.test.ts
81
+ │ └── preferences.route.test.ts
82
+ └── smoke.sh ← curl-baserede smoke tests
83
+ ```
84
+
85
+ ---
86
+
87
+ ## Checkpoint 1 — Projektopsætning og databaseskema
88
+
89
+ **Valider med:** `npm run build` producerer ingen fejl. `psql $DATABASE_URL -f src/db/migrations/001_init.sql` kører uden fejl.
90
+
91
+ ### 1.1 — Initialiser projektet
92
+
93
+ Opret `package.json` med disse præcise scripts:
94
+ ```json
95
+ {
96
+ "name": "availsync",
97
+ "version": "0.1.0",
98
+ "scripts": {
99
+ "build": "tsc --noEmit",
100
+ "start": "ts-node src/index.ts",
101
+ "dev": "ts-node-dev --respawn src/index.ts",
102
+ "test": "jest --runInBand",
103
+ "migrate": "psql $DATABASE_URL -f src/db/migrations/001_init.sql",
104
+ "lint": "eslint src --ext .ts"
105
+ }
106
+ }
107
+ ```
108
+
109
+ Installer disse dependencies (ikke mere):
110
+ ```
111
+ npm install express pg zod bcrypt uuid date-fns
112
+ npm install --save-dev typescript ts-node ts-node-dev @types/express @types/pg @types/bcrypt @types/uuid jest @types/jest supertest @types/supertest ts-jest eslint prettier
113
+ ```
114
+
115
+ ### 1.2 — TypeScript-konfiguration
116
+
117
+ `tsconfig.json`:
118
+ ```json
119
+ {
120
+ "compilerOptions": {
121
+ "target": "ES2022",
122
+ "module": "commonjs",
123
+ "lib": ["ES2022"],
124
+ "outDir": "./dist",
125
+ "rootDir": "./src",
126
+ "strict": true,
127
+ "esModuleInterop": true,
128
+ "skipLibCheck": true,
129
+ "forceConsistentCasingInFileNames": true,
130
+ "resolveJsonModule": true
131
+ },
132
+ "include": ["src/**/*"],
133
+ "exclude": ["node_modules", "dist", "tests"]
134
+ }
135
+ ```
136
+
137
+ `jest.config.js`:
138
+ ```js
139
+ module.exports = {
140
+ preset: 'ts-jest',
141
+ testEnvironment: 'node',
142
+ testMatch: ['**/tests/**/*.test.ts'],
143
+ runInBand: true,
144
+ setupFiles: ['<rootDir>/tests/setup.ts']
145
+ };
146
+ ```
147
+
148
+ Opret `tests/setup.ts`:
149
+ ```typescript
150
+ process.env.DATABASE_URL = process.env.TEST_DATABASE_URL || 'postgresql://postgres:postgres@localhost:5432/availsync_test';
151
+ process.env.NODE_ENV = 'test';
152
+ ```
153
+
154
+ ### 1.3 — Miljøvariabler
155
+
156
+ `.env.example`:
157
+ ```
158
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5432/availsync
159
+ TEST_DATABASE_URL=postgresql://postgres:postgres@localhost:5432/availsync_test
160
+ PORT=3000
161
+ NODE_ENV=development
162
+ API_KEY_SALT_ROUNDS=10
163
+ CORS_ORIGINS=http://localhost:3000
164
+ ```
165
+
166
+ ### 1.4 — Databaseskema
167
+
168
+ `src/db/migrations/001_init.sql` — implementér disse tabeller præcis:
169
+
170
+ ```sql
171
+ -- Aktiver UUID-extension
172
+ CREATE EXTENSION IF NOT EXISTS "pgcrypto";
173
+
174
+ -- Organisationer (en org kan have mange agenter)
175
+ CREATE TABLE IF NOT EXISTS orgs (
176
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
177
+ name TEXT NOT NULL,
178
+ plan TEXT NOT NULL DEFAULT 'free' CHECK (plan IN ('free', 'individual', 'team')),
179
+ agent_limit INTEGER NOT NULL DEFAULT 3,
180
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
181
+ );
182
+
183
+ -- Agenter (en per AI-tool: sales_bot, recruiting_bot, osv.)
184
+ CREATE TABLE IF NOT EXISTS agents (
185
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
186
+ org_id UUID NOT NULL REFERENCES orgs(id) ON DELETE CASCADE,
187
+ name TEXT NOT NULL,
188
+ api_key_hash TEXT NOT NULL,
189
+ agent_type TEXT NOT NULL DEFAULT 'generic'
190
+ CHECK (agent_type IN ('external_meeting', 'internal', 'focus', 'generic')),
191
+ priority INTEGER NOT NULL DEFAULT 0,
192
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
193
+ );
194
+ CREATE INDEX IF NOT EXISTS idx_agents_org_id ON agents(org_id);
195
+
196
+ -- Ledige tidsvinduet en agent opererer inden for
197
+ CREATE TABLE IF NOT EXISTS availability_windows (
198
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
199
+ agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
200
+ start_at TIMESTAMPTZ NOT NULL,
201
+ end_at TIMESTAMPTZ NOT NULL,
202
+ window_type TEXT NOT NULL DEFAULT 'available'
203
+ CHECK (window_type IN ('available', 'focus', 'blocked')),
204
+ recurrence TEXT, -- null = engangshændelse, 'daily'/'weekly' = gentagen
205
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
206
+ CONSTRAINT chk_window_order CHECK (end_at > start_at)
207
+ );
208
+ CREATE INDEX IF NOT EXISTS idx_windows_agent_time
209
+ ON availability_windows(agent_id, start_at, end_at);
210
+
211
+ -- En agent reserverer et tidsrum (hold) inden commit
212
+ CREATE TABLE IF NOT EXISTS holds (
213
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
214
+ agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
215
+ org_id UUID NOT NULL REFERENCES orgs(id) ON DELETE CASCADE,
216
+ start_at TIMESTAMPTZ NOT NULL,
217
+ end_at TIMESTAMPTZ NOT NULL,
218
+ reason TEXT,
219
+ status TEXT NOT NULL DEFAULT 'confirmed'
220
+ CHECK (status IN ('confirmed', 'superseded', 'released')),
221
+ booked_by_agent_id UUID REFERENCES agents(id),
222
+ metadata JSONB,
223
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
224
+ CONSTRAINT chk_hold_order CHECK (end_at > start_at)
225
+ );
226
+ CREATE INDEX IF NOT EXISTS idx_holds_org_time
227
+ ON holds(org_id, start_at, end_at) WHERE status = 'confirmed';
228
+ CREATE INDEX IF NOT EXISTS idx_holds_agent_id ON holds(agent_id);
229
+
230
+ -- Konfliktregler pr. org
231
+ CREATE TABLE IF NOT EXISTS conflict_rules (
232
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
233
+ org_id UUID NOT NULL REFERENCES orgs(id) ON DELETE CASCADE,
234
+ rule_type TEXT NOT NULL CHECK (rule_type IN ('priority', 'buffer', 'focus')),
235
+ config JSONB NOT NULL DEFAULT '{}',
236
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
237
+ );
238
+
239
+ -- Agent-præferencer (buffer, focus-blokke, osv.)
240
+ CREATE TABLE IF NOT EXISTS agent_preferences (
241
+ agent_id UUID PRIMARY KEY REFERENCES agents(id) ON DELETE CASCADE,
242
+ buffer_minutes_after INTEGER NOT NULL DEFAULT 15,
243
+ buffer_minutes_before INTEGER NOT NULL DEFAULT 0,
244
+ focus_blocks JSONB NOT NULL DEFAULT '[]',
245
+ priority_over_agents JSONB NOT NULL DEFAULT '[]',
246
+ booking_window_days INTEGER NOT NULL DEFAULT 30,
247
+ allow_back_to_back BOOLEAN NOT NULL DEFAULT false,
248
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
249
+ );
250
+
251
+ -- Audit log — ét event per hændelse
252
+ CREATE TABLE IF NOT EXISTS hold_events (
253
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
254
+ hold_id UUID NOT NULL REFERENCES holds(id) ON DELETE CASCADE,
255
+ event_type TEXT NOT NULL
256
+ CHECK (event_type IN ('created', 'superseded', 'released', 'conflict_won', 'conflict_lost')),
257
+ agent_id UUID NOT NULL REFERENCES agents(id),
258
+ org_id UUID NOT NULL REFERENCES orgs(id),
259
+ conflicting_hold_id UUID REFERENCES holds(id),
260
+ rule_applied TEXT,
261
+ metadata JSONB,
262
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
263
+ );
264
+ CREATE INDEX IF NOT EXISTS idx_hold_events_org ON hold_events(org_id, created_at DESC);
265
+ ```
266
+
267
+ ### 1.5 — Database-klient
268
+
269
+ `src/db/client.ts`:
270
+ ```typescript
271
+ import { Pool } from 'pg';
272
+
273
+ const pool = new Pool({
274
+ connectionString: process.env.DATABASE_URL,
275
+ max: 10,
276
+ idleTimeoutMillis: 30000,
277
+ connectionTimeoutMillis: 2000,
278
+ });
279
+
280
+ export default pool;
281
+ ```
282
+
283
+ ### 1.6 — Delte typer
284
+
285
+ `src/types/index.ts` — definer disse interfaces:
286
+ ```typescript
287
+ export interface Org {
288
+ id: string;
289
+ name: string;
290
+ plan: 'free' | 'individual' | 'team';
291
+ agent_limit: number;
292
+ created_at: Date;
293
+ }
294
+
295
+ export interface Agent {
296
+ id: string;
297
+ org_id: string;
298
+ name: string;
299
+ api_key_hash: string;
300
+ agent_type: 'external_meeting' | 'internal' | 'focus' | 'generic';
301
+ priority: number;
302
+ created_at: Date;
303
+ }
304
+
305
+ export interface Hold {
306
+ id: string;
307
+ agent_id: string;
308
+ org_id: string;
309
+ start_at: Date;
310
+ end_at: Date;
311
+ reason?: string;
312
+ status: 'confirmed' | 'superseded' | 'released';
313
+ booked_by_agent_id?: string;
314
+ metadata?: Record<string, unknown>;
315
+ created_at: Date;
316
+ }
317
+
318
+ export interface AvailabilityWindow {
319
+ id: string;
320
+ agent_id: string;
321
+ start_at: Date;
322
+ end_at: Date;
323
+ window_type: 'available' | 'focus' | 'blocked';
324
+ recurrence?: string;
325
+ }
326
+
327
+ export interface AgentPreferences {
328
+ agent_id: string;
329
+ buffer_minutes_after: number;
330
+ buffer_minutes_before: number;
331
+ focus_blocks: FocusBlock[];
332
+ priority_over_agents: string[];
333
+ booking_window_days: number;
334
+ allow_back_to_back: boolean;
335
+ }
336
+
337
+ export interface FocusBlock {
338
+ weekday: number; // 0 = søndag, 6 = lørdag
339
+ start_time: string; // "HH:MM"
340
+ end_time: string; // "HH:MM"
341
+ }
342
+
343
+ export interface AvailableSlot {
344
+ start: Date;
345
+ end: Date;
346
+ confidence: number;
347
+ competing_holds_count: number;
348
+ }
349
+
350
+ export interface RequestWithAgent extends Express.Request {
351
+ agent: Agent;
352
+ org: Org;
353
+ }
354
+ ```
355
+
356
+ ---
357
+
358
+ ## Checkpoint 2 — Kernelogik
359
+
360
+ **Valider med:** `npm test -- tests/unit/` kører grønt (alle unit tests passes).
361
+
362
+ ### 2.1 — Auth-middleware
363
+
364
+ `src/middleware/auth.ts`:
365
+
366
+ Implementér en Express middleware der:
367
+ 1. Læser `Authorization: Bearer <token>` headeren — returnér 401 `{"error": "missing_auth", "message": "Authorization header required"}` hvis den mangler
368
+ 2. Slår token op i `agents`-tabellen med `SELECT agents.*, orgs.* FROM agents JOIN orgs ON agents.org_id = orgs.id WHERE agents.api_key_hash = $1` — brug bcrypt.compare(token, row.api_key_hash)
369
+ 3. Returnér 401 `{"error": "invalid_key"}` hvis ingen match
370
+ 4. Sætter `req.agent` og `req.org` på request-objektet
371
+ 5. Kalder `next()`
372
+
373
+ Eksportér typen `AuthenticatedRequest extends Request` med `agent: Agent` og `org: Org`.
374
+
375
+ ### 2.2 — Konfliktresolution-motor
376
+
377
+ `src/core/conflict.ts`:
378
+
379
+ Implementér disse to funktioner:
380
+
381
+ **`determineWinner(holdA: Hold, agentA: Agent, holdB: Hold, agentB: Agent, prefs: AgentPreferences[]): Hold`**
382
+
383
+ Prioritetslogik i denne rækkefølge (returnér vinderen):
384
+ 1. Eksplicit `priority_over_agents`-liste: hvis agentA har agentB's ID i sin `priority_over_agents`-liste → agentA vinder
385
+ 2. `agent_type`-hierarki: `external_meeting` (4) > `internal` (3) > `focus` (2) > `generic` (1)
386
+ 3. Eksplicit `priority`-felt på agent: højere vinder
387
+ 4. Tie-breaker: ældste `created_at` på hold vinder (first-come-first-served)
388
+
389
+ **`checkAndResolveConflict(newHold: Omit<Hold, 'id'|'created_at'>, client: PoolClient): Promise<{winner: 'new'|'existing', conflictingHold?: Hold}>`**
390
+
391
+ Flowet:
392
+ 1. Kør `SELECT * FROM holds WHERE org_id = $1 AND status = 'confirmed' AND start_at < $3 AND end_at > $2 FOR UPDATE SKIP LOCKED` — brug $1=org_id, $2=start_at, $3=end_at for at finde overlappende confirmed holds
393
+ 2. Hvis ingen overlap → returnér `{winner: 'new'}`
394
+ 3. For hvert overlappende hold: hent begge agenter, kald `determineWinner()`
395
+ 4. Hvis new hold vinder alle sammenstød → returnér `{winner: 'new', conflictingHold: losingHold}`
396
+ 5. Hvis new hold taber ét sammenstød → returnér `{winner: 'existing', conflictingHold: winningHold}`
397
+
398
+ ### 2.3 — Availability-beregning
399
+
400
+ `src/core/availability.ts`:
401
+
402
+ **`getAvailableSlots(params: GetSlotsParams, client?: PoolClient): Promise<AvailableSlot[]>`**
403
+
404
+ ```typescript
405
+ interface GetSlotsParams {
406
+ agent_id: string;
407
+ org_id: string;
408
+ from: Date;
409
+ to: Date;
410
+ duration_minutes: number;
411
+ include_competing_agents?: boolean;
412
+ }
413
+ ```
414
+
415
+ Algoritme:
416
+ 1. Hent `availability_windows` for agenten hvor `window_type = 'available'` og perioden overlapper `from`–`to`
417
+ 2. Hent `agent_preferences` for agenten
418
+ 3. Hent alle confirmed holds i org i perioden (hvis `include_competing_agents = true`) eller kun agentens egne holds
419
+ 4. For hvert availability-vindue: generer slots med `duration_minutes` skridt (tætpakket, ikke hvert minut)
420
+ 5. Filtrer slots der overlapper med:
421
+ - Existing holds (direkte overlap)
422
+ - Focus blocks fra preferences (omregn weekday+tidspunkt til konkrete datoer i perioden)
423
+ - Buffer-tid rundt om eksisterende holds (brug `buffer_minutes_after` og `buffer_minutes_before`)
424
+ 6. For hvert tilbageværende slot: tæl `competing_holds_count` (antal holds fra andre agenter tæt på slottet, indenfor 30 min)
425
+ 7. Sæt `confidence = 1.0 - (competing_holds_count * 0.15)`, minimum 0.1
426
+ 8. Returnér sorteret liste af `AvailableSlot`
427
+
428
+ ### 2.4 — Unit tests
429
+
430
+ `tests/unit/conflict.test.ts` — skriv tests der dækker:
431
+ - To agenter med ingen priority_over_agents → agent_type-hierarki afgør
432
+ - external_meeting vinder over internal
433
+ - Tie på agent_type → priority-felt afgør
434
+ - Komplet tie → ældste hold vinder (first-come-first-served)
435
+ - Eksplicit priority_over_agents-liste tilsidesætter alt andet
436
+
437
+ `tests/unit/availability.test.ts` — skriv tests der dækker:
438
+ - Slot inden for vindue returneres
439
+ - Slot overlappende med hold filtreres
440
+ - Buffer-tid fjerner slots tæt på hold
441
+ - Focus-blok fjerner slots der overlapper
442
+ - confidence beregnes korrekt ved competing holds
443
+ - Tom liste returneres hvis ingen ledige slots
444
+
445
+ ---
446
+
447
+ ## Checkpoint 3 — REST API endpoints
448
+
449
+ **Valider med:** `npm test -- tests/integration/` kører grønt.
450
+
451
+ For alle endpoints gælder:
452
+ - Content-Type: application/json på alle responses
453
+ - Fejl returneres altid som `{"error": "<machine_readable_code>", "message": "<human_readable>", "details"?: {...}}`
454
+ - Alle timestamps i ISO 8601 / UTC
455
+ - Auth-middleware skal køre på alle endpoints undtagen `POST /v1/orgs` og `GET /health`
456
+
457
+ ### 3.1 — Health check og app entry point
458
+
459
+ `src/index.ts`:
460
+ ```typescript
461
+ import express from 'express';
462
+ import availabilityRouter from './routes/availability';
463
+ import holdsRouter from './routes/holds';
464
+ import preferencesRouter from './routes/preferences';
465
+ import orgsRouter from './routes/orgs';
466
+
467
+ const app = express();
468
+ app.use(express.json());
469
+
470
+ app.get('/health', async (req, res) => {
471
+ try {
472
+ await pool.query('SELECT 1');
473
+ res.json({ status: 'ok', db: 'connected', timestamp: new Date().toISOString() });
474
+ } catch {
475
+ res.status(503).json({ status: 'error', db: 'disconnected' });
476
+ }
477
+ });
478
+
479
+ app.use('/v1', orgsRouter);
480
+ app.use('/v1', availabilityRouter);
481
+ app.use('/v1', holdsRouter);
482
+ app.use('/v1', preferencesRouter);
483
+
484
+ const PORT = process.env.PORT || 3000;
485
+ app.listen(PORT, () => console.log(`Availsync running on :${PORT}`));
486
+
487
+ export default app;
488
+ ```
489
+
490
+ ### 3.2 — Org og agent provisioning
491
+
492
+ `src/routes/orgs.ts` — implementér:
493
+
494
+ **`POST /v1/orgs`** (ingen auth krævet)
495
+ - Body (Zod-valideret): `{ name: string }`
496
+ - Opret org med `plan = 'free'` og `agent_limit = 3`
497
+ - Returnér 201: `{ org: Org }`
498
+
499
+ **`POST /v1/orgs/:org_id/agents`** (ingen auth krævet til MVP)
500
+ - Body (Zod-valideret): `{ name: string, agent_type?: AgentType, priority?: number }`
501
+ - Check at org ikke overskrider `agent_limit` — returnér 402 `{"error": "agent_limit_reached"}` hvis den gør
502
+ - Generer API-nøgle: `crypto.randomBytes(32).toString('hex')`
503
+ - Hash nøglen: `bcrypt.hash(apiKey, parseInt(process.env.API_KEY_SALT_ROUNDS || '10'))`
504
+ - Gem agent med hash
505
+ - Opret `agent_preferences`-række med defaults
506
+ - Returnér 201: `{ agent: Agent, api_key: string }` — vis `api_key` kun i dette svar
507
+
508
+ **`GET /v1/orgs/:org_id/agents`** (ingen auth krævet til MVP)
509
+ - Returnér alle agenter i org (uden api_key_hash)
510
+
511
+ ### 3.3 — Availability-endpoint
512
+
513
+ `src/routes/availability.ts` — implementér:
514
+
515
+ **`GET /v1/availability`** (auth krævet)
516
+
517
+ Query params — valider med Zod:
518
+ ```
519
+ agent_id: string (UUID)
520
+ from: string (ISO8601, skal være fremtidig)
521
+ to: string (ISO8601, skal være efter from)
522
+ duration_minutes: number (1–480)
523
+ include_competing_agents?: boolean (default: false)
524
+ ```
525
+
526
+ Flow:
527
+ 1. Valider at `req.agent.id === agent_id` ELLER `req.agent.org_id === org_id_for_agent` — en agent må kun se sin egen org's availability
528
+ 2. Kald `getAvailableSlots(params)`
529
+ 3. Returnér 200:
530
+ ```json
531
+ {
532
+ "slots": [
533
+ { "start": "ISO8601", "end": "ISO8601", "confidence": 0.85, "competing_holds_count": 1 }
534
+ ],
535
+ "rules_applied": ["buffer_15min", "focus_block_mornings"],
536
+ "generated_at": "ISO8601",
537
+ "total_slots": 12
538
+ }
539
+ ```
540
+
541
+ Fejlcases:
542
+ - Manglende params → 400 med Zod-fejldetaljer
543
+ - `to` mere end `booking_window_days` ude i fremtiden → 400 `{"error": "outside_booking_window"}`
544
+ - `from` i fortiden → 400 `{"error": "past_datetime"}`
545
+
546
+ ### 3.4 — Holds-endpoints
547
+
548
+ `src/routes/holds.ts` — implementér:
549
+
550
+ **`POST /v1/holds`** (auth krævet)
551
+
552
+ Body (Zod-valideret):
553
+ ```
554
+ agent_id: string (UUID)
555
+ start_at: string (ISO8601)
556
+ end_at: string (ISO8601)
557
+ reason?: string (max 500 chars)
558
+ metadata?: object
559
+ ```
560
+
561
+ Flow — ALT SKAL SKE I ÉN POSTGRESQL-TRANSAKTION:
562
+ 1. Valider agent tilhører req.org
563
+ 2. Check at start_at/end_at falder inden for et availability_window for agenten
564
+ 3. Kald `checkAndResolveConflict()`
565
+ 4. **Hvis winner = 'new':**
566
+ - Indsæt hold med `status = 'confirmed'`
567
+ - Opret `hold_events` event: `type = 'created'`
568
+ - Hvis der var et superseded hold: opdatér det til `status = 'superseded'`, opret event `type = 'superseded'`
569
+ - COMMIT
570
+ - Returnér 201: `{ hold: Hold, conflict_resolved: boolean, superseded_hold_id?: string }`
571
+ 5. **Hvis winner = 'existing':**
572
+ - ROLLBACK
573
+ - Hent 3 alternative slots via `getAvailableSlots()`
574
+ - Returnér 409:
575
+ ```json
576
+ {
577
+ "error": "slot_taken",
578
+ "message": "Another agent has priority for this time slot",
579
+ "winning_hold_id": "uuid",
580
+ "suggested_alternatives": [ { "start": "...", "end": "...", "confidence": 0.9 } ]
581
+ }
582
+ ```
583
+
584
+ **`DELETE /v1/holds/:hold_id`** (auth krævet)
585
+ - Valider at hold tilhører req.agent
586
+ - Opdatér `status = 'released'`
587
+ - Opret `hold_events` event: `type = 'released'`
588
+ - Returnér 200: `{ hold: Hold }`
589
+
590
+ **`GET /v1/holds`** (auth krævet)
591
+ - Query params: `agent_id?`, `from?`, `to?`, `status?` (default: 'confirmed')
592
+ - Returnér holds filtreret af params, sorteret by start_at ASC
593
+ - Kun holds inden for req.org
594
+
595
+ ### 3.5 — Preferences-endpoints
596
+
597
+ `src/routes/preferences.ts` — implementér:
598
+
599
+ **`GET /v1/preferences/:agent_id`** (auth krævet)
600
+ - Valider agent tilhører req.org
601
+ - Hent fra `agent_preferences`
602
+ - Returnér 200: `{ preferences: AgentPreferences }`
603
+
604
+ **`PUT /v1/preferences/:agent_id`** (auth krævet)
605
+
606
+ Body — validér med Zod præcis:
607
+ ```typescript
608
+ const PreferencesSchema = z.object({
609
+ buffer_minutes_after: z.number().int().min(0).max(120).optional(),
610
+ buffer_minutes_before: z.number().int().min(0).max(120).optional(),
611
+ focus_blocks: z.array(z.object({
612
+ weekday: z.number().int().min(0).max(6),
613
+ start_time: z.string().regex(/^\d{2}:\d{2}$/),
614
+ end_time: z.string().regex(/^\d{2}:\d{2}$/)
615
+ })).max(20).optional(),
616
+ priority_over_agents: z.array(z.string().uuid()).max(50).optional(),
617
+ booking_window_days: z.number().int().min(1).max(365).optional(),
618
+ allow_back_to_back: z.boolean().optional()
619
+ });
620
+ ```
621
+
622
+ - UPSERT til `agent_preferences` (kun opdatér felter der er tilsendt)
623
+ - Returnér 200: `{ preferences: AgentPreferences }`
624
+ - Returnér 422 med Zod-fejldetaljer ved ugyldige felter
625
+
626
+ ### 3.6 — Availability windows-endpoint
627
+
628
+ `src/routes/availability.ts` — tilføj:
629
+
630
+ **`POST /v1/availability-windows`** (auth krævet)
631
+
632
+ Body:
633
+ ```
634
+ agent_id: string (UUID)
635
+ start_at: string (ISO8601)
636
+ end_at: string (ISO8601)
637
+ window_type: 'available' | 'focus' | 'blocked'
638
+ recurrence?: 'daily' | 'weekly'
639
+ ```
640
+
641
+ - Indsæt i `availability_windows`
642
+ - Returnér 201: `{ window: AvailabilityWindow }`
643
+
644
+ **`GET /v1/availability-windows`** (auth krævet)
645
+ - Query params: `agent_id`, `from?`, `to?`
646
+ - Returnér windows for agenten
647
+
648
+ ### 3.7 — Integration tests
649
+
650
+ `tests/integration/availability.route.test.ts`:
651
+ - Opret test-org og test-agent i beforeAll
652
+ - Test: GET /v1/availability med gyldige params returnerer slots
653
+ - Test: GET /v1/availability uden auth returnerer 401
654
+ - Test: GET /v1/availability med ugyldig agent_id returnerer 403
655
+ - Test: GET /v1/availability med `from` i fortiden returnerer 400
656
+
657
+ `tests/integration/holds.route.test.ts`:
658
+ - Test: POST /v1/holds booker succesfuldt og returnerer 201
659
+ - Test: POST /v1/holds med overlappende hold fra lavere-prioritet agent: returnerer 201 med conflict_resolved=true
660
+ - Test: POST /v1/holds med overlappende hold fra højere-prioritet agent: returnerer 409 med suggested_alternatives
661
+ - Test: DELETE /v1/holds/:id frigiver holdet korrekt
662
+ - Test: Concurrent booking — kør 10 simultane POST /v1/holds til samme slot, verificér at præcis 1 lykkes
663
+
664
+ `tests/integration/preferences.route.test.ts`:
665
+ - Test: GET /v1/preferences returnerer defaults
666
+ - Test: PUT /v1/preferences opdaterer buffer_minutes_after
667
+ - Test: PUT /v1/preferences med ugyldig weekday returnerer 422
668
+
669
+ ---
670
+
671
+ ## Checkpoint 4 — Docker og smoke tests
672
+
673
+ **Valider med:** `docker compose up -d` starter uden fejl, `bash tests/smoke.sh` printer kun OK-linjer.
674
+
675
+ ### 4.1 — Dockerfile
676
+
677
+ ```dockerfile
678
+ FROM node:20-alpine AS builder
679
+ WORKDIR /app
680
+ COPY package*.json ./
681
+ RUN npm ci
682
+ COPY . .
683
+ RUN npm run build
684
+
685
+ FROM node:20-alpine
686
+ WORKDIR /app
687
+ RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
688
+ COPY --from=builder /app/node_modules ./node_modules
689
+ COPY --from=builder /app/src ./src
690
+ COPY --from=builder /app/tsconfig.json ./
691
+ USER nodejs
692
+ ENV NODE_ENV=production
693
+ EXPOSE 3000
694
+ CMD ["node", "-r", "ts-node/register", "src/index.ts"]
695
+ ```
696
+
697
+ ### 4.2 — docker-compose.yml
698
+
699
+ ```yaml
700
+ version: '3.9'
701
+ services:
702
+ api:
703
+ build: .
704
+ ports:
705
+ - "3000:3000"
706
+ environment:
707
+ DATABASE_URL: postgresql://postgres:postgres@db:5432/availsync
708
+ PORT: 3000
709
+ NODE_ENV: production
710
+ API_KEY_SALT_ROUNDS: 10
711
+ depends_on:
712
+ db:
713
+ condition: service_healthy
714
+ restart: unless-stopped
715
+
716
+ db:
717
+ image: postgres:16-alpine
718
+ environment:
719
+ POSTGRES_DB: availsync
720
+ POSTGRES_USER: postgres
721
+ POSTGRES_PASSWORD: postgres
722
+ volumes:
723
+ - postgres_data:/var/lib/postgresql/data
724
+ - ./src/db/migrations/001_init.sql:/docker-entrypoint-initdb.d/001_init.sql
725
+ healthcheck:
726
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
727
+ interval: 5s
728
+ timeout: 5s
729
+ retries: 5
730
+ ports:
731
+ - "5432:5432"
732
+
733
+ volumes:
734
+ postgres_data:
735
+ ```
736
+
737
+ ### 4.3 — Smoke tests
738
+
739
+ `tests/smoke.sh` — en serie curl-kald der verificerer hele API'et end-to-end:
740
+
741
+ ```bash
742
+ #!/bin/bash
743
+ set -e
744
+ BASE="http://localhost:3000"
745
+ ERRORS=0
746
+
747
+ check() {
748
+ local desc=$1
749
+ local expected=$2
750
+ local actual=$3
751
+ if [ "$actual" = "$expected" ]; then
752
+ echo "OK: $desc"
753
+ else
754
+ echo "FAIL: $desc (expected $expected, got $actual)"
755
+ ERRORS=$((ERRORS + 1))
756
+ fi
757
+ }
758
+
759
+ echo "=== Availsync Smoke Tests ==="
760
+
761
+ # Health check
762
+ STATUS=$(curl -s -o /dev/null -w "%{http_code}" $BASE/health)
763
+ check "Health endpoint" "200" "$STATUS"
764
+
765
+ # Opret org
766
+ ORG_RESPONSE=$(curl -s -X POST $BASE/v1/orgs \
767
+ -H "Content-Type: application/json" \
768
+ -d '{"name": "Test Org"}')
769
+ ORG_ID=$(echo $ORG_RESPONSE | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
770
+ check "Create org returns id" "true" "$([ -n "$ORG_ID" ] && echo true || echo false)"
771
+
772
+ # Opret agent
773
+ AGENT_RESPONSE=$(curl -s -X POST $BASE/v1/orgs/$ORG_ID/agents \
774
+ -H "Content-Type: application/json" \
775
+ -d '{"name": "Sales Bot", "agent_type": "external_meeting"}')
776
+ API_KEY=$(echo $AGENT_RESPONSE | grep -o '"api_key":"[^"]*"' | cut -d'"' -f4)
777
+ AGENT_ID=$(echo $AGENT_RESPONSE | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
778
+ check "Create agent returns api_key" "true" "$([ -n "$API_KEY" ] && echo true || echo false)"
779
+
780
+ # Opret availability window (næste uge, man-fre 9-17)
781
+ TOMORROW=$(date -d "+1 day" +%Y-%m-%dT09:00:00Z 2>/dev/null || date -v+1d +%Y-%m-%dT09:00:00Z)
782
+ TOMORROW_END=$(date -d "+1 day" +%Y-%m-%dT17:00:00Z 2>/dev/null || date -v+1d +%Y-%m-%dT17:00:00Z)
783
+ WIN_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST $BASE/v1/availability-windows \
784
+ -H "Authorization: Bearer $API_KEY" \
785
+ -H "Content-Type: application/json" \
786
+ -d "{\"agent_id\":\"$AGENT_ID\",\"start_at\":\"$TOMORROW\",\"end_at\":\"$TOMORROW_END\",\"window_type\":\"available\"}")
787
+ check "Create availability window" "201" "$WIN_STATUS"
788
+
789
+ # Hent availability
790
+ AVAIL_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
791
+ "$BASE/v1/availability?agent_id=$AGENT_ID&from=$TOMORROW&to=$TOMORROW_END&duration_minutes=30" \
792
+ -H "Authorization: Bearer $API_KEY")
793
+ check "Get availability" "200" "$AVAIL_STATUS"
794
+
795
+ # Book hold
796
+ HOLD_RESPONSE=$(curl -s -X POST $BASE/v1/holds \
797
+ -H "Authorization: Bearer $API_KEY" \
798
+ -H "Content-Type: application/json" \
799
+ -d "{\"agent_id\":\"$AGENT_ID\",\"start_at\":\"$TOMORROW\",\"end_at\":\"$(date -d "+1 day" +%Y-%m-%dT10:00:00Z 2>/dev/null || date -v+1d +%Y-%m-%dT10:00:00Z)\",\"reason\":\"Test booking\"}")
800
+ HOLD_STATUS=$(echo $HOLD_RESPONSE | grep -o '"status":"[^"]*"' | head -1 | cut -d'"' -f4)
801
+ check "Book hold returns confirmed status" "confirmed" "$HOLD_STATUS"
802
+
803
+ # Test auth fejl
804
+ UNAUTH_STATUS=$(curl -s -o /dev/null -w "%{http_code}" $BASE/v1/availability?agent_id=$AGENT_ID)
805
+ check "Unauthenticated request returns 401" "401" "$UNAUTH_STATUS"
806
+
807
+ echo ""
808
+ echo "=== Results: $ERRORS failure(s) ==="
809
+ exit $ERRORS
810
+ ```
811
+
812
+ ---
813
+
814
+ ## Checkpoint 5 — MCP-server (bonus, byg kun hvis alle tests er grønne)
815
+
816
+ **Valider med:** `node src/mcp/server.js --help` kører uden fejl.
817
+
818
+ Kun hvis checkpoints 1–4 er bestået: Byg en minimal MCP-server i `src/mcp/server.ts`
819
+ med `@modelcontextprotocol/sdk`. Installér: `npm install @modelcontextprotocol/sdk`
820
+
821
+ Eksponér præcis tre tools:
822
+
823
+ **`check_availability`**
824
+ ```typescript
825
+ {
826
+ name: "check_availability",
827
+ description: "Check available time slots for an AI scheduling agent before booking. Always call this before book_slot.",
828
+ inputSchema: {
829
+ type: "object",
830
+ properties: {
831
+ agent_id: { type: "string", description: "UUID of the agent to check availability for" },
832
+ from: { type: "string", description: "Start of search window (ISO 8601 UTC)" },
833
+ to: { type: "string", description: "End of search window (ISO 8601 UTC)" },
834
+ duration_minutes: { type: "number", description: "Required meeting duration in minutes" }
835
+ },
836
+ required: ["agent_id", "from", "to", "duration_minutes"]
837
+ }
838
+ }
839
+ ```
840
+
841
+ **`book_slot`**
842
+ ```typescript
843
+ {
844
+ name: "book_slot",
845
+ description: "Reserve a time slot. Returns confirmed hold or 409 with alternative suggestions if slot is taken by higher-priority agent.",
846
+ inputSchema: { /* agent_id, start_at, end_at, reason */ }
847
+ }
848
+ ```
849
+
850
+ **`release_slot`**
851
+ ```typescript
852
+ {
853
+ name: "release_slot",
854
+ description: "Release a previously booked hold so other agents can use the time.",
855
+ inputSchema: { /* hold_id */ }
856
+ }
857
+ ```
858
+
859
+ Server læser `AVAILSYNC_API_URL` og `AVAILSYNC_API_KEY` fra environment.
860
+ Start på stdio transport: `new StdioServerTransport()`.
861
+
862
+ Tilføj til `package.json`:
863
+ ```json
864
+ "mcp": "ts-node src/mcp/server.ts"
865
+ ```
866
+
867
+ Opret `MCP_SETUP.md` med:
868
+ ```markdown
869
+ # Tilslut Availsync til Claude Desktop
870
+
871
+ Tilføj til ~/Library/Application Support/Claude/claude_desktop_config.json:
872
+
873
+ {
874
+ "mcpServers": {
875
+ "availsync": {
876
+ "command": "npx",
877
+ "args": ["ts-node", "/path/to/availsync/src/mcp/server.ts"],
878
+ "env": {
879
+ "AVAILSYNC_API_URL": "http://localhost:3000",
880
+ "AVAILSYNC_API_KEY": "din-agent-api-nøgle-her"
881
+ }
882
+ }
883
+ }
884
+ }
885
+ ```
886
+
887
+ ---
888
+
889
+ ## Vigtige implementeringsnoter
890
+
891
+ **Race conditions:** `SELECT FOR UPDATE SKIP LOCKED` er kritisk i `checkAndResolveConflict()`.
892
+ Uden det kan to agenter booke samme slot simultant. Alle holds-operationer skal køre
893
+ i en eksplicit transaktion: `BEGIN` → conflict check → INSERT/UPDATE → `COMMIT`.
894
+
895
+ **Timezone-håndtering:** Gem ALT i UTC i databasen (TIMESTAMPTZ). Frontend konverterer
896
+ til lokal tid. Brug aldrig `new Date()` uden eksplicit timezone. `date-fns` bruges
897
+ til dato-matematik i `getAvailableSlots()`.
898
+
899
+ **Zod-validering:** Validér altid input med Zod FØR det rammer databasen.
900
+ Returnér Zod-fejl direkte som 422-response:
901
+ ```typescript
902
+ const result = Schema.safeParse(req.body);
903
+ if (!result.success) {
904
+ return res.status(422).json({
905
+ error: 'validation_error',
906
+ details: result.error.flatten()
907
+ });
908
+ }
909
+ ```
910
+
911
+ **Error handling:** Pak alle route-handlers ind i try/catch.
912
+ Log fejlen til console.error og returnér 500:
913
+ ```typescript
914
+ } catch (err) {
915
+ console.error('[holds] unexpected error:', err);
916
+ return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
917
+ }
918
+ ```
919
+
920
+ **Test-isolation:** Hvert integration-test-suite skal:
921
+ - I `beforeAll`: kør migration på test-DB, opret test-org og test-agent
922
+ - I `afterAll`: `DELETE FROM hold_events; DELETE FROM holds; DELETE FROM agents; DELETE FROM orgs;`
923
+ - Brug `TEST_DATABASE_URL` (separat test-database, aldrig production-DB)