{"id":50394328,"url":"https://github.com/alextheradu/curator","last_synced_at":"2026-05-30T20:02:22.516Z","repository":{"id":356424582,"uuid":"1216543070","full_name":"alextheradu/curator","owner":"alextheradu","description":"FRC AI Chatbot and Assistant","archived":false,"fork":false,"pushed_at":"2026-05-15T23:01:02.000Z","size":12023,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-16T00:55:15.074Z","etag":null,"topics":["ai","assistant","chatbot","curator","frc","qdrant","rag","rag-chatbot","vector"],"latest_commit_sha":null,"homepage":"https://curatorfrc.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alextheradu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-21T02:26:40.000Z","updated_at":"2026-05-15T23:01:06.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/alextheradu/curator","commit_stats":null,"previous_names":["alextheradu/curator"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/alextheradu/curator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextheradu%2Fcurator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextheradu%2Fcurator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextheradu%2Fcurator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextheradu%2Fcurator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alextheradu","download_url":"https://codeload.github.com/alextheradu/curator/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextheradu%2Fcurator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33707328,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","assistant","chatbot","curator","frc","qdrant","rag","rag-chatbot","vector"],"created_at":"2026-05-30T20:02:21.765Z","updated_at":"2026-05-30T20:02:22.494Z","avatar_url":"https://github.com/alextheradu.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003ca href=\"https://curatorfrc.com\"\u003e\n    \u003cimg src=\"public/logo-full.png\" alt=\"Curator\" width=\"620\" /\u003e\n  \u003c/a\u003e\n\n  \u003cp\u003e\n    A season-aware AI workspace for FIRST Robotics Competition teams.\n  \u003c/p\u003e\n\n  \u003cp\u003e\n    \u003ca href=\"https://curatorfrc.com\"\u003eLive app\u003c/a\u003e\n    |\n    \u003ca href=\"https://github.com/alextheradu/curator\"\u003eRepository\u003c/a\u003e\n    |\n    \u003ca href=\"public/privacy-policy.md\"\u003ePrivacy Policy\u003c/a\u003e\n    |\n    \u003ca href=\"public/terms-of-service.md\"\u003eTerms\u003c/a\u003e\n    |\n    \u003ca href=\"LICENSE\"\u003eLicense\u003c/a\u003e\n  \u003c/p\u003e\n\n  \u003cp\u003e\n    \u003cimg src=\"https://hackatime-badge.hackclub.com/U0AFWJX9CP2/curator\" alt=\"Hackatime project activity\" /\u003e\n  \u003c/p\u003e\n\n  \u003cp\u003e\n    \u003cimg src=\"https://img.shields.io/github/last-commit/alextheradu/curator?style=for-the-badge\u0026logo=github\u0026color=0066B3\" alt=\"Last commit\" /\u003e\n    \u003cimg src=\"https://img.shields.io/github/stars/alextheradu/curator?style=for-the-badge\u0026logo=github\u0026color=ED1C24\" alt=\"GitHub stars\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/Next.js-16.2.3-000000?style=for-the-badge\u0026logo=nextdotjs\" alt=\"Next.js 16.2.3\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/React-19.2.4-149ECA?style=for-the-badge\u0026logo=react\u0026logoColor=white\" alt=\"React 19.2.4\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/TypeScript-5-3178C6?style=for-the-badge\u0026logo=typescript\u0026logoColor=white\" alt=\"TypeScript 5\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/Postgres-16-4169E1?style=for-the-badge\u0026logo=postgresql\u0026logoColor=white\" alt=\"Postgres 16\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/Qdrant-vector_search-DC244C?style=for-the-badge\" alt=\"Qdrant vector search\" /\u003e\n  \u003c/p\u003e\n\u003c/div\u003e\n\n## Overview\n\nCurator is a Next.js 16 app for FRC teams that need grounded answers, source-backed rules help, live event context, and structured team memory in one workspace. It combines a chat interface, retrieval over indexed season documents, optional live web and The Blue Alliance lookups, project organization, public chat sharing, support/reporting tools, a public news feed, and an admin console for operations and content management.\n\nCurator is not affiliated with FIRST. Official manuals, Q\u0026A, awards guidance, event data, and field documentation remain the authority.\n\n## What It Does\n\n- Answers FRC questions with season-aware prompts tuned for rookie and veteran audiences.\n- Supports `fast`, `balanced`, and `deep` search modes for different retrieval depth and latency tradeoffs.\n- Pulls citations from indexed PDFs, optional live web search, and optional The Blue Alliance tool calls.\n- Stores season-scoped and general document chunks in Qdrant and serves source files from MinIO-compatible storage.\n- Organizes chats into projects with context summaries and project-level memory.\n- Supports guest chats in browser storage, then transfers them into the account after sign-in.\n- Lets users publish chats with shareable public links and browse public chat views safely.\n- Includes onboarding, default chat/search preferences, theme controls, cookie preferences, data export, and support forms.\n- Captures answer-quality feedback, message reports, support requests, and moderation signals.\n- Publishes a `/news` feed backed by admin-managed blog posts.\n- Ships an admin workspace for stats, documents, news posts, users, chats, reports, feedback, and operational triage.\n- Runs as a PWA and includes a Capacitor iOS shell with native Google sign-in support, offline handling, and keyboard/runtime fixes.\n\n## Stack\n\n| Area | Technology |\n| --- | --- |\n| App | Next.js 16 App Router, React 19, TypeScript |\n| UI | Tailwind CSS 4, Radix UI, Base UI, lucide-react, Framer Motion |\n| Auth | Auth.js / NextAuth with Google OAuth, Apple Sign In, and native iOS Google token sign-in |\n| State | Zustand persisted client state for chat UI preferences and guest conversations |\n| Database | PostgreSQL with Drizzle ORM and SQL migrations |\n| Retrieval | Qdrant vector search plus MinIO-compatible object storage |\n| AI | OpenRouter chat, title, and document-description models |\n| Search | Indexed FRC docs, optional LangSearch web search, optional The Blue Alliance MCP bridge |\n| Observability | Sentry, app logs, client error ingestion, rate limiting |\n| Delivery | PWA assets, service worker, metadata, Open Graph, sitemap, robots, IndexNow, Capacitor iOS |\n| Tests | Vitest |\n\n## Quick Start\n\nRequirements:\n\n- Node.js 20+\n- npm\n- PostgreSQL\n- Qdrant\n- Google OAuth credentials\n- OpenRouter API key\n\nOptional but commonly used:\n\n- Docker for local Postgres and Qdrant from `docker-compose.yml`\n- MinIO or another S3-compatible object store for document uploads and source-file viewing\n- LangSearch API key for live web search\n- The Blue Alliance API key for TBA-backed live lookups\n\nInstall dependencies:\n\n```bash\nnpm install\n```\n\nCreate local environment files:\n\n```bash\ncp .env.example .env.local\ncp .env.example .env.development.local\n```\n\nIf you plan to use `npm run build`, `npm run build:ci`, or `npm start`, also create `.env` because those scripts load that file explicitly:\n\n```bash\ncp .env.example .env\n```\n\nIf you use the included Docker services, make sure your DB settings match the compose port:\n\n```bash\nDATABASE_URL=postgresql://curator:\u003cyour-local-postgres-password\u003e@localhost:5437/curator\nPOSTGRES_PASSWORD=\u003cyour-local-postgres-password\u003e\nQDRANT_URL=http://localhost:6333\nQDRANT_API_KEY=\u003cyour-local-qdrant-api-key\u003e\n```\n\nStart local infrastructure:\n\n```bash\ndocker compose --env-file .env.local up -d postgres qdrant\n```\n\nThe compose file does not start MinIO. Point the MinIO vars at an existing bucket or run your own S3-compatible store separately if you want document upload and document viewing to work end to end.\n\nRun migrations:\n\n```bash\nnpm run db:migrate:dev\n```\n\nStart the app:\n\n```bash\nnpm run dev\n```\n\nOpen [http://localhost:3000](http://localhost:3000).\n\n## Environment\n\nThe baseline template lives in [.env.example](.env.example). The app currently references the following groups of variables:\n\n| Group | Variables |\n| --- | --- |\n| Core | `NEXT_PUBLIC_SITE_URL`, `AUTH_URL`, `AUTH_SECRET`, `DATABASE_URL`, `PORT` |\n| Web auth | `AUTH_GOOGLE_ID`, `AUTH_GOOGLE_SECRET`, `NEXT_PUBLIC_AUTH_GOOGLE_ID`, `ADMIN_EMAILS` |\n| Native and Apple auth | `AUTH_GOOGLE_IOS_CLIENT_ID`, `AUTH_APPLE_ID`, `AUTH_APPLE_SECRET`, `NEXT_PUBLIC_APPLE_SIGNIN_ENABLED` |\n| AI | `OPENROUTER_API_KEY`, `OPENROUTER_CHAT_MODELS`, `OPENROUTER_DESCRIPTION_MODELS`, `OPENROUTER_TITLE_MODEL` |\n| Retrieval | `QDRANT_URL`, `QDRANT_COLLECTION`, `MINIO_ENDPOINT`, `MINIO_USE_SSL`, `MINIO_ACCESS_KEY`, `MINIO_SECRET_KEY`, `MINIO_BUCKET` |\n| Optional live search | `LANGSEARCH_API_KEY`, `TBA_MCP_ENABLED`, `TBA_API_KEY` |\n| Site and analytics | `NEXT_PUBLIC_GOOGLE_ANALYTICS_ID`, `NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION`, `NEXT_PUBLIC_APP_BUILD_ID` |\n| Sentry | `NEXT_PUBLIC_SENTRY_DSN`, `SENTRY_DSN`, `SENTRY_ENVIRONMENT`, `SENTRY_TRACES_SAMPLE_RATE`, `NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE`, `NEXT_PUBLIC_SENTRY_REPLAY_SESSION_SAMPLE_RATE`, `NEXT_PUBLIC_SENTRY_REPLAY_ON_ERROR_SAMPLE_RATE`, `SENTRY_RELEASE_MANAGEMENT_ENABLED`, `SENTRY_AUTH_TOKEN`, `SENTRY_ORG`, `SENTRY_PROJECT` |\n| Indexing | `INDEXNOW_KEY`, `INDEXNOW_SITE_URL`, `INDEXNOW_SUBMIT_ON_START`, `INDEXNOW_SUBMIT_DELAY_MS` |\n| Runtime helpers | `PM2_PROCESS_NAME`, `EVAL_BASE_URL` |\n\nGenerate an auth secret with:\n\n```bash\nopenssl rand -base64 32\n```\n\nFor Apple Sign In, generate the client secret separately if you configure that provider:\n\n```bash\nnode scripts/generate-apple-secret.mjs\n```\n\n## User Flows\n\n### Chat workspace\n\n- Empty-state prompt suggestions, season selection, streaming responses, markdown/code rendering, and citations.\n- Search modes:\n  - `fast`: answer from current context and a smaller document pass.\n  - `balanced`: short document, web, and live-tool loop.\n  - `deep`: broader tool loop with larger document searches and more web coverage.\n- Optional fact-check pass for answers that should be re-verified against retrieved document context.\n- Guest usage is stored locally and currently limited to `3` message sends before sign-in is required.\n\n### Accounts and settings\n\n- Google sign-in on web.\n- Apple sign-in on web when Apple credentials are configured.\n- Native Google sign-in for Capacitor iOS using an ID-token credentials flow.\n- Onboarding captures preferred name, team number, chat mode, and search mode.\n- Settings support theme selection, default chat mode, default search mode, cookie preferences, account export, and reset controls.\n\n### Collaboration and support\n\n- Projects group chats and maintain a reusable project context summary.\n- Public conversation sharing exposes read-only share links for opted-in chats.\n- Users can report assistant messages, submit quality feedback, and send support requests from the support form.\n- Account export includes user profile data, settings, projects, conversations with messages, support requests, and reports.\n\n### Content and admin\n\n- `/news` renders published blog posts managed from the admin panel.\n- Admin routes cover stats, news, documents, users, chats, reports, feedback, and ops.\n- Document uploads can be stored in object storage, chunked, embedded, and indexed into Qdrant.\n- Admin stats compare database chunk counts with Qdrant vector counts and surface moderation and auth health.\n\n## Scripts\n\n| Command | Purpose |\n| --- | --- |\n| `npm run dev` | Start the Next.js dev server on the default port. |\n| `npm run dev:local` | Start the dev server on port `3001`. |\n| `npm run build:next` | Remove `.next` and build using values loaded from `.env`. |\n| `npm run build` | Run the production build, then restart the PM2 process. |\n| `npm run build:ci` | Build without the PM2 restart step. |\n| `npm start` | Start the production server through `scripts/start-next.js` using `.env`. |\n| `npm run lint` | Run ESLint. |\n| `npm test` | Run the Vitest suite. |\n| `npm run eval:chat` | Run chat evaluation cases against the configured base URL. |\n| `npm run db:generate` | Generate Drizzle migrations. |\n| `npm run db:migrate` | Run Drizzle migrations with the current shell environment. |\n| `npm run db:migrate:dev` | Run Drizzle migrations using `.env.development.local`. |\n| `npm run db:studio` | Open Drizzle Studio with the current shell environment. |\n| `npm run db:studio:dev` | Open Drizzle Studio using `.env.development.local`. |\n| `npm run site:check-assets` | Check public SEO and asset endpoints against a target URL. |\n| `npm run indexnow:submit` | Submit key site URLs to IndexNow. |\n| `npm run cap:assets` | Generate Capacitor iOS app icons from `resources`. |\n| `npm run cap:sync` | Sync web assets and plugins into the Capacitor project. |\n| `npm run cap:open:ios` | Open the iOS workspace in Xcode. |\n\n## Production Notes\n\nBuild and run:\n\n```bash\nnpm run build\nnpm start\n```\n\n`npm run build` assumes a PM2-managed process and calls `scripts/restart-curator.mjs`. Set `PM2_PROCESS_NAME` if the deployed process is not named `curator`.\n\n`npm start` uses `scripts/start-next.js` so process managers can pass ports consistently. The same startup path can also submit IndexNow once per process start:\n\n```bash\nINDEXNOW_SUBMIT_ON_START=true\nINDEXNOW_SUBMIT_DELAY_MS=5000\n```\n\nCurator exposes public discovery surfaces through:\n\n- `robots.txt` from `app/robots.ts`\n- `sitemap.xml` from `app/sitemap.ts`\n- Open Graph and app metadata in `app/` and `public/`\n- `llms.txt` in `public/llms.txt`\n- an IndexNow key route at `/indexnow-key.txt`\n\nAdmin routes and admin APIs are marked `noindex`, and shared/private conversation boundaries are enforced in route handlers before content is returned.\n\n## Observability, Consent, and PWA Behavior\n\nGoogle Analytics stays off until the visitor accepts analytics cookies:\n\n```bash\nNEXT_PUBLIC_GOOGLE_ANALYTICS_ID=G-XXXXXXXXXX\n```\n\nSentry supports browser and server errors, tracing, and optional replay sampling:\n\n```bash\nNEXT_PUBLIC_SENTRY_DSN=https://...\nSENTRY_DSN=https://...\nSENTRY_ENVIRONMENT=production\nSENTRY_TRACES_SAMPLE_RATE=0.1\nNEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE=0.1\nNEXT_PUBLIC_SENTRY_REPLAY_SESSION_SAMPLE_RATE=0\nNEXT_PUBLIC_SENTRY_REPLAY_ON_ERROR_SAMPLE_RATE=0\n```\n\nSource map upload only runs when release management is enabled and Sentry build credentials are present:\n\n```bash\nSENTRY_RELEASE_MANAGEMENT_ENABLED=true\nSENTRY_AUTH_TOKEN=sntrys_...\nSENTRY_ORG=your-org-slug\nSENTRY_PROJECT=your-project-slug\n```\n\nThe app also:\n\n- registers a service worker outside local dev\n- serves an offline screen at `/offline`\n- attempts asset recovery when stale Next.js chunks fail to load\n- shows a slow-connection banner and a native Capacitor offline shell when appropriate\n\n## Project Map\n\n```text\napp/                         App Router pages, route handlers, metadata, PWA entrypoints\napp/(main)/                  Main chat and public news routes\napp/admin/                   Admin workspace routes\napp/api/                     Chat, account, admin, support, feedback, and document APIs\ncomponents/analytics/        Consent banner and Google Analytics loader\ncomponents/auth/             Auth, onboarding, and ToS modals\ncomponents/chat/             Chat app, window, input, markdown, search activity, citations\ncomponents/admin/            Admin panels for docs, chats, reports, users, blog, and ops\ncomponents/pwa/              Service-worker runtime and offline UX helpers\ncomponents/sidebar/          Main workspace sidebar, projects, history, and dialogs\nlib/                         AI, retrieval, auth helpers, DB access, logging, rate limiting, SEO\nlib/db/                      Drizzle schema and SQL migrations\npublic/                      Legal docs, PWA assets, logos, icons, and `llms.txt`\nscripts/                     Build/runtime helpers, evals, asset checks, IndexNow, Apple secret\ntests/                       Vitest coverage\ndocs/superpowers/            Internal design specs and implementation plans\n```\n\n## Legal Document Rule\n\nThe live legal documents are:\n\n- [public/privacy-policy.md](public/privacy-policy.md)\n- [public/terms-of-service.md](public/terms-of-service.md)\n\nKeep them in sync with product changes. If a feature changes data collection, storage, third-party services, cookies, data retention, authentication, user rights, or acceptable use, update the relevant sections and bump the \"Last updated\" date in the same change.\n\n## Verification\n\nBefore shipping a change, run the checks that match the touched surface:\n\n```bash\nnpm run lint\nnpm test\nnpm run build:ci\n```\n\nFor analytics, Sentry, SEO, PWA, or static-asset work, also run:\n\n```bash\nnpm run site:check-assets\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falextheradu%2Fcurator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falextheradu%2Fcurator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falextheradu%2Fcurator/lists"}