Tax-aware YNAB budget review and approval-gated write-back for Claude Code. Part of the claude-workbench marketplace.
Status: pre-release / under construction. This repo is being built sprint-by-sprint by the workbench dev team (Lestrade triage → Watson development → Holmes review). Some commands and the MCP launcher referenced below ship in later sprints — each is marked with the sprint it lands in. See
docs/ROADMAP.mdfor the full plan and issue backlog.
A productized weekly financial review for YNAB. It speaks as your own Claude agent by default — or as Hobbes, the shipped default persona, when no agent is configured (the name is configurable — see docs/persona.md). Each run reads your budget, categorizes transactions, flags duplicates, surfaces tax-aware insights, and proposes ledger-only fixes inside YNAB for your approval.
It productizes a proven prototype: a hand-run, deeply tax-aware review that has run as an ad-hoc scheduled task since April 2026. This plugin turns that into a first-class, shareable tool — a formalized persona, a reusable tax-aware methodology, a frozen HTML report template, and approval-gated write-back.
Two things make this plugin unusual among workbench plugins, and both are front and center — not buried:
- It writes back to YNAB. Beyond reading, it can categorize, allocate, fix duplicates, and reconcile inside your budget. Every write waits for your explicit approval, and the plugin never moves real money — see The read / propose / approve loop.
- It handles a financial access token. Your YNAB Personal Access Token is stored only in the macOS Keychain — never in the repo, never in a config file, never logged. See Privacy / where the token lives.
⚠️ Estimates only — not tax advice. Consult a qualified professional before filing or paying.This tool produces estimates for organizational purposes only. It is not tax, legal, or financial advice, it makes simplifying assumptions, and its figures may be incomplete or wrong for your situation. Consult a qualified tax professional before you file or pay. (Canonical wording:
skills/shared/disclaimer.md.)
Currency & tax scope (v1). The review reads your budget's
currency_formatand renders every amount in that currency — correct symbol, symbol placement, separators, and decimal digits — so a non-USD budget (EUR1.234,56 €, JPY¥1,234, …) displays correctly via one shared money helper (assets/format-money.js). This is a presentation capability only: the tax engine is US-only and is not extended to any other jurisdiction. A non-USD budget gets correct currency display but the same US-only tax logic — the tax sections assume US federal rules regardless of budget currency.
Mirrors the sibling workbench plugins: a single entry point dispatches a read-only orchestrator that plans the review, the main conversation drives the interactive review + propose/approve protocol via skills, and a vendored YNAB MCP is the only thing that ever talks to the YNAB API.
┌──────────────────────────────┐
│ /workbench-ynab:ynab-review │ ← entry point
│ (or the scheduled task) │ (Sprint 3)
└───────────────┬──────────────┘
│ dispatches
▼
┌──────────────────────────────┐
│ ynab-orchestrator │ ← read-only sub-agent
│ • inspects budget state │ returns a structured
│ • plans which analyses run │ review PLAN — it never
│ • sizes the data pull │ writes to YNAB
└───────────────┬──────────────┘
│ plan
▼
You ↔ assistant (main) ◁───────── drives the interactive review protocol
│ (12-section methodology, change-set,
│ propose → approve → apply)
│
│ reads config ┌────────────────────────────────────┐
├───────────────────────▶ │ budget · tax-profile · persona │
│ (skills ONLY) │ config — lives OUTSIDE the repo │
│ └────────────────────────────────────┘
│
│ dispatches YNAB verbs
▼
┌──────────────────────────────┐
│ vendored YNAB MCP │ the ONLY thing that talks to the
│ bin/launcher.sh → node │ YNAB API. Receives the token +
│ @dizzlkheinz/ynab-mcpb │ its package-native env — never
│ (frozen bundle) │ this plugin's config.
└───────────────┬──────────────┘
│ HTTPS
▼
💰 YNAB API
The config split is deliberate and load-bearing:
| Layer | Reads | Never sees |
|---|---|---|
| 🧠 Skills (main conversation) | budget id, tax profile, persona config | the YNAB token |
| 🔌 Vendored YNAB MCP (tool server) | the YNAB token (YNAB_ACCESS_TOKEN) + its package-native env |
budget / tax / persona config |
The skills do all the tax and persona reasoning from config; the MCP does all the API talking from the token. Neither crosses into the other's lane — the token never reaches a skill, and the config never reaches the MCP.
- macOS (darwin) — setup stores the YNAB token in the macOS Keychain via
security(1), and the launcher + vendored MCP run on macOS. - System
node— Node >= 24 (the latest LTS — by policy the supported floor is the newest Node LTS major at the last bundle vendoring) — the vendored YNAB MCP is a self-contained Node bundle launched bybin/launcher.sh; a system Node runtime at or above the pinned floor must be onPATH. The bundle runs onnodedirectly — there is nonode_modulesinstall step and nonpx-on-demand. The floor is pinned invendor/ynab-mcp/NODE_VERSIONand enforced by both setup and the launcher; it is re-checked against the current LTS line on every bundle bump by the re-vendor flow (see docs/vendoring.md, which governs the pinned bundle version). jq— used by the launcher and setup tooling to read and validate JSON config.security(1)— the macOS Keychain CLI; stores and retrieves the YNAB Personal Access Token.workbench-core@claude-workbench— shared memory vault, session lifecycle, and plugin infrastructure (also the source of the agent name the persona falls back to).
claude plugin marketplace add mike-bronner/claude-workbench
claude plugin install workbench-ynab@claude-workbench
Link your clone into the skills directory, which Claude Code loads local plugin checkouts from:
git clone https://github.com/mike-bronner/workbench-ynab
mkdir -p ~/.claude/skills
ln -s "$PWD/workbench-ynab" ~/.claude/skills/workbench-ynab
The link name must match the plugin name in
.claude-plugin/plugin.json. Claude Code loads the
checkout as workbench-ynab@skills-dir; confirm it with claude plugin list.
claude plugin install takes a plugin name resolved against a registered
marketplace, not a filesystem path, and claude plugin marketplace add takes a
marketplace — this repo ships a plugin manifest, not a marketplace manifest.
Neither command accepts this checkout. See
docs/fresh-install-test.md for the verification.
After installing either way, restart Claude Code so the plugin's agents, skills, commands, and the vendored MCP server are picked up.
First-run configuration is a one-time step. Run the setup command (ships in Sprint 1):
/workbench-ynab:setup
It:
- Seeds the Keychain token — prompts for your YNAB Personal Access Token and stores it in the macOS Keychain (never in the repo or a config file).
- Writes config — creates
config.jsonin the plugin data directory (outside the repo) with your budget, tax profile, and persona settings. - Pre-approves the tool glob — pre-approves the namespaced
mcp__plugin_workbench-ynab_ynab__*read tools so reviews run without per-call prompts. Write verbs stay behind the approval gate. - Offers legacy migration — detects and offers to retire the old hand-run prototype and its scheduled task.
Get a YNAB Personal Access Token from YNAB → Account Settings → Developer Settings.
Each review reads your budget (read-only) and produces a tax-aware report organized into twelve analysis sections: transaction hygiene (tax-aware classification, duplicate detection, uncategorized, stale uncleared), spending and budget health (cost-cutting, budget health, unusual/large, reconciliation status), then a six-sub-score financial health score, a forecast, a prioritized action list, and a year-to-date tax summary.
The tax-aware sections are driven entirely by a data-driven, shareable tax profile — never hard-coded owner detail. The section-by-section table — what each analysis surfaces, which tier runs it, and how it diverges from the prototype — is docs/methodology.md; for the tax model and profile schema, see docs/tax-mapping.md and assets/tax/README.md.
This is the core safety story. Write-back never happens silently.
- Read — the plugin reads your YNAB budget, accounts, categories, and transactions.
- Propose — it surfaces the fixes it found (categorizations, Ready-to-Assign allocations, duplicate fixes, reconciliations) as a single proposed change-set. Nothing has touched the ledger yet.
- Approve — it waits for your explicit approval before any write. One approval covers one batch — approving one change-set never pre-approves the next.
- Apply — only after approval, the batch is applied. Apply defaults to a dry-run that reports exactly what would change.
Every write is ledger-only, strictly limited to:
- Categorize — assign a transaction to a category.
- Allocate — move money from Ready-to-Assign into a category.
- Fix duplicates — delete a double-entered transaction.
- Reconcile — bring an account's cleared/reconciled balance into line.
The plugin NEVER moves real money. It initiates no transfers and no payments to the outside world — no money ever leaves or moves between your real accounts. This is enforced structurally: every change-set carries a money_movement: false invariant that cannot be set otherwise, and a runtime guardrail hard-blocks any apply that maps to a money-moving operation. The full safety model — allowed vs. forbidden operations, the batch-approval gate, and the exact write tools — is docs/write-back-safety.md; the machine contract is assets/changeset-contract.md.
Your YNAB Personal Access Token is stored ONLY in the macOS Keychain. It is never committed to the repo, never written to a config file, never logged, and is injected into the vendored MCP at launch time as the YNAB_ACCESS_TOKEN environment variable — read fresh from the Keychain on every launch, never persisted elsewhere.
The token lives under the Keychain service ynab-mcp, account access-token.
Store it (setup does this for you):
security add-generic-password -s "ynab-mcp" -a "access-token" -w "$TOKEN" -URead it back (the launcher does this at MCP start):
security find-generic-password -s "ynab-mcp" -a "access-token" -wConfiguration lives outside the repo. Your budget id, tax profile, and persona settings live at:
~/.claude/plugins/data/workbench-ynab-claude-workbench/config.json
This path is deliberately outside the installed plugin tree, so plugin updates never clobber it — re-installing or upgrading workbench-ynab leaves your config and tax profile untouched. The config never holds the token (that's Keychain-only), and it is never committed.
Generated reports & data are unencrypted, plaintext financial records. Every review run writes files to your local disk — the HTML report (default ~/Documents/Claude/Reports/) plus the audit log, monitor state, and estimated-tax tracker under the data directory above. Together they hold your complete transaction history, balances, payees, and tax detail in cleartext. The plugin creates them owner-only (mode 0600, directories 0700) at write time, but does not encrypt them. Two things to know:
⚠️ ~/Documentsmay sync to iCloud Drive. With macOS Desktop & Documents syncing enabled, your financial reports can be silently uploaded to iCloud. Keep them on local, disk-encrypted storage (enable FileVault) and don't point.report.output_dirat a shared or cloud-synced folder unless you intend those records to travel there.- Prune old reports. Reports accumulate one file per run.
bin/ynab-prune.shremoves reports older than a retention threshold (default 30 days, dry-run by default). See the Generated Artifacts section ofSECURITY.mdfor the full artifact inventory, locations, and retention policy. - Removing the plugin leaves all of this behind. Uninstalling
workbench-ynabdoes not remove the scheduled tasks, the Keychain token, thesettings.jsonpre-approvals, or the data directory. Run/workbench-ynab:uninstallbefore you remove the plugin — it tears down each one, asks before touching your financial records, and keeps them by default. If the plugin is already gone, follow the by-hand checklist indocs/uninstall.md. Either way, revoke the token at YNAB — deleting the Keychain entry does not revoke server-side access.
Every command is namespaced under /workbench-ynab:. The plugin is mid-build; the Ships in column marks the sprint each command lands in (see docs/ROADMAP.md).
| Command | Description | Ships in |
|---|---|---|
/workbench-ynab:setup |
First-run setup: seed the YNAB token into the Keychain, write config, pre-approve the read-tool glob, offer legacy migration. | Sprint 1 |
/workbench-ynab:ynab-review |
Run a tax-aware review for a tier (weekly / monthly / quarterly-tax / annual); produces the report and the proposed change-set. | Sprint 3 |
/workbench-ynab:ynab-weekly-review |
Run the weekly review ad-hoc — plans via the orchestrator, then forces the weekly tier only. | Sprint 3 |
/workbench-ynab:ynab-monthly-review |
Run the monthly review ad-hoc — plans via the orchestrator, then forces the monthly tier only. | Sprint 3 |
/workbench-ynab:ynab-quarterly-tax-review |
Run the quarterly estimated-tax review ad-hoc — plans via the orchestrator, then forces the quarterly-tax tier only. | Sprint 3 |
/workbench-ynab:ynab-annual-review |
Run the annual review ad-hoc — plans via the orchestrator, then forces the annual tier only. | Sprint 3 |
/workbench-ynab:ynab-apply |
Review a proposed change-set and, on explicit approval, apply the ledger-only writes (dry-run by default). | Sprint 4 |
/workbench-ynab:ynab-migrate |
Retire the legacy hand-run prototype: the old Desktop connector, its token, and the prototype scheduled tasks/directories. | Sprint 5 |
/workbench-ynab:ynab-prune |
Prune old generated reports under the retention policy — previews by default, deletes only with --apply. Keeps unencrypted financial history from accumulating unbounded. |
Sprint 5 |
/workbench-ynab:uninstall |
Tear down every piece of system state setup created — the two scheduled tasks, the Keychain token, the settings.json pre-approvals, and (only on explicit confirmation) the plaintext data directory. Idempotent. By-hand equivalent: docs/uninstall.md. |
Sprint 5 |
/workbench-ynab:ynab-monitor |
Run one proactive between-run monitoring pass: advance the monitor state store from fresh YNAB data, run the four alert detectors (overdrawn, large/unusual transaction, budget overrun, bill due), dispatch any new finding, and exit silently when nothing is alert-worthy. | Sprint 6 (v-Next) |
/workbench-ynab:ynab-portfolio |
Run the cross-budget portfolio rollup: one consolidated report across every configured budget — combined net worth, aggregate income vs spending, cross-budget Ready-to-Assign, a unified health score, and a single YTD tax picture across the business-tagged budgets. Read-only. See docs/portfolio-rollup.md. |
Sprint 6 (v-Next) |
The plugin and its vendored YNAB MCP bundle are pinned together in git. The bundle is @dizzlkheinz/ynab-mcpb, version-frozen at 0.27.1 and vendored as a self-contained vendor/ynab-mcp/index.cjs — no npx-on-demand, no floating dependency. The pinned version, tarball hash, and provenance are recorded in vendor/ynab-mcp/vendored.json; the bundle is only ever updated via the re-vendor script (bin/revendor.sh), never by hand. See docs/vendoring.md for how to update the bundle, verify the result, and the version-marker format.
Pinning both versions in git means a given workbench-ynab commit always runs against the exact MCP bundle it was tested with — boot is offline, frozen, and reproducible.
Two version numbers live in this repo. They track different things, are deliberately independent, and are never co-bumped.
- The plugin's own version lives in
.claude-plugin/plugin.json(currently0.1.1). This is the only version release automation bumps — the release workflow's sole bump target is.claude-plugin/plugin.json, and no other manifest, JSON, or config file in the repo carries a release version. It starts at0.1.0and is cut to1.0.0at first release. - The vendored YNAB MCP version is recorded in
vendor/ynab-mcp/vendored.json(@dizzlkheinz/ynab-mcpb@0.27.1). It is frozen, provenance-only — a record of exactly which upstream bundle is checked into git, not a number this plugin releases against. Release automation never touches it; it changes only when the bundle is deliberately re-vendored.
The two schemes do not move together: bumping the plugin version leaves the vendored bundle version untouched, and re-vendoring the bundle leaves the plugin version untouched.
This project is licensed under the MIT License — see the LICENSE file for details.