Skip to content

Commit 7564e13

Browse files
authored
Merge pull request #3 from thedavidweng/plaid-dashboard-login-plans
Add Plaid dashboard flows and complete verification gate
2 parents d0ddc8d + 7206f76 commit 7564e13

68 files changed

Lines changed: 7916 additions & 5092 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ jobs:
1313
os: [ubuntu-latest, macos-latest]
1414
runs-on: ${{ matrix.os }}
1515
steps:
16-
- uses: actions/checkout@v4
16+
- uses: actions/checkout@v6
1717

18-
- uses: actions/setup-go@v5
18+
- uses: actions/setup-go@v6
1919
with:
20-
go-version: "1.25"
20+
go-version-file: 'go.mod'
2121
cache: true
2222

2323
- run: go vet ./...

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
go-version-file: 'go.mod'
2929

3030
- name: Install Cosign
31-
uses: sigstore/cosign-installer@v4.1.1
31+
uses: sigstore/cosign-installer@v4.1.2
3232

3333
- name: Run GoReleaser
3434
uses: goreleaser/goreleaser-action@v7

.github/workflows/website.yml

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,22 @@ jobs:
2222
build:
2323
runs-on: ubuntu-latest
2424
steps:
25-
- uses: actions/checkout@v4
26-
- uses: actions/setup-node@v4
25+
- uses: actions/checkout@v6
26+
- uses: actions/setup-node@v6
2727
with:
28-
node-version: '22'
29-
cache: npm
30-
cache-dependency-path: website/package-lock.json
28+
node-version: '22.12.0'
29+
- uses: oven-sh/setup-bun@v2
30+
with:
31+
bun-version: '1.3'
3132
- name: Install and build
3233
run: |
33-
npm ci
34-
npm audit --audit-level=high
35-
npm run build
34+
bun install --frozen-lockfile
35+
bun run test
36+
bun run build
3637
working-directory: website
3738
- name: Upload Pages artifact
3839
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
39-
uses: actions/upload-pages-artifact@v3
40+
uses: actions/upload-pages-artifact@v5
4041
with:
4142
path: website/dist
4243

@@ -52,4 +53,4 @@ jobs:
5253
url: ${{ steps.deployment.outputs.page_url }}
5354
steps:
5455
- id: deployment
55-
uses: actions/deploy-pages@v4
56+
uses: actions/deploy-pages@v5

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ dist/
2424
# Frontend (Astro).
2525
website/node_modules/
2626
website/dist/
27-
website/test/
2827

2928
# Local test artifacts
3029
.playwright-mcp/

.goreleaser.yaml

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ builds:
1515
- arm64
1616

1717
archives:
18-
- format: tar.gz
18+
- formats: [tar.gz]
1919
name_template: "money_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
2020

2121
checksum:
@@ -26,15 +26,13 @@ release:
2626
owner: thedavidweng
2727
name: money
2828

29-
brews:
29+
homebrew_casks:
3030
- name: money
31+
binaries:
32+
- money
3133
repository:
3234
owner: thedavidweng
3335
name: homebrew-tap
3436
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
35-
directory: Formula
3637
homepage: "https://github.com/thedavidweng/money"
37-
description: "A local-first, self-hostable personal finance backend for AI agents and power users."
38-
license: MIT
39-
install: |
40-
bin.install "money"
38+
description: "Local-first personal finance backend for agents and power users"

IMPLEMENTATION_PLAN.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ The Ray OpenRay documents are especially important. They define the product lang
4141
- A **Sync** updates local data only.
4242
- Read commands must not require AI keys or provider credentials.
4343
- Provider commands require provider credentials and should fail explicitly when missing.
44+
- Plaid Dashboard login may bootstrap local Plaid API credentials, but it remains a Plaid-specific setup convenience: it writes the same Provider credential contract as manual configuration, stores Dashboard auth beside the resolved config, and keeps manual `money providers configure plaid` as the supported fallback if Dashboard contracts reject or drift.
4445

4546
When command, data model, or Provider behavior can be answered by donor code, prefer donor evidence before asking product questions. Use Ray Finance as the primary engineering donor for local data model decisions, Provider sync, Plaid/Bridge flow, encrypted local storage behavior, imports, local annotations, and query fields. Use `monarchmoney-cli` for command naming habits, JSON envelopes, stdout/stderr behavior, exit codes, safety gates, and agent-facing ergonomics. If Ray lacks a feature, do not invent a first-version stable contract from Monarch alone; defer it unless it is required by `money`'s goals. Ask only when donor behavior conflicts with `money` boundaries or a real product trade-off remains.
4647

