Quiet AI teammates for the parts of school you keep forgetting.
UniPilot is a college-student productivity platform built around a handful of focused AI agents. It pulls your real academic life in from Canvas, Ed Discussion, and Google Calendar, then puts a few calm, single-purpose agents on top:
- The Briefer — texts a short "here's your day" to your phone every morning (via Telegram).
- The Scheduler — turn plain English ("office hours Tuesday 3pm") into calendar events; you confirm before anything is written.
- The Messenger — drafts emails to professors when you're sick or slammed. It never sends without you.
- Syllabus Reader — drop in a syllabus (PDF/DOCX/text) and it extracts grading, key dates, and policies, then lets you chat about the class.
Plus supporting engines: a grade evaluator, an assignment planner, and a RAG course tutor that answers questions from your own uploaded materials.
Built for a hackathon. Everything degrades gracefully — the app runs and is usable even with no API keys configured (agents fall back to deterministic templates and local heuristics).
| Layer | Choice |
|---|---|
| Frontend | Vanilla JS (no framework, no build step), a tiny hash router, hand-written CSS. Served as static files from public/. |
| Backend | Node.js + Express (TypeScript, ESM), run with tsx in dev and compiled with tsc for prod. |
| Database | SQLite via Node's built-in node:sqlite (no native deps / no compile toolchain). Schema in server/src/db/schema.sql. |
| LLM | Anthropic Claude (@anthropic-ai/sdk) for brief wording, message interpretation, email/answer drafting, and syllabus extraction. |
| Integrations | Canvas LMS (REST + token), Google Calendar + Gmail (OAuth via googleapis), Ed Discussion, Telegram Bot API. |
| Parsing / utils | pdf-parse + mammoth (syllabus/doc text), rrule (recurring events), dayjs (time), zod (request validation), multer (uploads), nanoid (ids). |
| Security | Sensitive fields (tokens, grades) encrypted at rest with AES-256-GCM (server/src/crypto.ts). |
The frontend and backend are served from a single origin (Express serves public/ and falls back to the SPA shell), so there's no CORS/proxy setup needed in dev or prod.
- Node.js >= 22.5 — required, because the backend uses the built-in
node:sqlitemodule. Check withnode -v. - npm (ships with Node).
- Optional API keys (see Configuration) — only needed to light up the LLM and live integrations.
# 1. Clone, then install root + server dependencies
npm run install:all
# 2. Set up environment (optional, but recommended)
cp .env.example .env
# open .env and fill in any keys you have (all optional — see below)
# 3. Run the dev server (tsx watch — restarts on changes)
npm run devThen open http://localhost:4000.
On first boot the server creates data.db, applies the schema, generates a local encryption key (.enc-key) if one isn't set, and — if Canvas credentials are present — imports your courses/assignments once.
Just run npm run dev and open the app. You can browse Today, Classes, Deploy, and the agent pages with seeded/fallback data. To populate realistic data, run the seed script:
npm run seed- Go to Deploy and click the Syllabus Reader card → its page opens.
- Choose a file (PDF, DOCX, HTML, or
.txt/.md) or expand "or paste the syllabus text." - Click Read syllabus — you'll get a structured summary (grading breakdown, key dates, policies, office hours, materials).
- Use the chat box to ask anything about the class — answers are grounded strictly in that syllabus. (Chat requires
ANTHROPIC_API_KEY; the summary works without it via a regex fallback.)
Run from the repo root:
| Command | What it does |
|---|---|
npm run install:all |
Install root + server/ dependencies. |
npm run dev |
Start the backend in watch mode (tsx watch) and serve the frontend at :4000. |
npm run build |
Type-check + compile the server to server/dist/ (copies the SQL schema). |
npm run start |
Run the compiled production build (node dist/index.js). |
npm run seed |
Seed the SQLite DB with sample courses/assignments/data. |
Copy .env.example → .env. Every value is optional; missing keys just disable that capability.
| Variable | Purpose |
|---|---|
ANTHROPIC_API_KEY |
The "brain." Enables LLM brief wording, message interpretation, email/answer drafting, and syllabus extraction. Without it, agents use deterministic fallbacks. |
ANTHROPIC_MODEL |
Claude model name (default claude-opus-4-8). |
TELEGRAM_BOT_TOKEN |
The Briefer's channel. Create a bot with @BotFather, paste the token, then message your bot once to link it. |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET |
Google OAuth for the Scheduler (write to Google Calendar) and Gmail drafts. Enable the Calendar API, create a Web OAuth client, set redirect URI to http://localhost:4000/api/auth/google/callback, then click Connect in the app. |
GOOGLE_REDIRECT_URI |
Override the OAuth callback (defaults to the localhost URI above). |
CANVAS_BASE_URL / CANVAS_TOKEN |
Server-wide Canvas LMS access (alternatively, connect per-user in-app on the You page). |
ED_API_TOKEN |
Ed Discussion announcements/threads. |
PORT |
Backend port (default 4000). |
ENCRYPTION_KEY |
32-byte hex key for encrypting tokens/grades at rest. Generate: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))". If unset, a key is generated and persisted to .enc-key on first run. |
DB_PATH |
Override the SQLite file location (default server/../data.db). |
RMP_ENABLED / REDDIT_TOKEN |
Best-effort enrichment sources (RateMyProfessors / Reddit), off by default. |
ai-hackathon-2026/
├── public/ # Vanilla JS frontend (no build step)
│ ├── index.html # App shell
│ ├── app.js # Hash router + all views
│ ├── api.js # Backend client + bootstrap/data mapping
│ ├── data.js # Agent config + live state containers
│ ├── styles.css # Hand-written styles
│ └── logo.svg / favicon.svg
├── server/
│ └── src/
│ ├── index.ts # Express app, static serving, boot tasks
│ ├── http/ # API routes (routes.ts) + request context
│ ├── agents/ # brief, calendar, email, faq, grade, planner, syllabus
│ ├── integrations/ # canvas, google, ed, telegram, RateMyProf/Reddit
│ ├── db/ # SQLite adapter, repo layer, schema.sql, seed
│ ├── llm/ # Anthropic client wrapper
│ ├── domain/ # time/date helpers
│ ├── crypto.ts # AES-256-GCM field encryption
│ ├── scheduler.ts # daily-brief scheduler
│ └── telegram-router.ts
├── .env.example # Copy to .env
└── package.json # Root scripts (proxy to server/)
flowchart TB
subgraph Browser["Browser — Vanilla JS SPA (public/)"]
UI["index.html · app.js (hash router + views)"]
APIJS["api.js — fetch client + bootstrap"]
UI --> APIJS
end
subgraph Server["Node.js + Express (server/) — single origin :4000"]
STATIC["Static serving + SPA fallback"]
ROUTES["http/routes.ts — /api/* (zod-validated)"]
subgraph Agents["Agents (engines)"]
BRIEF["Briefer"]
CAL["Scheduler / calendar"]
EMAIL["Messenger / email (draft-only)"]
SYL["Syllabus Reader"]
FAQ["RAG tutor / faq"]
GRADE["Grade eval"]
PLAN["Planner"]
end
subgraph Bg["Background"]
SCHED["scheduler.ts — daily brief"]
TG["telegram-router.ts"]
end
CRYPTO["crypto.ts — AES-256-GCM at rest"]
REPO["db/repo.ts"]
DB[("SQLite — data.db<br/>node:sqlite")]
ROUTES --> Agents
SCHED --> BRIEF
TG --> BRIEF
TG --> EMAIL
Agents --> REPO
REPO --> CRYPTO --> DB
end
subgraph LLM["LLM"]
CLAUDE["Anthropic Claude<br/>@anthropic-ai/sdk"]
end
subgraph Ext["External integrations (all optional)"]
CANVAS["Canvas LMS"]
GOOGLE["Google Calendar + Gmail"]
ED["Ed Discussion"]
TELEGRAM["Telegram Bot API"]
end
APIJS -- "HTTP /api/*" --> STATIC
STATIC --> ROUTES
Agents -. "wording / extraction<br/>(falls back to templates)" .-> CLAUDE
CAL <--> GOOGLE
EMAIL <--> GOOGLE
ROUTES <--> CANVAS
FAQ <--> ED
TG <--> TELEGRAM
Request flow (e.g. add a calendar event):
sequenceDiagram
participant U as User (Browser)
participant S as Express /api
participant A as Scheduler agent
participant L as Claude
participant G as Google Calendar
U->>S: POST /agents/calendar/parse {text}
S->>A: parseEvent(text)
A->>L: interpret "office hours Tue 3pm"
L-->>A: structured event (+conflicts)
A-->>U: preview (no write yet)
U->>S: POST /agents/calendar/commit {event}
S->>G: create event (only write)
G-->>U: confirmed
The preview → confirm → commit split means side-effecting actions (calendar writes, email send) never happen until you approve them.
- Single user, local-first. Data lives in a local SQLite file (
data.db). Sensitive fields are encrypted at rest. - Preview → confirm → commit. Side-effecting actions (calendar writes, email "send", deletes) are split so the server never auto-sends email or posts externally. Email is draft-only unless you explicitly send.
- Graceful degradation. Each integration and the LLM are optional; the app detects what's configured and falls back to templates/heuristics otherwise.
- Background agents. On boot, the server starts the Telegram channel (brief push + commands) and the daily-brief scheduler.
All routes are under /api. A few highlights:
GET /api/health— liveness check.GET /api/me,GET /api/status,GET /api/courses,GET /api/eventsGET /api/agents/brief— the morning brief.POST /api/agents/calendar/parse→POST /api/agents/calendar/commit— natural-language → event (with conflict detection).POST /api/agents/email/draft→POST /api/agents/email/save-draft/send— draft-only email writer.POST /api/agents/syllabus/parse→POST /api/agents/syllabus/chat/commit— Syllabus Reader.POST /api/courses/:id/docs+POST /api/courses/:id/chat— upload materials and chat with the RAG tutor.POST /api/integrations/canvas/connect/import,GET /api/auth/google— integrations.
node:sqliteerrors / module not found → you're on Node < 22.5. Upgrade Node.- Agents give templated, generic output → no
ANTHROPIC_API_KEYset. Add one to.envand restart. - No courses show up → connect Canvas on the You page, or set
CANVAS_BASE_URL/CANVAS_TOKEN, or runnpm run seed. - Frontend changes not appearing → assets are cache-busted via
?v=Ninindex.html; a normal reload should suffice (the server sendsno-cache).