Kino is a spend management workspace for people who want their financial picture to be both accountable and human. Money has two faces: the objective one—balances, inflows and outflows, categories, sync with banks, and the math that has to add up—and the subjective one—what a purchase meant, which goals mattered this month, how tradeoffs felt, and how your priorities evolve. Most tools optimize for the first and treat the second as an afterthought. Kino is built to hold both without collapsing them into each other.
The app is organized around seeing finance over time. Trends, comparisons, and long-running context matter as much as individual transactions: you’re not only asking “what did I spend?” but “how is my spending moving—relative to income, obligations, and the story I’m trying to tell with my money?” Charts, reports, and time-based views are first-class so patterns surface before they become surprises.
Under the hood it is a modern web application (Next.js + Supabase) with wallets, transactions, labels, bills, optional bank linking, and automation—see below for setup and architecture.
- Features
- Tech stack
- Prerequisites
- Getting started
- Environment variables
- Local Supabase
- Scripts
- Project structure
- Database & migrations
- API routes & automation
- Testing
- CI/CD
- Deployment
- Security
- Contributing
- Authentication — Email/password via Supabase Auth; protected
/approutes; sign-in, sign-up, password reset. - Wallets & workspaces — Multi-wallet layout, workspace membership, roles, and shared configuration.
- Transactions — Listing, filtering, tags, categories, labels, transfers, templates, and recurring patterns.
- Plaid — Link accounts, sync transactions, encrypted token storage, and server-side Plaid APIs.
- Bills & cashflow — Bills, recurrent bills, reminders (SMS via Twilio when configured), and reporting views.
- Visualization & analysis — Infographics, forecasts, currency conversions, and views designed to read money across time.
- Google Calendar — Optional visualization of time spent per calendar (OAuth-related env vars).
- AI assistant — Finance chat API route backed by OpenAI when
OPENAI_API_KEYis set. - Settings — Categories, tags, labels, views, assets, integrations, preferences, members, and more.
For engineering conventions (imports, lint, PR expectations), see AGENTS.md.
| Area | Choice |
|---|---|
| Framework | Next.js 16 (App Router), React 18 |
| Language | TypeScript (strict) |
| UI | Tailwind CSS 4, Radix, shadcn-style components |
| Data & auth | Supabase (@supabase/ssr, @supabase/supabase-js) |
| Forms & validation | react-hook-form, Zod |
| Server state / cache | TanStack Query (+ persistence helpers) |
| Charts & tables | Recharts, TanStack Table, TanStack Virtual |
| Banking | Plaid (plaid, react-plaid-link) |
| Tests | Vitest (*.test.ts, __tests__/**/*.ts) |
Request interception uses Next’s proxy entry (proxy.ts) with the Supabase session helper in utils/supabase/middleware.ts.
- Node.js 20.x (aligned with
@types/nodein this repo) - npm (scripts in
package.jsonuse npm; the repo may also contain other lockfiles—pick one package manager and stay consistent) - Supabase CLI — for local stack and linked type generation (
supabaseon yourPATH) - Accounts/keys as needed: Supabase, Plaid (if using banking), OpenAI (if using finance chat), Twilio (if using bill SMS), Google Cloud (if using Calendar), and a currency API token if you enable conversion fetches
-
Clone the repository and install dependencies:
npm install
-
Configure environment — Copy the example file and fill in values:
cp .env.example .env.local
.env.exampledocuments the minimum; the Environment variables section lists additional variables used across the app. -
Run the dev server:
npm run dev
Open http://localhost:3000. Unauthenticated users hitting
/are redirected to sign-in; authenticated users are redirected toward/app(see middleware helper above). -
Verify — Run lint (and tests if you touch tested modules):
npm run lint npm run test:run
Store secrets in .env.local (gitignored). Never commit real keys.
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_SUPABASE_URL |
Supabase project URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Supabase anonymous (public) key |
| Variable | Purpose |
|---|---|
SUPABASE_SERVICE_ROLE_KEY |
Service role key for trusted server scripts and admin Supabase client usage |
| Variable | Purpose |
|---|---|
PLAID_CLIENT_ID |
Plaid application client ID |
PLAID_SECRET |
Plaid secret |
PLAID_ENV |
e.g. sandbox or production (see utils/plaid/server.ts) |
PLAID_TOKEN_ENCRYPTION_KEY |
Key used to encrypt stored Plaid tokens (32-byte value; base64 recommended in .env.example) |
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_GOOGLE_CLIENT_ID |
OAuth client ID |
NEXT_PUBLIC_GOOGLE_API_KEY |
API key for client-side Calendar usage |
| Variable | Purpose |
|---|---|
CURRENCY_API_TOKEN |
Token for the currency conversion provider used in utils/fetch-conversions-server.ts |
| Variable | Purpose |
|---|---|
OPENAI_API_KEY |
API key for app/api/ai/finance-chat/route.ts |
OPENAI_FINANCE_MODEL or OPENAI_MODEL |
Model override (defaults exist in code) |
These wire default transfer behavior to specific category rows:
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_TRANSFER_CATEGORY_IN_ID |
“Transfer in” category |
NEXT_PUBLIC_TRANSFER_CATEGORY_OUT_ID |
“Transfer out” category |
NEXT_PUBLIC_TRANSFER_CATEGORY_BETWEEN_ID |
“Between wallets” category |
TRANSFER_CATEGORY_BETWEEN_ID |
Server-side companion used in some actions (see actions/link-transfers.ts) |
| Variable | Purpose |
|---|---|
CRON_SECRET |
Bearer token expected by cron route handlers |
TWILIO_ACCOUNT_SID |
Twilio account SID (bill reminders) |
TWILIO_AUTH_TOKEN |
Twilio auth token |
TWILIO_FROM_NUMBER |
Sender number |
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_APP_VERSION |
Cache-busting / versioning (see utils/query-cache.ts) |
VERCEL_URL |
Set automatically on Vercel; used for metadata base URL in app/layout.tsx |
Start the local stack when you need Postgres, Auth, or to run migrations against a dev instance:
npm run supabase:start
# ...
npm run supabase:stopAfter schema changes, add SQL under supabase/migrations/ and regenerate TypeScript types for the client:
npm run supabase:typesOutput is written to utils/supabase/database.types.ts (per package.json script; requires a linked Supabase project).
| Script | Description |
|---|---|
npm run dev |
Next.js development server (default port 3000) |
npm run build |
Production build |
npm run start |
Serve production build |
npm run lint |
ESLint (Next + import sorting + unused imports) |
npm run test |
Vitest watch mode |
npm run test:run |
Vitest single run (CI-friendly) |
npm run supabase:start / supabase:stop |
Local Supabase lifecycle |
npm run supabase:types |
Generate DB types into utils/supabase/database.types.ts |
npm run reconcile-bills |
scripts/reconcile-bills.ts (needs service role env) |
npm run verify-csv-balance |
scripts/verify-csv-balance.ts |
Root-level helper scripts may live beside package.json; see AGENTS.md for conventions.
app/ # App Router: pages, layouts, route handlers, assets
components/ # Shared UI (shadcn/Radix/Tailwind)
actions/ # Server actions
hooks/ # React hooks
contexts/ # React contexts
lib/ # Shared libraries
utils/ # Utilities (Supabase clients, Plaid, conversions, etc.)
supabase/ # Config, migrations, local settings
public/ # Static assets
docs/ # Additional documentation
scripts/ # Maintenance scripts (ts-node)
Path alias @/ maps to the repository root (see tsconfig.json and Vitest config).
- SQL migrations live in
supabase/migrations/and are applied in order. - RLS policies, views, RPCs, and wallet/transaction/bill models evolve through these files.
- After pulling migration changes, apply them to your environment (local
supabase dbworkflow or hosted project) and regenerate types so TypeScript stays in sync.
Notable App Router handlers under app/api/:
/api/plaid/*— Link token, connect, exchange, transactions, preview, connection management/api/cron/*— Scheduled jobs (daily-tasks,monthly-backfill,bill-reminders) — protect withCRON_SECRET/api/ai/finance-chat— OpenAI-backed chat/api/currency-conversions,/api/forecast,/api/run-recurring— Supporting finance features
Configure your scheduler (e.g. Vercel Cron) to call these endpoints with the correct Authorization: Bearer <CRON_SECRET> header where implemented.
- Runner: Vitest (
vitest.config.mjs) - Patterns:
**/*.test.ts,**/__tests__/**/*.ts - Environment: Node (no DOM by default)
When adding tests, prefer colocated *.test.ts files and mock Supabase or network I/O; document required env vars in the test description.
.github/workflows/production.yml runs on pushes to main (and manual dispatch):
- Checks out the repo
- Uses Supabase CLI with repository secrets:
SUPABASE_ACCESS_TOKEN,PRODUCTION_DB_PASSWORD,PRODUCTION_PROJECT_ID - Runs
supabase link,supabase db push, andsupabase functions deploy
Ensure GitHub Actions secrets are configured before relying on this pipeline.
- Vercel is the natural host for Next.js; set the same environment variables as production (see Environment variables).
- Supabase hosts Postgres, Auth, and related services; keep anon and service role keys scoped correctly (service role only on the server).
- This repo’s
next.config.jsenables the React Compiler and treatsarimaas an external server package (forecasting).
- Do not commit
.env,.env.local, or*.pem(see.gitignore). - Service role keys bypass RLS—use only in server code and scripts, never in client bundles.
- Cron routes should reject requests without the shared secret.
- Rotate keys if they are ever exposed; review Supabase RLS when adding tables or views.
- Follow
AGENTS.mdfor structure, linting, imports, and PR expectations. - Use short, present-tense commit messages and keep changes focused.
- Call out migrations, env var additions, and
npm run supabase:typesupdates in PR descriptions.
Kino builds on common Next.js + Supabase patterns (SSR client, cookie-based sessions). Useful references: Supabase + Next.js, Next.js App Router.