@cloudgrid-io/mcp 0.13.0 → 0.15.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 (364) hide show
  1. package/package.json +1 -1
  2. package/src/corpus/capability-map.md +81 -7
  3. package/src/corpus/templates/_cloudgrid.yaml.reference +228 -0
  4. package/src/corpus/templates/admin-dashboard/cloudgrid.yaml +226 -8
  5. package/src/corpus/templates/admin-dashboard/index.md +3 -0
  6. package/src/corpus/templates/ai-app/cloudgrid.yaml +225 -11
  7. package/src/corpus/templates/ai-app/index.md +3 -11
  8. package/src/corpus/templates/ai-knowledge-base/AGENTS.md +254 -0
  9. package/src/corpus/templates/ai-knowledge-base/README.md +28 -0
  10. package/src/corpus/templates/ai-knowledge-base/cloudgrid.yaml +238 -0
  11. package/src/corpus/templates/ai-knowledge-base/index.md +81 -0
  12. package/src/corpus/templates/analytics-dashboard/README.md +85 -0
  13. package/src/corpus/templates/analytics-dashboard/cloudgrid.yaml +232 -0
  14. package/src/corpus/templates/analytics-dashboard/index.md +316 -0
  15. package/src/corpus/templates/analytics-dashboard/services/web/app/api/events/route.js +60 -0
  16. package/src/corpus/templates/analytics-dashboard/services/web/app/event-form.js +100 -0
  17. package/src/corpus/templates/analytics-dashboard/services/web/app/layout.js +41 -0
  18. package/src/corpus/templates/analytics-dashboard/services/web/app/page.js +54 -0
  19. package/src/corpus/templates/analytics-dashboard/services/web/lib/db.js +32 -0
  20. package/src/corpus/templates/analytics-dashboard/services/web/package.json +16 -0
  21. package/src/corpus/templates/api-dashboard/README.md +84 -0
  22. package/src/corpus/templates/api-dashboard/cloudgrid.yaml +233 -0
  23. package/src/corpus/templates/api-dashboard/index.md +310 -0
  24. package/src/corpus/templates/api-dashboard/services/web/app/api/requests/route.js +62 -0
  25. package/src/corpus/templates/api-dashboard/services/web/app/dashboard.js +156 -0
  26. package/src/corpus/templates/api-dashboard/services/web/app/layout.js +42 -0
  27. package/src/corpus/templates/api-dashboard/services/web/app/page.js +39 -0
  28. package/src/corpus/templates/api-dashboard/services/web/lib/db.js +32 -0
  29. package/src/corpus/templates/api-dashboard/services/web/package.json +16 -0
  30. package/src/corpus/templates/api-docs/cloudgrid.yaml +222 -4
  31. package/src/corpus/templates/api-docs/index.md +3 -5
  32. package/src/corpus/templates/api-service/cloudgrid.yaml +224 -9
  33. package/src/corpus/templates/api-service/index.md +3 -11
  34. package/src/corpus/templates/app-with-data/cloudgrid.yaml +226 -8
  35. package/src/corpus/templates/app-with-data/index.md +3 -8
  36. package/src/corpus/templates/appointment-booking/AGENTS.md +223 -0
  37. package/src/corpus/templates/appointment-booking/README.md +16 -0
  38. package/src/corpus/templates/appointment-booking/cloudgrid.yaml +250 -0
  39. package/src/corpus/templates/appointment-booking/index.md +72 -0
  40. package/src/corpus/templates/approval-workflow/AGENTS.md +170 -0
  41. package/src/corpus/templates/approval-workflow/README.md +12 -0
  42. package/src/corpus/templates/approval-workflow/cloudgrid.yaml +237 -0
  43. package/src/corpus/templates/approval-workflow/index.md +58 -0
  44. package/src/corpus/templates/billing-dashboard/AGENTS.md +199 -0
  45. package/src/corpus/templates/billing-dashboard/README.md +18 -0
  46. package/src/corpus/templates/billing-dashboard/cloudgrid.yaml +235 -0
  47. package/src/corpus/templates/billing-dashboard/index.md +87 -0
  48. package/src/corpus/templates/blog-cms/README.md +94 -0
  49. package/src/corpus/templates/blog-cms/cloudgrid.yaml +233 -0
  50. package/src/corpus/templates/blog-cms/index.md +358 -0
  51. package/src/corpus/templates/blog-cms/services/web/app/admin/admin-editor.js +133 -0
  52. package/src/corpus/templates/blog-cms/services/web/app/admin/page.js +33 -0
  53. package/src/corpus/templates/blog-cms/services/web/app/api/posts/route.js +84 -0
  54. package/src/corpus/templates/blog-cms/services/web/app/layout.js +47 -0
  55. package/src/corpus/templates/blog-cms/services/web/app/page.js +56 -0
  56. package/src/corpus/templates/blog-cms/services/web/app/posts/[slug]/page.js +45 -0
  57. package/src/corpus/templates/blog-cms/services/web/lib/db.js +32 -0
  58. package/src/corpus/templates/blog-cms/services/web/package.json +16 -0
  59. package/src/corpus/templates/booking-system/AGENTS.md +189 -0
  60. package/src/corpus/templates/booking-system/README.md +17 -0
  61. package/src/corpus/templates/booking-system/cloudgrid.yaml +248 -0
  62. package/src/corpus/templates/booking-system/index.md +86 -0
  63. package/src/corpus/templates/calendar-scheduler/AGENTS.md +226 -0
  64. package/src/corpus/templates/calendar-scheduler/README.md +25 -0
  65. package/src/corpus/templates/calendar-scheduler/cloudgrid.yaml +259 -0
  66. package/src/corpus/templates/calendar-scheduler/index.md +85 -0
  67. package/src/corpus/templates/changelog/cloudgrid.yaml +222 -4
  68. package/src/corpus/templates/changelog/index.md +3 -5
  69. package/src/corpus/templates/company-website/README.md +54 -0
  70. package/src/corpus/templates/company-website/cloudgrid.yaml +233 -0
  71. package/src/corpus/templates/company-website/index.html +309 -0
  72. package/src/corpus/templates/company-website/index.md +29 -0
  73. package/src/corpus/templates/course-platform/AGENTS.md +161 -0
  74. package/src/corpus/templates/course-platform/README.md +26 -0
  75. package/src/corpus/templates/course-platform/cloudgrid.yaml +237 -0
  76. package/src/corpus/templates/course-platform/index.md +70 -0
  77. package/src/corpus/templates/crm/cloudgrid.yaml +226 -8
  78. package/src/corpus/templates/crm/index.md +3 -0
  79. package/src/corpus/templates/dashboard/cloudgrid.yaml +228 -4
  80. package/src/corpus/templates/dashboard/index.md +3 -5
  81. package/src/corpus/templates/deck/cloudgrid.yaml +228 -4
  82. package/src/corpus/templates/deck/index.md +3 -5
  83. package/src/corpus/templates/directory/README.md +75 -0
  84. package/src/corpus/templates/directory/cloudgrid.yaml +233 -0
  85. package/src/corpus/templates/directory/index.md +334 -0
  86. package/src/corpus/templates/directory/services/web/app/api/entries/route.js +63 -0
  87. package/src/corpus/templates/directory/services/web/app/directory-list.js +146 -0
  88. package/src/corpus/templates/directory/services/web/app/layout.js +44 -0
  89. package/src/corpus/templates/directory/services/web/app/page.js +38 -0
  90. package/src/corpus/templates/directory/services/web/lib/db.js +32 -0
  91. package/src/corpus/templates/directory/services/web/package.json +16 -0
  92. package/src/corpus/templates/docs-site/cloudgrid.yaml +222 -4
  93. package/src/corpus/templates/docs-site/index.md +3 -5
  94. package/src/corpus/templates/erp/AGENTS.md +116 -0
  95. package/src/corpus/templates/erp/README.md +17 -0
  96. package/src/corpus/templates/erp/cloudgrid.yaml +235 -0
  97. package/src/corpus/templates/erp/index.md +81 -0
  98. package/src/corpus/templates/event-board/README.md +74 -0
  99. package/src/corpus/templates/event-board/cloudgrid.yaml +233 -0
  100. package/src/corpus/templates/event-board/index.md +299 -0
  101. package/src/corpus/templates/event-board/services/web/app/api/events/route.js +61 -0
  102. package/src/corpus/templates/event-board/services/web/app/event-form.js +118 -0
  103. package/src/corpus/templates/event-board/services/web/app/layout.js +39 -0
  104. package/src/corpus/templates/event-board/services/web/app/page.js +34 -0
  105. package/src/corpus/templates/event-board/services/web/lib/db.js +32 -0
  106. package/src/corpus/templates/event-board/services/web/package.json +16 -0
  107. package/src/corpus/templates/expense-tracker/README.md +76 -0
  108. package/src/corpus/templates/expense-tracker/cloudgrid.yaml +233 -0
  109. package/src/corpus/templates/expense-tracker/index.md +305 -0
  110. package/src/corpus/templates/expense-tracker/services/web/app/api/expenses/route.js +61 -0
  111. package/src/corpus/templates/expense-tracker/services/web/app/expense-form.js +145 -0
  112. package/src/corpus/templates/expense-tracker/services/web/app/layout.js +42 -0
  113. package/src/corpus/templates/expense-tracker/services/web/app/page.js +34 -0
  114. package/src/corpus/templates/expense-tracker/services/web/lib/db.js +32 -0
  115. package/src/corpus/templates/expense-tracker/services/web/package.json +16 -0
  116. package/src/corpus/templates/feature-request-board/README.md +87 -0
  117. package/src/corpus/templates/feature-request-board/cloudgrid.yaml +233 -0
  118. package/src/corpus/templates/feature-request-board/index.md +317 -0
  119. package/src/corpus/templates/feature-request-board/services/web/app/api/requests/route.js +92 -0
  120. package/src/corpus/templates/feature-request-board/services/web/app/board.js +91 -0
  121. package/src/corpus/templates/feature-request-board/services/web/app/layout.js +43 -0
  122. package/src/corpus/templates/feature-request-board/services/web/app/page.js +35 -0
  123. package/src/corpus/templates/feature-request-board/services/web/lib/db.js +32 -0
  124. package/src/corpus/templates/feature-request-board/services/web/package.json +16 -0
  125. package/src/corpus/templates/financial-dashboard/README.md +80 -0
  126. package/src/corpus/templates/financial-dashboard/cloudgrid.yaml +233 -0
  127. package/src/corpus/templates/financial-dashboard/index.md +326 -0
  128. package/src/corpus/templates/financial-dashboard/services/web/app/api/entries/route.js +63 -0
  129. package/src/corpus/templates/financial-dashboard/services/web/app/entry-board.js +130 -0
  130. package/src/corpus/templates/financial-dashboard/services/web/app/layout.js +46 -0
  131. package/src/corpus/templates/financial-dashboard/services/web/app/page.js +31 -0
  132. package/src/corpus/templates/financial-dashboard/services/web/lib/db.js +32 -0
  133. package/src/corpus/templates/financial-dashboard/services/web/package.json +16 -0
  134. package/src/corpus/templates/forum/AGENTS.md +196 -0
  135. package/src/corpus/templates/forum/README.md +12 -0
  136. package/src/corpus/templates/forum/cloudgrid.yaml +237 -0
  137. package/src/corpus/templates/forum/index.md +66 -0
  138. package/src/corpus/templates/hr-portal/AGENTS.md +174 -0
  139. package/src/corpus/templates/hr-portal/README.md +14 -0
  140. package/src/corpus/templates/hr-portal/cloudgrid.yaml +237 -0
  141. package/src/corpus/templates/hr-portal/index.md +56 -0
  142. package/src/corpus/templates/internal-tools-portal/AGENTS.md +201 -0
  143. package/src/corpus/templates/internal-tools-portal/README.md +17 -0
  144. package/src/corpus/templates/internal-tools-portal/cloudgrid.yaml +237 -0
  145. package/src/corpus/templates/internal-tools-portal/index.md +62 -0
  146. package/src/corpus/templates/inventory/README.md +71 -0
  147. package/src/corpus/templates/inventory/cloudgrid.yaml +233 -0
  148. package/src/corpus/templates/inventory/index.md +68 -0
  149. package/src/corpus/templates/inventory/services/web/app/api/items/route.js +88 -0
  150. package/src/corpus/templates/inventory/services/web/app/inventory-manager.js +101 -0
  151. package/src/corpus/templates/inventory/services/web/app/layout.js +36 -0
  152. package/src/corpus/templates/inventory/services/web/app/page.js +37 -0
  153. package/src/corpus/templates/inventory/services/web/lib/db.js +32 -0
  154. package/src/corpus/templates/inventory/services/web/package.json +16 -0
  155. package/src/corpus/templates/invoice/README.md +71 -0
  156. package/src/corpus/templates/invoice/cloudgrid.yaml +233 -0
  157. package/src/corpus/templates/invoice/index.md +68 -0
  158. package/src/corpus/templates/invoice/services/web/app/api/invoices/route.js +82 -0
  159. package/src/corpus/templates/invoice/services/web/app/invoice-manager.js +101 -0
  160. package/src/corpus/templates/invoice/services/web/app/layout.js +35 -0
  161. package/src/corpus/templates/invoice/services/web/app/page.js +34 -0
  162. package/src/corpus/templates/invoice/services/web/lib/db.js +32 -0
  163. package/src/corpus/templates/invoice/services/web/package.json +16 -0
  164. package/src/corpus/templates/job-board/README.md +71 -0
  165. package/src/corpus/templates/job-board/cloudgrid.yaml +233 -0
  166. package/src/corpus/templates/job-board/index.md +68 -0
  167. package/src/corpus/templates/job-board/services/web/app/api/jobs/route.js +66 -0
  168. package/src/corpus/templates/job-board/services/web/app/job-board.js +73 -0
  169. package/src/corpus/templates/job-board/services/web/app/layout.js +35 -0
  170. package/src/corpus/templates/job-board/services/web/app/page.js +32 -0
  171. package/src/corpus/templates/job-board/services/web/lib/db.js +32 -0
  172. package/src/corpus/templates/job-board/services/web/package.json +16 -0
  173. package/src/corpus/templates/kanban/cloudgrid.yaml +226 -8
  174. package/src/corpus/templates/kanban/index.md +3 -0
  175. package/src/corpus/templates/landing-page/cloudgrid.yaml +228 -4
  176. package/src/corpus/templates/landing-page/index.md +3 -5
  177. package/src/corpus/templates/lms/AGENTS.md +193 -0
  178. package/src/corpus/templates/lms/README.md +27 -0
  179. package/src/corpus/templates/lms/cloudgrid.yaml +237 -0
  180. package/src/corpus/templates/lms/index.md +79 -0
  181. package/src/corpus/templates/marketplace/AGENTS.md +208 -0
  182. package/src/corpus/templates/marketplace/README.md +22 -0
  183. package/src/corpus/templates/marketplace/cloudgrid.yaml +233 -0
  184. package/src/corpus/templates/marketplace/index.md +76 -0
  185. package/src/corpus/templates/membership-site/AGENTS.md +160 -0
  186. package/src/corpus/templates/membership-site/README.md +18 -0
  187. package/src/corpus/templates/membership-site/cloudgrid.yaml +236 -0
  188. package/src/corpus/templates/membership-site/index.md +66 -0
  189. package/src/corpus/templates/monitoring-dashboard/README.md +84 -0
  190. package/src/corpus/templates/monitoring-dashboard/cloudgrid.yaml +233 -0
  191. package/src/corpus/templates/monitoring-dashboard/index.md +328 -0
  192. package/src/corpus/templates/monitoring-dashboard/services/web/app/api/checks/route.js +76 -0
  193. package/src/corpus/templates/monitoring-dashboard/services/web/app/check-form.js +124 -0
  194. package/src/corpus/templates/monitoring-dashboard/services/web/app/layout.js +44 -0
  195. package/src/corpus/templates/monitoring-dashboard/services/web/app/page.js +49 -0
  196. package/src/corpus/templates/monitoring-dashboard/services/web/lib/db.js +32 -0
  197. package/src/corpus/templates/monitoring-dashboard/services/web/package.json +16 -0
  198. package/src/corpus/templates/online-store/AGENTS.md +153 -0
  199. package/src/corpus/templates/online-store/README.md +16 -0
  200. package/src/corpus/templates/online-store/cloudgrid.yaml +234 -0
  201. package/src/corpus/templates/online-store/index.md +81 -0
  202. package/src/corpus/templates/portfolio/cloudgrid.yaml +222 -4
  203. package/src/corpus/templates/portfolio/index.md +3 -5
  204. package/src/corpus/templates/product-catalog/README.md +74 -0
  205. package/src/corpus/templates/product-catalog/cloudgrid.yaml +232 -0
  206. package/src/corpus/templates/product-catalog/index.md +330 -0
  207. package/src/corpus/templates/product-catalog/services/web/app/api/products/route.js +69 -0
  208. package/src/corpus/templates/product-catalog/services/web/app/catalog.js +153 -0
  209. package/src/corpus/templates/product-catalog/services/web/app/layout.js +49 -0
  210. package/src/corpus/templates/product-catalog/services/web/app/page.js +36 -0
  211. package/src/corpus/templates/product-catalog/services/web/lib/db.js +32 -0
  212. package/src/corpus/templates/product-catalog/services/web/package.json +16 -0
  213. package/src/corpus/templates/product-launch/README.md +46 -0
  214. package/src/corpus/templates/product-launch/cloudgrid.yaml +234 -0
  215. package/src/corpus/templates/product-launch/index.html +320 -0
  216. package/src/corpus/templates/product-launch/index.md +34 -0
  217. package/src/corpus/templates/project-management/README.md +83 -0
  218. package/src/corpus/templates/project-management/cloudgrid.yaml +233 -0
  219. package/src/corpus/templates/project-management/index.md +346 -0
  220. package/src/corpus/templates/project-management/services/web/app/api/projects/route.js +48 -0
  221. package/src/corpus/templates/project-management/services/web/app/api/tasks/route.js +85 -0
  222. package/src/corpus/templates/project-management/services/web/app/board.js +182 -0
  223. package/src/corpus/templates/project-management/services/web/app/layout.js +45 -0
  224. package/src/corpus/templates/project-management/services/web/app/page.js +49 -0
  225. package/src/corpus/templates/project-management/services/web/lib/db.js +32 -0
  226. package/src/corpus/templates/project-management/services/web/package.json +16 -0
  227. package/src/corpus/templates/property-listings/README.md +76 -0
  228. package/src/corpus/templates/property-listings/cloudgrid.yaml +233 -0
  229. package/src/corpus/templates/property-listings/index.md +335 -0
  230. package/src/corpus/templates/property-listings/services/web/app/api/properties/route.js +73 -0
  231. package/src/corpus/templates/property-listings/services/web/app/layout.js +45 -0
  232. package/src/corpus/templates/property-listings/services/web/app/listings.js +152 -0
  233. package/src/corpus/templates/property-listings/services/web/app/page.js +53 -0
  234. package/src/corpus/templates/property-listings/services/web/lib/db.js +32 -0
  235. package/src/corpus/templates/property-listings/services/web/package.json +16 -0
  236. package/src/corpus/templates/quiz-platform/README.md +84 -0
  237. package/src/corpus/templates/quiz-platform/cloudgrid.yaml +233 -0
  238. package/src/corpus/templates/quiz-platform/index.md +313 -0
  239. package/src/corpus/templates/quiz-platform/services/web/app/api/questions/route.js +69 -0
  240. package/src/corpus/templates/quiz-platform/services/web/app/layout.js +47 -0
  241. package/src/corpus/templates/quiz-platform/services/web/app/page.js +37 -0
  242. package/src/corpus/templates/quiz-platform/services/web/app/quiz.js +188 -0
  243. package/src/corpus/templates/quiz-platform/services/web/lib/db.js +32 -0
  244. package/src/corpus/templates/quiz-platform/services/web/package.json +16 -0
  245. package/src/corpus/templates/report/cloudgrid.yaml +228 -4
  246. package/src/corpus/templates/report/index.md +3 -5
  247. package/src/corpus/templates/restaurant-reservations/AGENTS.md +258 -0
  248. package/src/corpus/templates/restaurant-reservations/README.md +17 -0
  249. package/src/corpus/templates/restaurant-reservations/cloudgrid.yaml +258 -0
  250. package/src/corpus/templates/restaurant-reservations/index.md +62 -0
  251. package/src/corpus/templates/revenue-dashboard/README.md +75 -0
  252. package/src/corpus/templates/revenue-dashboard/cloudgrid.yaml +233 -0
  253. package/src/corpus/templates/revenue-dashboard/index.md +307 -0
  254. package/src/corpus/templates/revenue-dashboard/services/web/app/api/sales/route.js +60 -0
  255. package/src/corpus/templates/revenue-dashboard/services/web/app/layout.js +40 -0
  256. package/src/corpus/templates/revenue-dashboard/services/web/app/page.js +87 -0
  257. package/src/corpus/templates/revenue-dashboard/services/web/app/sale-form.js +63 -0
  258. package/src/corpus/templates/revenue-dashboard/services/web/lib/db.js +32 -0
  259. package/src/corpus/templates/revenue-dashboard/services/web/package.json +16 -0
  260. package/src/corpus/templates/saas-marketing/cloudgrid.yaml +222 -4
  261. package/src/corpus/templates/saas-marketing/index.md +3 -5
  262. package/src/corpus/templates/semantic-search/AGENTS.md +192 -0
  263. package/src/corpus/templates/semantic-search/README.md +74 -0
  264. package/src/corpus/templates/semantic-search/cloudgrid.yaml +217 -0
  265. package/src/corpus/templates/semantic-search/index.md +97 -0
  266. package/src/corpus/templates/semantic-search/services/backend/requirements.txt +7 -0
  267. package/src/corpus/templates/semantic-search/services/backend/src/app/__init__.py +0 -0
  268. package/src/corpus/templates/semantic-search/services/backend/src/app/answer.py +90 -0
  269. package/src/corpus/templates/semantic-search/services/backend/src/app/auth.py +65 -0
  270. package/src/corpus/templates/semantic-search/services/backend/src/app/config.py +110 -0
  271. package/src/corpus/templates/semantic-search/services/backend/src/app/db.py +89 -0
  272. package/src/corpus/templates/semantic-search/services/backend/src/app/embeddings.py +44 -0
  273. package/src/corpus/templates/semantic-search/services/backend/src/app/indexing.py +222 -0
  274. package/src/corpus/templates/semantic-search/services/backend/src/app/main.py +259 -0
  275. package/src/corpus/templates/semantic-search/services/backend/src/app/search.py +222 -0
  276. package/src/corpus/templates/semantic-search/services/backend/src/app/source.py +259 -0
  277. package/src/corpus/templates/semantic-search/services/backend/src/main.py +12 -0
  278. package/src/corpus/templates/semantic-search/services/web/index.html +12 -0
  279. package/src/corpus/templates/semantic-search/services/web/package-lock.json +1680 -0
  280. package/src/corpus/templates/semantic-search/services/web/package.json +19 -0
  281. package/src/corpus/templates/semantic-search/services/web/src/App.jsx +55 -0
  282. package/src/corpus/templates/semantic-search/services/web/src/api.js +125 -0
  283. package/src/corpus/templates/semantic-search/services/web/src/components/Dashboard.jsx +61 -0
  284. package/src/corpus/templates/semantic-search/services/web/src/components/ManagerLogin.jsx +39 -0
  285. package/src/corpus/templates/semantic-search/services/web/src/components/SearchView.jsx +166 -0
  286. package/src/corpus/templates/semantic-search/services/web/src/components/Settings.jsx +148 -0
  287. package/src/corpus/templates/semantic-search/services/web/src/main.jsx +10 -0
  288. package/src/corpus/templates/semantic-search/services/web/src/styles.css +78 -0
  289. package/src/corpus/templates/semantic-search/services/web/vite.config.js +10 -0
  290. package/src/corpus/templates/status-page/cloudgrid.yaml +222 -4
  291. package/src/corpus/templates/status-page/index.md +3 -5
  292. package/src/corpus/templates/subscription-management/AGENTS.md +191 -0
  293. package/src/corpus/templates/subscription-management/README.md +21 -0
  294. package/src/corpus/templates/subscription-management/cloudgrid.yaml +236 -0
  295. package/src/corpus/templates/subscription-management/index.md +73 -0
  296. package/src/corpus/templates/task-manager/cloudgrid.yaml +226 -8
  297. package/src/corpus/templates/task-manager/index.md +3 -0
  298. package/src/corpus/templates/ticket-system/README.md +71 -0
  299. package/src/corpus/templates/ticket-system/cloudgrid.yaml +233 -0
  300. package/src/corpus/templates/ticket-system/index.md +68 -0
  301. package/src/corpus/templates/ticket-system/services/web/app/api/tickets/route.js +81 -0
  302. package/src/corpus/templates/ticket-system/services/web/app/layout.js +36 -0
  303. package/src/corpus/templates/ticket-system/services/web/app/page.js +35 -0
  304. package/src/corpus/templates/ticket-system/services/web/app/ticket-queue.js +112 -0
  305. package/src/corpus/templates/ticket-system/services/web/lib/db.js +32 -0
  306. package/src/corpus/templates/ticket-system/services/web/package.json +16 -0
  307. package/src/corpus/templates/time-tracking/README.md +81 -0
  308. package/src/corpus/templates/time-tracking/cloudgrid.yaml +233 -0
  309. package/src/corpus/templates/time-tracking/index.md +298 -0
  310. package/src/corpus/templates/time-tracking/services/web/app/api/entries/route.js +66 -0
  311. package/src/corpus/templates/time-tracking/services/web/app/entry-form.js +106 -0
  312. package/src/corpus/templates/time-tracking/services/web/app/layout.js +39 -0
  313. package/src/corpus/templates/time-tracking/services/web/app/page.js +30 -0
  314. package/src/corpus/templates/time-tracking/services/web/lib/db.js +32 -0
  315. package/src/corpus/templates/time-tracking/services/web/package.json +16 -0
  316. package/src/corpus/templates/travel-booking/AGENTS.md +207 -0
  317. package/src/corpus/templates/travel-booking/README.md +17 -0
  318. package/src/corpus/templates/travel-booking/cloudgrid.yaml +249 -0
  319. package/src/corpus/templates/travel-booking/index.md +88 -0
  320. package/src/corpus/templates/waitlist/cloudgrid.yaml +222 -4
  321. package/src/corpus/templates/waitlist/index.md +3 -5
  322. package/src/corpus/templates/web-app/cloudgrid.yaml +228 -4
  323. package/src/corpus/templates/web-app/index.md +3 -5
  324. package/src/corpus/workflows/ai-knowledge-base.md +137 -0
  325. package/src/corpus/workflows/analytics-dashboard.md +104 -0
  326. package/src/corpus/workflows/api-dashboard.md +102 -0
  327. package/src/corpus/workflows/appointment-booking.md +143 -0
  328. package/src/corpus/workflows/approval-workflow.md +124 -0
  329. package/src/corpus/workflows/billing-dashboard.md +120 -0
  330. package/src/corpus/workflows/blog-cms.md +104 -0
  331. package/src/corpus/workflows/booking-system.md +116 -0
  332. package/src/corpus/workflows/calendar-scheduler.md +134 -0
  333. package/src/corpus/workflows/company-website.md +46 -0
  334. package/src/corpus/workflows/course-platform.md +126 -0
  335. package/src/corpus/workflows/directory.md +101 -0
  336. package/src/corpus/workflows/erp.md +125 -0
  337. package/src/corpus/workflows/event-board.md +102 -0
  338. package/src/corpus/workflows/expense-tracker.md +102 -0
  339. package/src/corpus/workflows/feature-request-board.md +105 -0
  340. package/src/corpus/workflows/financial-dashboard.md +103 -0
  341. package/src/corpus/workflows/forum.md +128 -0
  342. package/src/corpus/workflows/hr-portal.md +115 -0
  343. package/src/corpus/workflows/internal-tools-portal.md +125 -0
  344. package/src/corpus/workflows/inventory.md +101 -0
  345. package/src/corpus/workflows/invoice.md +101 -0
  346. package/src/corpus/workflows/job-board.md +101 -0
  347. package/src/corpus/workflows/lms.md +113 -0
  348. package/src/corpus/workflows/marketplace.md +123 -0
  349. package/src/corpus/workflows/membership-site.md +128 -0
  350. package/src/corpus/workflows/monitoring-dashboard.md +104 -0
  351. package/src/corpus/workflows/online-store.md +114 -0
  352. package/src/corpus/workflows/product-catalog.md +105 -0
  353. package/src/corpus/workflows/product-launch.md +43 -0
  354. package/src/corpus/workflows/project-management.md +106 -0
  355. package/src/corpus/workflows/property-listings.md +101 -0
  356. package/src/corpus/workflows/quiz-platform.md +105 -0
  357. package/src/corpus/workflows/restaurant-reservations.md +123 -0
  358. package/src/corpus/workflows/revenue-dashboard.md +102 -0
  359. package/src/corpus/workflows/semantic-search.md +133 -0
  360. package/src/corpus/workflows/subscription-management.md +141 -0
  361. package/src/corpus/workflows/ticket-system.md +101 -0
  362. package/src/corpus/workflows/time-tracking.md +104 -0
  363. package/src/corpus/workflows/travel-booking.md +115 -0
  364. package/src/tools.js +73 -12
