Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

Counterpoise Ledger

БесплатноНе проверен

A web-based double-entry accounting application for personal finance management, built with modern technologies and accounting best practices.

GitHubEmbed

Описание

A web-based double-entry accounting application for personal finance management, built with modern technologies and accounting best practices.

README

A web-based double-entry accounting application for personal finance management, built with modern technologies and accounting best practices. Supports multiple books per user, investment tracking, and bank sync via Plaid.

Screenshots

Every screenshot below is the sample data you get from Add demo book — no setup, and nothing real in it.

Transactions Recurring
A credit card register with running balances and category icons Recurring rules with a four-week upcoming calendar
Per-account register with a running balance, category icons, and inline entry. An upcoming calendar over the rules behind it, including a multi-split paycheck.
Securities Bank sync
Securities positions with cost basis and market value Plaid reconciliation queue showing a strong match
Holdings with FIFO cost basis, income, and market value. Plaid items matched against the ledger, with strong-match detection.

Features

Core Accounting

  • True Double-Entry Bookkeeping - Every transaction has balanced debits and credits
  • Chart of Accounts - Five account types: Assets, Liabilities, Equity, Income, Expenses
  • Account Subtypes - Bank, Credit Card, Loan, Investment, Cash accounts
  • Split Transactions - Support for complex transactions involving multiple accounts (e.g., paychecks)
  • Running Balance - Real-time balance calculation per account
  • Account Hierarchy - Organize accounts with parent-child relationships

Multi-Book Support

  • Multiple Books - Maintain separate sets of books (e.g., personal, business)
  • Demo Book - One click fills a brand-new book with the full sample dataset: three years of transactions, investment lots with cost basis, recurring rules, and a bank-sync queue waiting to be reconciled
  • User Authentication - Session-based auth with scrypt password hashing
  • Registration Control - Signup open, closed, or self-closing after the first account, via REGISTRATION_ENABLED
  • Book Isolation - Data isolated by bookId within a single database

Transaction Management

  • Simple Mode - Quick entry for two-account transfers
  • Journal Entry Mode - Full debit/credit ledger for advanced transactions
  • Transaction History - View all transactions with filtering by account
  • Future Transaction Highlighting - Scheduled transactions shown with visual indicator
  • Edit & Delete - Full transaction modification capabilities
  • Floating Transactions - Entries whose effective date auto-advances to today until reconciled

Investment Tracking

  • Securities Management - Track stocks, ETFs, and mutual funds
  • Buy/Sell/Dividend - Full investment transaction support
  • FIFO Lot Tracking - Automatic cost basis calculation
  • Position Summaries - View holdings with market values
  • Price History - Historical price data with Tiingo integration
  • Automatic Price Sync - End-of-day prices fetched from Tiingo after each market day via cron
  • Quick Price Entry - Banner prompts for marks on manually-priced securities (e.g., options)

Recurring Transactions

  • Automated Rules - Set up recurring income and expenses
  • Multiple Frequencies - Daily, weekly, monthly, yearly, with custom intervals (e.g., every 2 weeks)
  • Next Date Tracking - Automatically calculates next occurrence
  • Early Auto-Create Window - Auto-create X days before the scheduled date (per rule)
  • Cron Processing - Hourly automatic processing via Docker scheduler

Bank Sync (Plaid)

  • Bank Connection - Connect bank accounts via Plaid
  • Transaction Import - Import transactions from connected accounts
  • Reconciliation - Match imported transactions with existing records
  • Auto-Matching - Learned payee-based matching runs automatically after each sync

Financial Reporting

  • Dashboard - Net worth, assets, liabilities, income, expenses
  • Balance Sheet - Real-time snapshot of financial position
  • Income Statement - Track income vs. expenses
  • Account Balances - Automatic calculation with proper accounting signs
  • CSV Export - Download report and security data as CSV

AI Integration (MCP)

  • MCP Server - Read/write access to accounting data for AI assistants (see mcp/README.md)
  • API Keys - Per-user cpk_ keys managed on the Account page, scrypt-hashed at rest
  • Usage Analytics - Optional PostHog integration for usage events (no financial data captured)

User Experience

  • Clean Modern UI - Built with Tailwind CSS for a polished interface
  • Responsive Design - Works on desktop and mobile devices
  • Sidebar Navigation - Quick access to account filters
  • Autocomplete Search - Type-ahead account and payee selection
  • Active/Inactive Accounts - Hide accounts you're not using
  • Keyboard Shortcuts - Global shortcuts with a ? help overlay
  • In-App Issue Reporting - Report bugs and improvement ideas from any page

