{"id":51381209,"url":"https://github.com/afurm/mono-ledger-sync","last_synced_at":"2026-07-03T16:34:18.359Z","repository":{"id":357142898,"uuid":"1235446887","full_name":"afurm/mono-ledger-sync","owner":"afurm","description":"Local-first TypeScript CLI for syncing Monobank transactions into a private personal finance ledger.","archived":false,"fork":false,"pushed_at":"2026-06-19T10:27:10.000Z","size":1097,"stargazers_count":1,"open_issues_count":22,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-19T12:06:47.408Z","etag":null,"topics":["banking","banking-api","cli","finance","ledger","local-first","monobank","monobank-api","open-source","personal-finance","privacy","sqlite","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/mono-ledger-sync","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/afurm.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":"docs/support-bundle-retention.md","governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-11T10:33:43.000Z","updated_at":"2026-06-18T18:12:05.000Z","dependencies_parsed_at":null,"dependency_job_id":"ac924243-b732-4195-bfe2-84e28ee74c98","html_url":"https://github.com/afurm/mono-ledger-sync","commit_stats":null,"previous_names":["afurm/mono-ledger-sync"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/afurm/mono-ledger-sync","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/afurm%2Fmono-ledger-sync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/afurm%2Fmono-ledger-sync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/afurm%2Fmono-ledger-sync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/afurm%2Fmono-ledger-sync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/afurm","download_url":"https://codeload.github.com/afurm/mono-ledger-sync/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/afurm%2Fmono-ledger-sync/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35094064,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-03T02:00:05.635Z","response_time":110,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["banking","banking-api","cli","finance","ledger","local-first","monobank","monobank-api","open-source","personal-finance","privacy","sqlite","typescript"],"created_at":"2026-07-03T16:34:15.126Z","updated_at":"2026-07-03T16:34:18.339Z","avatar_url":"https://github.com/afurm.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mono-ledger-sync\n\n[![npm version](https://img.shields.io/npm/v/mono-ledger-sync.svg)](https://www.npmjs.com/package/mono-ledger-sync)\n[![CI](https://github.com/afurm/mono-ledger-sync/actions/workflows/ci.yml/badge.svg)](https://github.com/afurm/mono-ledger-sync/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\nLocal-first TypeScript app for syncing Monobank transactions into a private personal finance ledger.\n\n`mono-ledger-sync` is an early TypeScript app/package for building a local-first Monobank ledger workflow. The product direction is a local web app backed by a local API server and SQLite. The project is designed for people who want to own their financial data locally: tokens and transaction data stay on the user's machine, production sync talks to Monobank directly, and raw Monobank payloads are preserved separately from normalized ledger entries.\n\n## Status\n\nThis package ships a local-first Monobank personal finance workspace: a Fastify local app, SQLite-backed storage, a typed Monobank HTTP adapter, ledger queries, webhook hint recording, deterministic exports, and a compact browser UI. The product talks to the real Monobank personal API at `https://api.monobank.ua` for a real user session. A clearly labeled first-run demo uses synthetic fixtures and is erased before a valid live token is activated.\n\n## Live by default\n\nFor a real user, every account, jar, statement, currency rate, and webhook comes from that user's own Monobank account via the personal API. The Ukrainian first-run flow leads with **Увійдіть через Monobank**: open `https://api.monobank.ua/` to create a personal API token, paste it into the local app, and the local Fastify server validates the token with a live `GET /personal/client-info` before saving it. The product never sends the token to any server controlled by this project.\n\nThe Monobank personal API is for the user's own data on their own machine. Do not use this project as a hosted, team, or business service for other people's banking data.\n\n`MONOBANK_TOKEN` is a CI-only escape hatch. It exists so the opt-in live smoke test (`npm run test:live-monobank`, gated by `MONO_LEDGER_SYNC_LIVE_MONOBANK_TESTS=1`) can call the real bank without going through the in-app paste-token flow. It is not part of the user-facing product flow and should not be set in a normal local dev shell.\n\n## Release notes\n\n- [v1.0.0](docs/release/1.0.0.md): completes the public-ready local app with an isolated first-run demo, native OS credential storage, per-account partial-sync diagnostics, completed budget and recurring workflows, transaction performance guardrails, accessibility checks, recovery/support contracts, and fixture-safe UI screenshots. The personal-token, loopback-only, local SQLite product boundary remains unchanged.\n- [v0.4.0](docs/release/0.4.0.md): adds Parquet ledger exports, redacted SQLite snapshots, DuckDB-friendly BI views, local BI/accountant handoff docs, raw payload retention controls, and a mock-only Monobank provider/FOP spike behind an experimental Settings flag. Provider mode does not graduate; the personal-token local app remains the default path. Test count 239 pass / 0 fail / 4 skipped.\n- [v0.3.0](docs/release/0.3.0.md): modularization of the local API server (`src/server/index.ts` 4419 → 1765 lines, 11 route modules under `src/server/routes/`) and the Vite web app (`src/web/App.tsx` 11154 → 1111 lines, 10 per-route components under `src/web/routes/`, new `src/web/api-types.ts`). Adds manual recurring items, on-the-fly category-rule creation, ledger review states (migration `0022`), local cockpit workflow settings (migration `0023`), the `local_exports` audit log (migration `0024`), richer ledger export filters, an `includedInReports` account toggle, and transfer-aware cashflow and savings-rate reports. Release workflow now runs `npm run smoke:web` (Playwright) as a gate. Test count 232 pass / 0 fail / 4 skipped.\n- [v0.2.0](docs/release/0.2.0.md): live-by-default sign-in flow, bulk edit, category version history, diagnostics endpoint, reporting suite, recurring-payments engine, and a long-running privacy/security hardening pass. 228 commits since `v0.1.1`; test count 226 pass / 0 fail / 4 skipped.\n- v0.1.1: GitHub Release `v0.1.1`; `mono-ledger-sync@0.1.1` on npm. Public discoverability metadata follow-up.\n- v0.1.0: Initial public package release. `mono-ledger-sync@0.1.0`; initial commit `5b1b6c2`.\n\n## Goals\n\n- Sync personal Monobank transactions into a durable local ledger.\n- Keep banking tokens and personal finance data off hosted project servers.\n- Support explicit fixture-backed development for tests and offline contributor workflows.\n- Provide a small TypeScript API, local server boundary, and browser UI that can grow into SQLite storage, exports, reports, and a Vite web app.\n\n## Quick start\n\n```sh\nnpx mono-ledger-sync\n```\n\nOpen `http://127.0.0.1:3000`, then choose one path:\n\n1. Select **Переглянути демо-дані** to inspect a synthetic local ledger without\n   a token. Every route displays a demo-data warning.\n2. For live data, create a personal token at `https://api.monobank.ua/`, open\n   **Налаштування**, paste the token, confirm local-only handling, and save it.\n3. Run sync from **Синхронізувати**, review transactions, configure\n   budgets/recurring payments, then use the Export wizard or create a local\n   backup.\n\nSaving a valid live token while demo mode is active clears every demo ledger\nrow before switching sources. Demo and live transactions are never mixed.\n\nThe app stores its SQLite database in the local application data directory. Use\n`MONO_LEDGER_SYNC_DATA_DIR` to select another location before first startup.\nRecovery and profile moves are documented in\n[Migration and recovery](docs/migration-and-recovery.md).\n\n## Screenshots\n\n![Daily money overview](docs/assets/overview.png)\n\n![Transaction review workflow](docs/assets/transactions.png)\n\n![Export wizard and privacy preview](docs/assets/exports.png)\n\n## Local development\n\n```sh\nnpm install\nnpm run dev\n```\n\n`npm run dev` builds the package and starts the local Fastify server at\n`http://127.0.0.1:3000`. The browser UI starts in Monobank API mode. The\nfirst-run demo is available without changing development environment variables.\n\nExport presets are available through the local API and browser UI for\n`accountant-handoff`, `monthly-personal-finance`, `bookkeeping`,\n`budget-analysis`, and `raw-transaction-archive`. CSV, JSON, JSONL, journal\nCSV, Parquet, and redacted SQLite snapshot exports are deterministic for the\nsame database state and filters so users can diff or version their own local\ndata. See [DuckDB workflow](docs/integrations/duckdb.md),\n[local BI](docs/integrations/local-bi.md), and\n[accountant handoff](docs/accountant-handoff.md).\n\nThe in-app sign-in flow is the supported way to start syncing a real Monobank account: open `http://127.0.0.1:3000`, paste a personal API token from `https://api.monobank.ua/`, and the local server validates it before saving. `MONOBANK_TOKEN` is for the opt-in live smoke test only — see **Live by default** above.\n\n## Library API\n\n```ts\nimport { createSyncPlan } from \"mono-ledger-sync\";\n\nconst plan = createSyncPlan({\n  profile: \"default\",\n  source: \"monobank\",\n});\n```\n\n## Privacy model\n\n- No hosted token relay.\n- No default cloud storage.\n- No hosted account is required for local browsing, local backups, or local exports.\n- Personal API tokens use macOS Keychain Services, Windows Credential\n  Manager/Credential Locker, or Linux Secret Service, with an explicit\n  session-only fallback when the provider is unavailable.\n- Use personal Monobank API tokens only for your own data on your own machine; do not use this project as a hosted or shared service for other people's banking data.\n- Webhook events should be treated as hints and reconciled through statement pulls.\n- Logs and errors must redact tokens and sensitive financial identifiers.\n- Secure token persistence should follow\n  [`docs/decisions/0008-secure-token-storage.md`](docs/decisions/0008-secure-token-storage.md):\n  use OS credential stores for packaged builds, keep SQLite out of token\n  storage, and fall back to session-only handling when no secure provider is\n  available.\n- Raw statement payload retention defaults to 90 days and can be set to `0` to\n  keep raw payloads indefinitely. See\n  [privacy and retention review](docs/privacy-retention-review.md).\n\n## Webhook endpoint safety\n\nThe local server exposes webhook settings in `/api/app/config.webhook`:\n\n- `webhook.host`: usually `127.0.0.1`\n- `webhook.port`: local API port\n- `webhook.path`: one high-entropy per-instance path (for example `/api/webhooks/monobank-ab12...`)\n- `webhook.url`: full URL to register in Monobank personal webhook settings\n\nThe default `webhook.url` is a loopback URL for the local app. It is useful for\nlocal health checks, but Monobank cannot deliver webhooks to `127.0.0.1` from\noutside your machine.\n\nIf you need live personal webhook delivery while developing locally:\n\n1. Start the local app with the intended port, then read the current\n   `webhook.path` from the UI or `/api/app/config`.\n2. Expose only that local port through a temporary HTTPS tunnel controlled by\n   you.\n3. Register the tunnel origin plus the exact high-entropy `webhook.path` in\n   Monobank personal webhook settings.\n4. Keep the tunnel open only while you are actively using it, then remove the\n   webhook URL from Monobank or stop the tunnel.\n\nDo not bind the local API to a public interface unless passcode protection is\nenabled, reuse stale tunnel URLs, share the tunnel URL publicly, or put tokens\nin webhook URLs. The route path is an unguessable local receiver path, not an\nauthentication system.\n\nWebhook payloads are recorded as local hints and are reconciled through\nstatement pulls before they affect the final ledger state.\n\n## Disclaimer\n\nThis project is a local data ownership tool, not financial, tax, accounting, or legal advice. Verify exported data before making financial decisions or sending records to an accountant.\n\n## Development\n\n```sh\nnpm install\nnpm run dev\nnpm run typecheck\nnpm test\nnpm run test:live-monobank\nnpm run coverage\nnpm run format\n```\n\n`npm run dev` starts the local Fastify app server on `http://127.0.0.1:3000`.\nThe app exposes the browser UI at `/`, health and configuration endpoints,\nledger summary/account/transaction endpoints, sync run endpoints, webhook\nhint ingestion, and CSV/JSON/JSONL exports. The default product path is\nlive — the local server talks to `https://api.monobank.ua` once a personal\nAPI token is saved in the in-app sign-in flow. Sanitized fixtures remain\navailable only through explicit development mode; pass\n`MONO_LEDGER_SYNC_SOURCE=fixture npm run dev` to skip live calls.\nUse `MONO_LEDGER_SYNC_PORT=3001 npm run dev` if port 3000 is already in use.\nThe local API binds to `127.0.0.1` by default. Binding to a non-loopback host\nrequires `MONO_LEDGER_SYNC_ACCESS_PASSCODE`; the server protects the browser UI\nand API with Basic auth while keeping the high-entropy Monobank webhook receiver\navailable for delivery.\nUse `npm run web:dev` when working on the Vite UI; it starts the same local API\nserver and proxies browser requests through `http://127.0.0.1:5173`.\n\n`npm run test:live-monobank` is an opt-in smoke test for the real Monobank\nadapter. It skips unless `MONO_LEDGER_SYNC_LIVE_MONOBANK_TESTS=1` and\n`MONOBANK_TOKEN` are set, so default local and pull-request validation never\ncalls the live API. This is the only supported use of `MONOBANK_TOKEN`.\n\nThe local API token endpoint stores saved Monobank tokens through the default\ntoken store. Linux uses Secret Service, macOS uses Keychain Services, and\nWindows uses Credential Manager/Credential Locker. Secrets travel through\nstdin, not command arguments. Unsupported or unavailable secure stores fall\nback to the running session instead of writing plaintext credentials to SQLite\nor config files.\n\n### Rotating a Monobank token\n\nRotate the personal API token from the local settings screen or the local API;\ndo not edit SQLite, generated exports, or config files to change credentials.\n\n1. Create a replacement personal token in Monobank.\n2. Open **Settings -\u003e Monobank token**, paste the replacement token, confirm the\n   local-only handling checkbox, and save it for the active local profile. The\n   same flow is available through `POST /api/app/token` with the active\n   `profile` and replacement `token`.\n3. Confirm the token status in settings. Persistent secure storage means the\n   token survived through the OS credential store; session-only storage means it\n   is available only until the local server process stops.\n4. Run a Monobank sync after saving the replacement token.\n5. Revoke the old token in Monobank after the replacement token works.\n\nIf a token may have been exposed, remove it from **Settings -\u003e Monobank token**\nwith the explicit deletion checkbox, revoke it in Monobank, and restart the\nlocal server if the UI reported session-only token handling.\n\nRelease automation is documented in [docs/release.md](docs/release.md).\nDomain contracts are documented in [docs/domain-model.md](docs/domain-model.md).\nThe privacy and security threat model is documented in\n[docs/threat-model.md](docs/threat-model.md).\nThe v1 distribution, accessibility, localization, release, recovery, and\nsupport contracts are documented in\n[distribution](docs/decisions/0012-v1-distribution.md),\n[accessibility](docs/accessibility.md),\n[localization](docs/decisions/0013-localization.md),\n[release checklist](docs/v1-release-checklist.md),\n[migration and recovery](docs/migration-and-recovery.md), and\n[support boundary](docs/support-boundary.md).\nCommon local workflows are documented in\n[examples/sample-workflows](examples/sample-workflows).\nStart with the\n[minimum local product flow](examples/sample-workflows/minimum-product-flow.md)\nfor the install, token, sync, review, categorization, and export path.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fafurm%2Fmono-ledger-sync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fafurm%2Fmono-ledger-sync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fafurm%2Fmono-ledger-sync/lists"}