5.2 KiB
Fiscal Clone 3.0
Turbopack-first rebuild of a fiscal.ai-style terminal with Vercel AI SDK integration.
Stack
- Next.js 16 App Router
- Bun runtime/tooling
- Elysia route layer mounted in Next Route Handlers
- Turbopack for
devandbuild - Better Auth (email/password + magic link)
- Drizzle ORM (SQLite) + Better Auth Drizzle adapter
- Internal API routes via Elysia app module (
lib/server/api/app.ts) - Eden Treaty for type-safe frontend API calls
- Workflow DevKit Postgres World for background task execution durability
- SQLite-backed app domain storage (watchlist, holdings, filings, task projection, insights)
- Vercel AI SDK (
ai) with dual-model routing:- Ollama (
@ai-sdk/openai) for lightweight filing extraction/parsing - Zhipu (
zhipu-ai-provider) for heavyweight narrative reports (https://api.z.ai/api/coding/paas/v4)
- Ollama (
Run locally
bun install
bun run db:generate
bun run db:migrate
bun run dev
Open http://localhost:3000.
The default database path is data/fiscal.sqlite via DATABASE_URL=file:data/fiscal.sqlite.
Production build
bun run db:migrate
bun run build
bun run start
Docker deployment
cp .env.example .env
docker compose up --build -d
For local Docker, host port mapping comes from docker-compose.override.yml (default http://localhost:3000 via APP_PORT).
The app calls Zhipu directly via AI SDK for heavy reports and calls Ollama for lightweight filing extraction.
When running in Docker and Ollama runs on the host, set OLLAMA_BASE_URL=http://host.docker.internal:11434.
Zhipu always targets the Coding API endpoint (https://api.z.ai/api/coding/paas/v4).
On container startup, the app applies Drizzle migrations automatically before launching Next.js.
The app stores SQLite data in Docker volume fiscal_sqlite_data (mounted to /app/data) and workflow world data in Postgres volume workflow_postgres_data.
Container startup runs:
workflow-postgres-setup(idempotent Workflow world bootstrap)- Drizzle migrations for SQLite app tables
- Next.js server boot
Docker images use Bun (oven/bun:1.3.5-alpine) for build and runtime.
Coolify deployment
This compose setup is compatible with Coolify as-is (it uses named Docker volumes, not host bind mounts).
Required environment variables in Coolify:
DATABASE_URL=file:/app/data/fiscal.sqliteBETTER_AUTH_SECRET=<long-random-secret>BETTER_AUTH_BASE_URL=https://fiscal.b11studio.xyzBETTER_AUTH_TRUSTED_ORIGINS=https://fiscal.b11studio.xyzWORKFLOW_TARGET_WORLD=@workflow/world-postgresWORKFLOW_POSTGRES_URL=postgres://workflow:workflow@workflow-postgres:5432/workflow- Optional:
WORKFLOW_POSTGRES_WORKER_CONCURRENCY=10 - Optional:
WORKFLOW_POSTGRES_JOB_PREFIX=fiscal_
Operational constraints for Coolify:
- Keep this service to a single instance/replica. SQLite is file-based and not appropriate for multi-replica shared-write deployments.
- Ensure both named volumes are persisted (
fiscal_sqlite_data,workflow_postgres_data). - Keep
WORKFLOW_POSTGRES_URLexplicit so Workflow does not fall back toDATABASE_URL(SQLite). - The app
/api/healthprobes Workflow backend connectivity and returns non-200 when Workflow world is unavailable.
Emergency rollback path:
- Set
WORKFLOW_TARGET_WORLD=local - Remove/disable
WORKFLOW_POSTGRES_URL - Redeploy
Environment
Use root .env or root .env.local:
# leave blank for same-origin API
NEXT_PUBLIC_API_URL=
DATABASE_URL=file:data/fiscal.sqlite
BETTER_AUTH_SECRET=replace-with-a-long-random-secret
BETTER_AUTH_BASE_URL=https://fiscal.b11studio.xyz
BETTER_AUTH_TRUSTED_ORIGINS=https://fiscal.b11studio.xyz
ZHIPU_API_KEY=
ZHIPU_MODEL=glm-4.7-flashx
# optional generation tuning
AI_TEMPERATURE=0.2
OLLAMA_BASE_URL=http://127.0.0.1:11434
OLLAMA_MODEL=qwen3:8b
OLLAMA_API_KEY=ollama
SEC_USER_AGENT=Fiscal Clone <support@fiscal.local>
WORKFLOW_TARGET_WORLD=@workflow/world-postgres
WORKFLOW_POSTGRES_URL=postgres://workflow:workflow@workflow-postgres:5432/workflow
WORKFLOW_POSTGRES_WORKER_CONCURRENCY=10
WORKFLOW_POSTGRES_JOB_PREFIX=fiscal_
# Optional local-world fallback
WORKFLOW_LOCAL_DATA_DIR=.workflow-data
WORKFLOW_LOCAL_QUEUE_CONCURRENCY=100
If ZHIPU_API_KEY is unset, the app uses local fallback analysis so task workflows still run.
If Ollama is unavailable, filing extraction falls back to deterministic metadata-based extraction and still proceeds to heavy report generation.
ZHIPU_BASE_URL is deprecated and ignored; runtime always uses https://api.z.ai/api/coding/paas/v4.
API surface
All endpoints below are defined in Elysia at lib/server/api/app.ts and exposed via app/api/[[...slugs]]/route.ts.
ALL /api/auth/*(Better Auth handler)GET /api/healthGET /api/meGET|POST /api/watchlistDELETE /api/watchlist/:idGET|POST /api/portfolio/holdingsPATCH|DELETE /api/portfolio/holdings/:idGET /api/portfolio/summaryPOST /api/portfolio/refresh-pricesPOST /api/portfolio/insights/generateGET /api/portfolio/insights/latestGET /api/financials/companyGET /api/filingsPOST /api/filings/syncPOST /api/filings/:accessionNumber/analyzeGET /api/tasksGET /api/tasks/:taskId