Tamias is a Bun workspaces monorepo for the product workspace, API, Cloudflare async worker runtime, public website, integrations, and shared packages behind invoicing, banking, inbox capture, time tracking, reporting, AI workflows, and UK compliance support.
Naming note The public brand is
Tamias; package and code identifiers use lowercasetamias.
| Surface | Directory | Local URL | What it does |
|---|---|---|---|
| Dashboard | dashboard |
http://localhost:3001 |
Main authenticated app, public invoice/customer/report links, auth, SSR, client providers, lightweight public homepage |
| API | api |
http://localhost:3001 |
Hono API routes bundled into the main Cloudflare Worker with tRPC, REST, OpenAPI, MCP, webhooks, and health checks |
| Worker | worker |
Cloudflare Workers | Queue, workflow, recurring schedule, notification, and document/PDF modules split across main and documents Workers |
| Website | dashboard |
https://tamias.xyz |
Marketing site, integrations catalog, comparison pages, and MCP install guides served from the dashboard deployment |
The current codebase covers these main product surfaces:
- Overview widgets and business health metrics
- Transactions, bank connections, CSV/manual import, categorization, review, and export
- Inbox capture from email and messaging sources with document matching
- Invoicing, products, recurring invoices, public invoice links, and Stripe-based payments
- Time tracking with projects, timers, and exports
- Customers, customer portal access, and customer-level analytics
- Vault/files and file-linked workflows
- Apps/integrations, API keys, developer settings, and OAuth applications
- AI chat, suggested actions, weekly insights, and MCP-based developer/agent access
- UK compliance with VAT filing, Companies House XML annual-accounts filing, year-end packs and corporation-tax prep with HMRC CT Transaction Engine support, and payroll import/export workflows
flowchart LR
Browser["Browser"] --> Dashboard["Dashboard deployment<br/>Dashboard app + API + public site"]
AIClient["AI / MCP Client"] --> MCP["API MCP endpoint"]
Dashboard -->|SSR local queries| QueryLayer["packages/app-data query layer"]
Dashboard -->|client + server API calls| TRPC["API tRPC / REST"]
QueryLayer --> D1["Cloudflare D1<br/>durable app data + identity"]
TRPC --> D1
TRPC --> R2["Cloudflare R2<br/>file storage"]
TRPC --> AsyncRuntime["Cloudflare async transport<br/>main Worker runtime"]
TRPC --> DocumentsWorker["Documents Worker<br/>PDF render + statement extraction"]
TRPC --> Providers["External providers"]
MCP --> QueryLayer
AsyncRuntime --> Worker["Main Worker<br/>capture + ledger queues, workflows, schedules"]
AsyncRuntime --> DocumentsQueue["Documents queue"]
DocumentsQueue --> DocumentsWorker
Worker --> D1
Worker --> R2
DocumentsWorker --> D1
DocumentsWorker --> R2
Worker --> Providers
DocumentsWorker --> Providers
- Requests into the dashboard deployment run request middleware from
dashboard/src/start/start.ts(createStart+createMiddleware). Host-aware routing and authenticated route gating happen there. - The dashboard root layout mounts first-party auth, tRPC, i18n, theming, analytics, and shared client providers.
- The authenticated sidebar shell preloads the current user/team and mounts global chrome like the sidebar, header, timers, sheets, and export status.
- Hot server-rendered reads use route loaders backed by
dashboard/src/start/server/route-data/*(for exampledashboard.ts,transactions.ts), which call tRPC server helpers and the shared query layer instead of round-tripping through HTTP for every SSR request. - Client mutations and most interactive reads go through tRPC to
api. - The API exposes:
- tRPC for first-party app calls
- REST routes for files, invoices, customers, chat, insights, webhooks, transcription, and integrations
- OpenAPI + Scalar docs
- MCP tools/resources/prompts for agent clients
- Long-running work is enqueued through Cloudflare queues/workflows and processed by
worker. - Document processing, invoice PDF rendering, and PDF statement extraction run in the separate documents Worker (
worker/documents.wrangler.jsonc) viaDOCUMENTS_QUEUEand theDOCUMENTS_WORKERservice binding. - Durable app state is stored through Cloudflare D1 and file blobs live in R2; async status is coordinated through the shared app-data layer and Cloudflare worker runtime.
packages/app-datais the shared application data layer used by the dashboard, API, and worker.- Cloudflare D1 stores users, teams, app state, documents metadata, inbox, invoices, tracker data, links, widgets, insights state, tags, transaction metadata, and async records.
- Cloudflare R2 stores file blobs and generated artifacts.
- Cloudflare queues/workflows provide the async execution plane. Durable product state does not live in the worker runtime.
- The documents queue is a separate physical queue consumed only by the documents Worker; the main Worker can produce document jobs but does not consume them.
| Area | Main routes / entry points | Backing packages and services |
|---|---|---|
| Overview and widgets | /dashboard |
api/src/trpc/routers/widgets.ts, packages/insights |
| Transactions and banking | /transactions, /settings/accounts |
packages/banking, packages/import, packages/categories, transaction/banking routers, worker transaction processors |
| Inbox and document capture | /inbox, /inbox/settings |
packages/inbox, packages/documents, inbox/document workers, Gmail/Outlook/Slack/WhatsApp integrations |
| Invoicing and payments | /invoices, /invoices/products, public /i/<token> |
packages/invoice, invoice/payment routers, Stripe and Stripe Payments integrations |
| Time tracking | /tracker |
tracker project/entry routers, export flows, Raycast and MCP integrations |
| Customers and portal | /customers, public /p/<portalId> |
customer analytics, worker/src/customers, enrichment jobs |
| Vault and files | /vault, file download/proxy routes |
packages/storage, file routes, document processing |
| Reports and public links | /dashboard, public /r/<linkId> and /s/<shortId> |
reports routers, short links, report links in packages/app-data |
| AI assistant and insights | /chat/[id], insight notifications/audio |
api/src/ai, packages/insights, MCP server/tools, suggested actions |
| Compliance | /compliance, /compliance/vat, /compliance/settings, /compliance/year-end, /compliance/payroll |
packages/compliance, HMRC VAT integration, year-end packs, payroll runs, evidence/export bundles |
| Apps and developer tooling | /apps, /settings/developer |
packages/app-store, OAuth applications, API keys, MCP, public integrations catalog |
- Bun runtime and package manager
- Bun workspaces (
dashboard,api,worker,packages/*) - TypeScript across apps and packages
- ESLint and Prettier for linting and formatting (repo root)
- TanStack Start in
dashboard - React 19
- Vite build/dev pipeline
- Shared UI primitives in
packages/ui - TanStack Router, TanStack Query, and tRPC client
- First-party auth client integration
- Playwright smoke tests in repo-root
e2e/(against the dashboard build)
- Hono on Cloudflare Workers for the API surface
- tRPC for first-party app APIs
- OpenAPI via
@hono/zod-openapiand Scalar docs - Model Context Protocol server in
api/src/mcp - Cloudflare Queues, Workflows, Cron Triggers, Durable Objects, and Containers in
worker
- Cloudflare D1 for durable app data, identity, and async records
- Cloudflare R2 for files and generated artifacts
packages/app-dataas the shared app-data layerpackages/accounting,packages/banking,packages/compliance,packages/documents,packages/inbox,packages/invoice,packages/insights,packages/storage
- AI SDK with OpenAI, Google, and Mistral providers in different flows
- ElevenLabs for optional insight audio
- Exa and Plain in supporting flows
- TrueLayer for banking
- QuickBooks and Fortnox for accounting exports/sync
- Companies House OAuth, filing transaction, and public-register readiness integration groundwork
- Stripe and Polar for payments/billing
- Cloudflare Email Service, Slack, Gmail, Outlook, and WhatsApp integrations
These are the packages you will touch most often:
packages/accounting: accounting-provider adapters and export helperspackages/app-store: integration catalog, MCP client definitions, app metadatapackages/banking: bank-provider adapters and normalizationpackages/categories: transaction categories and tax-rate helperspackages/compliance: UK compliance domain logic and HMRC VAT helperspackages/app-data: shared app-data and domain query surfacepackages/documents: document loading, classification, embedding, extraction helperspackages/encryption: OAuth state encryption, file keys, shared crypto helperspackages/inbox: inbox connectors and provider integrationspackages/insights: metrics, summaries, audio, and insight content generationpackages/invoice: invoice rendering, templates, recurring logic, public invoice supportpackages/job-client: async client used by API/dashboard/worker to enqueue Cloudflare jobs, workflows, and recurring schedulespackages/notifications: email and in-app notification logicpackages/plans: billing plan metadatapackages/storage: R2-backed storage helperspackages/trpc: shared tRPC helpers and internal clientpackages/ui: shared UI components, icons, globals, animationspackages/utils: environment helpers and cross-cutting utilities
App-owned modules that are no longer shared packages:
api/src/health: dependency probes and readiness helpersdashboard/src/lib/telemetry: telemetry client/server wrappersworker/src/customers: customer enrichment pipeline
- Bun
1.3.x
Run bun install at the repository root only. Bun uses a hoisted layout: most dependencies live in the root node_modules, and individual workspaces may still show small local node_modules folders for scoped packages or tooling (for example Vite or Wrangler caches).
bun installCreate a single .env at the repository root (gitignored). Put every variable the app needs there: dashboard, API (bundled in the same Worker in production), worker-style jobs, and scripts. Vite loads this file via envDir at the repo root; root package.json backfill scripts use bun --env-file=.env.
Optional .env.local at the repo root is also loaded (after .env) by Vite and by scripts/lib/load-repo-env.ts for local overrides.
Wrangler without Bun: bun run preview:cf (in dashboard) runs Wrangler with bun --env-file=../.env and --config ../wrangler.jsonc, so secrets match the root .env. If you run wrangler dev yourself, point it at wrangler.jsonc in the repo root; Wrangler will read .dev.vars next to that file (same KEY=value format as .env). You can symlink .dev.vars to .env at the repo root.
If you still have old dashboard/.env.local, api/.env, or worker/.env files, merge their contents into the repo root .env and remove the duplicates.
Use one root .env with the blocks below (grouped by surface so you can see what each part of the stack needs). Values that appear in multiple blocks must match.
DASHBOARD_URL=http://localhost:3001
WEBSITE_URL=http://localhost:3000
API_URL=http://localhost:3001
INTERNAL_API_KEY=...
INVOICE_JWT_SECRET=...
FILE_KEY_SECRET=...ALLOWED_API_ORIGINS=http://localhost:3001
DASHBOARD_URL=http://localhost:3001
API_URL=http://localhost:3001
INTERNAL_API_KEY=...
INVOICE_JWT_SECRET=...
FILE_KEY_SECRET=...
TAMIAS_ENCRYPTION_KEY=<64-char hex string>
COMPANIES_HOUSE_CLIENT_ID=...
COMPANIES_HOUSE_CLIENT_SECRET=...
COMPANIES_HOUSE_OAUTH_REDIRECT_URL=http://localhost:3001/apps/companies-house/oauth-callback
COMPANIES_HOUSE_ENVIRONMENT=sandbox
COMPANIES_HOUSE_API_KEY=...
COMPANIES_HOUSE_XML_ENVIRONMENT=test
COMPANIES_HOUSE_XML_PRESENTER_ID=...
COMPANIES_HOUSE_XML_PRESENTER_AUTHENTICATION_CODE=...
# Optional in test mode; defaults to OPSLDG
COMPANIES_HOUSE_XML_PACKAGE_REFERENCE=...
HMRC_CT_ENVIRONMENT=test
HMRC_CT_SENDER_ID=...
HMRC_CT_SENDER_PASSWORD=...
HMRC_CT_VENDOR_ID=...
HMRC_CT_TEST_UTR=...
HMRC_CT_PRODUCT_NAME=Tamias
HMRC_CT_PRODUCT_VERSION=0.1.0INTERNAL_API_KEY,INVOICE_JWT_SECRET, andFILE_KEY_SECRETmust match everywhere they are used.- Public site features run inside
dashboard; keep their variables in the same root.envalongside API/worker keys. HMRC_CT_ENVIRONMENTdefaults totest. Keep it there in deployed environments until you intentionally want live HMRC CT filing.- In
test, CT submissions useHMRC_CT_TEST_UTRwhen present. Inproduction, the filing profile UTR is required. - Companies House annual accounts filing uses the XML gateway presenter runtime on the Cloudflare Worker; it does not use the OAuth app credentials.
- In the main Cloudflare Worker, capture/ledger async jobs run through queue, workflow, and durable-object bindings in the dashboard/API deployment.
- Document/PDF-heavy work runs in the separate documents Worker configured by
worker/documents.wrangler.jsonc. - Cloudflare D1, R2, queue, workflow, service, and runtime bindings are declared in
wrangler.jsoncandworker/documents.wrangler.jsonc; runbun run types:cloudflareafter binding changes.
Users can pick a provider in the dashboard. The API returns HTTP 503 if the matching secret is unset:
- OpenAI (default):
OPENAI_API_KEY, optionalOPENAI_ASSISTANT_MODEL_*overrides where the code reads them. - Kimi:
KIMI_API_KEY, optionalKIMI_BASE_URL,KIMI_MODEL_*. - OpenRouter:
OPENROUTER_API_KEY, optionalOPENROUTER_BASE_URL(defaults tohttps://openrouter.ai/api/v1), optionalOPENROUTER_ASSISTANT_MODEL_*(defaults includeqwen/qwen3.6-plus:free), optionalOPENROUTER_HTTP_REFERERandOPENROUTER_APP_NAME.
The current UK filing paths split by authority and transport:
HMRC VAT: live OAuth/API submission flow.HMRC corporation tax: CT600/iXBRL generation plus Transaction Engine submit/poll. Runtime is switchable betweentestandproduction, but should stay ontestby default until you have live sender credentials and a real company UTR.Companies House annual accounts: XML gateway submission from the year-end workspace using presenter credentials and the company authentication code saved in compliance settings.
Additional compliance and filing notes can be appended to docs.md or the README as they are written.
Recommended main app startup:
bun run dev:localThat starts the local dashboard on http://localhost:3001 against the shared deployed backend:
- deployed API route at
https://api.tamias.xyz - Cloudflare D1/R2-backed durable app state
bun run dev:dashboardThe current local setup routes dashboard SSR calls to the deployed API route, so separate local api and worker Cloudflare processes are not required for the normal dashboard flow.
Use bun --filter @tamias/dashboard preview:cf when you need a local Cloudflare preview of both the main Worker and the documents Worker.
- Open
http://localhost:3001/login - Sign in with an existing Tamias account, or create one
- You will be using the configured Cloudflare app data bindings
| Surface | URL |
|---|---|
| Dashboard | http://localhost:3001 |
| Dashboard health | http://localhost:3001/api/health |
| API | http://localhost:3001 (routed by path) |
| API health | http://localhost:3001/health |
| API readiness | http://localhost:3001/health/ready |
| OpenAPI spec | http://localhost:3001/openapi |
| Scalar API docs | http://localhost:3001/ |
| MCP endpoint | http://localhost:3001/mcp |
| Public invoice link | http://localhost:3001/i/<token> |
| Customer portal | http://localhost:3001/p/<portalId> |
| Public report | http://localhost:3001/r/<linkId> |
| Short link redirect | http://localhost:3001/s/<shortId> |
bun run dev
bun run dev:local
bun run dev:dashboard
bun run build
bun run test
bun run test:e2e
bun run typecheck
bun run lint
bun run formatEnd-to-end smoke tests use Playwright: specs in e2e/, config playwright.config.ts at the repo root. Run bun run test:e2e (or test:e2e:headed) from the repository root. Saved auth ends up under e2e/.auth/ (gitignored); playwright-report/ and test-results/ are also gitignored.
bun run deploy:cloudflare:productionbun run preflight:cloudflare
bun run preflight:cloudflare:documents
bun run preflight:cloudflare:production
bun run preflight:cloudflare:documents:production- Cloudflare deployment uses the main Worker (
tamias) for the dashboard, public site, API route, capture/ledger queues, cron triggers, Durable Objects, workflows, D1, R2, email, and image handling. - Document-heavy work runs in the documents Worker (
tamias-documents) with its ownDOCUMENTS_QUEUEconsumer. wrangler.jsoncat the repository root configures the main Cloudflare Worker and runtime bindings.worker/documents.wrangler.jsoncconfigures the documents Worker and its document queue consumer..wrangler/(under the repo ordashboard) is Wrangler’s local cache; it is gitignored and should not be committed.dashboardservesapp.tamias.xyzandtamias.xyz; public-site routes are host-rewritten into the internal/sitetree. API traffic toapi.tamias.xyzreaches the same Worker.- Cloudflare preflight runs a documents Worker dry-run and the Vite production build plus Wrangler
deploy --dry-runwith../wrangler.jsonc. - GitHub Actions deploys expect these repository secrets:
CLOUDFLARE_API_TOKENCLOUDFLARE_ACCOUNT_ID
workeruses a Cloudflare Images binding for image resize and HEIC-to-JPEG conversion inside the Worker runtime.- Current runtime defaults in code assume:
- dashboard URLs under
app.tamias.xyz - API URLs under
api.tamias.xyz - website SEO metadata still pointing at
tamias.xyz
- dashboard URLs under
- If you change public domains, update the matching env vars and the website
baseUrlso generated links, metadata, emails, and public endpoints stay aligned.
Consolidated engineering documentation is in docs.md (design system, banking, accounting, categories, inbox, insights). API assistant markdown prompts live in agent-prompts/ and are bundled with bun run --cwd api prompts:generate into api/src/ai/agents/config/generated-prompts.ts.
- Dashboard loads but API-backed data fails:
API_URLis wrong, or the API is not running. - Login/signup fails:
TAMIAS_AUTH_SECRET,INTERNAL_API_KEY, or the API auth route is misconfigured. - Queue-backed features do nothing: the unified async runtime is missing D1, queue, workflow, or durable-object bindings.
- Public invoice downloads fail:
INVOICE_JWT_SECRETdoes not match across services. - Internal service calls fail:
INTERNAL_API_KEYis missing or mismatched. - OAuth/integration setup breaks with encryption errors:
TAMIAS_ENCRYPTION_KEYis missing or invalid. - Compliance pages show disabled states or missing data: the team is not GB-scoped, the filing profile is not enabled, or HMRC/app connections and annual pack data have not been configured yet.
