@fuzzle/opencode-accountant 0.0.12 → 0.0.13

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.
@@ -0,0 +1,581 @@
1
+ # update-prices Tool
2
+
3
+ The `update-prices` tool fetches end-of-day currency exchange rates and updates the price journals in `ledger/currencies/`. It uses the external `pricehist` tool to fetch data from various sources (Yahoo Finance, CoinMarketCap, ECB, etc.).
4
+
5
+ This tool is **restricted to the accountant agent only**.
6
+
7
+ ## Arguments
8
+
9
+ | Argument | Type | Default | Description |
10
+ | ---------- | ------- | ------- | -------------------------------------------------------------- |
11
+ | `backfill` | boolean | `false` | If true, fetch historical prices from configured backfill_date |
12
+
13
+ ## Output Format
14
+
15
+ ### Daily Update Success (backfill: false)
16
+
17
+ When fetching only yesterday's prices:
18
+
19
+ ```json
20
+ {
21
+ "success": true,
22
+ "endDate": "2026-02-21",
23
+ "backfill": false,
24
+ "results": [
25
+ {
26
+ "ticker": "EUR",
27
+ "priceLine": "P 2026-02-21 EUR 0.944 CHF",
28
+ "file": "eur.journal"
29
+ },
30
+ {
31
+ "ticker": "USD",
32
+ "priceLine": "P 2026-02-21 USD 0.881 CHF",
33
+ "file": "usd.journal"
34
+ },
35
+ {
36
+ "ticker": "BTC",
37
+ "priceLine": "P 2026-02-21 BTC 52341.50 CHF",
38
+ "file": "btc.journal"
39
+ }
40
+ ]
41
+ }
42
+ ```
43
+
44
+ **Note:** The `priceLine` shows the latest (most recent) price added. In daily mode, only one price per currency is fetched.
45
+
46
+ ### Backfill Success (backfill: true)
47
+
48
+ When fetching historical prices:
49
+
50
+ ```json
51
+ {
52
+ "success": true,
53
+ "endDate": "2026-02-21",
54
+ "backfill": true,
55
+ "results": [
56
+ {
57
+ "ticker": "EUR",
58
+ "priceLine": "P 2026-02-21 EUR 0.944 CHF",
59
+ "file": "eur.journal"
60
+ },
61
+ {
62
+ "ticker": "USD",
63
+ "priceLine": "P 2026-02-21 USD 0.881 CHF",
64
+ "file": "usd.journal"
65
+ }
66
+ ]
67
+ }
68
+ ```
69
+
70
+ **Note:** The output shows the latest price line, but many historical prices were added to the journal files. Check the journal files to see all added prices.
71
+
72
+ ### Partial Failure
73
+
74
+ When some currencies succeed and others fail:
75
+
76
+ ```json
77
+ {
78
+ "success": false,
79
+ "endDate": "2026-02-21",
80
+ "backfill": false,
81
+ "results": [
82
+ {
83
+ "ticker": "EUR",
84
+ "priceLine": "P 2026-02-21 EUR 0.944 CHF",
85
+ "file": "eur.journal"
86
+ },
87
+ {
88
+ "ticker": "BTC",
89
+ "error": "No price data found within date range 2026-02-21 to 2026-02-21"
90
+ },
91
+ {
92
+ "ticker": "AAPL",
93
+ "error": "API rate limit exceeded"
94
+ }
95
+ ]
96
+ }
97
+ ```
98
+
99
+ The tool processes all currencies independently. Partial success is possible.
100
+
101
+ ### Configuration Error
102
+
103
+ When `config/prices.yaml` is missing or invalid:
104
+
105
+ ```json
106
+ {
107
+ "error": "Failed to load configuration: config/prices.yaml not found"
108
+ }
109
+ ```
110
+
111
+ ### Agent Restriction Error
112
+
113
+ When called by the wrong agent:
114
+
115
+ ```json
116
+ {
117
+ "error": "This tool is restricted to the accountant agent only.",
118
+ "hint": "Use: Task(subagent_type='accountant', prompt='update prices')",
119
+ "caller": "main assistant"
120
+ }
121
+ ```
122
+
123
+ ## Daily vs Backfill Modes
124
+
125
+ ### Daily Mode (backfill: false, default)
126
+
127
+ **Behavior:**
128
+
129
+ - Fetches only yesterday's price for each currency
130
+ - Fast execution (single date per currency)
131
+ - Typical use: Daily or weekly routine updates
132
+
133
+ **Date range:** Yesterday to yesterday
134
+
135
+ **Example:**
136
+
137
+ ```
138
+ update-prices()
139
+ # or
140
+ update-prices(backfill: false)
141
+ ```
142
+
143
+ **Use when:**
144
+
145
+ - Performing routine price updates
146
+ - Keeping prices current
147
+ - No historical gaps exist
148
+
149
+ ### Backfill Mode (backfill: true)
150
+
151
+ **Behavior:**
152
+
153
+ - Fetches historical prices from `backfill_date` to yesterday
154
+ - Slower execution (multiple dates per currency)
155
+ - Typical use: Initial setup, adding new currency, filling gaps
156
+
157
+ **Date range:** Per-currency `backfill_date` (or default) to yesterday
158
+
159
+ **Example:**
160
+
161
+ ```
162
+ update-prices(backfill: true)
163
+ ```
164
+
165
+ **Use when:**
166
+
167
+ - Adding a new currency (populate full history)
168
+ - Fixing missing dates (fill gaps)
169
+ - Initial repository setup (populate all currencies)
170
+ - Recovering from extended outage
171
+
172
+ ### Backfill Date Configuration
173
+
174
+ **Per-currency backfill_date:**
175
+
176
+ ```yaml
177
+ currencies:
178
+ EUR:
179
+ source: yahoo
180
+ pair: EURCHF=X
181
+ fmt_base: CHF
182
+ backfill_date: '2024-01-01' # Start from this date
183
+ ```
184
+
185
+ **Default backfill_date:**
186
+
187
+ - If currency has no `backfill_date` configured: January 1st of current year
188
+ - Example: In 2026, default is `2026-01-01`
189
+
190
+ **Why per-currency?**
191
+
192
+ - Different assets have different availability histories
193
+ - Cryptocurrencies may have shorter histories
194
+ - Some stocks/commodities have specific start dates
195
+
196
+ ## Date Range Behavior
197
+
198
+ ### End Date (Always Yesterday)
199
+
200
+ The tool **always fetches up to yesterday**, never today.
201
+
202
+ **Why?**
203
+
204
+ - End-of-day prices are used for accounting
205
+ - Today's prices are incomplete (market still open)
206
+ - Consistency: yesterday is the latest complete day
207
+
208
+ **Example:**
209
+
210
+ - If run on 2026-02-22, `endDate` will be `2026-02-21`
211
+
212
+ ### Start Date
213
+
214
+ **Daily mode:**
215
+
216
+ - Start date = end date (yesterday)
217
+ - Fetches only one day
218
+
219
+ **Backfill mode:**
220
+
221
+ - Start date = currency's `backfill_date` (or default: Jan 1 of current year)
222
+ - Fetches range from start to yesterday
223
+
224
+ ### Date Filtering
225
+
226
+ The tool filters price data to only include dates within the requested range.
227
+
228
+ **Why?**
229
+
230
+ - Prevents accidentally adding future dates
231
+ - Ensures data consistency
232
+ - Some sources may return data outside requested range
233
+
234
+ ## Deduplication Logic
235
+
236
+ The tool updates journal files **in place** with automatic deduplication.
237
+
238
+ ### How Deduplication Works
239
+
240
+ 1. **Read existing** price lines from journal file (or empty if new)
241
+ 2. **Build map** of `date → price line`
242
+ 3. **Add existing** prices to map
243
+ 4. **Add/override** with new prices (newer overwrites older for same date)
244
+ 5. **Sort** by date (ascending: oldest first, newest at bottom)
245
+ 6. **Write** back to file
246
+
247
+ ### Key Behaviors
248
+
249
+ | Behavior | Description |
250
+ | --------------- | -------------------------------------------------- |
251
+ | Duplicate dates | Newer price overwrites older for the same date |
252
+ | Timestamps | Preserved from original source (if present) |
253
+ | Sort order | Chronological (oldest first, newest at bottom) |
254
+ | File updates | In-place (no backups created) |
255
+ | Idempotent | Running twice produces same result as running once |
256
+
257
+ ### Deduplication Example
258
+
259
+ **Before (existing file):**
260
+
261
+ ```
262
+ P 2026-02-19 EUR 0.942 CHF
263
+ P 2026-02-20 EUR 0.943 CHF
264
+ ```
265
+
266
+ **New data fetched:**
267
+
268
+ ```
269
+ P 2026-02-20 EUR 0.945 CHF # Different price for same date
270
+ P 2026-02-21 EUR 0.944 CHF # New date
271
+ ```
272
+
273
+ **After deduplication:**
274
+
275
+ ```
276
+ P 2026-02-19 EUR 0.942 CHF # Unchanged
277
+ P 2026-02-20 EUR 0.945 CHF # Updated (newer overwrites)
278
+ P 2026-02-21 EUR 0.944 CHF # Added
279
+ ```
280
+
281
+ ### Timestamps
282
+
283
+ Some sources provide timestamps:
284
+
285
+ ```
286
+ P 2026-02-21 00:00:00 EUR 0.944 CHF
287
+ ```
288
+
289
+ The tool preserves timestamps if present, but deduplication is based on **date only** (ignoring time).
290
+
291
+ ## Price File Format
292
+
293
+ ### Journal File Structure
294
+
295
+ ```
296
+ # ledger/currencies/eur.journal
297
+ P 2026-01-15 EUR 0.945 CHF
298
+ P 2026-01-16 EUR 0.943 CHF
299
+ P 2026-02-21 EUR 0.944 CHF
300
+ ```
301
+
302
+ ### Price Line Format
303
+
304
+ **Format:** `P date commodity price base-currency`
305
+
306
+ **Components:**
307
+
308
+ - `P` = Price directive (hledger syntax)
309
+ - `date` = YYYY-MM-DD (may include timestamp HH:MM:SS)
310
+ - `commodity` = Currency being priced (e.g., EUR, USD, BTC)
311
+ - `price` = Exchange rate value
312
+ - `base-currency` = Base currency for conversion (e.g., CHF)
313
+
314
+ **Example:**
315
+
316
+ ```
317
+ P 2026-02-21 EUR 0.944 CHF
318
+ ```
319
+
320
+ Means: 1 EUR = 0.944 CHF on 2026-02-21
321
+
322
+ ### File Locations
323
+
324
+ - All price journals stored in `ledger/currencies/`
325
+ - One file per currency (configured in `config/prices.yaml`)
326
+ - Files updated in place (existing prices preserved, new ones added/merged)
327
+
328
+ **Example structure:**
329
+
330
+ ```
331
+ ledger/
332
+ └── currencies/
333
+ ├── eur.journal
334
+ ├── usd.journal
335
+ ├── btc.journal
336
+ └── eth.journal
337
+ ```
338
+
339
+ ## Typical Workflow
340
+
341
+ ### Scenario 1: Daily Price Update
342
+
343
+ **Goal:** Keep prices current with daily/weekly updates
344
+
345
+ 1. Run `update-prices()` (or `update-prices(backfill: false)`)
346
+ 2. Check output for any errors
347
+ 3. Verify prices were added:
348
+ ```bash
349
+ tail -3 ledger/currencies/eur.journal
350
+ ```
351
+ 4. Success: Latest prices now available for hledger
352
+
353
+ **Frequency:** Daily, weekly, or as needed for current prices
354
+
355
+ ### Scenario 2: Adding a New Currency
356
+
357
+ **Goal:** Add a new currency with full historical data
358
+
359
+ 1. Add currency config to `config/prices.yaml`:
360
+ ```yaml
361
+ currencies:
362
+ GBP:
363
+ source: yahoo
364
+ pair: GBPCHF=X
365
+ fmt_base: CHF
366
+ file: gbp.journal
367
+ backfill_date: '2024-01-01'
368
+ ```
369
+ 2. Run `update-prices(backfill: true)` to fetch historical data
370
+ 3. Check output and verify `ledger/currencies/gbp.journal` created
371
+ 4. Inspect file to confirm date range:
372
+ ```bash
373
+ head -3 ledger/currencies/gbp.journal
374
+ tail -3 ledger/currencies/gbp.journal
375
+ ```
376
+ 5. Subsequent updates: use daily mode (`update-prices()`)
377
+
378
+ ### Scenario 3: Fixing Missing Dates
379
+
380
+ **Goal:** Fill gaps in existing price history
381
+
382
+ 1. Identify date gaps in price journals:
383
+ ```bash
384
+ cat ledger/currencies/eur.journal
385
+ # Notice: prices for Feb 15-20 are missing
386
+ ```
387
+ 2. Run `update-prices(backfill: true)` to fill gaps
388
+ 3. Deduplication ensures:
389
+ - Existing prices preserved
390
+ - Missing dates added
391
+ - No duplicate entries
392
+ 4. Verify gaps are filled:
393
+ ```bash
394
+ cat ledger/currencies/eur.journal | grep "2026-02"
395
+ ```
396
+
397
+ ### Scenario 4: Handling Errors
398
+
399
+ **Goal:** Recover from partial failures
400
+
401
+ 1. Run `update-prices()`
402
+ 2. Output shows `success: false` with partial results:
403
+ ```json
404
+ {
405
+ "success": false,
406
+ "results": [
407
+ { "ticker": "EUR", "priceLine": "..." },
408
+ { "ticker": "BTC", "error": "API rate limit exceeded" }
409
+ ]
410
+ }
411
+ ```
412
+ 3. Check error messages for each failed currency
413
+ 4. Common fixes:
414
+ - **Network issues**: Retry later
415
+ - **API rate limits**: Wait (usually resets hourly/daily) and retry
416
+ - **Invalid config**: Fix `config/prices.yaml` syntax or source/pair
417
+ - **Missing pricehist**: Install external dependency
418
+ 5. Re-run tool (idempotent - safe to retry)
419
+
420
+ **Note:** Successful currencies are already updated. Only failed currencies need retry.
421
+
422
+ ## Configuration
423
+
424
+ ### Config File: `config/prices.yaml`
425
+
426
+ **Structure:**
427
+
428
+ ```yaml
429
+ currencies:
430
+ <TICKER>:
431
+ source: <source-name> # e.g., yahoo, coinbase, coinmarketcap, ecb
432
+ pair: <trading-pair> # Source-specific format
433
+ file: <journal-filename> # e.g., eur.journal
434
+ fmt_base: <base-currency> # Optional, e.g., CHF, USD
435
+ backfill_date: <YYYY-MM-DD> # Optional, per-currency backfill start
436
+ ```
437
+
438
+ ### Field Descriptions
439
+
440
+ | Field | Required | Description |
441
+ | --------------- | -------- | -------------------------------------------------------------- |
442
+ | `source` | Yes | Price data source (passed to pricehist) |
443
+ | `pair` | Yes | Trading pair identifier (source-specific format) |
444
+ | `file` | Yes | Journal filename in `ledger/currencies/` |
445
+ | `fmt_base` | No | Base currency for price notation (default: inferred from pair) |
446
+ | `backfill_date` | No | Override default backfill start date for this currency |
447
+
448
+ ### Configuration Examples
449
+
450
+ **Fiat currencies (Yahoo Finance):**
451
+
452
+ ```yaml
453
+ currencies:
454
+ EUR:
455
+ source: yahoo
456
+ pair: EURCHF=X
457
+ fmt_base: CHF
458
+ file: eur.journal
459
+ backfill_date: '2024-01-01'
460
+
461
+ USD:
462
+ source: yahoo
463
+ pair: USDCHF=X
464
+ fmt_base: CHF
465
+ file: usd.journal
466
+ ```
467
+
468
+ **Cryptocurrencies (CoinMarketCap):**
469
+
470
+ ```yaml
471
+ currencies:
472
+ BTC:
473
+ source: coinmarketcap
474
+ pair: BTC/CHF
475
+ fmt_base: CHF
476
+ file: btc.journal
477
+ backfill_date: '2025-01-01'
478
+
479
+ ETH:
480
+ source: coinmarketcap
481
+ pair: ETH/CHF
482
+ fmt_base: CHF
483
+ file: eth.journal
484
+ ```
485
+
486
+ **EUR via ECB (European Central Bank):**
487
+
488
+ ```yaml
489
+ currencies:
490
+ EUR:
491
+ source: ecb
492
+ pair: EUR/CHF
493
+ fmt_base: CHF
494
+ file: eur.journal
495
+ ```
496
+
497
+ ### External Dependency: pricehist
498
+
499
+ The tool uses the `pricehist` command-line tool to fetch price data.
500
+
501
+ **Requirements:**
502
+
503
+ - `pricehist` must be installed and available in PATH
504
+ - Install: `pip install pricehist` (or distribution-specific package)
505
+
506
+ **Supported sources:** Yahoo Finance, CoinMarketCap, Coinbase, ECB, and more. See `pricehist` documentation for full list.
507
+
508
+ **Note:** The tool abstracts pricehist details - you only need to configure `source` and `pair` in the YAML.
509
+
510
+ ## Error Handling
511
+
512
+ ### Common Errors
513
+
514
+ | Error | Cause | Solution |
515
+ | ------------------- | --------------------------------------- | ------------------------------------------------------------------------------ |
516
+ | External tool error | pricehist not installed or not in PATH | Install pricehist: `pip install pricehist` |
517
+ | No price data found | No data available for date range | Check if date range is valid; API may not have historical data for that period |
518
+ | API rate limit | Too many requests to price source | Wait and retry; rate limits typically reset hourly or daily |
519
+ | Network error | Cannot reach price data source | Check internet connection; retry later |
520
+ | Configuration error | Missing or invalid `config/prices.yaml` | Ensure config file exists with proper YAML syntax and required fields |
521
+ | Invalid date range | Start date after end date | Check `backfill_date` configuration; must be before yesterday |
522
+ | Agent restriction | Called by wrong agent | Use `Task(subagent_type='accountant', prompt='update prices')` |
523
+ | Permission error | Cannot write to journal files | Check file permissions on `ledger/currencies/` directory |
524
+ | Invalid source/pair | Source or pair format incorrect | Check `pricehist` docs for correct format for your source |
525
+
526
+ ### Partial Failures
527
+
528
+ The tool processes all currencies **independently**.
529
+
530
+ **Behavior:**
531
+
532
+ - Each currency is fetched and updated separately
533
+ - Failure in one currency doesn't affect others
534
+ - Overall `success: false` if ANY currency fails
535
+ - Check `results` array for per-currency status
536
+
537
+ **Example:**
538
+
539
+ ```json
540
+ {
541
+ "success": false,
542
+ "results": [
543
+ { "ticker": "EUR", "priceLine": "P 2026-02-21 EUR 0.944 CHF", "file": "eur.journal" },
544
+ { "ticker": "BTC", "error": "API rate limit exceeded" }
545
+ ]
546
+ }
547
+ ```
548
+
549
+ EUR succeeded (updated), BTC failed (not updated).
550
+
551
+ **Recovery:**
552
+
553
+ - Can re-run tool safely (idempotent)
554
+ - Successful currencies won't re-fetch (deduplication handles this)
555
+ - Only failed currencies will retry
556
+
557
+ ### Debugging Tips
558
+
559
+ **Check pricehist directly:**
560
+
561
+ ```bash
562
+ pricehist fetch -o ledger -s 2026-02-21 -e 2026-02-21 yahoo EURCHF=X
563
+ ```
564
+
565
+ **Check journal file:**
566
+
567
+ ```bash
568
+ cat ledger/currencies/eur.journal
569
+ ```
570
+
571
+ **Check config syntax:**
572
+
573
+ ```bash
574
+ cat config/prices.yaml
575
+ ```
576
+
577
+ **Check permissions:**
578
+
579
+ ```bash
580
+ ls -la ledger/currencies/
581
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzzle/opencode-accountant",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "description": "An OpenCode accounting agent, specialized in double-entry-bookkepping with hledger",
5
5
  "author": {
6
6
  "name": "ali bengali",
@@ -23,8 +23,9 @@
23
23
  "access": "public"
24
24
  },
25
25
  "files": [
26
+ "agent",
26
27
  "dist",
27
- "agent"
28
+ "docs"
28
29
  ],
29
30
  "dependencies": {
30
31
  "@opencode-ai/plugin": "latest",