Tech Stack

  • Framework: Next.js 16 (App Router)
  • Language: TypeScript
  • Styling: Tailwind CSS
  • Database: PostgreSQL with postgres.js driver
  • ORM: Drizzle ORM
  • Testing: Vitest (unit), Playwright (E2E)
  • Runtime: Node.js

Prerequisites

  • Node.js 22+
  • npm
  • Docker (for PostgreSQL)

Installation

  1. Clone the repository:
git clone https://github.com/jeffjjohnston/counterpoise-ledger.git
cd counterpoise-ledger
  1. Install dependencies:
npm install
  1. Create the PostgreSQL data volume and start the database:
docker volume create counterpoise_pgdata
docker compose up -d postgres

Local development only needs the database. For a full Docker deployment, copy the example environment file and point DATABASE_URL at the postgres service — inside the app container, localhost is the container itself:

cp .env.example .env.production.local

# Then edit .env.production.local. Set an application-role password, and put
# that same password into DATABASE_URL — nothing derives one from the other:
#   APP_DB_PASSWORD=$(openssl rand -hex 32)
#   DATABASE_URL=postgresql://counterpoise_app:<that password>@postgres:5432/counterpoise

docker compose --env-file .env.production.local up -d --build

--env-file is required, not optional: Compose reads ${VAR} substitutions in docker-compose.yml from the shell, a .env file, or --env-file — a service-level env_file: populates the container but does not feed those substitutions. Without it, TZ and POSTGRES_PASSWORD silently keep their defaults.

  1. Create the local dev database (and per-worker test databases):
npm run db:create-test-dbs

Docker only creates the counterpoise database; local development uses counterpoise_dev, which this script creates (along with the databases used by the test suite).

  1. Seed with sample data (optional):
npm run db:seed

This resets the local database, creates a sample admin user with password password, creates a sample book, and seeds it with data. If you want to seed an existing book instead, first create the book, then run npm run db:list-books to find its ID and npm run db:seed -- --book-id <id>.

If you skip seeding, run npm run db:migrate instead to apply the schema — migrations are not applied automatically in local dev.

  1. Start the development server:
npm run dev
  1. Open http://localhost:3000.

If you ran npm run db:seed, sign in with admin / password. Otherwise, register an account and create a book.

To explore with realistic data instead of an empty book, click Add demo book on the books page. It creates a book named "Demo Book" and fills it with the same sample dataset the seed uses. Unlike npm run db:seed, which resets the entire database, this only ever writes to the book it just created — so it is safe to run on an instance that already holds real data, and you can add several. It writes thousands of rows one at a time, so give it a few seconds.

Database Architecture

Counterpoise uses a PostgreSQL database containing all meta tables (users, sessions, books) and book-scoped tables. Book-scoped tables have a bookId foreign key for data isolation. Local development defaults to postgresql://counterpoise:counterpoise@localhost:5432/counterpoise_dev when DATABASE_URL is unset; Docker deployment uses counterpoise via .env.production.local.

Core Tables

  • accounts - Chart of accounts with hierarchy (types: asset, liability, equity, income, expense)
  • transactions / transactionSplits - Double-entry transactions
  • securities / securityPrices - Investment securities and price history
  • investmentSplits / investmentLots - Investment transactions and FIFO lot tracking
  • payees - Deduplicated payees (normalized-name matching)
  • recurringRules / recurringTemplateSplits - Recurring transaction templates
  • plaidTokens / plaidAccounts / plaidTransactionReconciliation - Bank sync via Plaid
  • apiKeys - User API keys for MCP access
  • issueReports - In-app issue reports (meta table, scoped to user)

Docker Deployment

The full stack runs as three Docker Compose services:

Service Description
postgres PostgreSQL 16 database with persistent volume
app Next.js standalone server (runs migrations on startup)
scheduler PostgreSQL Alpine sidecar — recurring transactions, Plaid sync, backups, pruning, reindex

Configuration

The app and scheduler services read secrets from .env.production.local via env_file. Configure these variables:

# .env.production.local
CRON_SECRET=your-cron-secret-here

# Optional — signup control. Leave unset and registration is open only until the
# first account exists, then closes itself.
REGISTRATION_ENABLED=true|false

