spltty 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3bf97f1aff65eb471face732214bed57413de422a2e1bbae72d562931849db79
4
+ data.tar.gz: aaaf79b631b2972234bf56dbec3a9a2db7c37448addba068fdb84026cc04aa3c
5
+ SHA512:
6
+ metadata.gz: f5d18872e7428a31860edc70d027e4e922ea71c29eb0a008890986cbe1f24cc1ba6449fb47f48683c0b56af1a71157bb92b27a1d11aac4544b26c64df765135c
7
+ data.tar.gz: 3000707668e2ca9604cd5f411ed973dbcac99067192b172783d1a32a19ba2c4ae29afc76ae7f0488c57019376954ccc5e780c99c253a8576739eab8a0b11c892
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Thiago Diniz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,351 @@
1
+ # spltty
2
+
3
+ Shared-expense tracking in **plain markdown ledgers**, with the arithmetic owned by
4
+ a CLI instead of a spreadsheet — and a generated `CLAUDE.md` so an AI agent can do
5
+ the transcription while `spltty` does the math.
6
+
7
+ The data is markdown tables you can read, diff and grep. `spltty` owns the parts
8
+ that must be exact: appending rows, resolving split groups, computing who owes
9
+ whom, and recording settlements.
10
+
11
+ ```sh
12
+ mkdir ~/finances && cd ~/finances
13
+ spltty install # asks who splits what, scaffolds the workspace
14
+ spltty add "Groceries" -v 84.20 -p Ana -m cash -r Both
15
+ spltty totals
16
+ ```
17
+
18
+ ## Install
19
+
20
+ **Homebrew**
21
+
22
+ ```sh
23
+ brew install thiagodiniz/spltty/spltty
24
+ # from a checkout, before the tap exists:
25
+ brew install --build-from-source ./Formula/spltty.rb
26
+ ```
27
+
28
+ **RubyGems** (needs Ruby >= 3.1)
29
+
30
+ ```sh
31
+ gem install spltty
32
+ ```
33
+
34
+ **Linux/Unix script** — installs the gem, falls back to a per-user install and
35
+ tells you if the bin dir is not on your `PATH`:
36
+
37
+ ```sh
38
+ curl -fsSL https://raw.githubusercontent.com/thiagodiniz/spltty/main/install.sh | sh
39
+ ```
40
+
41
+ **From a checkout** (development). Ruby is pinned via [mise](https://mise.jdx.dev)
42
+ (`.tool-versions` → ruby 3.4.2); `bin/spltty` selects it automatically, so the CLI
43
+ runs from any directory without activating mise:
44
+
45
+ ```sh
46
+ mise install && bundle install
47
+ ./bin/spltty help
48
+ rake install # build the gem and put `spltty` on your PATH
49
+ ```
50
+
51
+ ## Setting up a workspace
52
+
53
+ `spltty install [PATH]` creates everything a tracker needs in a folder (the current
54
+ directory by default):
55
+
56
+ ```
57
+ .spltty/config.json participants, split groups, payment methods, ledger cache
58
+ accounts/ one ledger per account being tracked
59
+ sources/INDEX.md tracker for raw statements/receipts
60
+ CLAUDE.md generated workflow guide for the AI agent
61
+ .claude/skills/ingest/ the /ingest skill for raw inputs
62
+ ```
63
+
64
+ It asks for the participants (at least one is required), a shared split group, the
65
+ ledgers to create, optional payment-method defaults, and whether to `git init`.
66
+ Every step but the participants can be skipped — pressing enter through it yields
67
+ one monthly ledger named `expenses`.
68
+
69
+ Non-interactively:
70
+
71
+ ```sh
72
+ spltty install ~/finances -y -P Ana,Bruno -s Ana:60,Bruno:40 -l HOME,TRIP --no-git
73
+ ```
74
+
75
+ | Flag | Meaning |
76
+ |------|---------|
77
+ | `-P, --participants` | comma-separated names (required) |
78
+ | `-g, --group` / `-s, --split` | shared group name and `NAME:PCT,NAME:PCT` split (default: `Both`, even) |
79
+ | `-l, --ledgers` | comma-separated ledger names (default: `expenses`) |
80
+ | `--single` | create ledgers as single files instead of per-month |
81
+ | `--no-git` / `--git` | skip or force `git init` |
82
+ | `-f, --force` | overwrite an existing workspace config |
83
+
84
+ Every command afterwards finds the workspace by walking up from the current
85
+ directory for `.spltty/config.json` — like git. Override with `--config` /
86
+ `SPLTTY_CONFIG`.
87
+
88
+ ## Usage
89
+
90
+ ```sh
91
+ spltty install [PATH] # scaffold a new workspace (accounts, sources, CLAUDE.md)
92
+ spltty add [options] # (alias: a) append an entry (flags + interactive confirm)
93
+ spltty list # (alias: ls) list discovered ledgers (also runs the notes<->config sync)
94
+ spltty sync # sync ledger config between notes-file headers and config.json
95
+ spltty totals [LEDGER..] # per-person totals & settlement per ledger (--combined to merge)
96
+ spltty settle [LEDGER..] # record a settlement (one ledger = cash; several/none = offsets + optional cash)
97
+ spltty groups # list split groups (global + per-ledger)
98
+ spltty groups add NAME # define/update a split group (in a ledger header, or --global)
99
+ spltty groups rm NAME # remove a split group
100
+ spltty methods # list configured payment methods
101
+ spltty methods add NAME # add/update a payment method in the config
102
+ spltty help # show every command with its arguments/options
103
+ spltty add --help # options for a single command
104
+ ```
105
+
106
+ Add or update a payment method without hand-editing the config:
107
+
108
+ ```sh
109
+ spltty methods add "Visa 1234" -s visa-1234 -p Ana -r Both -b 10
110
+ # -s slug -p paid-by -r responsible -b bill-day (1-31 or "last")
111
+ # Only the properties you pass are set; existing ones are preserved.
112
+ ```
113
+
114
+ `add` takes the **title as a positional argument**; everything else is a flag
115
+ with a short alias. It prompts for any required field you omit, applies
116
+ payment-method defaults, shows the resolved row, and asks before writing. Pass
117
+ `--yes`/`-y` to skip prompts (required fields must then be supplied).
118
+
119
+ **Unknown ledger names are treated as typos, not new accounts.** `-l CAS` when
120
+ you meant `CASA` would otherwise scaffold a junk ledger that then shows up in
121
+ every totals run. Interactively you get a menu — create it, or pick the one you
122
+ meant:
123
+
124
+ ```
125
+ $ spltty add "Coffee" -l CAS -v 12.90
126
+ Ledger "CAS" is not known yet.
127
+ 1) Create a new ledger "CAS"
128
+ 2) HOME
129
+ 3) TRIP
130
+ Which ledger? [1]: 2 # or type HOME
131
+ ```
132
+
133
+ With `--yes` there is nobody to ask, so it errors and lists the known ledgers.
134
+ Creating a ledger from a script needs `--create-ledger`:
135
+
136
+ ```sh
137
+ spltty add "Mooring" -l BOAT -v 40 -p Ana -m cash -y --create-ledger
138
+ ```
139
+
140
+ ### Examples
141
+
142
+ ```sh
143
+ # Positional title + short flags:
144
+ spltty add "Leite 1L" -l HOME -v 14.20 -p Ana -m cash
145
+
146
+ # Card entry — Paid By/Responsible/Date default from the card's config:
147
+ spltty add "Extra" -l HOME -v 66.69 -m "Visa 1234" -y
148
+
149
+ # Multi-currency entry:
150
+ spltty add "Metro" -l TRIP -o 3.75 -c CAD -v 14.20 -p Bruno -m Wise -y
151
+
152
+ # Fully interactive (prompts for what's missing):
153
+ spltty add
154
+ ```
155
+
156
+ ### Arguments & options
157
+
158
+ | Flag | Short | Meaning |
159
+ |------------------|-------|-------------------------------------------------------------|
160
+ | `<title>` | | Expense description — **positional** (first bare argument) |
161
+ | `--ledger` | `-l` | Ledger name (default: `default_ledger` in config; case-insensitive) |
162
+ | `--date` | `-d` | `YYYY-MM-DD` (default: today, or the card's bill day) |
163
+ | `--value` | `-v` | Amount in R$ (`12.5`, `12,50`, `1.234,56` all accepted) |
164
+ | `--paid-by` | `-p` | Who fronted the money |
165
+ | `--method` | `-m` | Payment instrument; matches a configured payment method |
166
+ | `--responsible` | `-r` | Who bears the cost — a split-group name (`Both`, …) or a person (`Ana`) |
167
+ | `--paid-responsible` | `-pr` | Shortcut: sets **both** Paid By and Responsible (e.g. `-pr Ana`); `-p`/`-r` override |
168
+ | `--source` | `-s` | Provenance (default `text`) |
169
+ | `--orig-value` | `-o` | Original amount — multi-currency schema only |
170
+ | `--currency` | `-c` | Original currency — multi-currency schema only |
171
+ | `--create-ledger`| | Create `--ledger` when it doesn't exist (required with `--yes`) |
172
+ | `--yes` | `-y` | Non-interactive; no prompts or confirmation |
173
+ | `--config` | `-C` | Config path (default `.spltty/config.json`; or `SPLTTY_CONFIG`) |
174
+ | `--accounts-dir` | `-A` | Override accounts dir (or `SPLTTY_ACCOUNTS_DIR`) |
175
+
176
+ ## Config (`.spltty/config.json`)
177
+
178
+ Only `payment_methods` is authored by hand. The `ledgers` block is **optional and
179
+ auto-maintained**: on every run the CLI scans `accounts/` and records what it
180
+ finds. Discovery treats as a ledger:
181
+
182
+ - a root file named `<NAME>.ledger.md` (single-file ledger), or
183
+ - a subfolder containing `YYYY-MM.md` files (monthly ledger, e.g. `HOME`).
184
+
185
+ Everything else is ignored. Structural fields (`type`/`file`/`dir`/`schema`) are
186
+ set automatically; `title`, `default_responsible`, `default_currency`, and
187
+ `notes` are user overrides that discovery preserves. See `config.example.json`.
188
+
189
+ ### Notes-header sync
190
+
191
+ The three tunable fields — `title`, `default_responsible`, `default_currency` —
192
+ also live in each ledger's `*.notes.md` file, in a YAML frontmatter header under a
193
+ `spltty:` key, so the config sits next to the rules it documents:
194
+
195
+ ```markdown
196
+ ---
197
+ spltty:
198
+ default_responsible: Both
199
+ groups:
200
+ Both: {Ana: 65, Bruno: 35}
201
+ ---
202
+ # WEDDING — Wedding — Notes
203
+
204
+ ```
205
+
206
+ `spltty sync` (and `spltty list`) keep the header and `config.json` in agreement.
207
+ **The notes file is the source of truth.** Each `ledgers` entry carries a
208
+ `synced_at` timestamp; the direction is decided by comparing it to the notes
209
+ file's filesystem mtime:
210
+
211
+ - **notes file edited more recently** (or never synced) → the header wins and
212
+ updates config.
213
+ - **config newer, or the header is missing the field** → config is copied into
214
+ the header.
215
+
216
+ Every field is optional: only keys present on the winning side are merged, an
217
+ absent key is never cleared, and a ledger with no tunable config gets no
218
+ `spltty:` block. Any other frontmatter keys you add are left untouched. Don't
219
+ hand-edit `synced_at` — the sync manages it (and stamps it to the post-write
220
+ mtime so a write never ping-pongs). `spltty add` does **not** run this sync.
221
+
222
+ ### Split groups
223
+
224
+ A **split group** is a named split — `name → {participant: percentage}` — such as
225
+ `Both → {Ana: 70, Bruno: 30}` or `Presente → {Ana: 100}`. Percentages are
226
+ integers and should sum to 100 (a mismatch only warns).
227
+
228
+ Each ledger's own groups live in its notes header, under `spltty.groups`, and are
229
+ synced into `config.json` (as `ledgers.<NAME>.groups`) by the same
230
+ notes↔config sync above:
231
+
232
+ ```yaml
233
+ ---
234
+ spltty:
235
+ default_responsible: Both
236
+ groups:
237
+ Both: {Ana: 70, Bruno: 30}
238
+ Presente: {Ana: 100}
239
+ ---
240
+ # HOME — … — Notes
241
+ ```
242
+
243
+ When the **identical** definition (same name + participants + percentages) appears
244
+ in **two or more** ledgers, it is promoted to a shared **global** group at the top
245
+ level of `config.json` (`"groups": { … }`). Global groups are config-only — they
246
+ are never written back into any header, and the sync only manages ledger-specific
247
+ groups. A ledger group shadows a global group of the same name (e.g. a global
248
+ `Both` 50/50 vs HOME's own `Both` 70/30).
249
+
250
+ Manage groups without hand-editing YAML:
251
+
252
+ ```sh
253
+ spltty groups # list global + per-ledger groups
254
+ spltty groups add Both -s Ana:70,Bruno:30 -l HOME # write into HOME's header
255
+ spltty groups add Both -s Ana:50,Bruno:50 --global # write a global group
256
+ spltty groups rm Both -l HOME # remove from a ledger (or --global)
257
+ ```
258
+
259
+ **Consumed by `spltty totals`:** when computing settlement, each row's
260
+ `Responsible` value is resolved against these groups (ledger header groups first,
261
+ then global). A match splits the cost by the group's percentages; anything
262
+ unmatched is treated as a person (100%). There is no `Both (NN/NN T/C)`
263
+ string-parsing anymore — the split lives in the group. (`spltty add` still writes
264
+ the `Responsible` cell verbatim; it does not yet validate it against known groups.)
265
+
266
+ ## Totals & settlement
267
+
268
+ `spltty totals` compiles per-person **Paid**, **Owes**, **Net = Paid − Owes**, a
269
+ spending breakdown, and a greedy **Settlement** (who pays whom):
270
+
271
+ ```sh
272
+ spltty totals # one report per ledger (reference ledgers flagged)
273
+ spltty totals HOME TRIP # only these ledgers
274
+ spltty totals --combined # merge all non-reference ledgers into ONE settlement
275
+ ```
276
+
277
+ A monthly ledger (HOME) merges all its `YYYY-MM.md` files into one per-ledger
278
+ report. Splits are resolved via split groups (above).
279
+
280
+ ### Reference ledgers
281
+
282
+ A ledger that intentionally **duplicates** rows already recorded in another one — a
283
+ project tracker mirroring shared spend, so the project's cost is visible in one
284
+ place — must never be summed with the primaries; that double-counts. Mark it in its
285
+ notes header:
286
+
287
+ ```yaml
288
+ ---
289
+ spltty:
290
+ reference: true
291
+ ---
292
+ ```
293
+
294
+ Reference ledgers are flagged in per-ledger `totals`, **excluded** from `--combined`
295
+ (and listed as skipped), and refused in a cross-ledger `settle` — settle them on
296
+ their own in cash mode.
297
+
298
+ Each payment method may define an optional **`slug`** — a short alias you can pass
299
+ to `-m` instead of the full name. Both are matched case-insensitively, and the
300
+ row always stores the full canonical name:
301
+
302
+ ```json
303
+ "Visa 5678": { "slug": "visa-5678", "paid_by": "Bruno", "responsible": "Bruno", "bill_day": "last" }
304
+ ```
305
+
306
+ Then `spltty add "Coffee" -l HOME -v 12 -m visa-5678` fills Paid By / Responsible /
307
+ Date from that card. If a method has no default for a field, interactive mode
308
+ prompts for it (and `--yes` errors). `spltty methods` lists every method with its
309
+ slug.
310
+
311
+ ## Settling up
312
+
313
+ `spltty settle` **records** a settlement (the `Settlement:` line in `totals` is
314
+ only a suggestion). A settlement is written as a normal table row — a transfer
315
+ where **Paid By = debtor** and **Responsible = creditor** — so it flows through
316
+ the totals math and offsets the debt exactly. No table is ever hand-edited.
317
+
318
+ ```sh
319
+ spltty settle HOME # cash-settle HOME in full (Pix row in the current month file)
320
+ spltty settle TRIP -v 500 -y # partial cash settle, non-interactive
321
+ spltty settle # global offset mode: zero opposite-direction ledgers, rest stays open
322
+ spltty settle -v 2000 # global: offsets + R$ 2000 cash, smallest ledger first
323
+ spltty settle HOME WEDDING # offset mode restricted to these two ledgers
324
+ ```
325
+
326
+ Two modes, chosen by how many ledgers you pass:
327
+
328
+ - **One ledger — cash mode.** A real payment for that ledger's outstanding
329
+ balance (or `-v` part of it, titled `(partial)`), never touching other
330
+ ledgers. Payment method defaults to `Pix` (`-m` overrides).
331
+ - **None/several ledgers — offset mode.** None = all non-reference ledgers.
332
+ Ledgers whose debt points *opposite* to the combined direction are zeroed with
333
+ mirrored rows (payment method `offset` — no real money moves): e.g. HOME has
334
+ Bruno owing Ana 7000 and WEDDING has Ana owing Bruno 5000 ⇒
335
+ WEDDING gets an offset row Ana → Bruno 5000, HOME the mirror
336
+ Bruno → Ana 5000, leaving 2000 open in HOME. Without `-v` that is all
337
+ that happens — the remainder **stays open** (with nothing to offset, it just
338
+ prints the outstanding and writes nothing). With `-v X`, X is then recorded
339
+ as a cash payment against the remainder. Offsets and cash are both allocated
340
+ **smallest-outstanding ledger first**.
341
+
342
+ Rows are stamped with today's date (`-d` overrides; a card method's bill day is
343
+ ignored) and `Source = settle` (`-s` overrides). The plan is previewed and
344
+ confirmed before writing (`-y` skips). Ledgers marked `reference: true` never join
345
+ offset mode — settle them on their own in cash mode.
346
+
347
+ ## Tests
348
+
349
+ ```sh
350
+ bundle exec rake test
351
+ ```
@@ -0,0 +1,33 @@
1
+ {
2
+ "//": "Reference for .spltty/config.json — normally written by `spltty install`, not by hand. Only payment_methods really needs hand-authoring.",
3
+
4
+ "accounts_dir": "../accounts",
5
+ "//accounts_dir": "Ledger root, resolved relative to this config file. Override at runtime with --accounts-dir or SPLTTY_ACCOUNTS_DIR.",
6
+
7
+ "default_ledger": "expenses",
8
+ "date_format": "%Y-%m-%d",
9
+
10
+ "participants": ["Ana", "Bruno"],
11
+ "//participants": "Everyone who can appear in Paid By / Responsible. Recorded by `spltty install` and used to render the workspace CLAUDE.md; the split groups below decide how a cost is divided.",
12
+
13
+ "payment_methods": {
14
+ "//": "name -> defaults applied when --method matches (by full name or slug, case-insensitive). bill_day is the day the bill is paid; it fills the Date column ('last' = last day of month). slug is an optional shortcut you can pass to -m (e.g. -m visa-1234).",
15
+ "Visa 1234": { "slug": "visa-1234", "paid_by": "Ana", "responsible": "Both", "bill_day": "last" },
16
+ "Visa 5678": { "slug": "visa-5678", "paid_by": "Bruno", "responsible": "Both", "bill_day": 21 }
17
+ },
18
+
19
+ "ledgers": {
20
+ "//": "OPTIONAL and auto-maintained. Discovery scans accounts/ on every run and writes this block. type/file/dir/schema are set automatically; title, default_responsible, default_currency, reference, and notes are user overrides that are preserved.",
21
+ "//sync": "The tunable fields (title, default_responsible, default_currency, reference, groups) also live in each ledger's *.notes.md YAML header under `spltty:`, and are two-way synced by `spltty sync` (and by `spltty list`). synced_at is a per-ledger timestamp written by the sync: if the notes file's mtime is newer, the header wins and updates config; otherwise config is copied into the header. The notes file has precedence. Do not hand-edit synced_at.",
22
+ "//groups": "ledgers.<NAME>.groups holds that ledger's split groups (name -> {participant: pct}), mirrored from the header's spltty.groups. Manage with `spltty groups add NAME -s Ana:70,Bruno:30 -l <NAME>`.",
23
+ "//reference": "reference: true marks a ledger whose rows duplicate another ledger's. It is flagged in `totals`, excluded from --combined, and refused in a cross-ledger `settle`.",
24
+ "expenses": { "type": "monthly", "dir": "expenses", "schema": "standard", "notes": "notes.md", "default_responsible": "Both", "groups": { "Both": { "Ana": 70, "Bruno": 30 } }, "synced_at": "2026-07-05T12:00:00Z" },
25
+ "TRIP": { "type": "single", "file": "TRIP.ledger.md", "schema": "montreal", "notes": "TRIP.notes.md", "default_currency": "CAD", "synced_at": "2026-07-05T12:00:00Z" },
26
+ "RENOVATION": { "type": "single", "file": "RENOVATION.ledger.md", "schema": "standard", "notes": "RENOVATION.notes.md", "reference": true, "synced_at": "2026-07-05T12:00:00Z" }
27
+ },
28
+
29
+ "groups": {
30
+ "//": "GLOBAL split groups, shared across ledgers. Auto-populated when the identical definition (name + participants + percentages) appears in >=2 ledger headers; also editable via `spltty groups add NAME -s ... --global`. Never written back into any header. A ledger's own group shadows a global one of the same name.",
31
+ "Both": { "Ana": 50, "Bruno": 50 }
32
+ }
33
+ }
data/exe/spltty ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Gem entrypoint. Also works straight from a checkout (`exe/spltty …`) by
5
+ # putting the sibling lib/ on the load path when it exists.
6
+ lib = File.expand_path("../lib", __dir__)
7
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
8
+
9
+ require "spltty_cli"
10
+
11
+ SplttyCLI.run(ARGV)