{"id":51446410,"url":"https://github.com/gordcurrie/pacioli","last_synced_at":"2026-07-05T16:30:43.099Z","repository":{"id":368707750,"uuid":"1253478977","full_name":"gordcurrie/pacioli","owner":"gordcurrie","description":"ACB tracker for Canadian investors — Questrade sync, CRA-compliant capital gains, multi-user","archived":false,"fork":false,"pushed_at":"2026-07-01T20:46:57.000Z","size":292,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-01T22:14:10.037Z","etag":null,"topics":["acb","canada","go","htmx","personal-finance","sqlite"],"latest_commit_sha":null,"homepage":null,"language":"Go","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/gordcurrie.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"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-29T14:00:38.000Z","updated_at":"2026-07-01T20:43:55.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/gordcurrie/pacioli","commit_stats":null,"previous_names":["gordcurrie/pacioli"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/gordcurrie/pacioli","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gordcurrie%2Fpacioli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gordcurrie%2Fpacioli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gordcurrie%2Fpacioli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gordcurrie%2Fpacioli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gordcurrie","download_url":"https://codeload.github.com/gordcurrie/pacioli/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gordcurrie%2Fpacioli/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35162070,"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-05T02:00:06.290Z","response_time":100,"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":["acb","canada","go","htmx","personal-finance","sqlite"],"created_at":"2026-07-05T16:30:42.364Z","updated_at":"2026-07-05T16:30:43.087Z","avatar_url":"https://github.com/gordcurrie.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Pacioli\n\nAdjusted Cost Base (ACB) tracker for Canadian investors. Multi-user web app with Questrade API sync, Canaccord CSV import, and CRA-compliant capital gains reporting.\n\nNamed after [Luca Pacioli](https://en.wikipedia.org/wiki/Luca_Pacioli), the father of double-entry bookkeeping.\n\n## Features\n\n**ACB \u0026 Tax**\n- ACB tracking per security across all non-registered accounts (CRA average-cost method)\n- CAD and native-currency cost tracking with BoC exchange rates\n- Commission and FX conversion fee inclusion in ACB\n- Norbert's Gambit auto-detection and transaction pairing (journal and direct-path)\n- Return of Capital ACB reductions from T3 distribution data\n- Superficial loss detection with carry-forward adjustments per CRA rules\n- Capital gains report by tax year with CSV export\n\n**Data Import**\n- Questrade API sync (margin, cash, TFSA, RRSP, RESP, LRSP, SRSP accounts)\n- Canaccord Genuity activity CSV import\n- Manual transaction entry\n\n**Auth \u0026 Admin**\n- Multi-user with email/password login\n- TOTP two-factor authentication with recovery codes\n- Admin: user management, audit log viewer\n\n## Setup\n\n### Prerequisites\n\n- Go 1.26+\n- `golangci-lint` — `brew install golangci-lint`\n- `gosec` — `go install github.com/securego/gosec/v2/cmd/gosec@latest`\n- `govulncheck` — `go install golang.org/x/vuln/cmd/govulncheck@latest`\n- `air` — `go install github.com/air-verse/air@latest` (hot reload for local dev)\n\n### Local development\n\n```bash\ngit clone https://github.com/gordcurrie/pacioli\ncd pacioli\ngo mod download\nmake install-hooks   # installs pre-commit lint/sec/vuln checks\n```\n\nSet up environment variables (uses [direnv](https://direnv.net/)):\n\n```bash\ncp .envrc.example .envrc\n# TOKEN_ENCRYPTION_KEY is required for Questrade import and TOTP 2FA.\n# Generate: openssl rand -hex 32\ndirenv allow\n```\n\nStart the server:\n\n```bash\nmake dev   # hot reload via air\n```\n\nServer starts at `http://localhost:8080`. On first visit, a setup wizard creates the admin user.\n\n### Docker\n\nGenerate your encryption key once and save it — rotating it makes existing Questrade tokens and TOTP secrets unreadable:\n\n```bash\n[ -f .env ] || echo \"TOKEN_ENCRYPTION_KEY=$(openssl rand -hex 32)\" \u003e .env\ndocker compose up --build\n```\n\nWithout `TOKEN_ENCRYPTION_KEY` set, Questrade import and TOTP 2FA are unavailable.\n\nData persists in the `pacioli-data` Docker volume.\n\n## Environment Variables\n\n| Variable | Default | Purpose |\n|---|---|---|\n| `DATABASE_DSN` | `pacioli.db` | SQLite file path |\n| `ADDR` | `:8080` | HTTP listen address |\n| `TOKEN_ENCRYPTION_KEY` | *(unset)* | 64 hex chars (32 bytes) — AES-256-GCM key for Questrade tokens and TOTP secrets. Required for Questrade import and 2FA. Generate: `openssl rand -hex 32` |\n| `SECURE_COOKIES` | `false` | Set `true` in production (HTTPS only) |\n\n## Production\n\nRun behind a TLS-terminating reverse proxy (nginx, Caddy, Traefik). Set `SECURE_COOKIES=true` so session cookies are marked `Secure`.\n\nMinimal nginx snippet:\n\n```nginx\nlocation / {\n    proxy_pass http://127.0.0.1:8080;\n    proxy_set_header Host $host;\n    proxy_set_header X-Real-IP $remote_addr;\n}\n```\n\n**Data**\n\nThe SQLite database is a single file. Back it up by copying it while pacioli is stopped, or use `.backup` via the SQLite CLI for a hot copy:\n\n```bash\nsqlite3 pacioli.db \".backup pacioli.db.bak\"\n```\n\nWith Docker, the database lives in the `pacioli-data` volume. Back it up by mounting the volume into a throwaway container:\n\n```bash\ndocker run --rm -v pacioli-data:/data -v $(pwd):/out alpine \\\n  cp /data/pacioli.db /out/pacioli.db.bak\n```\n\n**First run**\n\nOn first visit the setup wizard prompts for an admin email and password. After setup is complete, the wizard is permanently disabled (no `FIRST_RUN` flag needed).\n\n## Development\n\n```bash\nmake check    # build + test + lint + gosec + govulncheck (also runs on pre-commit)\nmake test     # go test -race ./...\nmake build    # compile to bin/pacioli\nmake run      # go run ./cmd/server\nmake tidy     # go mod tidy\n```\n\n### Logs\n\nStructured logs via stdlib `slog`. Every request emits one line:\n\n```\ntime=... level=INFO msg=request method=GET path=/accounts status=200 latency_ms=3 request_id=3f2a8b1c4e5d6f7a\n```\n\nHandler errors include the same `request_id` for correlation. Service and store layers do not log — errors bubble to the handler.\n\n### Architecture\n\nPer-concept packages in `internal/`. Each owns its domain type(s) and a `Store` interface. SQLite implementations live in `internal/sqlite/`. Business logic (ACB engine, gains calculator, NG detector, ROC service, BoC FX fetcher) lives in `internal/service/`.\n\nSee [CLAUDE.md](CLAUDE.md) for full conventions, Canadian tax domain rules, and architecture decisions.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgordcurrie%2Fpacioli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgordcurrie%2Fpacioli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgordcurrie%2Fpacioli/lists"}