@@ -0,0 +1,222 @@
1
+ """CloudGrid-native hybrid search over Mongo.
2
+
3
+ Modes blended in one query:
4
+ - lexical/exact : Mongo $text index over chunks.content (+ synonym expansion)
5
+ - semantic : in-app cosine similarity (NumPy) over chunk embedding arrays
6
+ - metadata : Mongo filters on documents (date, collection)
7
+
8
+ Results aggregate to the document level; each result carries its single best
9
+ matching passage with a highlight. Counting questions use countDocuments.
10
+
11
+ Everything degrades gracefully:
12
+ - no embeddings / no query vector -> semantic score = 0, lexical carries it
13
+ - no $text index yet (empty catalog) -> lexical contributes nothing, no 500
14
+ - empty catalog -> empty results + a clear reason
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import re
19
+
20
+ import numpy as np
21
+ from pymongo.errors import OperationFailure
22
+
23
+ from . import config, db, embeddings
24
+
25
+
26
+ def _synonyms() -> dict[str, list[str]]:
27
+ doc = db.settings().find_one({"_id": "synonyms"}) or {}
28
+ return doc.get("map", {})
29
+
30
+
31
+ def _expand_query(q: str) -> str:
32
+ syn = _synonyms()
33
+ extra: list[str] = []
34
+ for token in q.split():
35
+ for alt in syn.get(token, []):
36
+ extra.append(alt)
37
+ return q if not extra else q + " " + " ".join(extra)
38
+
39
+
40
+ def _metadata_filter(collection: str | None, date_from: str | None, date_to: str | None) -> dict:
41
+ f: dict = {}
42
+ if collection:
43
+ f["collection"] = collection
44
+ date_clause: dict = {}
45
+ if date_from:
46
+ date_clause["$gte"] = date_from
47
+ if date_to:
48
+ date_clause["$lte"] = date_to
49
+ if date_clause:
50
+ f["date"] = date_clause
51
+ return f
52
+
53
+
54
+ def _cosine(query_vec: np.ndarray, mat: np.ndarray) -> np.ndarray:
55
+ if mat.size == 0:
56
+ return np.array([])
57
+ qn = query_vec / (np.linalg.norm(query_vec) + 1e-9)
58
+ norms = np.linalg.norm(mat, axis=1) + 1e-9
59
+ return (mat @ qn) / norms
60
+
61
+
62
+ def _highlight(content: str, query: str) -> str:
63
+ terms = [t for t in re.split(r"\s+", query.strip()) if len(t) >= 2]
64
+ highlighted = content
65
+ for t in sorted(set(terms), key=len, reverse=True):
66
+ highlighted = re.sub(
67
+ f"({re.escape(t)})", r"«\1»", highlighted, flags=re.IGNORECASE
68
+ )
69
+ return highlighted
70
+
71
+
72
+ def count_documents(collection: str | None = None,
73
+ date_from: str | None = None,
74
+ date_to: str | None = None) -> int:
75
+ """Direct count from the DB (countDocuments), not estimated from results."""
76
+ return db.documents().count_documents(_metadata_filter(collection, date_from, date_to))
77
+
78
+
79
+ def search(query: str,
80
+ mode: str = "hybrid",
81
+ collection: str | None = None,
82
+ date_from: str | None = None,
83
+ date_to: str | None = None,
84
+ limit: int = 20) -> dict:
85
+ """Blended search. mode in {hybrid, lexical, semantic, metadata}."""
86
+ meta_filter = _metadata_filter(collection, date_from, date_to)
87
+
88
+ # Candidate documents after metadata pre-filter (shrinks the semantic set).
89
+ doc_ids = None
90
+ if meta_filter:
91
+ doc_ids = [d["_id"] for d in db.documents().find(meta_filter, {"_id": 1})]
92
+ if not doc_ids:
93
+ return {"results": [], "total_matching_documents": 0, "mode": mode,
94
+ "note": "No documents match the metadata filter."}
95
+
96
+ # Pure metadata browse (no text query).
97
+ if mode == "metadata" or not query.strip():
98
+ return _metadata_only(meta_filter, limit)
99
+
100
+ chunk_scope: dict = {}
101
+ if doc_ids is not None:
102
+ chunk_scope["document_id"] = {"$in": doc_ids}
103
+
104
+ # ── lexical score via $text ──────────────────────────────────────────────
105
+ lexical: dict = {} # chunk_id -> (score, content, document_id)
106
+ if mode in ("hybrid", "lexical"):
107
+ expanded = _expand_query(query)
108
+ try:
109
+ cursor = db.chunks().find(
110
+ {**chunk_scope, "$text": {"$search": expanded}},
111
+ {"content": 1, "document_id": 1, "score": {"$meta": "textScore"}},
112
+ ).sort([("score", {"$meta": "textScore"})]).limit(500)
113
+ for c in cursor:
114
+ lexical[c["_id"]] = (float(c.get("score", 0.0)), c["content"], c["document_id"])
115
+ except OperationFailure:
116
+ # No text index yet (empty catalog, not indexed) — lexical contributes
117
+ # nothing rather than 500ing. ensure_indexes() at startup normally
118
+ # prevents this; this is belt-and-suspenders.
119
+ pass
120
+
121
+ # ── semantic score via in-app cosine ─────────────────────────────────────
122
+ semantic: dict = {} # chunk_id -> (score, content, document_id)
123
+ semantic_note = None
124
+ if mode in ("hybrid", "semantic"):
125
+ if not config.embeddings_configured():
126
+ semantic_note = "Semantic search unavailable (embeddings not configured)."
127
+ else:
128
+ try:
129
+ qvec = np.array(embeddings.embed_one(query), dtype=np.float32)
130
+ candidate = db.chunks().find(
131
+ {**chunk_scope, "embedding": {"$ne": None}},
132
+ {"content": 1, "document_id": 1, "embedding": 1},
133
+ ).limit(50000)
134
+ ids, contents, dids, mat = [], [], [], []
135
+ for c in candidate:
136
+ ids.append(c["_id"])
137
+ contents.append(c["content"])
138
+ dids.append(c["document_id"])
139
+ mat.append(c["embedding"])
140
+ if mat:
141
+ sims = _cosine(qvec, np.array(mat, dtype=np.float32))
142
+ for cid, content, did, s in zip(ids, contents, dids, sims):
143
+ semantic[cid] = (float(s), content, did)
144
+ except Exception as exc: # noqa: BLE001
145
+ semantic_note = f"Semantic search degraded: {exc}"
146
+
147
+ # ── normalize + blend at the chunk level ─────────────────────────────────
148
+ def _normalize(d: dict) -> dict:
149
+ if not d:
150
+ return {}
151
+ vals = [v[0] for v in d.values()]
152
+ lo, hi = min(vals), max(vals)
153
+ span = (hi - lo) or 1.0
154
+ return {k: (v[0] - lo) / span for k, v in d.items()}
155
+
156
+ lex_n = _normalize(lexical)
157
+ sem_n = _normalize(semantic)
158
+ sw, kw = config.semantic_weight(), config.keyword_weight()
159
+ if mode == "lexical":
160
+ sw, kw = 0.0, 1.0
161
+ elif mode == "semantic":
162
+ sw, kw = 1.0, 0.0
163
+
164
+ blended: dict = {} # chunk_id -> (score, content, document_id)
165
+ for cid in set(lex_n) | set(sem_n):
166
+ content, did = (lexical.get(cid) or semantic.get(cid))[1:3]
167
+ score = sw * sem_n.get(cid, 0.0) + kw * lex_n.get(cid, 0.0)
168
+ blended[cid] = (score, content, did)
169
+
170
+ # ── aggregate to document level: keep best passage per document ──────────
171
+ best_per_doc: dict = {} # document_id -> (score, content, chunk_id)
172
+ for cid, (score, content, did) in blended.items():
173
+ cur = best_per_doc.get(did)
174
+ if cur is None or score > cur[0]:
175
+ best_per_doc[did] = (score, content, cid)
176
+
177
+ ranked = sorted(best_per_doc.items(), key=lambda kv: kv[1][0], reverse=True)[:limit]
178
+
179
+ results = []
180
+ for did, (score, content, cid) in ranked:
181
+ doc = db.documents().find_one({"_id": did})
182
+ if not doc:
183
+ continue
184
+ results.append({
185
+ "document_id": str(did),
186
+ "title": doc.get("title"),
187
+ "collection": doc.get("collection"),
188
+ "date": doc.get("date"),
189
+ "has_media": doc.get("has_media", False),
190
+ "score": round(score, 4),
191
+ "best_passage": _highlight(content, query),
192
+ "source_filename": doc.get("source_filename"),
193
+ })
194
+
195
+ return {
196
+ "results": results,
197
+ "total_matching_documents": len(best_per_doc),
198
+ "mode": mode,
199
+ "note": semantic_note,
200
+ }
201
+
202
+
203
+ def _metadata_only(meta_filter: dict, limit: int) -> dict:
204
+ cursor = db.documents().find(meta_filter).sort("date", -1).limit(limit)
205
+ results = []
206
+ for doc in cursor:
207
+ results.append({
208
+ "document_id": str(doc["_id"]),
209
+ "title": doc.get("title"),
210
+ "collection": doc.get("collection"),
211
+ "date": doc.get("date"),
212
+ "has_media": doc.get("has_media", False),
213
+ "score": None,
214
+ "best_passage": None,
215
+ "source_filename": doc.get("source_filename"),
216
+ })
217
+ return {
218
+ "results": results,
219
+ "total_matching_documents": db.documents().count_documents(meta_filter),
220
+ "mode": "metadata",
221
+ "note": None,
222
+ }
@@ -0,0 +1,259 @@
1
+ """Pluggable document-source adapter, selected by SOURCE_TYPE.
2
+
3
+ The source is where the raw documents *live*, not the query database. Three
4
+ adapters ship here:
5
+
6
+ - dropbox : read-only Dropbox via the offline refresh-token flow.
7
+ - local : a directory on the container filesystem (e.g. a `needs: disk`
8
+ mount at DISK_PATH, or a bundled folder via LOCAL_SOURCE_PATH).
9
+ - url : a manifest of document URLs (URL_SOURCE_MANIFEST) fetched over HTTP.
10
+
11
+ Add your own by subclassing Source and returning it from get_source(). Never read
12
+ source secrets at import time; a missing config surfaces as SourceUnavailable,
13
+ handled gracefully by the indexing pipeline (it records a report and exits 0).
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import io
18
+ import os
19
+ from dataclasses import dataclass
20
+
21
+ import httpx
22
+
23
+ from . import config
24
+
25
+
26
+ class SourceUnavailable(RuntimeError):
27
+ """Raised when the configured source is not usable (missing secrets, etc)."""
28
+
29
+
30
+ # File classes we handle.
31
+ TEXT_EXTS = {".txt", ".md"}
32
+ PDF_EXTS = {".pdf"}
33
+ DOCX_EXTS = {".docx", ".doc"}
34
+ AUDIO_EXTS = {".mp3", ".m4a", ".wav", ".aac", ".ogg"}
35
+
36
+
37
+ @dataclass
38
+ class SourceFile:
39
+ path: str # source-internal path/URL (never exposed to clients)
40
+ name: str # filename only
41
+ rev: str # content revision / hash for incremental sync
42
+ size: int
43
+ is_audio: bool
44
+ is_text: bool # text-extractable (txt/md/pdf/docx)
45
+
46
+
47
+ def _classify(name: str) -> tuple[bool, bool]:
48
+ ext = os.path.splitext(name)[1].lower()
49
+ return (ext in AUDIO_EXTS, ext in (TEXT_EXTS | PDF_EXTS | DOCX_EXTS))
50
+
51
+
52
+ class Source:
53
+ def list_files(self) -> list[SourceFile]:
54
+ raise NotImplementedError
55
+
56
+ def download(self, path: str) -> bytes:
57
+ raise NotImplementedError
58
+
59
+
60
+ # ── Dropbox adapter ───────────────────────────────────────────────────────────
61
+
62
+ class DropboxSource(Source):
63
+ """Read-only Dropbox via the offline refresh-token flow."""
64
+
65
+ def __init__(self) -> None:
66
+ c = config.dropbox_config()
67
+ if not (c["app_key"] and c["app_secret"] and c["refresh_token"]):
68
+ raise SourceUnavailable(
69
+ "Dropbox is not configured — set DROPBOX_APP_KEY, "
70
+ "DROPBOX_APP_SECRET and DROPBOX_REFRESH_TOKEN."
71
+ )
72
+ self._app_key = c["app_key"]
73
+ self._app_secret = c["app_secret"]
74
+ self._refresh_token = c["refresh_token"]
75
+ self._folder = (c["folder_path"] or "").rstrip("/")
76
+ self._token: str | None = None
77
+
78
+ def _access_token(self) -> str:
79
+ if self._token:
80
+ return self._token
81
+ resp = httpx.post(
82
+ "https://api.dropbox.com/oauth2/token",
83
+ data={
84
+ "grant_type": "refresh_token",
85
+ "refresh_token": self._refresh_token,
86
+ },
87
+ auth=(self._app_key, self._app_secret),
88
+ timeout=30.0,
89
+ )
90
+ resp.raise_for_status()
91
+ self._token = resp.json()["access_token"]
92
+ return self._token
93
+
94
+ def _headers(self) -> dict[str, str]:
95
+ return {"Authorization": f"Bearer {self._access_token()}"}
96
+
97
+ def list_files(self) -> list[SourceFile]:
98
+ out: list[SourceFile] = []
99
+ url = "https://api.dropboxapi.com/2/files/list_folder"
100
+ body: dict = {"path": self._folder, "recursive": True}
101
+ while True:
102
+ resp = httpx.post(url, headers=self._headers(), json=body, timeout=60.0)
103
+ resp.raise_for_status()
104
+ payload = resp.json()
105
+ for entry in payload.get("entries", []):
106
+ if entry.get(".tag") != "file":
107
+ continue
108
+ out.append(self._to_source_file(entry))
109
+ if payload.get("has_more"):
110
+ url = "https://api.dropboxapi.com/2/files/list_folder/continue"
111
+ body = {"cursor": payload["cursor"]}
112
+ else:
113
+ break
114
+ return out
115
+
116
+ @staticmethod
117
+ def _to_source_file(entry: dict) -> SourceFile:
118
+ name = entry["name"]
119
+ is_audio, is_text = _classify(name)
120
+ return SourceFile(
121
+ path=entry["path_lower"],
122
+ name=name,
123
+ rev=entry.get("content_hash") or entry.get("rev", ""),
124
+ size=int(entry.get("size", 0)),
125
+ is_audio=is_audio,
126
+ is_text=is_text,
127
+ )
128
+
129
+ def download(self, path: str) -> bytes:
130
+ import json
131
+
132
+ resp = httpx.post(
133
+ "https://content.dropboxapi.com/2/files/download",
134
+ headers={
135
+ **self._headers(),
136
+ "Dropbox-API-Arg": json.dumps({"path": path}),
137
+ },
138
+ timeout=120.0,
139
+ )
140
+ resp.raise_for_status()
141
+ return resp.content
142
+
143
+
144
+ # ── Local-filesystem adapter ──────────────────────────────────────────────────
145
+
146
+ class LocalSource(Source):
147
+ """Read documents from a directory on the container filesystem.
148
+
149
+ Point LOCAL_SOURCE_PATH (or a `needs: disk` mount at DISK_PATH) at a folder
150
+ of .txt/.md/.pdf/.docx files. `rev` is the file mtime+size, so changed files
151
+ re-index on the next sync.
152
+ """
153
+
154
+ def __init__(self) -> None:
155
+ root = config.local_source_path()
156
+ if not root:
157
+ raise SourceUnavailable(
158
+ "Local source is not configured — set LOCAL_SOURCE_PATH "
159
+ "(or mount a disk and it will default to DISK_PATH)."
160
+ )
161
+ if not os.path.isdir(root):
162
+ raise SourceUnavailable(f"LOCAL_SOURCE_PATH does not exist: {root!r}")
163
+ self._root = root
164
+
165
+ def list_files(self) -> list[SourceFile]:
166
+ out: list[SourceFile] = []
167
+ for dirpath, _dirs, files in os.walk(self._root):
168
+ for name in files:
169
+ full = os.path.join(dirpath, name)
170
+ is_audio, is_text = _classify(name)
171
+ try:
172
+ st = os.stat(full)
173
+ except OSError:
174
+ continue
175
+ out.append(SourceFile(
176
+ path=full,
177
+ name=name,
178
+ rev=f"{int(st.st_mtime)}-{st.st_size}",
179
+ size=st.st_size,
180
+ is_audio=is_audio,
181
+ is_text=is_text,
182
+ ))
183
+ return out
184
+
185
+ def download(self, path: str) -> bytes:
186
+ with open(path, "rb") as fh:
187
+ return fh.read()
188
+
189
+
190
+ # ── URL-manifest adapter ──────────────────────────────────────────────────────
191
+
192
+ class UrlSource(Source):
193
+ """Fetch documents from a manifest of URLs.
194
+
195
+ URL_SOURCE_MANIFEST is a newline- or comma-separated list of document URLs.
196
+ The filename is the URL's last path segment; `rev` is the URL itself
197
+ (override by pointing the manifest at content-hashed URLs).
198
+ """
199
+
200
+ def __init__(self) -> None:
201
+ manifest = config.url_source_manifest()
202
+ if not manifest:
203
+ raise SourceUnavailable(
204
+ "URL source is not configured — set URL_SOURCE_MANIFEST to a "
205
+ "newline- or comma-separated list of document URLs."
206
+ )
207
+ raw = manifest.replace(",", "\n").splitlines()
208
+ self._urls = [u.strip() for u in raw if u.strip()]
209
+
210
+ def list_files(self) -> list[SourceFile]:
211
+ out: list[SourceFile] = []
212
+ for u in self._urls:
213
+ name = u.rstrip("/").rsplit("/", 1)[-1] or "document"
214
+ is_audio, is_text = _classify(name)
215
+ out.append(SourceFile(
216
+ path=u,
217
+ name=name,
218
+ rev=u,
219
+ size=0,
220
+ is_audio=is_audio,
221
+ is_text=is_text,
222
+ ))
223
+ return out
224
+
225
+ def download(self, path: str) -> bytes:
226
+ resp = httpx.get(path, timeout=120.0, follow_redirects=True)
227
+ resp.raise_for_status()
228
+ return resp.content
229
+
230
+
231
+ def get_source() -> Source:
232
+ """Build the configured source adapter. Raises SourceUnavailable if unusable."""
233
+ st = config.source_type()
234
+ if st == "dropbox":
235
+ return DropboxSource()
236
+ if st == "local":
237
+ return LocalSource()
238
+ if st == "url":
239
+ return UrlSource()
240
+ raise SourceUnavailable(f"Unknown SOURCE_TYPE: {st!r}")
241
+
242
+
243
+ # ── Text extraction (used by the pipeline) ───────────────────────────────────
244
+
245
+ def extract_text(name: str, raw: bytes) -> str:
246
+ ext = os.path.splitext(name)[1].lower()
247
+ if ext in TEXT_EXTS:
248
+ return raw.decode("utf-8", errors="replace")
249
+ if ext in PDF_EXTS:
250
+ from pypdf import PdfReader
251
+
252
+ reader = PdfReader(io.BytesIO(raw))
253
+ return "\n".join((page.extract_text() or "") for page in reader.pages)
254
+ if ext in DOCX_EXTS:
255
+ from docx import Document
256
+
257
+ doc = Document(io.BytesIO(raw))
258
+ return "\n".join(p.text for p in doc.paragraphs)
259
+ return ""
@@ -0,0 +1,12 @@
1
+ """CloudGrid python-service entry point (services/backend/src/main.py).
2
+
3
+ Launches the FastAPI app (app/main.py) with uvicorn, binding to the platform's
4
+ injected PORT (default 8080). No secret is read here or at import time.
5
+ """
6
+ import os
7
+
8
+ import uvicorn
9
+
10
+ if __name__ == "__main__":
11
+ port = int(os.environ.get("PORT", "8080"))
12
+ uvicorn.run("app.main:app", host="0.0.0.0", port=port, workers=1)
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Document Search</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.jsx"></script>
11
+ </body>
12
+ </html>