@@ -451,6 +452,10 @@ Initial commands:
451452
- `money link`
452453
- `money providers configure plaid`
453454
- `money providers configure bridge`
455+
- `money plaid login`
456+
- `money plaid logout`
457+
- `money providers plaid login`
458+
- `money providers plaid logout`
454459
- `money providers plaid link`
455460
- `money providers bridge link`
456461
- `money sync`

README.md

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ Existing personal finance tools either lock data behind a paid SaaS, embed opini
3939
### Quick Start
4040

4141
```bash
42-
# Install via Homebrew (macOS/Linux)
43-
brew install thedavidweng/tap/money
42+
# Install via Homebrew Cask (macOS/Linux)
43+
brew install --cask thedavidweng/tap/money
4444

45-
# Or install via Go (cross-platform)
45+
# Or install via Go (macOS/Linux/cross-platform)
4646
go install github.com/thedavidweng/money/cmd/money@latest
4747

4848
# Initialize configuration and encrypted database (interactive)
@@ -57,6 +57,17 @@ money accounts list --json
5757
money transactions search "Costco" --json
5858
```
5959

60+
If you installed an older Homebrew formula release, migrate to the cask:
61+
62+
```bash
63+
brew update
64+
brew uninstall --formula thedavidweng/tap/money
65+
brew install --cask thedavidweng/tap/money
66+
money version
67+
```
68+
69+
Your local `~/.money` config, secrets, and database are not removed by uninstalling the old formula.
70+
6071
Try it without real credentials:
6172

6273
```bash
@@ -91,6 +102,9 @@ money items remove <id> Remove a linked provider item with cascade d
91102
# Provider Management
92103
money link Link a financial institution
93104
money providers configure <provider> Configure provider credentials
105+
money plaid login Sign in to Plaid Dashboard and fetch API keys
106+
money plaid logout Remove Plaid Dashboard auth; keep API keys
107+
money plaid sandbox link Create and store a Plaid Sandbox Provider Item
94108
money providers plaid link Link a Plaid Provider Item
95109
money providers bridge link Link a Bridge Provider Item
96110
money sync Sync linked provider data (supports --start-date/--end-date)

docs/ARCHITECTURE.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,13 +161,17 @@ When an alternate config path is selected, its default env companion is `.env` i
161161

162162
Provider secret names should follow the provider's common official vocabulary. For Plaid, use `PLAID_CLIENT_ID`, `PLAID_SECRET`, `PLAID_ENV`, `PLAID_PRODUCTS`, `PLAID_COUNTRY_CODES`, and `PLAID_REDIRECT_URI`. For Bridge, use `BRIDGE_CLIENT_ID` and `BRIDGE_CLIENT_SECRET`. For MX, use `MX_CLIENT_ID` and `MX_API_KEY`. For Finicity, use `FINICITY_APP_KEY`, `FINICITY_PARTNER_ID`, and `FINICITY_PARTNER_SECRET`.
163163

164+
Plaid Dashboard login is a Plaid-specific credential bootstrap flow, not a generic Provider interface. `money plaid login` and `money providers plaid login` use the Plaid CLI-compatible Dashboard OAuth path to fetch API keys, then write only `PLAID_CLIENT_ID` and the selected environment's `PLAID_SECRET` through the normal Provider config writer. Dashboard OAuth tokens are stored separately as `plaid-dashboard-auth.json` beside the resolved config file with the same local-user file security expectations as `.env`. If Plaid rejects or changes the private Dashboard contract, the command fails explicitly and tells the user to configure Plaid manually; it must not try alternate private clients, scraping, or hidden fallback behavior.
165+
164166
Plaid defaults to the `transactions` product. Additional products such as `investments` and `liabilities` require explicit configuration and should not expand stable read contracts until their local data model is ready. Recurring transaction streams should be synced when the configured Provider returns them; otherwise `recurring.list` returns an empty local result with a normal success envelope.
165167

166168
Bridge link creates or reuses a Bridge external user ID without creating a `money` user account. By default `money` generates and stores the external user ID in Provider Item state during link. Advanced commands may accept an existing external user ID for reconnect or migration cases.
167169

168170
Provider link flows may use a short-lived localhost callback helper when required by the Provider, such as Plaid Link. This helper is not a required persistent server, local API, daemon, or background service. It should bind only for the active link session, use a random state value, shut down after completion or timeout, and fail explicitly when the callback cannot be started. Interactive link commands should follow GitHub CLI's browser flow: print the authorization URL and wait for the user to press Enter before opening the browser. If the user does not press Enter, nothing is opened and the printed URL remains usable for manual/headless handling. `--no-open` suppresses browser opening for SSH, cron, and headless environments while still printing the URL.
169171

170-
Plaid Link in CLI mode is implemented as a short-lived local Link page, not as a raw Plaid redirect URL. The command creates a Plaid `link_token`, starts a localhost callback/page server, prints the local URL, waits for Enter, then opens that local URL unless `--no-open` is set. The page loads Plaid Link from Plaid's CDN with the `link_token`; on `onSuccess`, it posts the `public_token`, Plaid institution metadata, selected account metadata, and random state back to the localhost helper. The CLI validates state, exchanges the `public_token` for an access token, stores only the encrypted access token and mapped metadata, then shuts down the helper. The helper must not expose general API endpoints or serve real financial data.
172+
Plaid Link in CLI mode is implemented as a short-lived local Link page, not as a raw Plaid redirect URL. The command creates a Plaid `link_token`, starts a localhost callback/page server, prints the local URL, waits for Enter, then opens that local URL unless `--no-open` is set. The page loads Plaid Link from Plaid's CDN with the `link_token`; on `onSuccess`, it posts a `success` callback containing the `public_token`, Plaid institution metadata, selected account metadata, and random state back to the localhost helper. On `onExit`, it posts either a `cancel` callback or an `error` callback with Plaid's error type/code/message plus request and link-session metadata when available. The CLI validates state and same-host origin, exchanges only success callbacks for access tokens, stores only the encrypted access token and mapped metadata, then shuts down the helper. Cancel and Link error callbacks do not exchange a token. The helper must not expose general API endpoints or serve real financial data.
173+
174+
Plaid Link consent product options are explicit configuration/flag inputs. `products` remains the initial product set. `additional_consented_products`, `required_if_supported_products`, and `optional_products` may be configured or passed to Plaid link commands to shape consent without expanding local stable read contracts. Unsupported Plaid product names fail validation before calling Plaid.
171175

172176
Bridge link uses the same browser ergonomics but does not pretend to be Plaid Link. The Bridge adapter creates a connect session, prints the connect URL, waits for Enter before opening unless `--no-open` is set, then polls or checks provider item state according to Bridge's API. If Bridge institution discovery is unavailable in the first adapter, `money link` omits Bridge from institution-first choices and `money providers bridge link` remains the supported path.
173177

docs/CONFIG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ providers:
2121
environment: sandbox
2222
products: [transactions]
2323
country_codes: [US]
24+
additional_consented_products: [investments]
25+
required_if_supported_products: [liabilities]
26+
optional_products: [auth]
2427
redirect_uri:
2528
env: PLAID_REDIRECT_URI
2629
bridge:
@@ -30,7 +33,7 @@ providers:
3033
env: BRIDGE_CLIENT_SECRET
3134
```
3235
33-
Direct scalar values are allowed only for non-secrets such as `database.path`, `providers.plaid.environment`, `products`, and `country_codes`. Direct scalar secrets are accepted for manually edited files, but config loading emits a structured warning recommending `.env` references.
36+
Direct scalar values are allowed only for non-secrets such as `database.path`, `providers.plaid.environment`, `products`, `country_codes`, and Plaid Link consent product lists. Direct scalar secrets are accepted for manually edited files, but config loading emits a structured warning recommending `.env` references.
3437