# Optional — Plaid bank sync (see "Connecting a Bank (Plaid)" below)
PLAID_CLIENT_ID=...
PLAID_SECRET=...
PLAID_ENV=sandbox|production

# Optional — Tiingo security prices
TIINGO_API_KEY=...

# Optional — PostHog analytics
# NEXT_PUBLIC_* values are Docker build args (inlined into the JS bundle at image build)
NEXT_PUBLIC_POSTHOG_KEY=...
NEXT_PUBLIC_POSTHOG_HOST=...
POSTHOG_PERSONAL_API_KEY=...  # runtime; used for querying the PostHog API

Set DATABASE_URL in .env.production.local, pointing at the internal postgres hostname and at the application role — for example postgresql://counterpoise_app:<app password>@postgres:5432/counterpoise. That role is created from APP_DB_PASSWORD by scripts/postgres-init/01-app-role.sh, which runs on first initialization only: the postgres image skips /docker-entrypoint-initdb.d once the volume holds a database. Set APP_DB_PASSWORD before the first docker compose up — setting it later does nothing. The app container refuses to start while DATABASE_URL still carries the published counterpoise:counterpoise default, which is in this repository and known to every reader of it.

Starting

# Create the persistent data volume (first time only)
docker volume create counterpoise_pgdata

# Build and start all services
docker compose --env-file .env.production.local up -d --build

# Or start just the database (for local dev)
docker compose up -d postgres

The app will be available at http://localhost:3000. Migrations run automatically on container startup via docker-entrypoint.sh.

Rebuilding

Rebuild the app image after code changes:

docker compose --env-file .env.production.local up -d --build app

Updating Environment Variables

Docker Compose reads env_file only when creating a container. After editing .env.production.local, force-recreate to pick up changes:

docker compose --env-file .env.production.local up -d --force-recreate app scheduler

Note: docker compose restart will not re-read the env file — it only stops and starts the existing container with the old environment.

Viewing Logs

# All services
docker compose logs -f

# Specific service
docker compose logs -f app

Stopping

# Stop all services (data persists in the pgdata volume)
docker compose down

# Stop and delete the database volume (external volume must be removed separately)
docker compose down
docker volume rm counterpoise_pgdata

Build Architecture

The Dockerfile uses a multi-stage build:

  1. deps — installs node_modules via npm ci
  2. builder — builds the Next.js standalone output
  3. runner — minimal production image with the standalone server, static assets, and migration runner

The entrypoint runs Drizzle migrations before starting the Next.js server, so schema changes are applied automatically on deploy.

Getting HTTPS

In a Docker deployment this is not just hardening advice — it is what makes login work at all.

The image runs with NODE_ENV=production, which marks the session cookie Secure. Browsers refuse to store a Secure cookie that arrives over plain http://, with one exception: localhost, which they treat as a trustworthy origin. So the same build behaves differently depending on how you reach it:

Reached at Login
http://localhost:3000 Works — browsers exempt localhost
http://192.168.1.50:3000 Fails silently. Correct password, 200 response, and straight back to the login page
https://books.example.com Works

The middle row has no error message, so it looks like a rejected password. It is what you get by setting APP_BIND=0.0.0.0 and pointing a phone at the LAN address. Counterpoise logs a warning when it happens — check docker compose logs app if login is bouncing.

Any of these fixes it:

Option What it needs Notes
Localhost only Nothing No TLS needed. Fine if you use Counterpoise on the machine it runs on.
Tailscale Serve A tailnet tailscale serve --bg 3000 publishes it at https://<machine>.<tailnet>.ts.net. No open ports, no domain, no certificate management, and it preserves Host. Easiest option for reaching your own instance from other devices.
Caddy A domain, ports 80/443 Automatic Let's Encrypt certificates from a two-line Caddyfile. Sets Host and X-Forwarded-Proto correctly by default.
Cloudflare Tunnel A domain on Cloudflare cloudflared dials out, so nothing needs to be opened inbound.
nginx + certbot A domain, ports 80/443 Works, but needs both proxy headers set by hand — see below.

Leave APP_BIND at its 127.0.0.1 default when the proxy runs on the same host; the proxy reaches the app over loopback and nothing else can.

A Caddyfile is the whole configuration:

books.example.com {
    reverse_proxy 127.0.0.1:3000
}

