https://github.com/jacklvd/frondly-world
https://github.com/jacklvd/frondly-world
Last synced: about 9 hours ago
JSON representation
- Host: GitHub
- URL: https://github.com/jacklvd/frondly-world
- Owner: jacklvd
- Created: 2026-06-26T07:22:51.000Z (13 days ago)
- Default Branch: main
- Last Pushed: 2026-07-05T18:47:00.000Z (3 days ago)
- Last Synced: 2026-07-05T20:15:57.958Z (3 days ago)
- Language: TypeScript
- Size: 19.8 MB
- Stars: 1
- Watchers: 0
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README

# πΏ Frondly
**Your friendly, AI-powered plant-care companion.**
_Snap a photo β get an agent diagnosis & care plan β watch each plant thrive._
*Frondly* = **frond** (a leaf) + **friendly**
[](https://docs.expo.dev/versions/v56.0.0/)
[](https://reactnative.dev/)
[](https://www.python.org/)
[](https://fastapi.tiangolo.com/)
[](https://google.github.io/adk-docs/)
[](https://ai.google.dev/)
[](https://firebase.google.com/)
[](#-testing)
[](https://www.kaggle.com/)
[](#-kaggle-capstone-context)
---
## πͺ΄ The problem
Two everyday plant problems share one root cause β **not knowing what's wrong, when to act, or what's safe.**
- **At home:** people kill houseplants not from neglect but from uncertainty β is it over- or under-watered? Is that spot a fungus or sunburn? How often should _this_ plant, in _this_ room, be watered? Generic care tags don't answer that, and plant-ID apps stop at a name.
- **Outdoors:** foragers face a higher-stakes version of the same question β is this wild plant edible, or a toxic lookalike? Getting it wrong can be dangerous, and a bare species name is nowhere near enough to decide.
## π‘ The solution
**Frondly is a personal plant-care _and_ foraging concierge agent.** Point your camera at a plant and the agent:
- **Identifies** it (name + species) and prefills your garden entry.
- **Diagnoses** health from a photo, streaming back a plain-language explanation, a health score, and a care plan β and remembers the conversation so you can ask follow-ups.
- **Schedules watering** per plant using species, room, light, and live local weather β not a fixed calendar.
- **Forages safely** β identifies a plant in the wild against a research-verified PNW dataset, sorting it into **edible**, **not-edible** (recognized but not food), or **toxic** β with lookalike warnings, never a bare "it's edible", always paired with a safety reminder.
- **Tracks** every plant and find on-device, with optional secure cloud backup.
> **Why an agent?** Plant care isn't one API call β it's *reasoning over messy visual input, then choosing the right tool*: identify vs. diagnose vs. compute a watering schedule vs. fetch weather. The `plantcare` ADK agent orchestrates those tools and holds context across a diagnosis conversation, which a static classifier can't do.
## π Kaggle capstone context
Built for the **Kaggle 5-Day AI Agents Intensive β Vibe Coding Capstone**, submitted to the **Concierge Agents** track ("planning a garden β¦ agents that keep personal information safe and secure").
**Course concepts demonstrated (β₯3 required):**
| Concept | How Frondly demonstrates it | Where |
|---|---|---|
| π€ **Agent system (ADK)** | `plantcare` ADK agent (Gemini 2.5) with tool-calling: diagnosis, watering schedule, weather, decline tracking; streamed over `/run_sse` | `server/plantcare/`, `server/main.py` |
| π **Security features** | Firebase email/password auth + **per-user Firestore rules** (a user can only read/write their own backup); local-first β data lives on-device by default | `client/firebase/firestore.rules`, `client/src/lib/{auth.tsx,firebase.ts}` |
| π **Deployability** | Stateless FastAPI backend (persists nothing) + Expo dev-build client; single `GEMINI_API_KEY`, no server DB to provision | `server/`, [Quickstart](#-quickstart) |
## ποΈ Architecture
Local-first mobile client + a **stateless** agent backend. The device is the source of truth; the server holds no user data.
```mermaid
flowchart TB
subgraph Device["π± iPhone / Android β Expo Router + NativeWind"]
UI["UI routes
Home Β· Add Β· Forage Β· Care
Plant Detail Β· Diagnose Β· Account"]
DB[("WatermelonDB (SQLite)
Plant Β· Observation Β· Find")]
WX["lib/weather"]
API["lib/api Β· forage/api
lib/config β API_BASE"]
UI --> DB
UI --> WX
UI --> API
end
subgraph Backend["π FastAPI backend β server/ (stateless)"]
ADK["ADK agent β /run Β· /run_sse Β· /sessions
plantcare (Gemini 2.5)
tools: weather Β· decline Β· schedule Β· record_diagnosis"]
EP["POST /forage/identify
POST /plantcare/identify
POST /plantcare/watering_schedule"]
end
OM["π€οΈ Open-Meteo
(no key)"]
FB["π₯ Firebase
Auth + Firestore
backup/{uid}"]
GEM["β¨ Gemini 2.5
vision + chat"]
WX -->|https| OM
API -->|"SSE / http Β· EXPO_PUBLIC_API_BASE"| ADK
API --> EP
UI -.->|sign-in Β· backup / restore| FB
ADK --> GEM
EP --> GEM
classDef device fill:#e8f5e9,stroke:#43a047,color:#1b5e20;
classDef backend fill:#e3f2fd,stroke:#1e88e5,color:#0d47a1;
classDef ext fill:#fff8e1,stroke:#f9a825,color:#e65100;
class UI,DB,WX,API device;
class ADK,EP backend;
class OM,FB,GEM ext;
```
## β¨ Features
| Feature | What it does |
|---|---|
| πΈ **Plant identification** | Photo β name + species, prefilled into your garden (Gemini vision) |
| π©Ί **Diagnose flow** | Photo β streamed agent diagnosis, health score, care plan; saved as an observation; ask follow-ups in a thread |
| π§ **Smart watering schedule** | Per-plant schedule from species + room + light + live weather; Care tab sorts soonest/overdue first |
| π€οΈ **Live weather card** | Open-Meteo + device location (30-min cache), shown in Β°C and Β°F |
| π **Forage Finds** | Identify plants in the wild against a 100+-entry verified PNW dataset (edible / not-edible / toxic), persist them, browse a species library |
| π **Garden journal** | Per-plant history, growth timeline, tappable note cards |
| βοΈ **Secure cloud backup** | Firebase-auth-gated manual backup/restore of garden metadata to Firestore (per-user isolation) |
## π Quickstart
Monorepo: a Python agent backend (`server/`) and a React Native / Expo client (`client/`).
### 1. Backend (`server/`)
Requires [uv](https://docs.astral.sh/uv/) and a Google **`GEMINI_API_KEY`**.
```bash
# From the repo root β copy the template and add your Gemini key (loaded by server/main.py):
cp .env.example .env # then edit .env and set GEMINI_API_KEY=...
cd server
uv sync
uv run uvicorn main:app --reload # simulator β http://localhost:8000
# For a physical device, bind all interfaces so the phone can reach it over LAN:
# uv run uvicorn main:app --reload --host 0.0.0.0
```
### 2. Client (`client/`)
Requires Node + [yarn](https://yarnpkg.com/). WatermelonDB needs native modules, so use an Expo **development build** (not Expo Go).
```bash
cd client
yarn install
cp .env.example .env.local # then edit .env.local (see below)
# Build & run on a connected physical device (prompts you to pick from your devices):
npx expo run:ios --device # iOS β needs Xcode + a signing team; enable Developer Mode on the phone
npx expo run:android --device # Android
# β¦or build a dev client in the cloud via EAS (no local Xcode needed):
# npx eas build --profile development --platform ios # or android
yarn start # Metro dev server (once the dev build is installed)
```
**Configure `client/.env.local`:**
```bash
# Backend URL β defaults to http://localhost:8000 (simulator + adb-reverse).
# On a physical device, set your dev machine's LAN IP (find it with `ipconfig getifaddr en0`).
# The IP below is only an example β replace it with YOUR Mac's IP:
EXPO_PUBLIC_API_BASE=http://192.123.1.12:8000
# Firebase (free Spark plan) β only needed for cloud backup/restore.
# The app is fully usable signed-out; auth gates backup only.
EXPO_PUBLIC_FIREBASE_API_KEY=...
EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
EXPO_PUBLIC_FIREBASE_PROJECT_ID=your-project
EXPO_PUBLIC_FIREBASE_APP_ID=...
```
### 3. (Optional) Enable cloud backup
Create a Firebase project (free Spark plan) β enable **Authentication βΈ Email/Password** β create a **Firestore Database** β paste the Web-app config into `client/.env.local` β publish the rules in `client/firebase/firestore.rules` (Firebase console βΈ Firestore βΈ Rules). No paid plan or Storage bucket required β backup is metadata-only.
## π§ͺ Testing
```bash
cd client && yarn jest --forceExit # 89 tests (14 suites)
cd server && uv run pytest # backend tool + endpoint tests
```
## π§° Tech stack
| Layer | Tech |
|---|---|
| **Agent backend** | Python 3.14, FastAPI, **Google ADK 2.3**, **Gemini 2.5** (vision + chat), uv |
| **Client** | React Native 0.85 (Expo SDK 56), Expo Router, NativeWind (Tailwind v3), WatermelonDB (SQLite) |
| **Cloud / auth** | Firebase Auth + Firestore (free Spark plan) |
| **Data sources** | Open-Meteo (weather, no key) |
| **Tooling** | uv (server); yarn + ESLint + Prettier + Husky + Jest (client) |
## π Repo structure
```
frondly-world/
βββ server/ # FastAPI + Google ADK plantcare agent + Gemini vision (stateless)
βββ client/ # Expo / React Native app β Expo Router + NativeWind + WatermelonDB
βββ docs/ # Design specs, per-feature specs, and the project ROADMAP
βββ README.md
```
Feature-by-feature status and the full design history live in [`docs/ROADMAP.md`](docs/ROADMAP.md).
## π Notes
- **No secrets in the repo** β `GEMINI_API_KEY` and Firebase keys are read from gitignored `.env` / `.env.local`; only `.env.example` is committed.
- **Deployment is optional** for judging; this repo + the above setup instructions is the reproducible path. The backend is stateless and container-friendly if you do deploy it.
## πΊοΈ Next milestones
Frondly is feature-complete for the capstone; these are the deliberate next steps to take it from "portfolio project" to "daily driver."
### π Sync & data
- **Live multi-device sync** β today's cloud feature is *manual* backup/restore (whole-garden replace). True sync means WatermelonDB pull/push endpoints on a stateful backend, with conflict resolution and background push β a real architectural shift from today's stateless server.
- **Auto-sync** β push changes on write (debounced) and pull on app-foreground, so a new phone or reinstall just works with no "restore" button.
- **Cloud photo backup** β photos are device-local today (metadata-only backup). Add content-hash dedup + a blob store (e.g. paid Firebase Storage or an S3-compatible bucket) so images survive a reinstall.
- **Backup history** β keep the last _N_ snapshots instead of overwriting one, enabling point-in-time restore.
### π Auth & security
- **OAuth / Google & Apple sign-in** β one-tap onboarding beyond email/password.
- **Password reset + email verification** β table stakes for a real account system.
- **Restore cleanup** β clear the temp/cache download dir after re-persisting photos into durable storage.
### π Engagement
- **Watering reminders** β local/push notifications driven by the per-plant schedule ("Monstera is due today").
- **Diagnosis persistence** β the diagnose thread is ephemeral (`useState`); persist conversations so follow-ups survive leaving the screen.
### π’ Platform & deploy
- **Production EAS build** β an EAS profile that bakes a production `EXPO_PUBLIC_API_BASE`; publish to TestFlight / Play internal testing.
- **Deploy the backend** β containerize the stateless FastAPI service to Cloud Run (or similar) with reproducible deploy docs.
- **EAS project ownership** β settle which Expo account owns builds so non-Mac teammates can produce iOS builds.