尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Personal finance tracking and management
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗This project is for tracking and managing personal/household finances. Allowing you to see where you are spending, what you have overspent on, identifying where you can save/reduce expenses on the "should haves" and "nice to haves".
Stretch goal for this project:
Validated in src/env.ts. Pull them locally with vercel env pull; set them per-environment in the Vercel project.
| Variable | Required | Purpose |
|---|---|---|
DATABASE_URL | always | Pooled Postgres connection. |
DATABASE_URL_UNPOOLED | optional | Direct connection for drizzle-kit DDL. |
BETTER_AUTH_SECRET | production | Signs session cookies. |
KV_REST_API_URL | production | Upstash Redis REST endpoint. |
KV_REST_API_TOKEN | production | Upstash Redis REST token. |
INNGEST_EVENT_KEY | production | Inngest Cloud key for sending events. |
INNGEST_SIGNING_KEY | production | Inngest Cloud key serve uses to verify inbound requests. |
BLOB_READ_WRITE_TOKEN | production | Vercel Blob token; mints client-upload tokens at /api/receipts/upload (private store) and signs reads for the /api/receipts/[id]/image proxy. One store serves every environment, so the Blob integration injects the same token into all of them. |
BLOB_PATH_PREFIX | preview | Blob pathname prefix naming the same Neon branch as DATABASE_URL: local/<branch>/ (written by scripts/neon-branch.sh) or preview/<branch>/ (set by deploy.yml). Writes go under it and deletes skip files outside it, so a branch never deletes production's files. Empty and forced empty on production. See adr028. |
AWS_REGION | optional | Bedrock region, default ap-southeast-2. Terraform sets it on Vercel; assume exports it locally. |
AWS_ROLE_ARN | Vercel only | IAM role the deployment's OIDC token is exchanged for (src/server/aws/credentials.ts). Set by Terraform per environment; leave unset locally. |
BEDROCK_EMBEDDING_MODEL | optional | Bedrock foundation-model id the bank reconcile embeds descriptions with, default amazon.titan-embed-text-v2:0 (adr029). A bare in-region id: Titan has no inference profile. Set by Terraform from infrastructure/locals.tf. |
BEDROCK_RECEIPT_MODEL | optional | Bedrock inference-profile id the process-receipt worker calls, default au.anthropic.claude-sonnet-4-6. Set by Terraform from infrastructure/locals.tf. |
REDBARK_API_KEY | optional | RedBark account key for the daily CDR bank sync (adr017). Unset: the sync is a logged no-op and a history import fails with a reason. Vercel dashboard secret; don't set it in production until RedBark v2 is GA. |
REDBARK_API_URL | optional | RedBark API origin, default https://api.redbark.com (the client adds /v2). |
RESEND_API_KEY | production | Resend key the inbound webhook uses to fetch the email body and attachment bytes, which the email.received payload omits. |
RESEND_WEBHOOK_SECRET | production | Svix signing secret verifying the raw body at /api/receipts/inbound. Without it the route refuses every request. |
RESEND_INBOUND_ADDRESS | optional | The address members forward receipts to. Display-only, shown on /settings; nothing routes on it. |
POSTHOG_KEY | optional | PostHog project token (phc_…) for the Rekurve project. Unset: OTel registration is skipped, the logger writes stdout only and exception capture is a no-op. A personal phx_ key fails validation on purpose. |
POSTHOG_HOST | optional | PostHog ingestion endpoint, default https://us.i.posthog.com. |
POSTHOG_UI_HOST | optional | PostHog UI host (distinct from the ingest endpoint above), default https://us.posthog.com. Sole source of the receipt "Open trace" deep-link; with POSTHOG_PROJECT_ID it builds …/project/<id>/tracing?trace=<traceId>. |
POSTHOG_PROJECT_ID | optional | PostHog project id for the "Open trace" deep-link. Unset: no link is built and the button never renders (the trace id is still shown). |
LOG_LEVEL | optional | Minimum severity the logger emits (debug/info/warn/error), default info. |
BEDROCK_INPUT_TOKEN_PRICE_USD | optional | USD price per input token (not per 1M), e.g. 0.0000033000. Stamped onto ai.* spans; PostHog uses it as the custom price for AI cost. Set by Terraform from the live AWS Price List for the model in infrastructure/locals.tf (infrastructure/pricing.tf). Unset locally: PostHog falls back to its own model matching. |
BEDROCK_OUTPUT_TOKEN_PRICE_USD | optional | USD price per output token, e.g. 0.0000165000, as above. |
next build runs with NODE_ENV=production, so make build and pnpm run preview fail locally unless the production-required variables are in .env.
Receipt extraction calls Claude through Amazon Bedrock (src/server/aws/bedrock.ts), never the Anthropic API. The bank reconcile embeds descriptions through the same provider (Titan Text Embeddings V2) and caches each vector in description_embedding. Without credentials it logs embedding unavailable; a pair with an uncached description then scores on words alone and never adopts. @ai-sdk/amazon-bedrock has no default credential chain: on Vercel the deployment's OIDC token is exchanged for AWS_ROLE_ARN; locally you must assume <profile> first (it exports AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN) — AWS_PROFILE alone does nothing. Model switching and IAM live in Terraform; see infrastructure, adr014 and .claude/rules/bedrock-invocation.md.
Better Auth secondary storage — sessions and verification records dual-write to Redis and Postgres and read Redis-first, with Postgres as the durable copy. Rate-limit counters live here so they are shared across lambdas. See adr006 and adr007.
The KV_ names are fixed by Vercel's Upstash integration. Keys are namespaced ba:<VERCEL_ENV>:<db-fingerprint>:, so environments and Neon branches sharing one Upstash database cannot authenticate each other's sessions.
src/instrumentation.ts register() boots OpenTelemetry once per Node server instance via @vercel/otel, exporting spans and log records over OTLP to the PostHog project; onRequestError is the one place an unhandled route error is captured, logged and flushed before the serverless freeze. Tiers never touch the SDK — they take an injected Logger and the ambient @opentelemetry/api tracer.
Everything is gated on POSTHOG_KEY: unset, nothing registers, the logger writes stdout only and exception capture is a no-op, so tests and keyless local runs need no key. Outbound fetch carries no traceparent/baggage, so no correlation id reaches Bedrock or Resend. See adr015 and adr016.
The first server span is the trace root; the browser sends no traceparent (a browser-minted parent is never exported, which left every trace rootless and hidden from PostHog's trace lists). When telemetry is on (i.e. POSTHOG_KEY set, so a span is recording), the tRPC route and POST /api/receipts/upload echo the trace id back as an x-trace-id response header — read it from the Network tab to search PostHog; keyless local runs record no span, so no header is sent. One receipt upload is two traces: the Blob token mint (blob.token.mint) and the receipt.create ingest run; the receipt row stores the second. Inngest's own inngest.execution root is dropped by NameDropSampler so the function span sits under the producer. See adr020 and adr016.
An hourly PostHog alert emails the owner once a day's AI cost passes ai_daily_cost_alert_usd (infrastructure/locals.tf). It, its insight and the test-users cohort live in Terraform (infrastructure/posthog.tf); see infrastructure.
Side-effecting writes commit their canonical rows and an outbox row in one db.transaction(), then best-effort inngest.send; a cron re-delivers anything the send missed. At-least-once, idempotent on the outbox row id. See adr002; event names live in src/server/inngest/events.ts (EVENT_REGISTRY).
Inngest reaches the app at src/app/api/inngest/route.ts (/api/inngest), which serves three scheduled functions and two event-driven workers:
| Function | Trigger | Purpose |
|---|---|---|
outbox-sweep | 0 * * * * | Re-deliver unacked rows older than 30s; failures stay unacked for the next run. |
outbox-prune | 0 3 * * * | Delete rows acked over 7 days ago; never touches unacked or stuck rows. |
cdr-sync | 0 18 * * * (UTC, ~4–5 am Sydney) | Pull settled bank rows from RedBark per linked account, upsert them, then advance that account's cursor. One failed account doesn't block the others. Honours Retry-After up to 1 h; a deterministic failure doesn't retry. No-op when REDBARK_API_KEY is unset. See adr017. |
cdr-import | cdr/import.requested | Bring in older bank history for a member-picked range (/transactions/import, up to 7 years back). One step per account per 90-day window, so a retry redoes only the failed window; each reconciles like cdr-sync and adds its counts to the cdr_import row. Shares one concurrency slot with cdr-sync, so the two never overlap. Never deletes. Out of retries, or REDBARK_API_KEY unset → the row reads Failed with the reason. A row still queued/running after 24 h counts as not active, so the form opens again. |
process-receipt | receipt/uploaded, receipt/retry, receipt/email.received | Extract vendor/total/date + line items from a receipt via Claude on Bedrock, or short-circuit an identical re-upload as a duplicate on its content hash. All three triggers carry the same { receiptId }, so an emailed receipt rides the upload path unchanged. |
Local runs use the Inngest Dev Server (no keys needed). Run make setup once — it trusts the Portless CA so the Go Dev Server binary trusts the app's HTTPS cert — then make start boots the app plus the Dev Server as sibling Portless processes. The Dev Server syncs the app's /api/inngest, lists outbox-sweep + outbox-prune + process-receipt + cdr-sync + cdr-import (plus its cdr-import-failure handler), and can invoke them from its dashboard at https://inngest.personal-finance.localhost (worktree: https://<branch>.inngest.personal-finance.localhost).
Terraform in infrastructure/ owns infrastructure only: the Vercel project, the per-environment AWS IAM role, the Bedrock env vars, the production protection-bypass secret and PostHog alerting config (production only); Vercel's git auto-deploy is off (vercel.json). .github/workflows/deploy.yml runs the whole deploy on every push as one staged flow: terraform apply → vercel deploy (CLI, un-aliased) → resolve the Neon DB branch → migrate (unpooled). For main it then gates the staged deployment on /api/health behind the bypass header and only then vercel promotes it onto the domain. A failed migrate fails the job before promote; the previous deployment stays Current (no automated rollback — migrations are expand-only). Locally: assume <profile>, export VERCEL_API_TOKEN=… POSTHOG_API_KEY=… TF_VAR_posthog_key=… TF_VAR_posthog_project_id=…, then make plan env=preview. Bootstrap, cutover and model switching: infrastructure.md. Decisions: adr013, adr014.
Renovate (.github/renovate.json) auto-merges patches; minor and major PRs wait for a human. .github/workflows/claude-renovate.yml runs Claude on them: when a minor/major PR opens it reads the release notes and comments on breaking changes, deprecations, bug fixes and features worth adopting (no edits); when Quality Control or Deployment fails on a renovate/* push it fixes the cause, runs the checks and pushes to the PR branch, or comments why not. One fix attempt per Renovate push — if Claude's commit fails CI, a human takes over. Claude never starts, re-runs or waits on CI; its push does that. Both jobs log Claude's full output (show_full_output) for troubleshooting. Re-run either by hand with workflow_dispatch (pr, mode). Needs the CLAUDE_CODE_OAUTH_TOKEN secret (claude setup-token) and the Claude GitHub App on the repo; like any workflow_run / workflow_dispatch workflow it only runs from main.
MIT, see LICENSE. Self-host it, learn from it, contribute to it.
Open core, like PostHog: if paid hosted plans arrive, paid-only code will live in an ee/ directory under its own commercial license. Everything outside ee/ stays MIT.