Personal life dashboard — compares a defined plan (financial, health, communication, and
work goals) against live state pulled from Asana, Actual Budget, Fantastical, Gmail/Outlook,
iMessage/Matrix, Home Assistant, AFFiNE, and Gatus, surfacing only where reality has drifted
from the plan. See PRODUCT.md for product context and
design-brief.md for the frontend/UX direction.
npm install
npm run devOpen http://localhost:3000.
Postgres via Drizzle ORM — this is the app's real data now, not mock data. Point
DATABASE_URL at a Postgres instance (for local dev, brew install postgresql@16 and
createdb blueprint_dev works fine), then:
npm run db:generate # generate a migration from schema changes
npm run db:migrate # apply migrations
npm run db:seed # load mock-data.ts's content as a starting datasetv1's population strategy is deliberately the simplest thing that works: Blueprint exposes
an MCP server (src/app/api/[transport]/route.ts, reachable at /api/mcp) with five tools
— list_goals, list_cards, create_redline, create_win, resolve_redline — and an
agent (the AIOS daily-brief skill, updated with a Blueprint section) calls them directly
after evaluating the real sources itself. No per-source sync workers, no scheduled jobs yet.
Connect a local Claude Code session to the dev server with:
claude mcp add --transport http blueprint http://localhost:3211/api/mcpOr the deployed endpoint directly: https://blueprint.tristonyoder.com/api/mcp.
Auth is currently off. The route supports bearer-token gating (MCP_AUTH_TOKEN,
checked via either an Authorization: Bearer <token> header or a ?token=<token> query
param — the latter for connector UIs that only take a bare URL, like Claude Desktop's
remote-connector dialog) but nothing sets that env var on david yet — the paired
nix-config change (blueprint-mcp-token agenix secret) is sitting on an undeployed branch
(feat/blueprint-mcp-auth), not merged. Deliberate for now: the endpoint is already
Tailscale-only (unreachable from the open internet), which is the real gate at this scale.
Merge and rebuild whenever card-mutation-over-the-tailnet-with-no-token stops being an
acceptable risk.
Per PRODUCT.md's "signal, not noise" principle, every tool's description tells the calling
agent to check list_cards first (avoid duplicate flags) and to only create a card for a
genuine, nameable deviation or alignment — never routine status. resolve_redline is a hard
delete; v1 keeps no history.
How an automated agent (e.g. the daily-brief skill) actually reaches this: not by
exposing the endpoint publicly. blueprint.tristonyoder.com resolves to david's Tailscale
address — unreachable from the open internet by design (the vHost is internal-only, no
Cloudflare Tunnel). A locally-bridged connector (the same mechanism that lets a scheduled
agent call local-only integrations like the Apple Mail MCP — an outbound tunnel from the
machine running the connector, not an inbound connection to it) reaches it fine, since that
machine is itself on the tailnet. Register it as a custom connector the same way those are
configured, pointed at the real URL + bearer header above — not as a public-internet
integration.
Once this loop is proven out, the plan (per conversation, not yet built) is to replace individual agent-written cards with real non-agentic sync adapters per source (Asana and Actual Budget first — see PRODUCT.md's "Capabilities and Constraints").
Built as a standalone Next.js server (output: "standalone"). Three equivalent build
paths, matching the stagePlotiphar convention:
Dockerfile+docker-compose.yml— plaindocker build/docker compose up.flake.nixpackages.docker— reproducible container build vianix build .#docker, used by CI (.github/workflows/build-container.yml, GitHub-hosted runner, pushes to GHCR). This is a standalone public project's CI — it doesn't depend on any private infra (david), unlike nix-config's own CI which legitimately targets david directly.flake.nixpackages.default— the native (non-container) build: the samebuildNpmPackagestandalone output, consumed directly by nix-config'smodules.services.productivity.blueprintNixOS module (no Docker layer at all).
flake.nix's npmDepsHash and flake.lock are kept fresh automatically —
.github/workflows/update-nix-hashes.yml recomputes npmDepsHash whenever
package-lock.json changes (plus a weekly backstop), and
.github/workflows/update-flake-lock.yml runs nix flake update weekly. Both verify
nix build .#default still succeeds before committing.
Deploys onto the home NixOS server (david) as a native systemd service (not a
container) behind Caddy via nix-config's vHosts registry, at blueprint.tristonyoder.com
— internal-only (no public Cloudflare Tunnel exposure). Secrets are supplied via agenix,
the same mechanism used for the rest of that infra — see ~/Projects/nix-config.
Not yet wired up / open items:
drizzle-kit migrateisn't run automatically on deploy — it's a build-time devDependency, not part of the standalone runtime output. Run it manually after first deploy and after any schema change (see the comment inblueprint.nix).- Goal-authoring UI still doesn't exist — goals are seeded from
mock-data.tsby hand.