3538
## Path Resolution
3639

@@ -60,6 +63,8 @@ Profile names must be alphanumeric, hyphen, or underscore only; path traversal c
6063

6164
Environment variables complete explicit references; they do not form a magic override chain. For example, `PLAID_SECRET` is used only when config says `secret: { env: PLAID_SECRET }` or a setup/configure command writes that reference.
6265

66+
Plaid Dashboard OAuth bootstrap state is stored outside YAML at `plaid-dashboard-auth.json` beside the resolved config file. It is local bootstrap state for `money plaid login`, written `0600`, and may include Dashboard access/refresh tokens plus selected `team_id` and `client_id`. Provider API credentials still use the normal `.env` plus YAML `env:` references model.
67+
6368
## Pseudocode
6469

6570
```text

docs/CONTRACTS.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,19 +152,39 @@ Provider errors are classified as:
152152

153153
Reconnect-required provider states are machine-readable through item status or classified provider errors. Removed provider transactions are soft-deleted: normal reads exclude them, `--removed include|only` can show them, and permanent purge is reserved for an explicit confirmed cleanup command.
154154

155+
Shared command error codes include:
156+
157+
| Code | Category | Retryable | Exit code | Meaning |
158+
| --- | --- | --- | --- | --- |
159+
| `BASE_CONFIG_MISSING` | `config` | false | 3 | `money plaid login` was run before the base config exists. |
160+
| `NOT_LOGGED_IN` | `auth` | false | 3 | Dashboard-only operation needs Plaid Dashboard auth. |
161+
| `TEAM_SELECTION_REQUIRED` | `validation` | false | 2 | Multiple Plaid Dashboard teams exist and no interactive or `--team` selection was available. |
162+
| `API_KEYS_FETCH_REQUIRED` | `auth` | true | 3 | Dashboard auth exists but Plaid API keys still need to be fetched. |
163+
| `DASHBOARD_TOKEN_REFRESH_FAILED` | `auth` | false | 3 | Stored Dashboard refresh token cannot refresh; rerun `money plaid login`. |
164+
| `DASHBOARD_CONTRACT_CHANGED` | `api` | false | 6 | Plaid Dashboard private response shape no longer matches the known CLI-compatible contract. |
165+
| `PLAID_DASHBOARD_LOGIN_REJECTED` | `auth` | false | 3 | Plaid rejected the CLI-compatible Dashboard OAuth path. |
166+
| `PLAID_CREDENTIALS_OVERWRITE_REQUIRED` | `safety` | false | 10 | Existing Plaid API credentials would be replaced and `--force` is required. |
167+
| `PLAID_ENVIRONMENT_NOT_PROVISIONED` | `validation` | false | 2 | Dashboard returned no secret for the selected Plaid environment. |
168+
| `READ_ONLY_VIOLATION` | `safety` | false | 4 | The command would mutate local config, env, store, or auth files while read-only mode is enabled. |
169+
155170
## Examples
156171

157172
```bash
158173
money setup --json
159174
money doctor --json
160175
money version --json
161176
money providers configure plaid --client-id ... --secret ... --environment sandbox --json
177+
money plaid login --json
178+
money providers plaid login --json
179+
money plaid logout --json
162180
money demo accounts list --json
163181
money demo transactions list --json --merchant Coffee --pending true --limit 10
164182
money demo transactions search coffee --json --limit 5
165183
money sync --json
166184
money sync --provider plaid --provider-item pi_example --json
167185
money sync --start-date 2024-01-01 --end-date 2024-03-01 --json
186+
money providers plaid link --optional-products auth --additional-consented-products investments
187+
money plaid sandbox link --products transactions --institution-id ins_56
168188

