AI-powered cannabis grow operating system. Web-first, mobile-compatible, with secure image upload, persisted plant analyses, timeline tracking, and a grow-aware chatbot that keeps its own thread history.
PhenoSage is not just a grow journal. It is an AI grow operating system built around four pillars:
- Visual Grow Doctor — Upload plant photos and receive structured, professional AI evaluations
- Longitudinal Plant Intelligence — Compare new photos to prior uploads; track health over time
- Grow-aware Chat Copilot — A chatbot scoped to your actual grow data
- Proactive Recommendations & Alerts — Daily summaries, reminders, task generation
Browser (Next.js)
└── Vercel (apps/web — the only public origin)
├── /api/chat → proxies OpenAI
├── /api/uploads/sign → proxies Supabase Storage signed URLs
└── /api/plants/*/ → proxies analysis service + DB
Supabase Railway
(auth/db/images) (FastAPI analysis service — never called by the browser directly)
| Layer | Technology |
|---|---|
| Web app | Next.js 16 App Router, TypeScript strict, Tailwind CSS |
| Analysis service | FastAPI (Python 3.12) |
| Shared types | TypeScript package |
| Auth | Supabase Auth |
| Database | Supabase Postgres (pgvector-ready) |
| Storage | Supabase Storage (private buckets) |
| Web deploy | Vercel |
| Analysis deploy | Railway |
phenosage/
├── apps/
│ ├── web/ # Next.js App Router web application
│ └── analysis/ # FastAPI Python analysis service
├── packages/
│ └── shared/ # Shared TypeScript types and schemas
├── supabase/
│ └── migrations/ # SQL migrations
├── docs/ # Product docs
└── .github/
└── workflows/ # CI
- Node.js 20.x
- pnpm 9.15.9
- Python >= 3.12
- Docker (for analysis service)
- Supabase CLI
pnpm install --frozen-lockfileCopy the root .env.example and fill in your values:
cp .env.example .env.localFor Supabase operations and MCP access, the root env example now includes:
SUPABASE_PROJECT_REF— project ref used by.mcp.json,supabase link, and the migrations workflowSUPABASE_DB_PASSWORD— direct Postgres password for CLI/admin flowsSUPABASE_DB_URL— direct Postgres connection string (db.<ref>.supabase.co:5432) for admin sessions and recovery work
The checked-in .mcp.json already points at the production Supabase MCP endpoint for yjemotnclrnlxgcfntaf; full MCP read/write access is granted through the Supabase OAuth consent flow in your agent, not by committing secrets into the repo.
See each app's .env.example for service-specific variables.
pnpm --filter web devcd apps/analysis
pip install -r requirements.txt
uvicorn app.main:app --reloadOr with Docker:
cd apps/analysis
docker build -t phenosage-analysis .
docker run -p 8000:8000 --env-file .env phenosage-analysisSee docs/deployment.md for the full list of required environment variables per service.
The contract is enforced in two places:
scripts/check-env-contract.mjs(run viapnpm run check:env) — fails CI if any required key is missing from.env.example, or if a server-only key leaks into client code.apps/web/src/lib/server/env.ts— runtime validator for Route Handlers; callassertServerEnv()at the top of a server entry to fail loudly on misconfiguration.
| Layer | Command |
|---|---|
| Unit (web + shared) | pnpm turbo run test |
| Type-check + lint | pnpm turbo run type-check lint |
| Pre-merge guardrails | pnpm run validate |
| Route security audit | pnpm run security:routes |
| End-to-end (Playwright) | pnpm --filter web exec playwright install && pnpm --filter web test:e2e |
The auth flow is covered by unit tests in apps/web/src/app/auth/__tests__/actions.test.ts, including network-failure (TypeError: fetch failed) and DNS-failure (ENOTFOUND) paths so a misconfigured Supabase URL never reaches the user as a raw "fetch failed" error.
- The browser only talks to the Next.js app on Vercel. It does not call Railway directly.
- Plant image uploads are signed server-side and uploaded straight to Supabase Storage.
- Plant analyses are persisted in
plant_analysesandplant_findings. - Analysis responses can fall back to an inconclusive mode when storage or OpenAI is unavailable. Treat those as non-diagnostic and retry once the upstream dependency is healthy.