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,514 @@
1
+ import express from 'express';
2
+ import { z } from 'zod';
3
+ import pool from '../db/client';
4
+ import { migrationSummary } from '../db/migrations';
5
+ import { appVersion, gitCommit } from '../lib/appInfo';
6
+ import { envReadiness, stripeReadiness } from '../lib/env';
7
+ import { requireAdminSession } from '../middleware/auth';
8
+ import { log } from '../lib/logger';
9
+
10
+ const router = express.Router();
11
+
12
+ const OrgListQuerySchema = z.object({
13
+ plan: z.enum(['free', 'individual', 'team']).optional(),
14
+ q: z.string().max(120).optional(),
15
+ active: z.enum(['today', '7d', '30d']).optional(),
16
+ limit: z.coerce.number().int().min(1).max(200).optional().default(50),
17
+ offset: z.coerce.number().int().min(0).optional().default(0),
18
+ });
19
+
20
+ const SupportListQuerySchema = z.object({
21
+ status: z.enum(['open', 'in_review', 'closed']).optional(),
22
+ plan: z.enum(['individual', 'team']).optional(),
23
+ q: z.string().max(120).optional(),
24
+ limit: z.coerce.number().int().min(1).max(100).optional().default(50),
25
+ offset: z.coerce.number().int().min(0).optional().default(0),
26
+ });
27
+
28
+ const SupportUpdateSchema = z.object({
29
+ status: z.enum(['open', 'in_review', 'closed']),
30
+ });
31
+
32
+ const WaitlistListQuerySchema = z.object({
33
+ status: z.enum(['new', 'contacted', 'closed']).optional(),
34
+ plan: z.enum(['individual', 'team']).optional(),
35
+ q: z.string().max(120).optional(),
36
+ limit: z.coerce.number().int().min(1).max(100).optional().default(50),
37
+ offset: z.coerce.number().int().min(0).optional().default(0),
38
+ });
39
+
40
+ const WaitlistUpdateSchema = z.object({
41
+ status: z.enum(['new', 'contacted', 'closed']),
42
+ });
43
+
44
+ router.get('/admin/overview', requireAdminSession, async (_req, res) => {
45
+ try {
46
+ const result = await pool.query(
47
+ `SELECT
48
+ (SELECT COUNT(*)::int FROM orgs) AS total_workspaces,
49
+ (SELECT COUNT(DISTINCT org_id)::int FROM agent_activity_events WHERE created_at >= NOW() - INTERVAL '24 hours') AS active_workspaces,
50
+ (SELECT COUNT(*)::int FROM orgs WHERE plan = 'free') AS free_workspaces,
51
+ (SELECT COUNT(*)::int FROM orgs WHERE plan = 'individual') AS individual_workspaces,
52
+ (SELECT COUNT(*)::int FROM orgs WHERE plan = 'team') AS team_workspaces,
53
+ (SELECT COUNT(*)::int FROM orgs WHERE plan <> 'free') AS paid_workspaces,
54
+ (SELECT COALESCE(SUM(CASE WHEN plan = 'individual' THEN 10 WHEN plan = 'team' THEN 25 ELSE 0 END), 0)::int FROM orgs WHERE subscription_status IN ('active', 'trialing')) AS estimated_mrr,
55
+ (SELECT COUNT(*)::int FROM orgs WHERE subscription_status = 'canceled' OR cancel_at_period_end = true) AS canceled_workspaces,
56
+ (SELECT COUNT(*)::int FROM agents) AS total_agents,
57
+ (SELECT COUNT(*)::int FROM agents WHERE GREATEST(COALESCE(last_seen_at, 'epoch'::timestamptz), COALESCE(mcp_last_seen_at, 'epoch'::timestamptz)) > NOW() - INTERVAL '15 minutes') AS connected_agents,
58
+ (SELECT COUNT(*)::int FROM agent_activity_events WHERE created_at >= date_trunc('day', NOW())) AS api_calls_today,
59
+ (SELECT COUNT(*)::int FROM agent_activity_events WHERE created_at >= date_trunc('month', NOW())) AS api_calls_month,
60
+ (SELECT COUNT(*)::int FROM agent_activity_events WHERE status = 'error' AND created_at >= date_trunc('day', NOW())) AS errors_today,
61
+ (SELECT COUNT(DISTINCT org_id)::int FROM agent_activity_events WHERE created_at >= NOW() - INTERVAL '7 days' AND activity_type NOT LIKE 'setup_test%' AND activity_type NOT IN ('verification_run', 'connection_test') AND COALESCE(metadata->>'source', '') NOT IN ('setup_test', 'onboarding_verification')) AS active_workspaces_7d,
62
+ (SELECT COUNT(*)::int FROM agents WHERE last_seen_at IS NULL AND mcp_last_seen_at IS NULL AND NOT EXISTS (SELECT 1 FROM agent_activity_events WHERE agent_activity_events.agent_id = agents.id AND activity_type NOT LIKE 'setup_test%' AND activity_type NOT IN ('verification_run', 'connection_test') AND COALESCE(metadata->>'source', '') NOT IN ('setup_test', 'onboarding_verification'))) AS never_connected_agents,
63
+ (SELECT COUNT(*)::int FROM agents WHERE GREATEST(COALESCE(last_seen_at, 'epoch'::timestamptz), COALESCE(mcp_last_seen_at, 'epoch'::timestamptz), COALESCE((SELECT MAX(created_at) FROM agent_activity_events WHERE agent_activity_events.agent_id = agents.id AND activity_type NOT LIKE 'setup_test%' AND activity_type NOT IN ('verification_run', 'connection_test') AND COALESCE(metadata->>'source', '') NOT IN ('setup_test', 'onboarding_verification')), 'epoch'::timestamptz)) < NOW() - INTERVAL '3 days' AND (last_seen_at IS NOT NULL OR mcp_last_seen_at IS NOT NULL OR EXISTS (SELECT 1 FROM agent_activity_events WHERE agent_activity_events.agent_id = agents.id AND activity_type NOT LIKE 'setup_test%' AND activity_type NOT IN ('verification_run', 'connection_test') AND COALESCE(metadata->>'source', '') NOT IN ('setup_test', 'onboarding_verification')))) AS quiet_agents,
64
+ ((SELECT COUNT(*)::int FROM work_claim_events WHERE event_type = 'blocked' AND created_at >= NOW() - INTERVAL '7 days' AND COALESCE(metadata->>'source', '') NOT IN ('setup_test', 'onboarding_verification')) + (SELECT COUNT(*)::int FROM hold_events WHERE event_type IN ('conflict_won', 'conflict_lost', 'superseded') AND created_at >= NOW() - INTERVAL '7 days')) AS conflicts_prevented_7d,
65
+ (SELECT COUNT(*)::int FROM work_claims WHERE status = 'active' AND expires_at >= NOW()) AS active_work_claims,
66
+ (SELECT COUNT(*)::int FROM work_claim_events WHERE event_type = 'blocked') AS blocked_agent_runs,
67
+ (SELECT COUNT(*)::int FROM paid_plan_waitlist) AS waitlist_leads,
68
+ (SELECT COUNT(*)::int FROM paid_plan_waitlist WHERE status = 'new') AS waitlist_new,
69
+ (SELECT MAX(created_at) FROM orgs) AS latest_signup_at`,
70
+ );
71
+
72
+ return res.json(result.rows[0]);
73
+ } catch (err) {
74
+ log.error('[admin] overview error', { error: (err as Error).message });
75
+ return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
76
+ }
77
+ });
78
+
79
+ router.get('/admin/system', requireAdminSession, async (_req, res) => {
80
+ try {
81
+ const [migrations, latest] = await Promise.all([
82
+ migrationSummary(),
83
+ pool.query(
84
+ `SELECT
85
+ (SELECT MAX(created_at) FROM agent_activity_events) AS latest_activity_at,
86
+ (SELECT MAX(created_at) FROM hold_events) AS latest_hold_event_at,
87
+ (SELECT MAX(created_at) FROM work_claim_events) AS latest_work_event_at`,
88
+ ),
89
+ ]);
90
+ await pool.query('SELECT 1');
91
+
92
+ const env = envReadiness();
93
+ const stripe = stripeReadiness();
94
+ const status = migrations.failed > 0 || !env.ok ? 'degraded' : 'ok';
95
+
96
+ return res.json({
97
+ status,
98
+ app: {
99
+ version: appVersion(),
100
+ git_commit: gitCommit(),
101
+ node_version: process.version,
102
+ uptime_seconds: Math.floor(process.uptime()),
103
+ },
104
+ database: {
105
+ connected: true,
106
+ },
107
+ migrations,
108
+ env,
109
+ stripe,
110
+ latest_events: latest.rows[0],
111
+ timestamp: new Date().toISOString(),
112
+ });
113
+ } catch (err) {
114
+ log.error('[admin] system error', { error: (err as Error).message });
115
+ return res.status(500).json({
116
+ status: 'error',
117
+ error: 'system_diagnostics_failed',
118
+ message: 'Unable to load system diagnostics',
119
+ });
120
+ }
121
+ });
122
+
123
+ router.get('/admin/support/tickets', requireAdminSession, async (req, res) => {
124
+ const parsed = SupportListQuerySchema.safeParse(req.query);
125
+ if (!parsed.success) {
126
+ return res.status(400).json({ error: 'validation_error', details: parsed.error.flatten() });
127
+ }
128
+
129
+ try {
130
+ const result = await pool.query(
131
+ `SELECT
132
+ support_tickets.id,
133
+ support_tickets.org_id,
134
+ orgs.name AS org_name,
135
+ support_tickets.user_id,
136
+ support_tickets.email,
137
+ support_tickets.subject,
138
+ support_tickets.category,
139
+ support_tickets.message,
140
+ support_tickets.status,
141
+ support_tickets.priority,
142
+ support_tickets.plan,
143
+ support_tickets.context,
144
+ support_tickets.created_at,
145
+ support_tickets.updated_at,
146
+ COUNT(*) OVER()::int AS total_count
147
+ FROM support_tickets
148
+ JOIN orgs ON orgs.id = support_tickets.org_id
149
+ WHERE ($1::text IS NULL OR support_tickets.status = $1)
150
+ AND ($2::text IS NULL OR support_tickets.plan = $2)
151
+ AND (
152
+ $3::text IS NULL
153
+ OR support_tickets.email ILIKE '%' || $3 || '%'
154
+ OR support_tickets.subject ILIKE '%' || $3 || '%'
155
+ OR orgs.name ILIKE '%' || $3 || '%'
156
+ )
157
+ ORDER BY
158
+ CASE support_tickets.status WHEN 'open' THEN 0 WHEN 'in_review' THEN 1 ELSE 2 END,
159
+ support_tickets.created_at DESC
160
+ LIMIT $4 OFFSET $5`,
161
+ [
162
+ parsed.data.status ?? null,
163
+ parsed.data.plan ?? null,
164
+ parsed.data.q ?? null,
165
+ parsed.data.limit,
166
+ parsed.data.offset,
167
+ ],
168
+ );
169
+
170
+ return res.json({
171
+ tickets: result.rows.map(({ total_count: _total, ...row }) => row),
172
+ limit: parsed.data.limit,
173
+ offset: parsed.data.offset,
174
+ count: result.rows.length,
175
+ total: result.rows[0]?.total_count ?? 0,
176
+ });
177
+ } catch (err) {
178
+ log.error('[admin] support list error', { error: (err as Error).message });
179
+ return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
180
+ }
181
+ });
182
+
183
+ router.put('/admin/support/tickets/:ticket_id', requireAdminSession, async (req, res) => {
184
+ const ticketId = z.string().uuid().safeParse(req.params.ticket_id);
185
+ const parsed = SupportUpdateSchema.safeParse(req.body);
186
+ if (!ticketId.success || !parsed.success) {
187
+ return res.status(400).json({ error: 'validation_error', message: 'Invalid ticket update' });
188
+ }
189
+
190
+ try {
191
+ const result = await pool.query(
192
+ `UPDATE support_tickets
193
+ SET status = $2,
194
+ updated_at = NOW()
195
+ WHERE id = $1
196
+ RETURNING id, org_id, user_id, email, subject, category, message, status, priority, plan, context, created_at, updated_at`,
197
+ [ticketId.data, parsed.data.status],
198
+ );
199
+
200
+ if (result.rows.length === 0) {
201
+ return res.status(404).json({ error: 'ticket_not_found', message: 'Support ticket not found' });
202
+ }
203
+
204
+ return res.json({ ticket: result.rows[0] });
205
+ } catch (err) {
206
+ log.error('[admin] support update error', { error: (err as Error).message, ticket_id: ticketId.data });
207
+ return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
208
+ }
209
+ });
210
+
211
+ router.get('/admin/waitlist', requireAdminSession, async (req, res) => {
212
+ const parsed = WaitlistListQuerySchema.safeParse(req.query);
213
+ if (!parsed.success) {
214
+ return res.status(400).json({ error: 'validation_error', details: parsed.error.flatten() });
215
+ }
216
+
217
+ try {
218
+ const result = await pool.query(
219
+ `SELECT
220
+ paid_plan_waitlist.id,
221
+ paid_plan_waitlist.org_id,
222
+ orgs.name AS org_name,
223
+ paid_plan_waitlist.user_id,
224
+ paid_plan_waitlist.email,
225
+ paid_plan_waitlist.plan,
226
+ paid_plan_waitlist.source,
227
+ paid_plan_waitlist.message,
228
+ paid_plan_waitlist.status,
229
+ paid_plan_waitlist.context,
230
+ paid_plan_waitlist.created_at,
231
+ paid_plan_waitlist.updated_at,
232
+ COUNT(*) OVER()::int AS total_count
233
+ FROM paid_plan_waitlist
234
+ LEFT JOIN orgs ON orgs.id = paid_plan_waitlist.org_id
235
+ WHERE ($1::text IS NULL OR paid_plan_waitlist.status = $1)
236
+ AND ($2::text IS NULL OR paid_plan_waitlist.plan = $2)
237
+ AND (
238
+ $3::text IS NULL
239
+ OR paid_plan_waitlist.email ILIKE '%' || $3 || '%'
240
+ OR paid_plan_waitlist.message ILIKE '%' || $3 || '%'
241
+ OR orgs.name ILIKE '%' || $3 || '%'
242
+ )
243
+ ORDER BY
244
+ CASE paid_plan_waitlist.status WHEN 'new' THEN 0 WHEN 'contacted' THEN 1 ELSE 2 END,
245
+ paid_plan_waitlist.created_at DESC
246
+ LIMIT $4 OFFSET $5`,
247
+ [
248
+ parsed.data.status ?? null,
249
+ parsed.data.plan ?? null,
250
+ parsed.data.q ?? null,
251
+ parsed.data.limit,
252
+ parsed.data.offset,
253
+ ],
254
+ );
255
+
256
+ return res.json({
257
+ leads: result.rows.map(({ total_count: _total, ...row }) => row),
258
+ limit: parsed.data.limit,
259
+ offset: parsed.data.offset,
260
+ count: result.rows.length,
261
+ total: result.rows[0]?.total_count ?? 0,
262
+ });
263
+ } catch (err) {
264
+ log.error('[admin] waitlist list error', { error: (err as Error).message });
265
+ return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
266
+ }
267
+ });
268
+
269
+ router.put('/admin/waitlist/:lead_id', requireAdminSession, async (req, res) => {
270
+ const leadId = z.string().uuid().safeParse(req.params.lead_id);
271
+ const parsed = WaitlistUpdateSchema.safeParse(req.body);
272
+ if (!leadId.success || !parsed.success) {
273
+ return res.status(400).json({ error: 'validation_error', message: 'Invalid waitlist update' });
274
+ }
275
+
276
+ try {
277
+ const result = await pool.query(
278
+ `UPDATE paid_plan_waitlist
279
+ SET status = $2,
280
+ updated_at = NOW()
281
+ WHERE id = $1
282
+ RETURNING id, org_id, user_id, email, plan, source, message, status, context, created_at, updated_at`,
283
+ [leadId.data, parsed.data.status],
284
+ );
285
+
286
+ if (result.rows.length === 0) {
287
+ return res.status(404).json({ error: 'lead_not_found', message: 'Waitlist lead not found' });
288
+ }
289
+
290
+ return res.json({ lead: result.rows[0] });
291
+ } catch (err) {
292
+ log.error('[admin] waitlist update error', { error: (err as Error).message, lead_id: leadId.data });
293
+ return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
294
+ }
295
+ });
296
+
297
+ router.get('/admin/orgs', requireAdminSession, async (req, res) => {
298
+ const parsed = OrgListQuerySchema.safeParse(req.query);
299
+ if (!parsed.success) {
300
+ return res.status(400).json({ error: 'validation_error', details: parsed.error.flatten() });
301
+ }
302
+
303
+ const activeIntervals: Record<string, string> = {
304
+ today: '24 hours',
305
+ '7d': '7 days',
306
+ '30d': '30 days',
307
+ };
308
+ const activeInterval = parsed.data.active ? activeIntervals[parsed.data.active] : null;
309
+
310
+ try {
311
+ const result = await pool.query(
312
+ `WITH org_stats AS (
313
+ SELECT
314
+ orgs.id,
315
+ orgs.name,
316
+ orgs.plan,
317
+ orgs.agent_limit,
318
+ orgs.subscription_status,
319
+ orgs.current_period_end,
320
+ orgs.cancel_at_period_end,
321
+ orgs.stripe_customer_id IS NOT NULL AS has_stripe_customer,
322
+ orgs.stripe_subscription_id IS NOT NULL AS has_stripe_subscription,
323
+ orgs.created_at,
324
+ owner.email AS owner_email,
325
+ (SELECT COUNT(*)::int FROM agents WHERE agents.org_id = orgs.id) AS agent_count,
326
+ (SELECT COUNT(*)::int FROM agents WHERE agents.org_id = orgs.id AND GREATEST(COALESCE(agents.last_seen_at, 'epoch'::timestamptz), COALESCE(agents.mcp_last_seen_at, 'epoch'::timestamptz)) > NOW() - INTERVAL '15 minutes') AS connected_agent_count,
327
+ (SELECT COUNT(*)::int FROM work_claims WHERE work_claims.org_id = orgs.id AND work_claims.status = 'active' AND work_claims.expires_at >= NOW()) AS active_work_claims,
328
+ (SELECT COUNT(*)::int FROM work_resources WHERE work_resources.org_id = orgs.id) AS protected_resources,
329
+ (SELECT COUNT(*)::int FROM agent_activity_events WHERE agent_activity_events.org_id = orgs.id AND agent_activity_events.created_at > NOW() - INTERVAL '24 hours') AS api_calls_24h,
330
+ (SELECT COUNT(*)::int FROM agent_activity_events WHERE agent_activity_events.org_id = orgs.id AND agent_activity_events.created_at >= date_trunc('month', NOW())) AS api_calls_month,
331
+ (SELECT COUNT(*)::int FROM agent_activity_events WHERE agent_activity_events.org_id = orgs.id AND agent_activity_events.status = 'error' AND agent_activity_events.created_at > NOW() - INTERVAL '24 hours') AS errors_24h,
332
+ (SELECT MAX(created_at) FROM agent_activity_events WHERE agent_activity_events.org_id = orgs.id) AS last_activity_at
333
+ FROM orgs
334
+ LEFT JOIN LATERAL (
335
+ SELECT email
336
+ FROM workspace_users
337
+ WHERE workspace_users.org_id = orgs.id
338
+ ORDER BY CASE WHEN role = 'owner' THEN 0 ELSE 1 END, created_at ASC
339
+ LIMIT 1
340
+ ) owner ON true
341
+ )
342
+ SELECT *, COUNT(*) OVER()::int AS total_count
343
+ FROM org_stats
344
+ WHERE ($1::text IS NULL OR plan = $1)
345
+ AND ($2::text IS NULL OR name ILIKE '%' || $2 || '%' OR owner_email ILIKE '%' || $2 || '%')
346
+ AND ($3::text IS NULL OR last_activity_at > NOW() - ($3::interval))
347
+ ORDER BY created_at DESC
348
+ LIMIT $4 OFFSET $5`,
349
+ [
350
+ parsed.data.plan ?? null,
351
+ parsed.data.q ?? null,
352
+ activeInterval,
353
+ parsed.data.limit,
354
+ parsed.data.offset,
355
+ ],
356
+ );
357
+
358
+ return res.json({
359
+ orgs: result.rows.map(({ total_count: _total, ...row }) => row),
360
+ limit: parsed.data.limit,
361
+ offset: parsed.data.offset,
362
+ count: result.rows.length,
363
+ total: result.rows[0]?.total_count ?? 0,
364
+ });
365
+ } catch (err) {
366
+ log.error('[admin] org list error', { error: (err as Error).message });
367
+ return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
368
+ }
369
+ });
370
+
371
+ router.get('/admin/orgs/:org_id', requireAdminSession, async (req, res) => {
372
+ const orgId = z.string().uuid().safeParse(req.params.org_id);
373
+ if (!orgId.success) {
374
+ return res.status(400).json({ error: 'validation_error', message: 'Invalid org id' });
375
+ }
376
+
377
+ try {
378
+ const org = await pool.query(
379
+ `SELECT
380
+ id,
381
+ name,
382
+ plan,
383
+ agent_limit,
384
+ subscription_status,
385
+ current_period_end,
386
+ cancel_at_period_end,
387
+ stripe_customer_id IS NOT NULL AS has_stripe_customer,
388
+ stripe_subscription_id IS NOT NULL AS has_stripe_subscription,
389
+ created_at
390
+ FROM orgs
391
+ WHERE id = $1`,
392
+ [orgId.data],
393
+ );
394
+ if (org.rows.length === 0) {
395
+ return res.status(404).json({ error: 'org_not_found', message: 'Organization not found' });
396
+ }
397
+
398
+ const [users, agents, usage, activity, holdEvents, workEvents] = await Promise.all([
399
+ pool.query(
400
+ `SELECT id, email, role, created_at
401
+ FROM workspace_users
402
+ WHERE org_id = $1
403
+ ORDER BY created_at ASC`,
404
+ [orgId.data],
405
+ ),
406
+ pool.query(
407
+ `SELECT
408
+ id,
409
+ name,
410
+ agent_type,
411
+ priority,
412
+ last_seen_at,
413
+ mcp_last_seen_at,
414
+ mcp_client_name,
415
+ created_at,
416
+ (SELECT MAX(created_at) FROM agent_activity_events WHERE agent_id = agents.id) AS last_activity_at,
417
+ (SELECT MAX(created_at) FROM agent_activity_events WHERE agent_id = agents.id AND status = 'error') AS last_error_at
418
+ FROM agents
419
+ WHERE org_id = $1
420
+ ORDER BY created_at DESC`,
421
+ [orgId.data],
422
+ ),
423
+ pool.query(
424
+ `SELECT
425
+ (SELECT COUNT(*)::int FROM agents WHERE org_id = $1) AS agents,
426
+ (SELECT COUNT(*)::int FROM agents WHERE org_id = $1 AND GREATEST(COALESCE(last_seen_at, 'epoch'::timestamptz), COALESCE(mcp_last_seen_at, 'epoch'::timestamptz)) > NOW() - INTERVAL '15 minutes') AS connected_agents,
427
+ (SELECT COUNT(*)::int FROM work_resources WHERE org_id = $1) AS protected_resources,
428
+ (SELECT COUNT(*)::int FROM agent_activity_events WHERE org_id = $1 AND created_at > NOW() - INTERVAL '24 hours') AS api_calls_24h,
429
+ (SELECT COUNT(*)::int FROM agent_activity_events WHERE org_id = $1 AND created_at >= date_trunc('month', NOW())) AS api_calls_month,
430
+ (SELECT COUNT(*)::int FROM agent_activity_events WHERE org_id = $1 AND status = 'error' AND created_at > NOW() - INTERVAL '24 hours') AS errors_24h,
431
+ (SELECT COALESCE(SUM(total_tokens), 0)::int FROM agent_activity_events WHERE org_id = $1 AND created_at > NOW() - INTERVAL '24 hours') AS tokens_24h,
432
+ (SELECT COUNT(*)::int FROM agents WHERE org_id = $1 AND last_seen_at IS NULL AND mcp_last_seen_at IS NULL AND NOT EXISTS (SELECT 1 FROM agent_activity_events WHERE agent_activity_events.agent_id = agents.id AND activity_type NOT LIKE 'setup_test%' AND activity_type NOT IN ('verification_run', 'connection_test') AND COALESCE(metadata->>'source', '') NOT IN ('setup_test', 'onboarding_verification'))) AS never_connected_agents,
433
+ (SELECT COUNT(*)::int FROM agents WHERE org_id = $1 AND GREATEST(COALESCE(last_seen_at, 'epoch'::timestamptz), COALESCE(mcp_last_seen_at, 'epoch'::timestamptz), COALESCE((SELECT MAX(created_at) FROM agent_activity_events WHERE agent_activity_events.agent_id = agents.id AND activity_type NOT LIKE 'setup_test%' AND activity_type NOT IN ('verification_run', 'connection_test') AND COALESCE(metadata->>'source', '') NOT IN ('setup_test', 'onboarding_verification')), 'epoch'::timestamptz)) < NOW() - INTERVAL '3 days' AND (last_seen_at IS NOT NULL OR mcp_last_seen_at IS NOT NULL OR EXISTS (SELECT 1 FROM agent_activity_events WHERE agent_activity_events.agent_id = agents.id AND activity_type NOT LIKE 'setup_test%' AND activity_type NOT IN ('verification_run', 'connection_test') AND COALESCE(metadata->>'source', '') NOT IN ('setup_test', 'onboarding_verification')))) AS quiet_agents,
434
+ ((SELECT COUNT(*)::int FROM work_claim_events WHERE org_id = $1 AND event_type = 'blocked' AND created_at >= NOW() - INTERVAL '7 days' AND COALESCE(metadata->>'source', '') NOT IN ('setup_test', 'onboarding_verification')) + (SELECT COUNT(*)::int FROM hold_events WHERE org_id = $1 AND event_type IN ('conflict_won', 'conflict_lost', 'superseded') AND created_at >= NOW() - INTERVAL '7 days')) AS conflicts_prevented_7d,
435
+ (SELECT COUNT(*)::int FROM work_claims WHERE org_id = $1 AND status = 'active' AND expires_at >= NOW()) AS active_work_claims,
436
+ (SELECT COUNT(*)::int FROM work_claim_events WHERE org_id = $1 AND event_type = 'blocked') AS blocked_agent_runs,
437
+ (SELECT MAX(created_at) FROM agent_activity_events WHERE org_id = $1) AS last_activity_at`,
438
+ [orgId.data],
439
+ ),
440
+ pool.query(
441
+ `SELECT
442
+ agent_activity_events.id,
443
+ agent_activity_events.agent_id,
444
+ agents.name AS agent_name,
445
+ agent_activity_events.activity_type,
446
+ agent_activity_events.endpoint,
447
+ agent_activity_events.method,
448
+ agent_activity_events.status_code,
449
+ agent_activity_events.status,
450
+ agent_activity_events.error_code,
451
+ agent_activity_events.error_message,
452
+ agent_activity_events.created_at
453
+ FROM agent_activity_events
454
+ LEFT JOIN agents ON agents.id = agent_activity_events.agent_id
455
+ WHERE agent_activity_events.org_id = $1
456
+ ORDER BY agent_activity_events.created_at DESC
457
+ LIMIT 20`,
458
+ [orgId.data],
459
+ ),
460
+ pool.query(
461
+ `SELECT
462
+ hold_events.id,
463
+ hold_events.event_type,
464
+ hold_events.agent_id,
465
+ agents.name AS agent_name,
466
+ hold_events.rule_applied,
467
+ hold_events.actor_type,
468
+ hold_events.actor_label,
469
+ hold_events.created_at
470
+ FROM hold_events
471
+ LEFT JOIN agents ON agents.id = hold_events.agent_id
472
+ WHERE hold_events.org_id = $1
473
+ ORDER BY hold_events.created_at DESC
474
+ LIMIT 20`,
475
+ [orgId.data],
476
+ ),
477
+ pool.query(
478
+ `SELECT
479
+ work_claim_events.id,
480
+ work_claim_events.event_type,
481
+ work_claim_events.agent_id,
482
+ agents.name AS agent_name,
483
+ work_resources.resource_type,
484
+ work_resources.resource_key,
485
+ work_claim_events.rule_applied,
486
+ work_claim_events.actor_type,
487
+ work_claim_events.actor_label,
488
+ work_claim_events.created_at
489
+ FROM work_claim_events
490
+ LEFT JOIN agents ON agents.id = work_claim_events.agent_id
491
+ LEFT JOIN work_resources ON work_resources.id = work_claim_events.resource_id
492
+ WHERE work_claim_events.org_id = $1
493
+ ORDER BY work_claim_events.created_at DESC
494
+ LIMIT 20`,
495
+ [orgId.data],
496
+ ),
497
+ ]);
498
+
499
+ return res.json({
500
+ org: org.rows[0],
501
+ users: users.rows,
502
+ agents: agents.rows,
503
+ usage: usage.rows[0],
504
+ recent_activity: activity.rows,
505
+ recent_hold_events: holdEvents.rows,
506
+ recent_work_events: workEvents.rows,
507
+ });
508
+ } catch (err) {
509
+ log.error('[admin] org detail error', { error: (err as Error).message });
510
+ return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
511
+ }
512
+ });
513
+
514
+ export default router;
@@ -0,0 +1,68 @@
1
+ import express from 'express';
2
+ import { z } from 'zod';
3
+ import pool from '../db/client';
4
+ import { requireWorkspaceSession, type WorkspaceAuthenticatedRequest } from '../middleware/auth';
5
+ import { log } from '../lib/logger';
6
+
7
+ const router = express.Router();
8
+
9
+ const AuditQuerySchema = z.object({
10
+ limit: z.coerce.number().int().min(1).max(200).optional().default(50),
11
+ offset: z.coerce.number().int().min(0).optional().default(0),
12
+ agent_id: z.string().uuid().optional(),
13
+ event_type: z.enum(['created', 'superseded', 'released', 'conflict_won', 'conflict_lost']).optional(),
14
+ });
15
+
16
+ router.get('/audit', requireWorkspaceSession, async (req, res) => {
17
+ const parsed = AuditQuerySchema.safeParse(req.query);
18
+ if (!parsed.success) {
19
+ return res.status(400).json({ error: 'validation_error', details: parsed.error.flatten() });
20
+ }
21
+
22
+ const authReq = req as WorkspaceAuthenticatedRequest;
23
+
24
+ try {
25
+ const result = await pool.query(
26
+ `SELECT
27
+ he.id,
28
+ he.hold_id,
29
+ he.event_type,
30
+ he.agent_id,
31
+ a.name AS agent_name,
32
+ he.org_id,
33
+ he.conflicting_hold_id,
34
+ he.rule_applied,
35
+ he.metadata,
36
+ he.actor_type,
37
+ he.actor_id,
38
+ he.actor_label,
39
+ he.created_at
40
+ FROM hold_events he
41
+ LEFT JOIN agents a ON a.id = he.agent_id
42
+ WHERE he.org_id = $1
43
+ AND ($4::uuid IS NULL OR he.agent_id = $4)
44
+ AND ($5::text IS NULL OR he.event_type = $5)
45
+ ORDER BY he.created_at DESC
46
+ LIMIT $2 OFFSET $3`,
47
+ [
48
+ authReq.org.id,
49
+ parsed.data.limit,
50
+ parsed.data.offset,
51
+ parsed.data.agent_id ?? null,
52
+ parsed.data.event_type ?? null,
53
+ ],
54
+ );
55
+
56
+ return res.json({
57
+ events: result.rows,
58
+ limit: parsed.data.limit,
59
+ offset: parsed.data.offset,
60
+ count: result.rows.length,
61
+ });
62
+ } catch (err) {
63
+ log.error('[audit] unexpected error', { error: (err as Error).message });
64
+ return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
65
+ }
66
+ });
67
+
68
+ export default router;