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,840 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useRef, useState, useCallback } from 'react';
4
+ import { loadSession, type StoredSession } from '@/lib/storage';
5
+ import {
6
+ listAgents,
7
+ checkAvailability,
8
+ createAvailabilityWindow,
9
+ updateAvailabilityWindow,
10
+ deleteAvailabilityWindow,
11
+ getAvailabilityWindows,
12
+ getPreferences,
13
+ updatePreferences,
14
+ createHold,
15
+ getActivityLog,
16
+ } from '@/lib/api';
17
+ import { Badge } from '@/components/ui/Badge';
18
+ import { useToast } from '@/components/ui/Toast';
19
+ import { SkeletonRow } from '@/components/ui/Skeleton';
20
+ import { EmptyState } from '@/components/ui/EmptyState';
21
+ import { CalendarClock, Check, Pencil, Plus, Trash2, X } from 'lucide-react';
22
+ import type { Agent } from '@/lib/schemas';
23
+ import { toLocalInputValue } from '@/lib/format';
24
+
25
+ type FocusBlock = { weekday: number; start_time: string; end_time: string };
26
+
27
+ const weekdayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
28
+
29
+ function toIsoLocalMinute(value: string) {
30
+ const date = new Date(value);
31
+ const offset = date.getTimezoneOffset();
32
+ return new Date(date.getTime() - offset * 60_000).toISOString().slice(0, 16).replace('T', ' ');
33
+ }
34
+
35
+ function localFieldValue(date: Date) {
36
+ return toLocalInputValue(date).replace('T', ' ');
37
+ }
38
+
39
+ function fieldValueToDate(value: string) {
40
+ return new Date(value.trim().replace(' ', 'T'));
41
+ }
42
+
43
+ function formatSlotRange(start: string, end: string) {
44
+ return `${toIsoLocalMinute(start).slice(11)} - ${toIsoLocalMinute(end).slice(11)}`;
45
+ }
46
+
47
+ function activitySummary(event: { activity_type: string; metadata: Record<string, unknown> | null; status: string; status_code: number | null; error_code: string | null }) {
48
+ const metadata = event.metadata ?? {};
49
+ if (event.activity_type === 'availability_check') {
50
+ return `checked ${metadata.duration_minutes ?? ''}min slots`.replace(' ', ' ');
51
+ }
52
+ if (event.activity_type === 'hold_create') {
53
+ return event.status === 'success' ? 'claimed slot' : 'claim failed';
54
+ }
55
+ if (event.activity_type === 'conflict_preview') {
56
+ return 'previewed conflict';
57
+ }
58
+ if (event.activity_type.startsWith('work_') || event.activity_type.startsWith('automation_')) {
59
+ return event.activity_type.replaceAll('_', ' ');
60
+ }
61
+ return event.activity_type.replaceAll('_', ' ');
62
+ }
63
+
64
+ export default function AvailabilityPage() {
65
+ const { toast } = useToast();
66
+ const [session, setSession] = useState<StoredSession | null>(null);
67
+ const [agents, setAgents] = useState<Agent[]>([]);
68
+ const [selectedAgent, setSelectedAgent] = useState<string>('');
69
+ const [loading, setLoading] = useState(true);
70
+
71
+ // Availability windows
72
+ const [windows, setWindows] = useState<Array<{
73
+ id: string;
74
+ start_at: string;
75
+ end_at: string;
76
+ window_type: string;
77
+ }>>([]);
78
+ const [editingWindowId, setEditingWindowId] = useState<string | null>(null);
79
+ const [editWindowForm, setEditWindowForm] = useState({
80
+ start_at: '',
81
+ end_at: '',
82
+ window_type: 'available' as 'available' | 'focus' | 'blocked',
83
+ });
84
+
85
+ // Slot checker
86
+ const [from, setFrom] = useState(localFieldValue(new Date(Date.now() + 86400000)));
87
+ const [to, setTo] = useState(localFieldValue(new Date(Date.now() + 86400000 * 2)));
88
+ const [duration, setDuration] = useState(30);
89
+ const [slots, setSlots] = useState<Array<{
90
+ start: string;
91
+ end: string;
92
+ confidence: number;
93
+ competing_holds_count: number;
94
+ }>>([]);
95
+ const [checking, setChecking] = useState(false);
96
+ const [recentActivity, setRecentActivity] = useState<Array<{
97
+ id: string;
98
+ activity_type: string;
99
+ status: string;
100
+ status_code: number | null;
101
+ created_at: string;
102
+ metadata: Record<string, unknown> | null;
103
+ error_code: string | null;
104
+ }>>([]);
105
+
106
+ // New window form
107
+ const [showWindowForm, setShowWindowForm] = useState(false);
108
+ const [windowForm, setWindowForm] = useState({
109
+ start_at: localFieldValue(new Date(Date.now() + 86400000)),
110
+ end_at: localFieldValue(new Date(Date.now() + 86400000 + 28800000)),
111
+ window_type: 'available' as 'available' | 'focus' | 'blocked',
112
+ });
113
+
114
+ // Preferences
115
+ const [prefs, setPrefs] = useState<{
116
+ buffer_minutes_after: number;
117
+ buffer_minutes_before: number;
118
+ allow_back_to_back: boolean;
119
+ booking_window_days: number;
120
+ focus_blocks: FocusBlock[];
121
+ priority_over_agents: string[];
122
+ } | null>(null);
123
+ const [bufferDraft, setBufferDraft] = useState({
124
+ buffer_minutes_after: 0,
125
+ buffer_minutes_before: 0,
126
+ });
127
+ const preferenceSaveTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
128
+ const selectedAgentName = agents.find((agent) => agent.id === selectedAgent)?.name || 'agent';
129
+
130
+ useEffect(() => {
131
+ const s = loadSession();
132
+ setSession(s);
133
+ if (s) {
134
+ listAgents(s.orgId)
135
+ .then((data) => {
136
+ setAgents(data);
137
+ if (data.length > 0) setSelectedAgent(data[0].id);
138
+ setLoading(false);
139
+ })
140
+ .catch(() => setLoading(false));
141
+ } else {
142
+ setLoading(false);
143
+ }
144
+ }, []);
145
+
146
+ // Load windows and prefs when agent changes
147
+ useEffect(() => {
148
+ if (!session || !selectedAgent) return;
149
+ getAvailabilityWindows({ agent_id: selectedAgent })
150
+ .then((w) => setWindows(w.map((win: any) => ({
151
+ id: win.id,
152
+ start_at: win.start_at,
153
+ end_at: win.end_at,
154
+ window_type: win.window_type,
155
+ }))))
156
+ .catch(() => {});
157
+ getPreferences(selectedAgent)
158
+ .then((r) => setPrefs(r.preferences))
159
+ .catch(() => {});
160
+ }, [session, selectedAgent]);
161
+
162
+ useEffect(() => {
163
+ if (!prefs) return;
164
+ setBufferDraft({
165
+ buffer_minutes_after: prefs.buffer_minutes_after,
166
+ buffer_minutes_before: prefs.buffer_minutes_before,
167
+ });
168
+ }, [prefs?.buffer_minutes_after, prefs?.buffer_minutes_before]);
169
+
170
+ useEffect(() => {
171
+ return () => {
172
+ if (preferenceSaveTimer.current) {
173
+ clearTimeout(preferenceSaveTimer.current);
174
+ }
175
+ };
176
+ }, []);
177
+
178
+ const refreshWindows = async () => {
179
+ if (!selectedAgent) return;
180
+ const w = await getAvailabilityWindows({ agent_id: selectedAgent });
181
+ setWindows(w.map((win: any) => ({
182
+ id: win.id,
183
+ start_at: win.start_at,
184
+ end_at: win.end_at,
185
+ window_type: win.window_type,
186
+ })));
187
+ };
188
+
189
+ const refreshRecentActivity = useCallback(async () => {
190
+ if (!selectedAgent) return;
191
+ const activity = await getActivityLog({
192
+ agent_id: selectedAgent,
193
+ limit: 8,
194
+ }).catch(() => ({ events: [] }));
195
+ setRecentActivity(activity.events.map((event: any) => ({
196
+ id: event.id,
197
+ activity_type: event.activity_type,
198
+ status: event.status,
199
+ status_code: event.status_code,
200
+ created_at: event.created_at,
201
+ metadata: event.metadata,
202
+ error_code: event.error_code,
203
+ })));
204
+ }, [selectedAgent]);
205
+
206
+ useEffect(() => {
207
+ refreshRecentActivity();
208
+ }, [refreshRecentActivity]);
209
+
210
+ const handleCheckSlots = async () => {
211
+ if (!session || !selectedAgent) return;
212
+ setChecking(true);
213
+ try {
214
+ const result = await checkAvailability({
215
+ agent_id: selectedAgent,
216
+ from: fieldValueToDate(from).toISOString(),
217
+ to: fieldValueToDate(to).toISOString(),
218
+ duration_minutes: duration,
219
+ });
220
+ setSlots(result.slots.map((s) => ({
221
+ start: s.start,
222
+ end: s.end,
223
+ confidence: s.confidence,
224
+ competing_holds_count: s.competing_holds_count,
225
+ })));
226
+ await refreshRecentActivity();
227
+ } catch (err) {
228
+ toast((err as Error).message, 'error');
229
+ } finally {
230
+ setChecking(false);
231
+ }
232
+ };
233
+
234
+ const handleCreateWindow = async () => {
235
+ if (!session || !selectedAgent) return;
236
+ try {
237
+ await createAvailabilityWindow({
238
+ agent_id: selectedAgent,
239
+ start_at: fieldValueToDate(windowForm.start_at).toISOString(),
240
+ end_at: fieldValueToDate(windowForm.end_at).toISOString(),
241
+ window_type: windowForm.window_type,
242
+ });
243
+ toast('Window created', 'success');
244
+ setShowWindowForm(false);
245
+ await refreshWindows();
246
+ } catch (err) {
247
+ toast((err as Error).message, 'error');
248
+ }
249
+ };
250
+
251
+ const startEditWindow = (window: { id: string; start_at: string; end_at: string; window_type: string }) => {
252
+ setEditingWindowId(window.id);
253
+ setEditWindowForm({
254
+ start_at: localFieldValue(new Date(window.start_at)),
255
+ end_at: localFieldValue(new Date(window.end_at)),
256
+ window_type: window.window_type as 'available' | 'focus' | 'blocked',
257
+ });
258
+ };
259
+
260
+ const handleUpdateWindow = async () => {
261
+ if (!editingWindowId) return;
262
+ try {
263
+ await updateAvailabilityWindow({
264
+ window_id: editingWindowId,
265
+ start_at: fieldValueToDate(editWindowForm.start_at).toISOString(),
266
+ end_at: fieldValueToDate(editWindowForm.end_at).toISOString(),
267
+ window_type: editWindowForm.window_type,
268
+ });
269
+ toast('Window updated', 'success');
270
+ setEditingWindowId(null);
271
+ await refreshWindows();
272
+ } catch (err) {
273
+ toast((err as Error).message, 'error');
274
+ }
275
+ };
276
+
277
+ const handleDeleteWindow = async (windowId: string) => {
278
+ if (!window.confirm('Delete this availability window?')) return;
279
+ try {
280
+ await deleteAvailabilityWindow(windowId);
281
+ toast('Window deleted', 'success');
282
+ if (editingWindowId === windowId) setEditingWindowId(null);
283
+ await refreshWindows();
284
+ } catch (err) {
285
+ toast((err as Error).message, 'error');
286
+ }
287
+ };
288
+
289
+ const handleUpdatePrefs = async (data: Record<string, unknown>) => {
290
+ if (!session || !selectedAgent) return;
291
+ try {
292
+ await updatePreferences(selectedAgent, data);
293
+ const r = await getPreferences(selectedAgent);
294
+ setPrefs(r.preferences);
295
+ toast('Preferences updated', 'success');
296
+ } catch (err) {
297
+ toast((err as Error).message, 'error');
298
+ }
299
+ };
300
+
301
+ const schedulePreferenceUpdate = (data: Record<string, unknown>) => {
302
+ if (preferenceSaveTimer.current) {
303
+ clearTimeout(preferenceSaveTimer.current);
304
+ }
305
+ preferenceSaveTimer.current = setTimeout(() => {
306
+ preferenceSaveTimer.current = null;
307
+ handleUpdatePrefs(data);
308
+ }, 500);
309
+ };
310
+
311
+ const addFocusBlock = () => {
312
+ if (!prefs) return;
313
+ handleUpdatePrefs({
314
+ focus_blocks: [
315
+ ...prefs.focus_blocks,
316
+ { weekday: 1, start_time: '09:00', end_time: '10:00' },
317
+ ],
318
+ });
319
+ };
320
+
321
+ const updateFocusBlock = (index: number, block: FocusBlock) => {
322
+ if (!prefs) return;
323
+ handleUpdatePrefs({
324
+ focus_blocks: prefs.focus_blocks.map((item, itemIndex) => (itemIndex === index ? block : item)),
325
+ });
326
+ };
327
+
328
+ const removeFocusBlock = (index: number) => {
329
+ if (!prefs) return;
330
+ handleUpdatePrefs({
331
+ focus_blocks: prefs.focus_blocks.filter((_, itemIndex) => itemIndex !== index),
332
+ });
333
+ };
334
+
335
+ const togglePriorityOverAgent = (agentId: string) => {
336
+ if (!prefs) return;
337
+ const current = new Set(prefs.priority_over_agents);
338
+ if (current.has(agentId)) {
339
+ current.delete(agentId);
340
+ } else {
341
+ current.add(agentId);
342
+ }
343
+ handleUpdatePrefs({ priority_over_agents: Array.from(current) });
344
+ };
345
+
346
+ const handleBookSlot = async (start: string, end: string) => {
347
+ if (!session || !selectedAgent) return;
348
+ try {
349
+ await createHold({
350
+ agent_id: selectedAgent,
351
+ start_at: start,
352
+ end_at: end,
353
+ reason: 'Booked from dashboard',
354
+ });
355
+ toast('Hold booked', 'success');
356
+ await handleCheckSlots();
357
+ } catch (err) {
358
+ toast((err as Error).message, 'error');
359
+ }
360
+ };
361
+
362
+ if (loading) {
363
+ return (
364
+ <div className="w-full max-w-none p-4 sm:p-6">
365
+ <h1 className="text-title font-semibold text-text-primary mb-6">Availability</h1>
366
+ <div className="space-y-2">{Array.from({ length: 3 }).map((_, i) => <SkeletonRow key={i} />)}</div>
367
+ </div>
368
+ );
369
+ }
370
+
371
+ return (
372
+ <div className="w-full max-w-none p-4 sm:p-6">
373
+ <h1 className="text-title font-semibold text-text-primary mb-6">Availability</h1>
374
+
375
+ <div className="grid grid-cols-1 gap-6 xl:grid-cols-[minmax(0,1fr)_360px]">
376
+ {/* Left: Windows + Slot checker */}
377
+ <div className="lg:col-span-2 space-y-6">
378
+ {/* Agent selector */}
379
+ <div className="flex items-center gap-3">
380
+ <label className="text-label uppercase text-text-tertiary">Agent</label>
381
+ <select
382
+ value={selectedAgent}
383
+ onChange={(e) => setSelectedAgent(e.target.value)}
384
+ className="rounded bg-surface border border-border px-3 py-1.5 text-body text-text-primary outline-none focus:border-border-focus"
385
+ >
386
+ {agents.map((a) => (
387
+ <option key={a.id} value={a.id}>{a.name}</option>
388
+ ))}
389
+ </select>
390
+ </div>
391
+
392
+ {/* Availability windows */}
393
+ <div>
394
+ <div className="flex items-center justify-between mb-3">
395
+ <h2 className="text-heading font-medium text-text-primary">Windows</h2>
396
+ <button
397
+ onClick={() => setShowWindowForm(!showWindowForm)}
398
+ className="flex items-center gap-1 text-body text-accent hover:text-accent-hover"
399
+ >
400
+ <Plus className="h-3.5 w-3.5" />
401
+ Add window
402
+ </button>
403
+ </div>
404
+
405
+ {showWindowForm && (
406
+ <div className="rounded border border-border bg-surface p-4 mb-3 space-y-3">
407
+ <div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
408
+ <div>
409
+ <label className="block text-label uppercase text-text-tertiary mb-1">Start</label>
410
+ <input
411
+ type="text"
412
+ value={windowForm.start_at}
413
+ onChange={(e) => setWindowForm({ ...windowForm, start_at: e.target.value })}
414
+ placeholder="2026-05-13 09:00"
415
+ className="w-full rounded bg-bg border border-border px-3 py-1.5 font-mono text-body text-text-primary outline-none focus:border-border-focus"
416
+ />
417
+ </div>
418
+ <div>
419
+ <label className="block text-label uppercase text-text-tertiary mb-1">End</label>
420
+ <input
421
+ type="text"
422
+ value={windowForm.end_at}
423
+ onChange={(e) => setWindowForm({ ...windowForm, end_at: e.target.value })}
424
+ placeholder="2026-05-13 17:00"
425
+ className="w-full rounded bg-bg border border-border px-3 py-1.5 font-mono text-body text-text-primary outline-none focus:border-border-focus"
426
+ />
427
+ </div>
428
+ </div>
429
+ <div>
430
+ <label className="block text-label uppercase text-text-tertiary mb-1">Type</label>
431
+ <select
432
+ value={windowForm.window_type}
433
+ onChange={(e) => setWindowForm({ ...windowForm, window_type: e.target.value as any })}
434
+ className="rounded bg-bg border border-border px-3 py-1.5 text-body text-text-primary outline-none focus:border-border-focus"
435
+ >
436
+ <option value="available">Available</option>
437
+ <option value="focus">Focus</option>
438
+ <option value="blocked">Blocked</option>
439
+ </select>
440
+ </div>
441
+ <button
442
+ onClick={handleCreateWindow}
443
+ className="rounded bg-accent text-white px-3 py-1.5 text-body font-medium hover:bg-accent-hover transition-colors"
444
+ >
445
+ Create window
446
+ </button>
447
+ </div>
448
+ )}
449
+
450
+ {windows.length === 0 ? (
451
+ <EmptyState
452
+ icon={CalendarClock}
453
+ title="No windows"
454
+ description="Create availability windows so agents know when they can reserve time."
455
+ />
456
+ ) : (
457
+ <div className="overflow-x-auto rounded border border-border dark-scroll">
458
+ <table className="w-full min-w-[720px]">
459
+ <thead>
460
+ <tr className="border-b border-border bg-surface text-label uppercase text-text-tertiary">
461
+ <th className="text-left px-4 py-2 font-medium">Start</th>
462
+ <th className="text-left px-4 py-2 font-medium">End</th>
463
+ <th className="text-left px-4 py-2 font-medium">Type</th>
464
+ <th className="text-right px-4 py-2 font-medium">Actions</th>
465
+ </tr>
466
+ </thead>
467
+ <tbody>
468
+ {windows.map((w) => (
469
+ <tr key={w.id} className="border-b border-border h-9 hover:bg-surface-raised transition-colors duration-150">
470
+ {editingWindowId === w.id ? (
471
+ <>
472
+ <td className="px-4 py-2">
473
+ <input
474
+ type="text"
475
+ value={editWindowForm.start_at}
476
+ onChange={(e) => setEditWindowForm({ ...editWindowForm, start_at: e.target.value })}
477
+ className="w-full rounded bg-bg border border-border px-2 py-1 font-mono text-body text-text-primary outline-none focus:border-border-focus"
478
+ />
479
+ </td>
480
+ <td className="px-4 py-2">
481
+ <input
482
+ type="text"
483
+ value={editWindowForm.end_at}
484
+ onChange={(e) => setEditWindowForm({ ...editWindowForm, end_at: e.target.value })}
485
+ className="w-full rounded bg-bg border border-border px-2 py-1 font-mono text-body text-text-primary outline-none focus:border-border-focus"
486
+ />
487
+ </td>
488
+ <td className="px-4 py-2">
489
+ <select
490
+ value={editWindowForm.window_type}
491
+ onChange={(e) => setEditWindowForm({ ...editWindowForm, window_type: e.target.value as 'available' | 'focus' | 'blocked' })}
492
+ className="rounded bg-bg border border-border px-2 py-1 text-body text-text-primary outline-none focus:border-border-focus"
493
+ >
494
+ <option value="available">Available</option>
495
+ <option value="focus">Focus</option>
496
+ <option value="blocked">Blocked</option>
497
+ </select>
498
+ </td>
499
+ <td className="px-4 py-2">
500
+ <div className="flex items-center justify-end gap-2">
501
+ <button
502
+ onClick={handleUpdateWindow}
503
+ className="text-success hover:text-success/80"
504
+ title="Save changes"
505
+ >
506
+ <Check className="h-3.5 w-3.5" />
507
+ </button>
508
+ <button
509
+ onClick={() => setEditingWindowId(null)}
510
+ className="text-text-tertiary hover:text-text-secondary"
511
+ title="Cancel"
512
+ >
513
+ <X className="h-3.5 w-3.5" />
514
+ </button>
515
+ </div>
516
+ </td>
517
+ </>
518
+ ) : (
519
+ <>
520
+ <td className="px-4 font-mono text-body text-text-secondary">
521
+ {toIsoLocalMinute(w.start_at)}
522
+ </td>
523
+ <td className="px-4 font-mono text-body text-text-secondary">
524
+ {toIsoLocalMinute(w.end_at)}
525
+ </td>
526
+ <td className="px-4">
527
+ <Badge variant={w.window_type === 'available' ? 'success' : w.window_type === 'focus' ? 'accent' : 'error'}>
528
+ {w.window_type}
529
+ </Badge>
530
+ </td>
531
+ <td className="px-4">
532
+ <div className="flex items-center justify-end gap-2">
533
+ <button
534
+ onClick={() => startEditWindow(w)}
535
+ className="text-text-tertiary hover:text-text-secondary"
536
+ title="Edit window"
537
+ >
538
+ <Pencil className="h-3.5 w-3.5" />
539
+ </button>
540
+ <button
541
+ onClick={() => handleDeleteWindow(w.id)}
542
+ className="text-error hover:text-error/80"
543
+ title="Delete window"
544
+ >
545
+ <Trash2 className="h-3.5 w-3.5" />
546
+ </button>
547
+ </div>
548
+ </td>
549
+ </>
550
+ )}
551
+ </tr>
552
+ ))}
553
+ </tbody>
554
+ </table>
555
+ </div>
556
+ )}
557
+ </div>
558
+
559
+ {/* Slot checker */}
560
+ <div>
561
+ <h2 className="text-heading font-medium text-text-primary mb-3">Check slots</h2>
562
+ <div className="rounded border border-border bg-surface p-4 space-y-3">
563
+ <div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
564
+ <div>
565
+ <label className="block text-label uppercase text-text-tertiary mb-1">From</label>
566
+ <input
567
+ type="text"
568
+ value={from}
569
+ onChange={(e) => setFrom(e.target.value)}
570
+ placeholder="2026-05-13 09:00"
571
+ className="w-full rounded bg-bg border border-border px-3 py-1.5 font-mono text-body text-text-primary outline-none focus:border-border-focus"
572
+ />
573
+ </div>
574
+ <div>
575
+ <label className="block text-label uppercase text-text-tertiary mb-1">To</label>
576
+ <input
577
+ type="text"
578
+ value={to}
579
+ onChange={(e) => setTo(e.target.value)}
580
+ placeholder="2026-05-13 17:00"
581
+ className="w-full rounded bg-bg border border-border px-3 py-1.5 font-mono text-body text-text-primary outline-none focus:border-border-focus"
582
+ />
583
+ </div>
584
+ <div>
585
+ <label className="block text-label uppercase text-text-tertiary mb-1">Duration (min)</label>
586
+ <input
587
+ type="number"
588
+ value={duration}
589
+ onChange={(e) => setDuration(Number(e.target.value))}
590
+ min={1}
591
+ max={480}
592
+ className="w-full rounded bg-bg border border-border px-3 py-1.5 text-body text-text-primary outline-none focus:border-border-focus"
593
+ />
594
+ </div>
595
+ </div>
596
+ <button
597
+ onClick={handleCheckSlots}
598
+ disabled={checking}
599
+ className="rounded bg-accent text-white px-3 py-1.5 text-body font-medium hover:bg-accent-hover transition-colors disabled:opacity-40"
600
+ >
601
+ {checking ? 'Checking...' : 'Check availability'}
602
+ </button>
603
+ </div>
604
+
605
+ {slots.length > 0 && (
606
+ <div className="mt-3 overflow-x-auto rounded border border-border bg-surface dark-scroll">
607
+ <div className="border-b border-border px-4 py-3">
608
+ <h3 className="text-body font-medium text-text-primary">Available slots</h3>
609
+ </div>
610
+ <table className="w-full min-w-[700px]">
611
+ <thead>
612
+ <tr className="border-b border-border bg-surface text-label uppercase text-text-tertiary">
613
+ <th className="text-left px-4 py-2 font-medium">Slot</th>
614
+ <th className="text-left px-4 py-2 font-medium">Confidence</th>
615
+ <th className="text-left px-4 py-2 font-medium">Competition</th>
616
+ <th className="text-left px-4 py-2 font-medium"></th>
617
+ </tr>
618
+ </thead>
619
+ <tbody>
620
+ {slots.map((slot, i) => (
621
+ <tr key={i} className="border-b border-border h-9 hover:bg-surface-raised transition-colors duration-150">
622
+ <td className="px-4 font-mono text-body text-text-secondary">
623
+ {formatSlotRange(slot.start, slot.end)}
624
+ </td>
625
+ <td className="px-4">
626
+ <Badge variant={slot.confidence > 0.7 ? 'success' : slot.confidence > 0.4 ? 'warning' : 'error'}>
627
+ {Math.round(slot.confidence * 100)}%
628
+ </Badge>
629
+ </td>
630
+ <td className="px-4 font-mono text-body text-text-tertiary">
631
+ {slot.competing_holds_count} competing holds
632
+ </td>
633
+ <td className="px-4">
634
+ <button
635
+ onClick={() => handleBookSlot(slot.start, slot.end)}
636
+ className="text-body text-accent hover:text-accent-hover"
637
+ >
638
+ Book
639
+ </button>
640
+ </td>
641
+ </tr>
642
+ ))}
643
+ </tbody>
644
+ </table>
645
+ </div>
646
+ )}
647
+ </div>
648
+ </div>
649
+
650
+ {/* Right: Preferences panel */}
651
+ <div className="space-y-6">
652
+ <div>
653
+ <h2 className="text-heading font-medium text-text-primary mb-3">Preferences</h2>
654
+ {prefs ? (
655
+ <div className="rounded border border-border bg-surface p-4 space-y-4">
656
+ <div>
657
+ <label className="block text-label uppercase text-text-tertiary mb-1">
658
+ Buffer after (min)
659
+ </label>
660
+ <input
661
+ type="range"
662
+ min={0}
663
+ max={60}
664
+ step={5}
665
+ value={bufferDraft.buffer_minutes_after}
666
+ onChange={(e) => {
667
+ const value = Number(e.target.value);
668
+ setBufferDraft((current) => ({ ...current, buffer_minutes_after: value }));
669
+ schedulePreferenceUpdate({ buffer_minutes_after: value });
670
+ }}
671
+ className="w-full accent-accent"
672
+ />
673
+ <span className="font-mono text-body text-text-secondary">{bufferDraft.buffer_minutes_after} min</span>
674
+ </div>
675
+ <div>
676
+ <label className="block text-label uppercase text-text-tertiary mb-1">
677
+ Buffer before (min)
678
+ </label>
679
+ <input
680
+ type="range"
681
+ min={0}
682
+ max={60}
683
+ step={5}
684
+ value={bufferDraft.buffer_minutes_before}
685
+ onChange={(e) => {
686
+ const value = Number(e.target.value);
687
+ setBufferDraft((current) => ({ ...current, buffer_minutes_before: value }));
688
+ schedulePreferenceUpdate({ buffer_minutes_before: value });
689
+ }}
690
+ className="w-full accent-accent"
691
+ />
692
+ <span className="font-mono text-body text-text-secondary">{bufferDraft.buffer_minutes_before} min</span>
693
+ </div>
694
+ <div className="flex items-center justify-between">
695
+ <label className="text-body text-text-secondary">Back-to-back</label>
696
+ <button
697
+ onClick={() => handleUpdatePrefs({ allow_back_to_back: !prefs.allow_back_to_back })}
698
+ className={`w-10 h-5 rounded-full transition-colors ${
699
+ prefs.allow_back_to_back ? 'bg-accent' : 'bg-border'
700
+ }`}
701
+ >
702
+ <div
703
+ className={`h-4 w-4 rounded-full bg-white transition-transform ${
704
+ prefs.allow_back_to_back ? 'translate-x-5' : 'translate-x-0.5'
705
+ }`}
706
+ />
707
+ </button>
708
+ </div>
709
+ <div>
710
+ <label className="block text-label uppercase text-text-tertiary mb-1">
711
+ Booking window
712
+ </label>
713
+ <input
714
+ type="number"
715
+ min={1}
716
+ max={365}
717
+ value={prefs.booking_window_days}
718
+ onChange={(e) => handleUpdatePrefs({ booking_window_days: Number(e.target.value) })}
719
+ className="w-full rounded bg-bg border border-border px-3 py-1.5 text-body text-text-primary outline-none focus:border-border-focus"
720
+ />
721
+ <span className="mt-1 block font-mono text-body text-text-secondary">
722
+ {prefs.booking_window_days} days
723
+ </span>
724
+ </div>
725
+ <div className="border-t border-border pt-4">
726
+ <div className="mb-2 flex items-center justify-between">
727
+ <label className="text-label uppercase text-text-tertiary">Focus blocks</label>
728
+ <button onClick={addFocusBlock} className="text-body text-accent hover:text-accent-hover">
729
+ Add
730
+ </button>
731
+ </div>
732
+ <div className="space-y-2">
733
+ {prefs.focus_blocks.length === 0 ? (
734
+ <p className="text-body text-text-tertiary">No focus blocks.</p>
735
+ ) : (
736
+ prefs.focus_blocks.map((block, index) => (
737
+ <div className="grid grid-cols-[1fr_1fr_auto] gap-2 sm:grid-cols-[72px_1fr_1fr_auto]" key={`${block.weekday}-${block.start_time}-${index}`}>
738
+ <select
739
+ value={block.weekday}
740
+ onChange={(e) => updateFocusBlock(index, { ...block, weekday: Number(e.target.value) })}
741
+ className="col-span-3 rounded bg-bg border border-border px-2 py-1 text-body text-text-primary outline-none focus:border-border-focus sm:col-span-1"
742
+ >
743
+ {weekdayLabels.map((label, value) => (
744
+ <option key={label} value={value}>{label}</option>
745
+ ))}
746
+ </select>
747
+ <input
748
+ type="time"
749
+ value={block.start_time}
750
+ onChange={(e) => updateFocusBlock(index, { ...block, start_time: e.target.value })}
751
+ className="rounded bg-bg border border-border px-2 py-1 text-body text-text-primary outline-none focus:border-border-focus"
752
+ />
753
+ <input
754
+ type="time"
755
+ value={block.end_time}
756
+ onChange={(e) => updateFocusBlock(index, { ...block, end_time: e.target.value })}
757
+ className="rounded bg-bg border border-border px-2 py-1 text-body text-text-primary outline-none focus:border-border-focus"
758
+ />
759
+ <button
760
+ onClick={() => removeFocusBlock(index)}
761
+ className="text-error hover:text-error/80"
762
+ title="Remove focus block"
763
+ >
764
+ <Trash2 className="h-3.5 w-3.5" />
765
+ </button>
766
+ </div>
767
+ ))
768
+ )}
769
+ </div>
770
+ </div>
771
+ <div className="border-t border-border pt-4">
772
+ <label className="block text-label uppercase text-text-tertiary mb-2">
773
+ Priority over agents
774
+ </label>
775
+ <div className="space-y-2">
776
+ {agents.filter((agent) => agent.id !== selectedAgent).length === 0 ? (
777
+ <p className="text-body text-text-tertiary">Create another agent to set overrides.</p>
778
+ ) : (
779
+ agents.filter((agent) => agent.id !== selectedAgent).map((agent) => (
780
+ <label key={agent.id} className="flex items-center justify-between rounded border border-border bg-bg px-3 py-2">
781
+ <span className="text-body text-text-secondary">{agent.name}</span>
782
+ <input
783
+ type="checkbox"
784
+ checked={prefs.priority_over_agents.includes(agent.id)}
785
+ onChange={() => togglePriorityOverAgent(agent.id)}
786
+ className="h-4 w-4 accent-accent"
787
+ />
788
+ </label>
789
+ ))
790
+ )}
791
+ </div>
792
+ </div>
793
+ </div>
794
+ ) : (
795
+ <p className="text-body text-text-tertiary">Select an agent to view preferences.</p>
796
+ )}
797
+ </div>
798
+ </div>
799
+ </div>
800
+
801
+ <section className="mt-6">
802
+ <h2 className="text-heading font-medium text-text-primary mb-3">Recent activity - {selectedAgentName}</h2>
803
+ {recentActivity.length === 0 ? (
804
+ <div className="rounded border border-border bg-surface p-4 text-body text-text-tertiary">
805
+ No slot checks or claims observed for this agent yet.
806
+ </div>
807
+ ) : (
808
+ <div className="overflow-x-auto rounded border border-border dark-scroll">
809
+ <table className="w-full min-w-[760px]">
810
+ <thead>
811
+ <tr className="border-b border-border bg-surface text-label uppercase text-text-tertiary">
812
+ <th className="text-left px-4 py-2 font-medium">Time</th>
813
+ <th className="text-left px-4 py-2 font-medium">Action</th>
814
+ <th className="text-left px-4 py-2 font-medium">Outcome</th>
815
+ </tr>
816
+ </thead>
817
+ <tbody>
818
+ {recentActivity.map((event) => (
819
+ <tr key={event.id} className="border-b border-border h-9 hover:bg-surface-raised">
820
+ <td className="px-4 font-mono text-body text-text-tertiary">
821
+ {toIsoLocalMinute(event.created_at)}
822
+ </td>
823
+ <td className="px-4 text-body text-text-secondary">
824
+ {activitySummary(event)}
825
+ </td>
826
+ <td className="px-4">
827
+ <Badge variant={event.status === 'success' ? 'success' : 'error'}>
828
+ {event.error_code || event.status_code || event.status}
829
+ </Badge>
830
+ </td>
831
+ </tr>
832
+ ))}
833
+ </tbody>
834
+ </table>
835
+ </div>
836
+ )}
837
+ </section>
838
+ </div>
839
+ );
840
+ }