@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.
- package/package.json +1 -1
- package/src/corpus/capability-map.md +81 -7
- package/src/corpus/templates/_cloudgrid.yaml.reference +228 -0
- package/src/corpus/templates/admin-dashboard/cloudgrid.yaml +226 -8
- package/src/corpus/templates/admin-dashboard/index.md +3 -0
- package/src/corpus/templates/ai-app/cloudgrid.yaml +225 -11
- package/src/corpus/templates/ai-app/index.md +3 -11
- package/src/corpus/templates/ai-knowledge-base/AGENTS.md +254 -0
- package/src/corpus/templates/ai-knowledge-base/README.md +28 -0
- package/src/corpus/templates/ai-knowledge-base/cloudgrid.yaml +238 -0
- package/src/corpus/templates/ai-knowledge-base/index.md +81 -0
- package/src/corpus/templates/analytics-dashboard/README.md +85 -0
- package/src/corpus/templates/analytics-dashboard/cloudgrid.yaml +232 -0
- package/src/corpus/templates/analytics-dashboard/index.md +316 -0
- package/src/corpus/templates/analytics-dashboard/services/web/app/api/events/route.js +60 -0
- package/src/corpus/templates/analytics-dashboard/services/web/app/event-form.js +100 -0
- package/src/corpus/templates/analytics-dashboard/services/web/app/layout.js +41 -0
- package/src/corpus/templates/analytics-dashboard/services/web/app/page.js +54 -0
- package/src/corpus/templates/analytics-dashboard/services/web/lib/db.js +32 -0
- package/src/corpus/templates/analytics-dashboard/services/web/package.json +16 -0
- package/src/corpus/templates/api-dashboard/README.md +84 -0
- package/src/corpus/templates/api-dashboard/cloudgrid.yaml +233 -0
- package/src/corpus/templates/api-dashboard/index.md +310 -0
- package/src/corpus/templates/api-dashboard/services/web/app/api/requests/route.js +62 -0
- package/src/corpus/templates/api-dashboard/services/web/app/dashboard.js +156 -0
- package/src/corpus/templates/api-dashboard/services/web/app/layout.js +42 -0
- package/src/corpus/templates/api-dashboard/services/web/app/page.js +39 -0
- package/src/corpus/templates/api-dashboard/services/web/lib/db.js +32 -0
- package/src/corpus/templates/api-dashboard/services/web/package.json +16 -0
- package/src/corpus/templates/api-docs/cloudgrid.yaml +222 -4
- package/src/corpus/templates/api-docs/index.md +3 -5
- package/src/corpus/templates/api-service/cloudgrid.yaml +224 -9
- package/src/corpus/templates/api-service/index.md +3 -11
- package/src/corpus/templates/app-with-data/cloudgrid.yaml +226 -8
- package/src/corpus/templates/app-with-data/index.md +3 -8
- package/src/corpus/templates/appointment-booking/AGENTS.md +223 -0
- package/src/corpus/templates/appointment-booking/README.md +16 -0
- package/src/corpus/templates/appointment-booking/cloudgrid.yaml +250 -0
- package/src/corpus/templates/appointment-booking/index.md +72 -0
- package/src/corpus/templates/approval-workflow/AGENTS.md +170 -0
- package/src/corpus/templates/approval-workflow/README.md +12 -0
- package/src/corpus/templates/approval-workflow/cloudgrid.yaml +237 -0
- package/src/corpus/templates/approval-workflow/index.md +58 -0
- package/src/corpus/templates/billing-dashboard/AGENTS.md +199 -0
- package/src/corpus/templates/billing-dashboard/README.md +18 -0
- package/src/corpus/templates/billing-dashboard/cloudgrid.yaml +235 -0
- package/src/corpus/templates/billing-dashboard/index.md +87 -0
- package/src/corpus/templates/blog-cms/README.md +94 -0
- package/src/corpus/templates/blog-cms/cloudgrid.yaml +233 -0
- package/src/corpus/templates/blog-cms/index.md +358 -0
- package/src/corpus/templates/blog-cms/services/web/app/admin/admin-editor.js +133 -0
- package/src/corpus/templates/blog-cms/services/web/app/admin/page.js +33 -0
- package/src/corpus/templates/blog-cms/services/web/app/api/posts/route.js +84 -0
- package/src/corpus/templates/blog-cms/services/web/app/layout.js +47 -0
- package/src/corpus/templates/blog-cms/services/web/app/page.js +56 -0
- package/src/corpus/templates/blog-cms/services/web/app/posts/[slug]/page.js +45 -0
- package/src/corpus/templates/blog-cms/services/web/lib/db.js +32 -0
- package/src/corpus/templates/blog-cms/services/web/package.json +16 -0
- package/src/corpus/templates/booking-system/AGENTS.md +189 -0
- package/src/corpus/templates/booking-system/README.md +17 -0
- package/src/corpus/templates/booking-system/cloudgrid.yaml +248 -0
- package/src/corpus/templates/booking-system/index.md +86 -0
- package/src/corpus/templates/calendar-scheduler/AGENTS.md +226 -0
- package/src/corpus/templates/calendar-scheduler/README.md +25 -0
- package/src/corpus/templates/calendar-scheduler/cloudgrid.yaml +259 -0
- package/src/corpus/templates/calendar-scheduler/index.md +85 -0
- package/src/corpus/templates/changelog/cloudgrid.yaml +222 -4
- package/src/corpus/templates/changelog/index.md +3 -5
- package/src/corpus/templates/company-website/README.md +54 -0
- package/src/corpus/templates/company-website/cloudgrid.yaml +233 -0
- package/src/corpus/templates/company-website/index.html +309 -0
- package/src/corpus/templates/company-website/index.md +29 -0
- package/src/corpus/templates/course-platform/AGENTS.md +161 -0
- package/src/corpus/templates/course-platform/README.md +26 -0
- package/src/corpus/templates/course-platform/cloudgrid.yaml +237 -0
- package/src/corpus/templates/course-platform/index.md +70 -0
- package/src/corpus/templates/crm/cloudgrid.yaml +226 -8
- package/src/corpus/templates/crm/index.md +3 -0
- package/src/corpus/templates/dashboard/cloudgrid.yaml +228 -4
- package/src/corpus/templates/dashboard/index.md +3 -5
- package/src/corpus/templates/deck/cloudgrid.yaml +228 -4
- package/src/corpus/templates/deck/index.md +3 -5
- package/src/corpus/templates/directory/README.md +75 -0
- package/src/corpus/templates/directory/cloudgrid.yaml +233 -0
- package/src/corpus/templates/directory/index.md +334 -0
- package/src/corpus/templates/directory/services/web/app/api/entries/route.js +63 -0
- package/src/corpus/templates/directory/services/web/app/directory-list.js +146 -0
- package/src/corpus/templates/directory/services/web/app/layout.js +44 -0
- package/src/corpus/templates/directory/services/web/app/page.js +38 -0
- package/src/corpus/templates/directory/services/web/lib/db.js +32 -0
- package/src/corpus/templates/directory/services/web/package.json +16 -0
- package/src/corpus/templates/docs-site/cloudgrid.yaml +222 -4
- package/src/corpus/templates/docs-site/index.md +3 -5
- package/src/corpus/templates/erp/AGENTS.md +116 -0
- package/src/corpus/templates/erp/README.md +17 -0
- package/src/corpus/templates/erp/cloudgrid.yaml +235 -0
- package/src/corpus/templates/erp/index.md +81 -0
- package/src/corpus/templates/event-board/README.md +74 -0
- package/src/corpus/templates/event-board/cloudgrid.yaml +233 -0
- package/src/corpus/templates/event-board/index.md +299 -0
- package/src/corpus/templates/event-board/services/web/app/api/events/route.js +61 -0
- package/src/corpus/templates/event-board/services/web/app/event-form.js +118 -0
- package/src/corpus/templates/event-board/services/web/app/layout.js +39 -0
- package/src/corpus/templates/event-board/services/web/app/page.js +34 -0
- package/src/corpus/templates/event-board/services/web/lib/db.js +32 -0
- package/src/corpus/templates/event-board/services/web/package.json +16 -0
- package/src/corpus/templates/expense-tracker/README.md +76 -0
- package/src/corpus/templates/expense-tracker/cloudgrid.yaml +233 -0
- package/src/corpus/templates/expense-tracker/index.md +305 -0
- package/src/corpus/templates/expense-tracker/services/web/app/api/expenses/route.js +61 -0
- package/src/corpus/templates/expense-tracker/services/web/app/expense-form.js +145 -0
- package/src/corpus/templates/expense-tracker/services/web/app/layout.js +42 -0
- package/src/corpus/templates/expense-tracker/services/web/app/page.js +34 -0
- package/src/corpus/templates/expense-tracker/services/web/lib/db.js +32 -0
- package/src/corpus/templates/expense-tracker/services/web/package.json +16 -0
- package/src/corpus/templates/feature-request-board/README.md +87 -0
- package/src/corpus/templates/feature-request-board/cloudgrid.yaml +233 -0
- package/src/corpus/templates/feature-request-board/index.md +317 -0
- package/src/corpus/templates/feature-request-board/services/web/app/api/requests/route.js +92 -0
- package/src/corpus/templates/feature-request-board/services/web/app/board.js +91 -0
- package/src/corpus/templates/feature-request-board/services/web/app/layout.js +43 -0
- package/src/corpus/templates/feature-request-board/services/web/app/page.js +35 -0
- package/src/corpus/templates/feature-request-board/services/web/lib/db.js +32 -0
- package/src/corpus/templates/feature-request-board/services/web/package.json +16 -0
- package/src/corpus/templates/financial-dashboard/README.md +80 -0
- package/src/corpus/templates/financial-dashboard/cloudgrid.yaml +233 -0
- package/src/corpus/templates/financial-dashboard/index.md +326 -0
- package/src/corpus/templates/financial-dashboard/services/web/app/api/entries/route.js +63 -0
- package/src/corpus/templates/financial-dashboard/services/web/app/entry-board.js +130 -0
- package/src/corpus/templates/financial-dashboard/services/web/app/layout.js +46 -0
- package/src/corpus/templates/financial-dashboard/services/web/app/page.js +31 -0
- package/src/corpus/templates/financial-dashboard/services/web/lib/db.js +32 -0
- package/src/corpus/templates/financial-dashboard/services/web/package.json +16 -0
- package/src/corpus/templates/forum/AGENTS.md +196 -0
- package/src/corpus/templates/forum/README.md +12 -0
- package/src/corpus/templates/forum/cloudgrid.yaml +237 -0
- package/src/corpus/templates/forum/index.md +66 -0
- package/src/corpus/templates/hr-portal/AGENTS.md +174 -0
- package/src/corpus/templates/hr-portal/README.md +14 -0
- package/src/corpus/templates/hr-portal/cloudgrid.yaml +237 -0
- package/src/corpus/templates/hr-portal/index.md +56 -0
- package/src/corpus/templates/internal-tools-portal/AGENTS.md +201 -0
- package/src/corpus/templates/internal-tools-portal/README.md +17 -0
- package/src/corpus/templates/internal-tools-portal/cloudgrid.yaml +237 -0
- package/src/corpus/templates/internal-tools-portal/index.md +62 -0
- package/src/corpus/templates/inventory/README.md +71 -0
- package/src/corpus/templates/inventory/cloudgrid.yaml +233 -0
- package/src/corpus/templates/inventory/index.md +68 -0
- package/src/corpus/templates/inventory/services/web/app/api/items/route.js +88 -0
- package/src/corpus/templates/inventory/services/web/app/inventory-manager.js +101 -0
- package/src/corpus/templates/inventory/services/web/app/layout.js +36 -0
- package/src/corpus/templates/inventory/services/web/app/page.js +37 -0
- package/src/corpus/templates/inventory/services/web/lib/db.js +32 -0
- package/src/corpus/templates/inventory/services/web/package.json +16 -0
- package/src/corpus/templates/invoice/README.md +71 -0
- package/src/corpus/templates/invoice/cloudgrid.yaml +233 -0
- package/src/corpus/templates/invoice/index.md +68 -0
- package/src/corpus/templates/invoice/services/web/app/api/invoices/route.js +82 -0
- package/src/corpus/templates/invoice/services/web/app/invoice-manager.js +101 -0
- package/src/corpus/templates/invoice/services/web/app/layout.js +35 -0
- package/src/corpus/templates/invoice/services/web/app/page.js +34 -0
- package/src/corpus/templates/invoice/services/web/lib/db.js +32 -0
- package/src/corpus/templates/invoice/services/web/package.json +16 -0
- package/src/corpus/templates/job-board/README.md +71 -0
- package/src/corpus/templates/job-board/cloudgrid.yaml +233 -0
- package/src/corpus/templates/job-board/index.md +68 -0
- package/src/corpus/templates/job-board/services/web/app/api/jobs/route.js +66 -0
- package/src/corpus/templates/job-board/services/web/app/job-board.js +73 -0
- package/src/corpus/templates/job-board/services/web/app/layout.js +35 -0
- package/src/corpus/templates/job-board/services/web/app/page.js +32 -0
- package/src/corpus/templates/job-board/services/web/lib/db.js +32 -0
- package/src/corpus/templates/job-board/services/web/package.json +16 -0
- package/src/corpus/templates/kanban/cloudgrid.yaml +226 -8
- package/src/corpus/templates/kanban/index.md +3 -0
- package/src/corpus/templates/landing-page/cloudgrid.yaml +228 -4
- package/src/corpus/templates/landing-page/index.md +3 -5
- package/src/corpus/templates/lms/AGENTS.md +193 -0
- package/src/corpus/templates/lms/README.md +27 -0
- package/src/corpus/templates/lms/cloudgrid.yaml +237 -0
- package/src/corpus/templates/lms/index.md +79 -0
- package/src/corpus/templates/marketplace/AGENTS.md +208 -0
- package/src/corpus/templates/marketplace/README.md +22 -0
- package/src/corpus/templates/marketplace/cloudgrid.yaml +233 -0
- package/src/corpus/templates/marketplace/index.md +76 -0
- package/src/corpus/templates/membership-site/AGENTS.md +160 -0
- package/src/corpus/templates/membership-site/README.md +18 -0
- package/src/corpus/templates/membership-site/cloudgrid.yaml +236 -0
- package/src/corpus/templates/membership-site/index.md +66 -0
- package/src/corpus/templates/monitoring-dashboard/README.md +84 -0
- package/src/corpus/templates/monitoring-dashboard/cloudgrid.yaml +233 -0
- package/src/corpus/templates/monitoring-dashboard/index.md +328 -0
- package/src/corpus/templates/monitoring-dashboard/services/web/app/api/checks/route.js +76 -0
- package/src/corpus/templates/monitoring-dashboard/services/web/app/check-form.js +124 -0
- package/src/corpus/templates/monitoring-dashboard/services/web/app/layout.js +44 -0
- package/src/corpus/templates/monitoring-dashboard/services/web/app/page.js +49 -0
- package/src/corpus/templates/monitoring-dashboard/services/web/lib/db.js +32 -0
- package/src/corpus/templates/monitoring-dashboard/services/web/package.json +16 -0
- package/src/corpus/templates/online-store/AGENTS.md +153 -0
- package/src/corpus/templates/online-store/README.md +16 -0
- package/src/corpus/templates/online-store/cloudgrid.yaml +234 -0
- package/src/corpus/templates/online-store/index.md +81 -0
- package/src/corpus/templates/portfolio/cloudgrid.yaml +222 -4
- package/src/corpus/templates/portfolio/index.md +3 -5
- package/src/corpus/templates/product-catalog/README.md +74 -0
- package/src/corpus/templates/product-catalog/cloudgrid.yaml +232 -0
- package/src/corpus/templates/product-catalog/index.md +330 -0
- package/src/corpus/templates/product-catalog/services/web/app/api/products/route.js +69 -0
- package/src/corpus/templates/product-catalog/services/web/app/catalog.js +153 -0
- package/src/corpus/templates/product-catalog/services/web/app/layout.js +49 -0
- package/src/corpus/templates/product-catalog/services/web/app/page.js +36 -0
- package/src/corpus/templates/product-catalog/services/web/lib/db.js +32 -0
- package/src/corpus/templates/product-catalog/services/web/package.json +16 -0
- package/src/corpus/templates/product-launch/README.md +46 -0
- package/src/corpus/templates/product-launch/cloudgrid.yaml +234 -0
- package/src/corpus/templates/product-launch/index.html +320 -0
- package/src/corpus/templates/product-launch/index.md +34 -0
- package/src/corpus/templates/project-management/README.md +83 -0
- package/src/corpus/templates/project-management/cloudgrid.yaml +233 -0
- package/src/corpus/templates/project-management/index.md +346 -0
- package/src/corpus/templates/project-management/services/web/app/api/projects/route.js +48 -0
- package/src/corpus/templates/project-management/services/web/app/api/tasks/route.js +85 -0
- package/src/corpus/templates/project-management/services/web/app/board.js +182 -0
- package/src/corpus/templates/project-management/services/web/app/layout.js +45 -0
- package/src/corpus/templates/project-management/services/web/app/page.js +49 -0
- package/src/corpus/templates/project-management/services/web/lib/db.js +32 -0
- package/src/corpus/templates/project-management/services/web/package.json +16 -0
- package/src/corpus/templates/property-listings/README.md +76 -0
- package/src/corpus/templates/property-listings/cloudgrid.yaml +233 -0
- package/src/corpus/templates/property-listings/index.md +335 -0
- package/src/corpus/templates/property-listings/services/web/app/api/properties/route.js +73 -0
- package/src/corpus/templates/property-listings/services/web/app/layout.js +45 -0
- package/src/corpus/templates/property-listings/services/web/app/listings.js +152 -0
- package/src/corpus/templates/property-listings/services/web/app/page.js +53 -0
- package/src/corpus/templates/property-listings/services/web/lib/db.js +32 -0
- package/src/corpus/templates/property-listings/services/web/package.json +16 -0
- package/src/corpus/templates/quiz-platform/README.md +84 -0
- package/src/corpus/templates/quiz-platform/cloudgrid.yaml +233 -0
- package/src/corpus/templates/quiz-platform/index.md +313 -0
- package/src/corpus/templates/quiz-platform/services/web/app/api/questions/route.js +69 -0
- package/src/corpus/templates/quiz-platform/services/web/app/layout.js +47 -0
- package/src/corpus/templates/quiz-platform/services/web/app/page.js +37 -0
- package/src/corpus/templates/quiz-platform/services/web/app/quiz.js +188 -0
- package/src/corpus/templates/quiz-platform/services/web/lib/db.js +32 -0
- package/src/corpus/templates/quiz-platform/services/web/package.json +16 -0
- package/src/corpus/templates/report/cloudgrid.yaml +228 -4
- package/src/corpus/templates/report/index.md +3 -5
- package/src/corpus/templates/restaurant-reservations/AGENTS.md +258 -0
- package/src/corpus/templates/restaurant-reservations/README.md +17 -0
- package/src/corpus/templates/restaurant-reservations/cloudgrid.yaml +258 -0
- package/src/corpus/templates/restaurant-reservations/index.md +62 -0
- package/src/corpus/templates/revenue-dashboard/README.md +75 -0
- package/src/corpus/templates/revenue-dashboard/cloudgrid.yaml +233 -0
- package/src/corpus/templates/revenue-dashboard/index.md +307 -0
- package/src/corpus/templates/revenue-dashboard/services/web/app/api/sales/route.js +60 -0
- package/src/corpus/templates/revenue-dashboard/services/web/app/layout.js +40 -0
- package/src/corpus/templates/revenue-dashboard/services/web/app/page.js +87 -0
- package/src/corpus/templates/revenue-dashboard/services/web/app/sale-form.js +63 -0
- package/src/corpus/templates/revenue-dashboard/services/web/lib/db.js +32 -0
- package/src/corpus/templates/revenue-dashboard/services/web/package.json +16 -0
- package/src/corpus/templates/saas-marketing/cloudgrid.yaml +222 -4
- package/src/corpus/templates/saas-marketing/index.md +3 -5
- package/src/corpus/templates/semantic-search/AGENTS.md +192 -0
- package/src/corpus/templates/semantic-search/README.md +74 -0
- package/src/corpus/templates/semantic-search/cloudgrid.yaml +217 -0
- package/src/corpus/templates/semantic-search/index.md +97 -0
- package/src/corpus/templates/semantic-search/services/backend/requirements.txt +7 -0
- package/src/corpus/templates/semantic-search/services/backend/src/app/__init__.py +0 -0
- package/src/corpus/templates/semantic-search/services/backend/src/app/answer.py +90 -0
- package/src/corpus/templates/semantic-search/services/backend/src/app/auth.py +65 -0
- package/src/corpus/templates/semantic-search/services/backend/src/app/config.py +110 -0
- package/src/corpus/templates/semantic-search/services/backend/src/app/db.py +89 -0
- package/src/corpus/templates/semantic-search/services/backend/src/app/embeddings.py +44 -0
- package/src/corpus/templates/semantic-search/services/backend/src/app/indexing.py +222 -0
- package/src/corpus/templates/semantic-search/services/backend/src/app/main.py +259 -0
- package/src/corpus/templates/semantic-search/services/backend/src/app/search.py +222 -0
- package/src/corpus/templates/semantic-search/services/backend/src/app/source.py +259 -0
- package/src/corpus/templates/semantic-search/services/backend/src/main.py +12 -0
- package/src/corpus/templates/semantic-search/services/web/index.html +12 -0
- package/src/corpus/templates/semantic-search/services/web/package-lock.json +1680 -0
- package/src/corpus/templates/semantic-search/services/web/package.json +19 -0
- package/src/corpus/templates/semantic-search/services/web/src/App.jsx +55 -0
- package/src/corpus/templates/semantic-search/services/web/src/api.js +125 -0
- package/src/corpus/templates/semantic-search/services/web/src/components/Dashboard.jsx +61 -0
- package/src/corpus/templates/semantic-search/services/web/src/components/ManagerLogin.jsx +39 -0
- package/src/corpus/templates/semantic-search/services/web/src/components/SearchView.jsx +166 -0
- package/src/corpus/templates/semantic-search/services/web/src/components/Settings.jsx +148 -0
- package/src/corpus/templates/semantic-search/services/web/src/main.jsx +10 -0
- package/src/corpus/templates/semantic-search/services/web/src/styles.css +78 -0
- package/src/corpus/templates/semantic-search/services/web/vite.config.js +10 -0
- package/src/corpus/templates/status-page/cloudgrid.yaml +222 -4
- package/src/corpus/templates/status-page/index.md +3 -5
- package/src/corpus/templates/subscription-management/AGENTS.md +191 -0
- package/src/corpus/templates/subscription-management/README.md +21 -0
- package/src/corpus/templates/subscription-management/cloudgrid.yaml +236 -0
- package/src/corpus/templates/subscription-management/index.md +73 -0
- package/src/corpus/templates/task-manager/cloudgrid.yaml +226 -8
- package/src/corpus/templates/task-manager/index.md +3 -0
- package/src/corpus/templates/ticket-system/README.md +71 -0
- package/src/corpus/templates/ticket-system/cloudgrid.yaml +233 -0
- package/src/corpus/templates/ticket-system/index.md +68 -0
- package/src/corpus/templates/ticket-system/services/web/app/api/tickets/route.js +81 -0
- package/src/corpus/templates/ticket-system/services/web/app/layout.js +36 -0
- package/src/corpus/templates/ticket-system/services/web/app/page.js +35 -0
- package/src/corpus/templates/ticket-system/services/web/app/ticket-queue.js +112 -0
- package/src/corpus/templates/ticket-system/services/web/lib/db.js +32 -0
- package/src/corpus/templates/ticket-system/services/web/package.json +16 -0
- package/src/corpus/templates/time-tracking/README.md +81 -0
- package/src/corpus/templates/time-tracking/cloudgrid.yaml +233 -0
- package/src/corpus/templates/time-tracking/index.md +298 -0
- package/src/corpus/templates/time-tracking/services/web/app/api/entries/route.js +66 -0
- package/src/corpus/templates/time-tracking/services/web/app/entry-form.js +106 -0
- package/src/corpus/templates/time-tracking/services/web/app/layout.js +39 -0
- package/src/corpus/templates/time-tracking/services/web/app/page.js +30 -0
- package/src/corpus/templates/time-tracking/services/web/lib/db.js +32 -0
- package/src/corpus/templates/time-tracking/services/web/package.json +16 -0
- package/src/corpus/templates/travel-booking/AGENTS.md +207 -0
- package/src/corpus/templates/travel-booking/README.md +17 -0
- package/src/corpus/templates/travel-booking/cloudgrid.yaml +249 -0
- package/src/corpus/templates/travel-booking/index.md +88 -0
- package/src/corpus/templates/waitlist/cloudgrid.yaml +222 -4
- package/src/corpus/templates/waitlist/index.md +3 -5
- package/src/corpus/templates/web-app/cloudgrid.yaml +228 -4
- package/src/corpus/templates/web-app/index.md +3 -5
- package/src/corpus/workflows/ai-knowledge-base.md +137 -0
- package/src/corpus/workflows/analytics-dashboard.md +104 -0
- package/src/corpus/workflows/api-dashboard.md +102 -0
- package/src/corpus/workflows/appointment-booking.md +143 -0
- package/src/corpus/workflows/approval-workflow.md +124 -0
- package/src/corpus/workflows/billing-dashboard.md +120 -0
- package/src/corpus/workflows/blog-cms.md +104 -0
- package/src/corpus/workflows/booking-system.md +116 -0
- package/src/corpus/workflows/calendar-scheduler.md +134 -0
- package/src/corpus/workflows/company-website.md +46 -0
- package/src/corpus/workflows/course-platform.md +126 -0
- package/src/corpus/workflows/directory.md +101 -0
- package/src/corpus/workflows/erp.md +125 -0
- package/src/corpus/workflows/event-board.md +102 -0
- package/src/corpus/workflows/expense-tracker.md +102 -0
- package/src/corpus/workflows/feature-request-board.md +105 -0
- package/src/corpus/workflows/financial-dashboard.md +103 -0
- package/src/corpus/workflows/forum.md +128 -0
- package/src/corpus/workflows/hr-portal.md +115 -0
- package/src/corpus/workflows/internal-tools-portal.md +125 -0
- package/src/corpus/workflows/inventory.md +101 -0
- package/src/corpus/workflows/invoice.md +101 -0
- package/src/corpus/workflows/job-board.md +101 -0
- package/src/corpus/workflows/lms.md +113 -0
- package/src/corpus/workflows/marketplace.md +123 -0
- package/src/corpus/workflows/membership-site.md +128 -0
- package/src/corpus/workflows/monitoring-dashboard.md +104 -0
- package/src/corpus/workflows/online-store.md +114 -0
- package/src/corpus/workflows/product-catalog.md +105 -0
- package/src/corpus/workflows/product-launch.md +43 -0
- package/src/corpus/workflows/project-management.md +106 -0
- package/src/corpus/workflows/property-listings.md +101 -0
- package/src/corpus/workflows/quiz-platform.md +105 -0
- package/src/corpus/workflows/restaurant-reservations.md +123 -0
- package/src/corpus/workflows/revenue-dashboard.md +102 -0
- package/src/corpus/workflows/semantic-search.md +133 -0
- package/src/corpus/workflows/subscription-management.md +141 -0
- package/src/corpus/workflows/ticket-system.md +101 -0
- package/src/corpus/workflows/time-tracking.md +104 -0
- package/src/corpus/workflows/travel-booking.md +115 -0
- package/src/tools.js +73 -12
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Single config point for embeddings (OpenAI-compatible).
|
|
2
|
+
|
|
3
|
+
Isolated behind one module so the provider can be swapped by env var
|
|
4
|
+
(EMBEDDINGS_BASE_URL / EMBEDDINGS_MODEL / EMBEDDINGS_API_KEY). NEVER reads the
|
|
5
|
+
API key at import time — only inside embed(). If the key is missing, embed()
|
|
6
|
+
raises EmbeddingsUnavailable, which callers translate into a graceful degraded
|
|
7
|
+
state (lexical + metadata search still work).
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
|
|
13
|
+
from . import config
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EmbeddingsUnavailable(RuntimeError):
|
|
17
|
+
"""Raised when embeddings are requested but not configured/reachable."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def embed(texts: list[str]) -> list[list[float]]:
|
|
21
|
+
"""Return one embedding vector per input text.
|
|
22
|
+
|
|
23
|
+
Raises EmbeddingsUnavailable if EMBEDDINGS_API_KEY is not set.
|
|
24
|
+
"""
|
|
25
|
+
if not texts:
|
|
26
|
+
return []
|
|
27
|
+
key = config.embeddings_api_key()
|
|
28
|
+
if not key:
|
|
29
|
+
raise EmbeddingsUnavailable(
|
|
30
|
+
"EMBEDDINGS_API_KEY is not set — semantic embedding is unavailable."
|
|
31
|
+
)
|
|
32
|
+
resp = httpx.post(
|
|
33
|
+
f"{config.embeddings_base_url()}/embeddings",
|
|
34
|
+
headers={"Authorization": f"Bearer {key}"},
|
|
35
|
+
json={"model": config.embeddings_model(), "input": texts},
|
|
36
|
+
timeout=60.0,
|
|
37
|
+
)
|
|
38
|
+
resp.raise_for_status()
|
|
39
|
+
data = resp.json()["data"]
|
|
40
|
+
return [row["embedding"] for row in data]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def embed_one(text: str) -> list[float]:
|
|
44
|
+
return embed([text])[0]
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""Incremental indexing pipeline: discover -> extract -> chunk -> embed -> store.
|
|
2
|
+
|
|
3
|
+
Shared by the manager 'Refresh now' endpoint (and, once platform issue #1585
|
|
4
|
+
lands, a `type: cron` refresh service). It is resilient: if the source or
|
|
5
|
+
embeddings are not configured it records a clear, non-crashing report and
|
|
6
|
+
returns. Only new/changed files (by rev / content-hash) are reprocessed.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import datetime as dt
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
|
|
14
|
+
from . import config, db, embeddings, source
|
|
15
|
+
|
|
16
|
+
CHUNK_CHARS = 1200
|
|
17
|
+
CHUNK_OVERLAP = 150
|
|
18
|
+
|
|
19
|
+
# Generic date detection from filename or document head (best effort, nullable).
|
|
20
|
+
_DATE_RE = re.compile(r"(\d{4})[./-](\d{1,2})[./-](\d{1,2})") # 2026-07-05
|
|
21
|
+
_DATE_RE_ALT = re.compile(r"(\d{1,2})[./-](\d{1,2})[./-](\d{2,4})") # 05/07/2026
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _utcnow() -> dt.datetime:
|
|
25
|
+
return dt.datetime.now(dt.timezone.utc)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _chunk_text(text: str) -> list[str]:
|
|
29
|
+
text = text.strip()
|
|
30
|
+
if not text:
|
|
31
|
+
return []
|
|
32
|
+
out: list[str] = []
|
|
33
|
+
start = 0
|
|
34
|
+
n = len(text)
|
|
35
|
+
while start < n:
|
|
36
|
+
end = min(start + CHUNK_CHARS, n)
|
|
37
|
+
out.append(text[start:end])
|
|
38
|
+
if end == n:
|
|
39
|
+
break
|
|
40
|
+
start = end - CHUNK_OVERLAP
|
|
41
|
+
return out
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _parse_metadata(name: str, text: str) -> dict:
|
|
45
|
+
"""Best-effort title / date / collection extraction. All fields nullable.
|
|
46
|
+
|
|
47
|
+
Generalize this for your domain: `collection` is a free-form grouping tag
|
|
48
|
+
(folder, series, category), `date` is an ISO date if one can be found.
|
|
49
|
+
"""
|
|
50
|
+
title = os.path.splitext(name)[0].strip()
|
|
51
|
+
# Collection tag = the leading non-numeric words of the title.
|
|
52
|
+
collection = re.sub(r"[\d].*$", "", title).strip() or None
|
|
53
|
+
|
|
54
|
+
date_iso = None
|
|
55
|
+
haystack = f"{name}\n{text[:2000]}"
|
|
56
|
+
m = _DATE_RE.search(haystack)
|
|
57
|
+
if m:
|
|
58
|
+
y, mo, d = m.groups()
|
|
59
|
+
try:
|
|
60
|
+
date_iso = dt.datetime(int(y), int(mo), int(d)).date().isoformat()
|
|
61
|
+
except ValueError:
|
|
62
|
+
date_iso = None
|
|
63
|
+
if date_iso is None:
|
|
64
|
+
m = _DATE_RE_ALT.search(haystack)
|
|
65
|
+
if m:
|
|
66
|
+
d, mo, y = m.groups()
|
|
67
|
+
if len(y) == 2:
|
|
68
|
+
y = "20" + y
|
|
69
|
+
try:
|
|
70
|
+
date_iso = dt.datetime(int(y), int(mo), int(d)).date().isoformat()
|
|
71
|
+
except ValueError:
|
|
72
|
+
date_iso = None
|
|
73
|
+
|
|
74
|
+
return {"title": title, "collection": collection, "date": date_iso}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def run_sync(triggered_by: str = "manual") -> dict:
|
|
78
|
+
"""Run one incremental sync. Never raises on missing config — returns a report."""
|
|
79
|
+
started = _utcnow()
|
|
80
|
+
report: dict = {
|
|
81
|
+
"started_at": started,
|
|
82
|
+
"triggered_by": triggered_by,
|
|
83
|
+
"files": [],
|
|
84
|
+
"status": "ok",
|
|
85
|
+
"note": None,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Mongo is required for indexing (and always injected). Fail loud only here.
|
|
89
|
+
db.ensure_indexes()
|
|
90
|
+
|
|
91
|
+
if not config.source_configured():
|
|
92
|
+
report["status"] = "skipped"
|
|
93
|
+
report["note"] = (
|
|
94
|
+
f"Source not configured (SOURCE_TYPE={config.source_type()!r} — "
|
|
95
|
+
"missing its secrets/config)."
|
|
96
|
+
)
|
|
97
|
+
_save_report(report)
|
|
98
|
+
return report
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
src = source.get_source()
|
|
102
|
+
files = src.list_files()
|
|
103
|
+
except Exception as exc: # noqa: BLE001 - resilient by design
|
|
104
|
+
report["status"] = "error"
|
|
105
|
+
report["note"] = f"Source listing failed: {exc}"
|
|
106
|
+
_save_report(report)
|
|
107
|
+
return report
|
|
108
|
+
|
|
109
|
+
embeddings_ok = config.embeddings_configured()
|
|
110
|
+
if not embeddings_ok:
|
|
111
|
+
report["note"] = (
|
|
112
|
+
"Embeddings not configured — documents indexed for lexical/metadata "
|
|
113
|
+
"search only (no semantic vectors)."
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
for f in files:
|
|
117
|
+
outcome = _process_file(src, f, embeddings_ok)
|
|
118
|
+
report["files"].append(outcome)
|
|
119
|
+
|
|
120
|
+
report["finished_at"] = _utcnow()
|
|
121
|
+
_save_report(report)
|
|
122
|
+
return report
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _process_file(src: source.Source, f: source.SourceFile, embeddings_ok: bool) -> dict:
|
|
126
|
+
existing = db.documents().find_one({"source_filename": f.name})
|
|
127
|
+
if existing and existing.get("source_rev") == f.rev:
|
|
128
|
+
return {"file": f.name, "result": "unchanged"}
|
|
129
|
+
|
|
130
|
+
if f.is_audio and not f.is_text:
|
|
131
|
+
# Catalogue audio/media with a flag; do not search inside it.
|
|
132
|
+
doc = {
|
|
133
|
+
"title": os.path.splitext(f.name)[0],
|
|
134
|
+
"source_filename": f.name,
|
|
135
|
+
"source_rev": f.rev,
|
|
136
|
+
"file_path": f.path,
|
|
137
|
+
"collection": None,
|
|
138
|
+
"date": None,
|
|
139
|
+
"has_media": True,
|
|
140
|
+
"media_path": f.path,
|
|
141
|
+
"word_count": 0,
|
|
142
|
+
"indexed_at": _utcnow(),
|
|
143
|
+
}
|
|
144
|
+
doc_id = _upsert_document(doc)
|
|
145
|
+
db.chunks().delete_many({"document_id": doc_id})
|
|
146
|
+
return {"file": f.name, "result": "catalogued_media"}
|
|
147
|
+
|
|
148
|
+
if not f.is_text:
|
|
149
|
+
return {"file": f.name, "result": "skipped_unsupported"}
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
raw = src.download(f.path)
|
|
153
|
+
text = source.extract_text(f.name, raw)
|
|
154
|
+
except Exception as exc: # noqa: BLE001
|
|
155
|
+
return {"file": f.name, "result": "error", "detail": str(exc)}
|
|
156
|
+
|
|
157
|
+
meta = _parse_metadata(f.name, text)
|
|
158
|
+
doc = {
|
|
159
|
+
**meta,
|
|
160
|
+
"source_filename": f.name,
|
|
161
|
+
"source_rev": f.rev,
|
|
162
|
+
"file_path": f.path,
|
|
163
|
+
"has_media": False,
|
|
164
|
+
"media_path": None,
|
|
165
|
+
"word_count": len(text.split()),
|
|
166
|
+
"indexed_at": _utcnow(),
|
|
167
|
+
}
|
|
168
|
+
doc_id = _upsert_document(doc)
|
|
169
|
+
|
|
170
|
+
# Rebuild chunks for this document.
|
|
171
|
+
db.chunks().delete_many({"document_id": doc_id})
|
|
172
|
+
parts = _chunk_text(text)
|
|
173
|
+
vectors: list[list[float] | None] = [None] * len(parts)
|
|
174
|
+
if embeddings_ok and parts:
|
|
175
|
+
try:
|
|
176
|
+
vectors = embeddings.embed(parts) # type: ignore[assignment]
|
|
177
|
+
except Exception as exc: # noqa: BLE001
|
|
178
|
+
return {
|
|
179
|
+
"file": f.name,
|
|
180
|
+
"result": "indexed_no_embeddings",
|
|
181
|
+
"detail": f"embedding failed: {exc}",
|
|
182
|
+
"chunks": len(parts),
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
docs = []
|
|
186
|
+
for i, content in enumerate(parts):
|
|
187
|
+
docs.append({
|
|
188
|
+
"document_id": doc_id,
|
|
189
|
+
"chunk_index": i,
|
|
190
|
+
"content": content,
|
|
191
|
+
"embedding": vectors[i] if i < len(vectors) else None,
|
|
192
|
+
"token_count": len(content.split()),
|
|
193
|
+
})
|
|
194
|
+
if docs:
|
|
195
|
+
db.chunks().insert_many(docs)
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
"file": f.name,
|
|
199
|
+
"result": "indexed" if embeddings_ok else "indexed_no_embeddings",
|
|
200
|
+
"chunks": len(docs),
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _upsert_document(doc: dict):
|
|
205
|
+
db.documents().update_one(
|
|
206
|
+
{"source_filename": doc["source_filename"]},
|
|
207
|
+
{"$set": doc},
|
|
208
|
+
upsert=True,
|
|
209
|
+
)
|
|
210
|
+
return db.documents().find_one({"source_filename": doc["source_filename"]})["_id"]
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _save_report(report: dict) -> None:
|
|
214
|
+
try:
|
|
215
|
+
db.index_reports().insert_one(dict(report))
|
|
216
|
+
db.settings().update_one(
|
|
217
|
+
{"_id": "catalog"},
|
|
218
|
+
{"$set": {"last_refresh": report.get("finished_at") or report["started_at"]}},
|
|
219
|
+
upsert=True,
|
|
220
|
+
)
|
|
221
|
+
except Exception: # noqa: BLE001 - reporting must never crash the caller
|
|
222
|
+
pass
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"""FastAPI backend for the semantic-search document app.
|
|
2
|
+
|
|
3
|
+
Mounted at /backend by CloudGrid. Public search + answer endpoints, plus
|
|
4
|
+
manager-only admin (Settings, Usage Dashboard, Refresh now) enforced server-side.
|
|
5
|
+
|
|
6
|
+
Health is green as soon as Mongo connects — it does NOT require the document
|
|
7
|
+
source or the embeddings key. No secret is read at import time.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import datetime as dt
|
|
12
|
+
|
|
13
|
+
from fastapi import Depends, FastAPI
|
|
14
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
15
|
+
from pydantic import BaseModel
|
|
16
|
+
|
|
17
|
+
from . import answer, auth, config, db, indexing, search
|
|
18
|
+
|
|
19
|
+
app = FastAPI(title="Semantic Search API")
|
|
20
|
+
|
|
21
|
+
app.add_middleware(
|
|
22
|
+
CORSMiddleware,
|
|
23
|
+
allow_origins=["*"], # frontend is served same-origin; open for safety
|
|
24
|
+
allow_methods=["*"],
|
|
25
|
+
allow_headers=["*"],
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.on_event("startup")
|
|
30
|
+
def _ensure_indexes_on_startup() -> None:
|
|
31
|
+
# Create the chunks $text index + documents indexes at boot so $text search
|
|
32
|
+
# returns [] on an empty catalog instead of erroring (IndexNotFound / 500).
|
|
33
|
+
# Never block startup if Mongo is briefly unavailable — an index run recreates
|
|
34
|
+
# them.
|
|
35
|
+
try:
|
|
36
|
+
db.ensure_indexes()
|
|
37
|
+
except Exception as e: # noqa: BLE001
|
|
38
|
+
print(f"startup: ensure_indexes skipped ({e})")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ── Health ────────────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
@app.get("/backend/health")
|
|
44
|
+
def health():
|
|
45
|
+
"""Green when Mongo connects. Independent of the source / embeddings key."""
|
|
46
|
+
try:
|
|
47
|
+
db.ping()
|
|
48
|
+
return {"status": "ok"}
|
|
49
|
+
except Exception as exc: # noqa: BLE001
|
|
50
|
+
return {"status": "degraded", "detail": str(exc)}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@app.get("/backend/status")
|
|
54
|
+
def status():
|
|
55
|
+
"""Public capability snapshot — powers the frontend's 'not indexed / missing
|
|
56
|
+
secret' states without exposing any secret value."""
|
|
57
|
+
try:
|
|
58
|
+
db.ping()
|
|
59
|
+
mongo_ok = True
|
|
60
|
+
except Exception: # noqa: BLE001
|
|
61
|
+
mongo_ok = False
|
|
62
|
+
settings_doc = db.settings().find_one({"_id": "catalog"}) if mongo_ok else None
|
|
63
|
+
features = db.settings().find_one({"_id": "features"}) if mongo_ok else None
|
|
64
|
+
return {
|
|
65
|
+
"mongo": mongo_ok,
|
|
66
|
+
"source_type": config.source_type(),
|
|
67
|
+
"source_configured": config.source_configured(),
|
|
68
|
+
"embeddings_configured": config.embeddings_configured(),
|
|
69
|
+
"manager_auth_configured": config.manager_auth_configured(),
|
|
70
|
+
"answer_mode_enabled": bool((features or {}).get("answer_mode_enabled", True)),
|
|
71
|
+
"indexed_documents": db.documents().count_documents({}) if mongo_ok else 0,
|
|
72
|
+
"last_refresh": (settings_doc or {}).get("last_refresh"),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ── Search (public) ───────────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
@app.get("/backend/search")
|
|
79
|
+
def do_search(q: str = "", mode: str = "hybrid", collection: str | None = None,
|
|
80
|
+
date_from: str | None = None, date_to: str | None = None,
|
|
81
|
+
limit: int = 20):
|
|
82
|
+
result = search.search(q, mode=mode, collection=collection,
|
|
83
|
+
date_from=date_from, date_to=date_to, limit=limit)
|
|
84
|
+
_log_event("search", {"q": q, "mode": mode,
|
|
85
|
+
"zero_result": len(result["results"]) == 0})
|
|
86
|
+
return result
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@app.get("/backend/count")
|
|
90
|
+
def do_count(collection: str | None = None, date_from: str | None = None,
|
|
91
|
+
date_to: str | None = None):
|
|
92
|
+
"""Counting answered directly from the DB (countDocuments)."""
|
|
93
|
+
return {"count": search.count_documents(collection, date_from, date_to)}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@app.get("/backend/collections")
|
|
97
|
+
def list_collections():
|
|
98
|
+
values = [c for c in db.documents().distinct("collection") if c]
|
|
99
|
+
return {"collections": sorted(values)}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class AnswerRequest(BaseModel):
|
|
103
|
+
question: str
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@app.post("/backend/answer")
|
|
107
|
+
def do_answer(req: AnswerRequest):
|
|
108
|
+
_log_event("answer", {"q": req.question})
|
|
109
|
+
return answer.compose_answer(req.question)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@app.get("/backend/documents/{document_id}/download")
|
|
113
|
+
def download_document(document_id: str):
|
|
114
|
+
"""Stream a document's text through the service — source paths never exposed."""
|
|
115
|
+
from bson import ObjectId
|
|
116
|
+
from fastapi import HTTPException
|
|
117
|
+
from fastapi.responses import PlainTextResponse
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
doc = db.documents().find_one({"_id": ObjectId(document_id)})
|
|
121
|
+
except Exception: # noqa: BLE001
|
|
122
|
+
raise HTTPException(status_code=404, detail="Not found.")
|
|
123
|
+
if not doc:
|
|
124
|
+
raise HTTPException(status_code=404, detail="Not found.")
|
|
125
|
+
parts = db.chunks().find({"document_id": doc["_id"]}).sort("chunk_index", 1)
|
|
126
|
+
text = "".join(p["content"] for p in parts)
|
|
127
|
+
return PlainTextResponse(text, headers={
|
|
128
|
+
"Content-Disposition": f'attachment; filename="{doc.get("title","document")}.txt"'
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# ── Manager auth ──────────────────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
class LoginRequest(BaseModel):
|
|
135
|
+
password: str
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@app.post("/backend/admin/login")
|
|
139
|
+
def admin_login(req: LoginRequest):
|
|
140
|
+
token = auth.login(req.password)
|
|
141
|
+
return {"token": token}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# ── Manager: Settings ─────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
@app.get("/backend/admin/settings", dependencies=[Depends(auth.require_manager)])
|
|
147
|
+
def get_settings():
|
|
148
|
+
features = db.settings().find_one({"_id": "features"}) or {}
|
|
149
|
+
synonyms = db.settings().find_one({"_id": "synonyms"}) or {}
|
|
150
|
+
catalog = db.settings().find_one({"_id": "catalog"}) or {}
|
|
151
|
+
return {
|
|
152
|
+
"answer_mode_enabled": bool(features.get("answer_mode_enabled", True)),
|
|
153
|
+
"synonyms": synonyms.get("map", {}),
|
|
154
|
+
"last_refresh": catalog.get("last_refresh"),
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class FeatureUpdate(BaseModel):
|
|
159
|
+
answer_mode_enabled: bool | None = None
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@app.put("/backend/admin/settings", dependencies=[Depends(auth.require_manager)])
|
|
163
|
+
def update_settings(req: FeatureUpdate):
|
|
164
|
+
update = {k: v for k, v in req.dict().items() if v is not None}
|
|
165
|
+
if update:
|
|
166
|
+
db.settings().update_one({"_id": "features"}, {"$set": update}, upsert=True)
|
|
167
|
+
return {"ok": True}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class SynonymUpdate(BaseModel):
|
|
171
|
+
map: dict[str, list[str]]
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@app.put("/backend/admin/synonyms", dependencies=[Depends(auth.require_manager)])
|
|
175
|
+
def update_synonyms(req: SynonymUpdate):
|
|
176
|
+
db.settings().update_one({"_id": "synonyms"}, {"$set": {"map": req.map}}, upsert=True)
|
|
177
|
+
return {"ok": True}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class PasswordChange(BaseModel):
|
|
181
|
+
current: str
|
|
182
|
+
new: str
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@app.post("/backend/admin/password", dependencies=[Depends(auth.require_manager)])
|
|
186
|
+
def change_password(req: PasswordChange):
|
|
187
|
+
new_hash = auth.change_password(req.current, req.new)
|
|
188
|
+
# Return the hash for the manager to store as MANAGER_PASSWORD_HASH.
|
|
189
|
+
return {
|
|
190
|
+
"new_hash": new_hash,
|
|
191
|
+
"note": "Store this as the MANAGER_PASSWORD_HASH secret "
|
|
192
|
+
"(`grid secrets set MANAGER_PASSWORD_HASH=<hash>`) to persist it.",
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@app.get("/backend/admin/index-report", dependencies=[Depends(auth.require_manager)])
|
|
197
|
+
def index_report():
|
|
198
|
+
reports = list(db.index_reports().find().sort("started_at", -1).limit(10))
|
|
199
|
+
for r in reports:
|
|
200
|
+
r["_id"] = str(r["_id"])
|
|
201
|
+
return {"reports": reports}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@app.post("/backend/admin/refresh", dependencies=[Depends(auth.require_manager)])
|
|
205
|
+
def refresh_now():
|
|
206
|
+
"""Manager-only on-demand incremental sync.
|
|
207
|
+
|
|
208
|
+
This is the supported refresh path today. A scheduled `type: cron` refresh
|
|
209
|
+
service is a follow-up (blocked on platform issue #1585); see AGENTS.md.
|
|
210
|
+
"""
|
|
211
|
+
report = indexing.run_sync(triggered_by="manager")
|
|
212
|
+
report.pop("_id", None)
|
|
213
|
+
return report
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# ── Manager: Usage Dashboard ──────────────────────────────────────────────────
|
|
217
|
+
|
|
218
|
+
@app.get("/backend/admin/usage", dependencies=[Depends(auth.require_manager)])
|
|
219
|
+
def usage():
|
|
220
|
+
ev = db.events()
|
|
221
|
+
total_searches = ev.count_documents({"kind": "search"})
|
|
222
|
+
by_mode = list(ev.aggregate([
|
|
223
|
+
{"$match": {"kind": "search"}},
|
|
224
|
+
{"$group": {"_id": "$meta.mode", "count": {"$sum": 1}}},
|
|
225
|
+
]))
|
|
226
|
+
top_queries = list(ev.aggregate([
|
|
227
|
+
{"$match": {"kind": "search", "meta.q": {"$ne": ""}}},
|
|
228
|
+
{"$group": {"_id": "$meta.q", "count": {"$sum": 1}}},
|
|
229
|
+
{"$sort": {"count": -1}}, {"$limit": 10},
|
|
230
|
+
]))
|
|
231
|
+
zero_result = list(ev.aggregate([
|
|
232
|
+
{"$match": {"kind": "search", "meta.zero_result": True}},
|
|
233
|
+
{"$group": {"_id": "$meta.q", "count": {"$sum": 1}}},
|
|
234
|
+
{"$sort": {"count": -1}}, {"$limit": 10},
|
|
235
|
+
]))
|
|
236
|
+
unique_users = len(ev.distinct("anon_id", {"kind": "search"}))
|
|
237
|
+
return {
|
|
238
|
+
"total_searches": total_searches,
|
|
239
|
+
"unique_users": unique_users,
|
|
240
|
+
"by_mode": {row["_id"] or "unknown": row["count"] for row in by_mode},
|
|
241
|
+
"top_queries": [{"query": r["_id"], "count": r["count"]} for r in top_queries],
|
|
242
|
+
"zero_result_queries": [{"query": r["_id"], "count": r["count"]} for r in zero_result],
|
|
243
|
+
"indexed_documents": db.documents().count_documents({}),
|
|
244
|
+
"last_refresh": (db.settings().find_one({"_id": "catalog"}) or {}).get("last_refresh"),
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# ── internal ──────────────────────────────────────────────────────────────────
|
|
249
|
+
|
|
250
|
+
def _log_event(kind: str, meta: dict) -> None:
|
|
251
|
+
try:
|
|
252
|
+
db.events().insert_one({
|
|
253
|
+
"kind": kind,
|
|
254
|
+
"meta": meta,
|
|
255
|
+
"anon_id": meta.get("anon_id", "anon"),
|
|
256
|
+
"at": dt.datetime.now(dt.timezone.utc),
|
|
257
|
+
})
|
|
258
|
+
except Exception: # noqa: BLE001 - telemetry must never break a request
|
|
259
|
+
pass
|