Fiscal Clone 3.0
Turbopack-first rebuild of a fiscal.ai-style terminal with OpenClaw 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 Local World for background task execution
- SQLite-backed domain storage (watchlist, holdings, filings, tasks, insights)
- OpenClaw/ZeroClaw analysis via OpenAI-compatible chat endpoint
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, and http://localhost:4000 for OpenClaw via OPENCLAW_PORT).
OpenClaw is included as a Compose service (openclaw) and is built by default from OPENCLAW_BUILD_CONTEXT (set to Francy51/coolify_ZeroClaw in .env.example).
If that Gitea repo is private, set OPENCLAW_BUILD_CONTEXT with embedded credentials (https://<username>:<token>@.../coolify_ZeroClaw.git) or point OPENCLAW_IMAGE to a prebuilt image you can pull.
The app container defaults to OPENCLAW_BASE_URL=http://openclaw:4000 unless you explicitly set a different OPENCLAW_BASE_URL.
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 local data in fiscal_workflow_data (mounted to /app/.workflow-data).
Workflow Local World uses filesystem state plus an in-memory queue. On container restart, queued in-flight jobs may be lost.
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=local- Optional:
WORKFLOW_LOCAL_DATA_DIR=/app/.workflow-data
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 the two named volumes are persisted (
fiscal_sqlite_data,fiscal_workflow_data). - Workflow Local queueing is in-memory; in-flight queued jobs may be lost on restarts.
- Docker build forces
WORKFLOW_TARGET_WORLD=localto avoid stale Coolify build args referencing@workflow/world-postgres. - Runtime Compose config also pins
WORKFLOW_TARGET_WORLD=localfor the same reason.
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
OPENCLAW_BASE_URL=
OPENCLAW_API_KEY=
OPENCLAW_MODEL=zeroclaw
OPENCLAW_AUTH_MODE=none
OPENCLAW_PORT=4000
OPENCLAW_IMAGE=coolify-zeroclaw:local
OPENCLAW_BUILD_CONTEXT=https://gitea-hs848cs8kgs840o8c8s8cwkk.b11studio.xyz/Francy51/coolify_ZeroClaw.git
OPENCLAW_DOCKERFILE=Dockerfile
# for OPENCLAW_AUTH_MODE=basic
# OPENCLAW_BASIC_AUTH_USERNAME=your_nginx_user
# OPENCLAW_BASIC_AUTH_PASSWORD=your_nginx_password
# optional: forward API key in a custom header when using basic/none auth
# OPENCLAW_API_KEY_HEADER=X-OpenClaw-API-Key
SEC_USER_AGENT=Fiscal Clone <support@fiscal.local>
WORKFLOW_TARGET_WORLD=local
WORKFLOW_LOCAL_DATA_DIR=.workflow-data
WORKFLOW_LOCAL_QUEUE_CONCURRENCY=100
If OpenClaw is unset or invalidly configured, the app uses local fallback analysis so task workflows still run.
For OpenClaw behind Nginx Basic Auth, use:
OPENCLAW_BASE_URL=https://your-nginx-host
OPENCLAW_AUTH_MODE=basic
OPENCLAW_BASIC_AUTH_USERNAME=your_nginx_user
OPENCLAW_BASIC_AUTH_PASSWORD=your_nginx_password
# optional if upstream still needs an API key in a non-Authorization header
OPENCLAW_API_KEY=your_key
OPENCLAW_API_KEY_HEADER=X-OpenClaw-API-Key
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/filingsPOST /api/filings/syncPOST /api/filings/:accessionNumber/analyzeGET /api/tasksGET /api/tasks/:taskId