Browse by type
Maple is now organized as a monorepo with a SPA frontend and an Effect-based backend API.
apps/web: TanStack Router SPA (Vite)apps/api: Effect HTTP API (Tinybird proxy + MCP server code)apps/ingest: OTLP ingest gateway (key auth + org enrichment + collector forwarding)apps/landing: Astro landing siteapps/alerting: Alert evaluation workerapps/chat-agent: Cloudflare Worker chat surfaceapps/cli: CLI utilitiesapps/mobile: Expo mobile apppackages/domain: Shared Effect HTTP contracts and domain typespackages/query-engine: Shared query and observability logicpackages/ui: Shared UI primitives and components>=1.3bun install
Run every available dev task in the monorepo:
bun run dev
Run individual apps from the repo root with workspace filters:
bun --filter=@maple/web dev
bun --filter=@maple/api dev
bun --filter=@maple/ingest dev
bun --filter=@maple/landing dev
There is also a dedicated root helper for alerting:
bun run dev:alerting
Turbo dev runs in TUI mode so interactive servers stay attached.
bun run typecheck
bun run build
bun run test
Run the local multi-service stack (API + web + ingest + otel collector):
docker compose -f docker-compose.yml up --build
Services:
http://localhost:3472http://localhost:3471http://localhost:34744317 (gRPC), 4318 (HTTP), 13133 (health/extensions)Deployments are per-app Alchemy runs pinned to Cloudflare Workers + D1:
apps/api/alchemy.run.ts — D1 database MAPLE_DB + api Worker with all env bindingsapps/landing/alchemy.run.ts — Astro build + Worker serving static assetsapps/web/alchemy.run.ts — TanStack Start app via the Vite() resourceStage grammar is prd / stg / pr-<number>, resolved via @maple/infra/cloudflare (parseMapleStage, resolveMapleDomains, resolveWorkerName, resolveD1Name).
Run locally:
bun run alchemy:deploy:prd
bun run alchemy:deploy:stg
PR_NUMBER=123 bun run alchemy:deploy:pr
Tear down:
bun run alchemy:destroy:prd
bun run alchemy:destroy:stg
PR_NUMBER=123 bun run alchemy:destroy:pr
CI workflows:
main): .github/workflows/deploy-stg.ymlworkflow_dispatch): .github/workflows/deploy-prd.yml.github/workflows/deploy-pr-preview.yml (pull_request opened/synchronize/reopened/closed)Secrets source model (CI):
DOPPLER_TOKENprd, stg, pr) must define:ALCHEMY_PASSWORDALCHEMY_STATE_TOKENCLOUDFLARE_API_TOKENCLOUDFLARE_DEFAULT_ACCOUNT_IDTINYBIRD_HOSTTINYBIRD_TOKENRESEND_API_KEYRESEND_FROM_EMAILMAPLE_INGEST_KEY_ENCRYPTION_KEYMAPLE_INGEST_KEY_LOOKUP_HMAC_KEYMAPLE_AUTH_MODEMAPLE_ROOT_PASSWORD (required in self_hosted mode)CLERK_SECRET_KEYCLERK_PUBLISHABLE_KEYCLERK_JWT_KEYFree/Starter note: when using a personal Doppler token, the workflow must also specify Doppler selectors (doppler-project, doppler-config). This repo uses maple with stage configs prd, stg, and pr.
Runtime API URL behavior:
VITE_API_BASE_URL from the Cloudflare api worker domain (api.maple.dev in prd, api-staging.maple.dev in stg, worker.dev URL for pr-*).bun --filter=@maple/web dev can still use root .env VITE_API_BASE_URL for local API routing..env.exampleapps/api/.env.example.env values are local-only and should stay untracked.The web app expects VITE_API_BASE_URL to point to the API (defaults to http://localhost:3472).
For ingest + key auth, set these at minimum in your root .env when running the ingest gateway:
MAPLE_INGEST_KEY_LOOKUP_HMAC_KEYINGEST_PORTINGEST_FORWARD_OTLP_ENDPOINTINGEST_FORWARD_TIMEOUT_MSINGEST_MAX_REQUEST_BODY_BYTESINGEST_REQUIRE_TLSMaple now persists dashboards in SQLite via libSQL:
MAPLE_DB_URL is unset, Maple uses apps/api/.data/maple.db.MAPLE_DB_URL to your Turso/libSQL URL and MAPLE_DB_AUTH_TOKEN to your token.apps/api/.data directory as a volume so dashboard state survives container/restart cycles.Migration commands:
bun --filter=@maple/api db:migrate
bun --filter=@maple/db db:generate
bun --filter=@maple/db db:push
bun --filter=@maple/db db:studio
When running the API (bun --filter=@maple/api dev or bun --filter=@maple/api start), migrations are applied automatically before boot.
public + private).MAPLE_INGEST_KEY_ENCRYPTION_KEY (base64-encoded 32-byte key).MAPLE_INGEST_KEY_LOOKUP_HMAC_KEY.Maple supports exactly two auth modes via MAPLE_AUTH_MODE:
clerkMAPLE_AUTH_MODE=clerkCLERK_SECRET_KEYCLERK_JWT_KEY for networkless verificationCLERK_PUBLISHABLE_KEY for the web appVITE_CLERK_SIGN_IN_URL and VITE_CLERK_SIGN_UP_URLself_hostedMAPLE_AUTH_MODE=self_hostedMAPLE_ROOT_PASSWORD (required)MAPLE_DEFAULT_ORG_ID (defaults to default)/sign-in with the root password before accessing the dashboard/API.Start apps:
bun --filter=@maple/api dev
bun --filter=@maple/web dev
Validate behavior:
/sign-in/org-required/sign-inMAPLE_ROOT_PASSWORD login issues a bearer session tokenBreaking change:
MAPLE_ROOT_PASSWORD is now required when MAPLE_AUTH_MODE=self_hosted.$ claude mcp add maple \
-- python -m otcore.mcp_server <graph>