169189
# Read synced data
170190
money investments holdings --json
@@ -174,6 +194,21 @@ money items list --json
174194
money items rename <id> "My Bank" --json
175195
```
176196

197+
## Link Commands
198+
199+
Plaid link commands are human-mode browser flows. JSON mode is rejected for live Link because completion depends on a local callback.
200+
201+
`money link <institution-query>` and `money providers plaid link` support:
202+
203+
- `--no-open`
204+
- `--additional-consented-products`
205+
- `--required-if-supported-products`
206+
- `--optional-products`
207+
208+
The three consent flags accept comma-separated Plaid product names and are validated before Plaid is called. `products` remains the configured initial product set. Canceling Plaid Link exits without exchanging a public token or writing a Provider Item. Plaid Link errors preserve Plaid error type/code/message plus request and link session metadata internally for diagnostics.
209+
210+
`money plaid sandbox link` creates a Plaid Sandbox public token, exchanges it through the same Provider Item persistence path as browser Link, and stores the linked item in the encrypted local store. It only runs when `providers.plaid.environment` is `sandbox`. `balance` is rejected as an initial product instead of being remapped.
211+
177212
## Setup Command
178213

179214
`setup --json` returns:
@@ -239,6 +274,38 @@ Default output is plain text: `money 0.1.0 (commit abc1234)`.
239274
}
240275
```
241276

277+
## Plaid Dashboard Login Commands
278+
279+
`plaid login --json` and `providers plaid login --json` return:
280+
281+
```json
282+
{
283+
"provider": "plaid",
284+
"team_id": "team_...",
285+
"environment": "sandbox",
286+
"keys_written": 2,
287+
"credential_action": "written",
288+
"dashboard_auth_path": "/Users/you/.money/plaid-dashboard-auth.json",
289+
"next_command": "money link <institution-query>",
290+
"config_path": "/Users/you/.money/config.yaml",
291+
"env_path": "/Users/you/.money/.env"
292+
}
293+
```
294+
295+
`plaid logout --json` and `providers plaid logout --json` return:
296+
297+
```json
298+
{
299+
"provider": "plaid",
300+
"dashboard_auth_removed": true,
301+
"dashboard_auth_path": "/Users/you/.money/plaid-dashboard-auth.json",
302+
"api_keys_preserved": true,
303+
"env_path": "/Users/you/.money/.env"
304+
}
305+
```
306+
307+
These commands never include Plaid API secrets, Dashboard OAuth tokens, masked secrets, or reversible previews.
308+
242309
## Monarch Compatibility Notes
243310

244311
The command names and stdout/stderr discipline follow Monarch CLI habits where useful. `money` differs by using object-wrapped collection fields, multi-error envelopes, explicit source provenance, encrypted local storage, and BYOK Provider adapters.

0 commit comments

Comments
 (0)