nginx needs two headers set explicitly. Its defaults break Counterpoise in two separate ways — Host becomes the upstream address, which makes every write fail the cross-origin check, and without X-Forwarded-Proto the app cannot tell that the original request was HTTPS:

location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Once TLS is working, set ENABLE_HSTS=true to add a Strict-Transport-Security header.

Security notes for self-hosting

Counterpoise was built for a single trusted household on a home LAN. Before exposing it to anything wider, understand these defaults:

  • Put it behind HTTPS. In a Docker deployment this is not optional: the session cookie is marked Secure under NODE_ENV=production, so login fails silently over plain HTTP anywhere but localhost. See "Getting HTTPS" above for the ways to do it. A baseline set of security headers (CSP, X-Frame-Options, Referrer-Policy, Permissions-Policy) is always sent; HSTS is added only when you set ENABLE_HSTS=true.
  • Registration closes after the first account. REGISTRATION_ENABLED has three states: unset means open only while no account exists, true means always open, false means always closed. The unset default is what lets a fresh install bootstrap its first account and then shut by itself, with no configuration step and no window where a forgotten default leaves signup open. To add someone later, set it to true, register them, and unset it again.
  • Auth endpoints are rate limited. Five failed attempts per username and twenty per client IP in fifteen minutes, with lockouts escalating from one minute to fifteen. State is in-process and resets when the container restarts.
  • The app binds to 127.0.0.1 by default, so a reverse proxy is the only way in. APP_BIND=0.0.0.0 publishes it on your LAN instead — which bypasses whatever authentication that proxy provides, and, if you reach it over plain HTTP, silently breaks login. See "Getting HTTPS" above.
  • npm run db:seed creates an admin / password account. Delete or change it before the instance is reachable by anyone else.
  • Postgres binds to 127.0.0.1 by default and its bootstrap superuser uses the password from POSTGRES_PASSWORD. Change it before altering that binding.
  • The application connects as its own non-superuser role. A fresh install creates counterpoise_app from APP_DB_PASSWORD, and the app container refuses to start if DATABASE_URL still carries the published default.
  • Cron endpoints fail closed. /api/cron/* returns 401 unless CRON_SECRET is set and presented as a bearer token.
  • A reverse proxy in front of Counterpoise must preserve the original Host header. The cross-origin write check in proxy.ts compares the request's Origin against its Host header. Tailscale Serve preserves Host by default, so this works out of the box behind it. nginx does not — its default proxy_set_header Host $proxy_host replaces Host with the upstream address — and a proxy on that default will 403 every write with an opaque "Cross-origin request rejected". Configure proxy_set_header Host $http_host; (or equivalent) if you front Counterpoise with nginx or a similar proxy.
  • A reverse proxy also needs a read timeout long enough for "Add demo book". That request runs the whole sample seed inline and holds the connection for seconds — it is the longest the app makes, and the only one a short read timeout will cut. The seed keeps running server-side when it does, so the symptom is a failed request plus a complete demo book the page never showed.

Backups

The scheduler container dumps the database hourly to ./backups, prunes dumps older than 30 days, and reindexes monthly.

# Take a backup now
docker exec counterpoise-scheduler-1 sh -c \
  'pg_dump -Fc "$DATABASE_URL" > /backups/manual-$(date +%Y%m%d-%H%M%S).dump'

# List a dump's contents without restoring
pg_restore --list backups/<file>.dump

# Full restore (drops and recreates all objects) — stop the app first
docker compose stop app
pg_restore --clean --if-exists -d "$DATABASE_URL" backups/<file>.dump
docker compose start app

Get the dumps off the machine

Everything above runs on one disk. Hourly dumps beside the database they came from protect you from a bad migration or a mistaken delete — not from disk failure, theft, or ransomware, all of which take the database and every dump together. Nothing in this repo can fix that for you; it needs a second place.

Two ways, either is fine:

  • A whole-disk backup service already covering the host — Backblaze, Time Machine to a separate drive, or equivalent. Nothing to configure here, as long as ./backups is not in an exclusion list. Check that it is actually being picked up rather than assuming it.

  • A scheduled copy of the newest dump to cloud storage or another machine. Run this from the host's crontab, not the scheduler container: that container is postgres:16-alpine and has no rclone. Use an absolute path to your checkout — /backups is the path inside the container, and cron has no working directory to speak of:

    0 5 * * * rclone copy "$(ls -t /srv/counterpoise/backups/counterpoise-*.dump | head -1)" remote:counterpoise/
    

    restic, rsync over SSH, or aws s3 cp all work the same way. Putting the copy in the scheduler's crontab instead means building your own image with the tool installed — the stock one cannot do it.

Whichever you pick, keep version history. A backup that mirrors the current state one-for-one will faithfully replicate a corruption or an encryption event to your only other copy. Thirty days of retention turns that from a disaster into an inconvenience.

You do not need to verify the dumps yourself — the scheduler already does, on every one (see Monitoring below). What it cannot do is put them somewhere else.

Monitoring

Counterpoise verifies each dump with pg_restore --list and records the outcome of every scheduled job to backups/status/. The app surfaces stale or unverified jobs in the navbar — silently, until something needs attention.

That design assumes you use the app. It detects a broken backup job while everything else works, but it cannot tell you the host is switched off, because it is running on that host. On a machine you open regularly that gap is covered by you noticing.

If you deploy this somewhere you don't look at daily, add an external dead-man switch — healthchecks.io or similar — by appending a ping to each cron line in docker-compose.yml. That is the only layer that still reports when the whole host is down.

Scheduler

The scheduler container runs all cron jobs on a postgres:16-alpine image (giving it access to pg_dump, reindexdb, and wget):

Job Schedule Description
Recurring transactions Hourly Calls /api/cron/recurring authenticated with CRON_SECRET
Plaid sync Every 6 hours Calls /api/cron/plaid-sync for all linked asset/liability accounts
Security price sync Tue–Sat 6am ET Calls /api/cron/price-sync to fetch Tiingo end-of-day prices
Database backup Hourly, 6am–9pm pg_dump to backups/counterpoise-<timestamp>.dump
Backup pruning Daily at 4am Deletes .dump files older than 30 days
REINDEX 1st of month at 3am reindexdb "$DATABASE_URL"

Manual trigger:

curl -H "authorization: Bearer ${CRON_SECRET}" http://localhost:3000/api/cron/recurring

Connecting a Bank (Plaid)

Bank sync is optional, and off until PLAID_CLIENT_ID and PLAID_SECRET are set — isPlaidConfigured() is false without them, so sync fails closed rather than reaching a live institution.

1. Get API credentials

Sign up at dashboard.plaid.com. Your client_id and per-environment secrets are under Developers → Keys.

Plaid has two environments: Sandbox, which serves fake institutions and fake transactions, and Production, which connects real banks. There is no longer a Development environment — Plaid retired it, so PLAID_ENV takes only sandbox or production.

Production is not gated behind a sales call for a personal deployment. Developers signing up in the US or Canada get the Trial plan: free, real production data, auto-approved for most applicants, capped at 10 connected Items. That is usually enough for one household's banks. (The older Limited Production tier closed to new signups on 15 April 2026.)

Use the Sandbox secret in .env.local and the Production secret in .env.production.local. .env.example explains why that separation is not optional: a production secret in .env.local means npm run dev reaches real banks and bills real API requests, and the separate counterpoise_dev database does nothing to prevent it — it bounds writes, not outbound calls.

2. Mint an access token

Counterpoise syncs against a stored access token per institution, but it does not run Plaid Link itself. scripts/plaid-link.ts produces the token:

npm run plaid:link      # sandbox, via .env.local

# Or, to connect a real bank with the deployment's credentials:
npx tsx --env-file=.env.production.local scripts/plaid-link.ts

It prints a Plaid-hosted URL. Open it in any browser, log in to the bank, and the script prints an Item ID and an Access Token when the session completes. In Sandbox, log in to any institution with user_good / pass_good.

It stops waiting after ten minutes and prints the command to resume. Use that rather than re-running plain: a fresh link token stops watching the session you opened, so an Item you had already created at the bank would sit on your Plaid plan with no token to exchange for it.

The script uses Hosted Link, where Plaid serves the Link UI on its own domain, so there is nothing to run locally and no redirect URI to register. That matters for OAuth institutions — Chase, Wells Fargo, US Bank — which require a redirect URI that is HTTPS and registered in the Plaid dashboard, and so cannot be completed against a http://localhost page at all.

3. Add the token to a book

Go to Sync → Manage Sync Tokens, then Add Token. Enter the institution name, and paste the Item ID and Access Token. Counterpoise fetches the institution's accounts, and Assign Accounts maps each one to a Counterpoise account.

From then on the scheduler sidecar syncs every six hours, staging transactions for reconciliation rather than writing them to the ledger directly. Review them on the Sync page.

An access token does not expire. Treat it as a credential: it reads the connected account's transactions until revoked from the Plaid dashboard.

Moneydance Import

Import data from Moneydance JSON exports:

# Dry run (recommended first)
npx tsx scripts/import-moneydance/index.ts path/to/export.json --book-id <existing-book-id> --dry-run

# Full import
npx tsx scripts/import-moneydance/index.ts path/to/export.json --book-id <existing-book-id> --verbose

Create the destination book first in the UI, or use npm run db:seed for a sample seeded book. Use npm run db:list-books to find the book ID before importing.

Imports accounts, payees, transactions, investment transactions, security prices, stock splits, and recurring reminders. See scripts/import-moneydance/README.md for details.

Usage Examples

Recording an Expense

Simple Mode:

  1. Go to Transactions page
  2. Select "From Account" (e.g., Checking)
  3. Select "To Account" (e.g., Groceries expense)
  4. Enter amount: $125.43
  5. Click "Add Transaction"

Result:

  • Checking account decreases by $125.43
  • Groceries expense increases by $125.43

Recording a Paycheck

Journal Entry Mode:

  1. Switch to "Journal Entry" mode
  2. Add split: Main Checking (Debit) $3,500
  3. Add split: 401k (Debit) $500
  4. Add split: Salary Income (Credit) $4,000
  5. Verify splits balance to zero
  6. Click "Add Transaction"

Setting Up Recurring Rent

  1. Go to Recurring page
  2. Click "New Rule"
  3. Name: "Monthly Rent"
  4. Frequency: Monthly
  5. Start Date: First of month
  6. Add splits:
    • Rent Expense (Debit) $1,500
    • Checking (Credit) $1,500
  7. Click "Create Rule"

The system will automatically show when it's due and allow one-click processing.

Double-Entry Accounting Primer

Account Types & Normal Balances

Account Type Normal Balance Increase Decrease
Asset Debit (+) Debit Credit
Liability Credit (-) Credit Debit
Equity Credit (-) Credit Debit
Income Credit (-) Credit Debit
Expense Debit (+) Debit Credit

Transaction Examples

Buying groceries with credit card:

  • Debit: Groceries (expense) +$50
  • Credit: Credit Card (liability) -$50

Paying off credit card:

  • Debit: Credit Card (liability) +$50
  • Credit: Checking (asset) -$50

Receiving salary:

  • Debit: Checking (asset) +$3,000
  • Credit: Salary (income) -$3,000

Development

Available Scripts

npm run dev          # Start development server
npm run build        # Build for production
npm run start        # Start production server
npm run lint         # Run ESLint
npm test             # Run unit tests (Vitest)
npm run test:ui      # Open Vitest UI
npm run test:coverage # Generate coverage report
npm run test:e2e     # Run Playwright E2E tests
npm run db:generate  # Generate a migration from /db/schema.ts into /db/migrations
npm run db:migrate   # Apply pending migrations
npm run db:create-test-dbs  # Create dev + per-worker test databases (one-time setup)
npm run db:list-books  # List books and their IDs
npm run db:seed -- --book-id 2  # Full reset + seed sample data for a specific book
npm run mcp:dev      # Start the MCP server (stdio)
npm run plaid:link   # Mint a Plaid access token for one bank (sandbox)
npx drizzle-kit studio  # Open Drizzle Studio (database GUI)

For book schema changes, use this workflow:

  1. Edit /db/schema.ts
  2. Run npm run db:generate
  3. Run npm run db:migrate
  4. Commit the SQL migration plus the updated snapshot and journal in /db/migrations/meta/

Migrations are NOT auto-applied by getDb(). Use runMigrations() explicitly in scripts; seed and test helpers handle migrations automatically.

Project Structure

/app
  /page.tsx                       # Home / book list
  /login, /register, /account     # Auth pages
  /b/[bookId]/                    # Book-scoped pages
    /page.tsx                     # Dashboard
    /accounts, /transactions      # Core accounting
    /securities, /recurring       # Investment & recurring
    /payees, /sync                # Payees & bank sync
    /reports, /search             # Financial reports & search
  /api/
    /auth/                        # Authentication
    /books/                       # Book management
    /b/[bookId]/                  # Book-scoped API routes
    /cron/                        # Cron endpoints (recurring, plaid-sync, price-sync)
/components
  /ui                             # Reusable UI components
  /accounts, /transactions        # Feature components
  /securities, /sync, /layout     # Domain components
  /reports                        # Financial report components
/db
  /schema.ts                      # Unified database schema (meta + book-scoped tables)
  /index.ts                       # Database connection (getDb)
  /seed.ts                        # Sample data
/lib
  /accounting.ts                  # Accounting helpers
  /investments.ts                 # Investment calculations
  /formatters.ts                  # Display formatters
  /api-auth.ts                    # API authentication
  /reports.ts                     # Financial report logic
/hooks
  /useBookId.ts                   # Client hooks (also useIsMobile, useRegisterShortcuts)
/mcp
  /server.ts                      # MCP server (AI access to accounting data)

License

MIT — see LICENSE.

Contributing

Counterpoise is developed as a personal project and is not accepting pull requests, feature requests, or bug reports. That is not unfriendliness — it is the point of publishing it.

Fork it and make it yours. The repository is built for exactly that: CLAUDE.md is a complete machine-readable contract for the codebase, so your own AI agents can pick it up and build on it without a human explaining the architecture first. The .claude/skills/ directory ships the maintainer's own workflows as worked examples.

If you want to track upstream changes, add this repository as a second remote and cherry-pick what you want. Releases are tagged vX.Y.Z.

from github.com/jeffjjohnston/counterpoise-ledger

Установка Counterpoise Ledger

У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.

▸ github.com/jeffjjohnston/counterpoise-ledger

FAQ

Counterpoise Ledger MCP бесплатный?

Да, Counterpoise Ledger MCP бесплатный — установка в пару кликов через Unyly без оплаты.

Нужен ли API-ключ для Counterpoise Ledger?

Нет, Counterpoise Ledger работает без API-ключей и переменных окружения.

Counterpoise Ledger — hosted или self-hosted?

Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.

Как установить Counterpoise Ledger в Claude Desktop, Claude Code или Cursor?

Открой Counterpoise Ledger на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.

Похожие MCP

$5

Stripe

Payments, customers, subscriptions

Stripeавтор: Stripe

malamutemayhem/unclick-agent-native-endpoints

110+ tools for AI agents spanning social media, finance, gaming, music, AU-specific services, and utilities. Zero-config local tools plus platform connectors. n

malamutemayhemавтор: malamutemayhem

whiteknightonhorse/APIbase

Unified API hub for AI agents with 56+ tools across travel (Amadeus, Sabre), prediction markets (Polymarket), crypto, and weather. Pay-per-call via x402 micropa

whiteknightonhorseавтор: whiteknightonhorse

trackerfitness729-jpg/sitelauncher-mcp-server

Deploy live HTTPS websites in seconds. Instant subdomains ($1 USDC) or custom .xyz domains ($10 USDC) on Base chain. Templates for crypto tokens and AI agent pr

trackerfitness729-jpgавтор: trackerfitness729-jpg

embeddedlayers/mcp-analytics

Statistical analysis, forecasting, and ML for business data (Shopify, Stripe, WooCommerce, eBay, GA4, Search Console). Upload a CSV or connect live data sources

embeddedlayersавтор: embeddedlayers

carrierone/verilexdata-mcp

20 structured datasets (NPI healthcare, SEC filings, OFAC sanctions, crypto whales, Polymarket signals, patents, economic indicators) via x402 pay-per-query wit

carrieroneавтор: carrierone

tipdotmd/tip-md-x402-mcp-server

MCP server for cryptocurrency tipping through AI interfaces using x402 payment protocol and CDP Wallet.

tipdotmdавтор: tipdotmd

laundromatic/shopgraph

Structured product data from the open web — Schema.org + AI extraction for e-commerce enrichment. Pay per call via Stripe. [shopgraph.dev](https://shopgraph.dev

laundromaticавтор: laundromatic

mrslbt/xendit-mcp

Xendit payment gateway for Southeast Asia. Invoices, disbursements, balance checks, and bank transfers across Indonesia, Philippines, Thailand, Vietnam, and Mal

mrslbtавтор: mrslbt

@arbitova/mcp-server

Non-custodial on-chain escrow + AI dispute arbitration for agent-to-agent USDC payments on Base. Seven tools covering the full EscrowV1 contract surface: create

jiayuanliang0716-maxавтор: jiayuanliang0716-max

Compare Counterpoise Ledger with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории finance