@fuzzle/opencode-accountant 0.14.0-next.1 → 0.14.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.
package/README.md CHANGED
@@ -16,7 +16,7 @@ An OpenCode accounting agent, specialized in double-entry-bookkepping with hledg
16
16
 
17
17
  ### Price Fetching Configuration
18
18
 
19
- The `fetch-prices` tool requires a configuration file to specify which currency pairs and stock tickers to fetch. Create a `config/prices.yaml` file in your project directory with the following structure:
19
+ The `fetch-currency-prices` tool requires a configuration file to specify which currency pairs to fetch. Create a `config/prices.yaml` file in your project directory with the following structure:
20
20
 
21
21
  ```yaml
22
22
  currencies:
@@ -38,16 +38,9 @@ currencies:
38
38
  file: usd-chf.journal
39
39
  fmt_base: USD
40
40
  backfill_date: '2025-06-01'
41
-
42
- stocks:
43
- AG.TO:
44
- DRLL:
45
- BOGO:
46
- pair: BOGO.V
47
- fmt_base: BOGO
48
41
  ```
49
42
 
50
- #### Currency Configuration Options
43
+ #### Configuration Options
51
44
 
52
45
  Each currency entry supports the following fields:
53
46
 
@@ -59,18 +52,6 @@ Each currency entry supports the following fields:
59
52
  | `fmt_base` | No | Base currency format override for pricehist (e.g., `USD` for yahoo source) |
60
53
  | `backfill_date` | No | Start date for backfill mode. Defaults to January 1st of the current year |
61
54
 
62
- #### Stock Configuration Options
63
-
64
- The `stocks` section is an optional mapping of ticker symbols. Bare keys (null values) use defaults; entries with properties override them:
65
-
66
- | Field | Default | Description |
67
- | --------------- | -------------------------------- | ---------------------------------------------------------------- |
68
- | `source` | `yahoo` | The pricehist data source |
69
- | `pair` | the ticker key itself | The ticker/pair to fetch (useful when exchange ticker differs) |
70
- | `file` | `{lowercase ticker}.journal` | Output filename in `ledger/stocks/` |
71
- | `fmt_base` | — | Base symbol override (e.g., `BOGO` when pair is `BOGO.V`) |
72
- | `backfill_date` | — | Start date for backfill mode (defaults to Jan 1 of current year) |
73
-
74
55
  #### Directory Structure
75
56
 
76
57
  The plugin expects the following directory structure in your project:
@@ -80,14 +61,10 @@ your-project/
80
61
  ├── config/
81
62
  │ └── prices.yaml # Price fetching configuration
82
63
  └── ledger/
83
- ├── currencies/ # Where currency price journals are written
84
- ├── btc-chf.journal
85
- ├── eur-chf.journal
86
- └── usd-chf.journal
87
- └── stocks/ # Where stock price journals are written
88
- ├── ag.to.journal
89
- ├── drll.journal
90
- └── bogo.journal
64
+ └── currencies/ # Where price journal files are written
65
+ ├── btc-chf.journal
66
+ ├── eur-chf.journal
67
+ └── usd-chf.journal
91
68
  ```
92
69
 
93
70
  #### Base Currency
@@ -8,7 +8,7 @@ tools:
8
8
  bash: true
9
9
  edit: true
10
10
  write: true
11
- # MCP tools available: import-pipeline, fetch-prices, hledger-mcp (read-only queries)
11
+ # MCP tools available: import-pipeline, fetch-currency-prices, hledger-mcp (read-only queries)
12
12
  permissions:
13
13
  bash: allow
14
14
  edit: allow
@@ -59,7 +59,7 @@ You have access to specialized MCP tools that MUST be used for their designated
59
59
  | Tool | Use For | NEVER Do Instead |
60
60
  | ----------------------- | ---------------------------------------------------- | --------------------------------------------------------- |
61
61
  | `import-pipeline` | Full import workflow (classify → import → reconcile) | Manual file moves, `hledger import`, manual journal edits |
62
- | `fetch-prices` | Fetching exchange rates and stock prices | `curl` to price APIs, manual price entries |
62
+ | `fetch-currency-prices` | Fetching exchange rates | `curl` to price APIs, manual price entries |
63
63
  | hledger MCP tools | Read-only queries (balance, register, accounts, etc.) | `hledger bal`, `hledger reg`, `hledger print` via bash |
64
64
 
65
65
  These tools handle validation, deduplication, error checking, and file organization automatically. Bypassing them risks data corruption, duplicate transactions, and inconsistent state.
@@ -84,7 +84,7 @@ Bash is **FORBIDDEN** for:
84
84
  - **`hledger import`** - Use `import-pipeline` tool instead
85
85
  - **Moving/copying CSV files** - Use `import-pipeline` tool instead
86
86
  - **Writing to files** - Use `edit`/`write` tools with user approval only
87
- - **Fetching prices** - Use `fetch-prices` tool instead
87
+ - **Fetching prices** - Use `fetch-currency-prices` tool instead
88
88
  - **`hledger -f <file>`** - Never specify journal files explicitly; `.hledger.journal` includes all journals automatically
89
89
 
90
90
  ## Manual Editing Policy
@@ -232,23 +232,22 @@ The following are MCP tools available to you. Always call these tools directly -
232
232
 
233
233
  ---
234
234
 
235
- ### fetch-prices
235
+ ### fetch-currency-prices
236
236
 
237
- **Purpose:** Fetches currency exchange rates and stock prices, updates `ledger/currencies/` and `ledger/stocks/` journals.
237
+ **Purpose:** Fetches currency exchange rates and updates `ledger/currencies/` journals.
238
238
 
239
239
  **Usage:**
240
240
 
241
- - Daily mode (default): `fetch-prices()` or `fetch-prices(backfill: false)`
242
- - Backfill mode: `fetch-prices(backfill: true)`
241
+ - Daily mode (default): `fetch-currency-prices()` or `fetch-currency-prices(backfill: false)`
242
+ - Backfill mode: `fetch-currency-prices(backfill: true)`
243
243
 
244
244
  **Behavior:**
245
245
 
246
246
  - Daily mode: Fetches yesterday's prices only
247
247
  - Backfill mode: Fetches from `backfill_date` (or Jan 1 of current year) to yesterday
248
248
  - Updates journal files in-place with deduplication (newer prices overwrite older for same date)
249
- - Processes all currencies and stocks independently (partial failures possible)
250
- - Stock symbols with dots (e.g., `AG.TO`) are automatically quoted for hledger compatibility
249
+ - Processes all currencies independently (partial failures possible)
251
250
 
252
- **Output:** Returns per-ticker results with latest price line or error message
251
+ **Output:** Returns per-currency results with latest price line or error message
253
252
 
254
- **Configuration:** `config/prices.yaml` defines currencies (with full config) and stocks (map with optional overrides)
253
+ **Configuration:** `config/prices.yaml` defines currencies, sources, pairs, and backfill dates
package/dist/index.js CHANGED
@@ -16870,7 +16870,7 @@ function tool(input) {
16870
16870
  return input;
16871
16871
  }
16872
16872
  tool.schema = exports_external;
16873
- // src/tools/fetch-prices.ts
16873
+ // src/tools/fetch-currency-prices.ts
16874
16874
  var {$ } = globalThis.Bun;
16875
16875
  import * as path4 from "path";
16876
16876
 
@@ -16939,26 +16939,6 @@ function validateCurrencyConfig(name, config2) {
16939
16939
  backfill_date: typeof configObj.backfill_date === "string" ? configObj.backfill_date : undefined
16940
16940
  };
16941
16941
  }
16942
- function normalizeStockConfig(ticker, raw) {
16943
- if (raw === null || raw === undefined) {
16944
- return {
16945
- source: "yahoo",
16946
- pair: ticker,
16947
- file: `${ticker.toLowerCase()}.journal`
16948
- };
16949
- }
16950
- if (typeof raw !== "object" || Array.isArray(raw)) {
16951
- throw new Error(`Invalid config for stock '${ticker}': expected an object or null`);
16952
- }
16953
- const obj = raw;
16954
- return {
16955
- source: typeof obj.source === "string" && obj.source !== "" ? obj.source : "yahoo",
16956
- pair: typeof obj.pair === "string" && obj.pair !== "" ? obj.pair : ticker,
16957
- file: typeof obj.file === "string" && obj.file !== "" ? obj.file : `${ticker.toLowerCase()}.journal`,
16958
- fmt_base: typeof obj.fmt_base === "string" ? obj.fmt_base : undefined,
16959
- backfill_date: typeof obj.backfill_date === "string" ? obj.backfill_date : undefined
16960
- };
16961
- }
16962
16942
  function loadPricesConfig(directory) {
16963
16943
  return loadYamlConfig(directory, CONFIG_FILE, (parsedObj) => {
16964
16944
  if (!parsedObj.currencies || typeof parsedObj.currencies !== "object") {
@@ -16972,15 +16952,17 @@ function loadPricesConfig(directory) {
16972
16952
  for (const [name, config2] of Object.entries(currenciesObj)) {
16973
16953
  currencies[name] = validateCurrencyConfig(name, config2);
16974
16954
  }
16975
- const stocks = {};
16976
- if (parsedObj.stocks !== undefined && parsedObj.stocks !== null) {
16977
- if (typeof parsedObj.stocks !== "object" || Array.isArray(parsedObj.stocks)) {
16978
- throw new Error(`Invalid config: 'stocks' must be a mapping`);
16955
+ let stocks;
16956
+ if (parsedObj.stocks !== undefined) {
16957
+ if (!Array.isArray(parsedObj.stocks) || parsedObj.stocks.length === 0) {
16958
+ throw new Error(`Invalid config: 'stocks' must be a non-empty array`);
16979
16959
  }
16980
- const stocksObj = parsedObj.stocks;
16981
- for (const [ticker, raw] of Object.entries(stocksObj)) {
16982
- stocks[ticker] = normalizeStockConfig(ticker, raw);
16960
+ for (const [i2, ticker] of parsedObj.stocks.entries()) {
16961
+ if (typeof ticker !== "string" || ticker === "") {
16962
+ throw new Error(`Invalid config: stock entry at index ${i2} must be a non-empty string`);
16963
+ }
16983
16964
  }
16965
+ stocks = parsedObj.stocks;
16984
16966
  }
16985
16967
  return { currencies, stocks };
16986
16968
  }, `Configuration file not found: ${CONFIG_FILE}. Please refer to the plugin's GitHub repository for setup instructions.`);
@@ -17218,12 +17200,12 @@ function buildToolSuccessResult(data) {
17218
17200
  return JSON.stringify(result);
17219
17201
  }
17220
17202
 
17221
- // src/tools/fetch-prices.ts
17203
+ // src/tools/fetch-currency-prices.ts
17222
17204
  async function defaultPriceFetcher(cmdArgs) {
17223
17205
  const result = await $`pricehist ${cmdArgs}`.quiet();
17224
17206
  return result.stdout.toString().trim();
17225
17207
  }
17226
- function buildPricehistArgs(startDate, endDate, config2) {
17208
+ function buildPricehistArgs(startDate, endDate, currencyConfig) {
17227
17209
  const cmdArgs = [
17228
17210
  "fetch",
17229
17211
  "-o",
@@ -17232,11 +17214,11 @@ function buildPricehistArgs(startDate, endDate, config2) {
17232
17214
  startDate,
17233
17215
  "-e",
17234
17216
  endDate,
17235
- config2.source,
17236
- config2.pair
17217
+ currencyConfig.source,
17218
+ currencyConfig.pair
17237
17219
  ];
17238
- if (config2.fmt_base) {
17239
- cmdArgs.push("--fmt-base", config2.fmt_base);
17220
+ if (currencyConfig.fmt_base) {
17221
+ cmdArgs.push("--fmt-base", currencyConfig.fmt_base);
17240
17222
  }
17241
17223
  return cmdArgs;
17242
17224
  }
@@ -17272,36 +17254,8 @@ function filterAndSortPriceLinesByDateRange(priceLines, startDate, endDate) {
17272
17254
  return parsed.date >= startDate && parsed.date <= endDate;
17273
17255
  }).sort((a, b) => a.date.localeCompare(b.date)).map((parsed) => parsed.formattedLine);
17274
17256
  }
17275
- async function fetchAndWritePrices(ticker, config2, outputSubdir, directory, endDate, defaultBackfillDate, backfill, priceFetcher) {
17276
- const startDate = backfill ? config2.backfill_date || defaultBackfillDate : endDate;
17277
- const cmdArgs = buildPricehistArgs(startDate, endDate, config2);
17278
- const output = await priceFetcher(cmdArgs);
17279
- const rawPriceLines = output.split(`
17280
- `).filter((line) => line.startsWith("P "));
17281
- if (rawPriceLines.length === 0) {
17282
- return {
17283
- ticker,
17284
- error: `No price lines in pricehist output: ${output}`
17285
- };
17286
- }
17287
- const priceLines = filterAndSortPriceLinesByDateRange(rawPriceLines, startDate, endDate).map(ensureQuotedSymbols);
17288
- if (priceLines.length === 0) {
17289
- return {
17290
- ticker,
17291
- error: `No price data found within date range ${startDate} to ${endDate}`
17292
- };
17293
- }
17294
- const journalPath = path4.join(directory, outputSubdir, config2.file);
17295
- updatePriceJournal(journalPath, priceLines);
17296
- const latestPriceLine = priceLines[priceLines.length - 1];
17297
- return {
17298
- ticker,
17299
- priceLine: latestPriceLine,
17300
- file: config2.file
17301
- };
17302
- }
17303
- async function fetchPrices(directory, agent, backfill, priceFetcher = defaultPriceFetcher, configLoader = loadPricesConfig) {
17304
- const restrictionError = checkAccountantAgent(agent, "fetch prices");
17257
+ async function fetchCurrencyPrices(directory, agent, backfill, priceFetcher = defaultPriceFetcher, configLoader = loadPricesConfig) {
17258
+ const restrictionError = checkAccountantAgent(agent, "fetch currency prices");
17305
17259
  if (restrictionError) {
17306
17260
  return restrictionError;
17307
17261
  }
@@ -17317,8 +17271,34 @@ async function fetchPrices(directory, agent, backfill, priceFetcher = defaultPri
17317
17271
  const results = [];
17318
17272
  for (const [ticker, currencyConfig] of Object.entries(config2.currencies)) {
17319
17273
  try {
17320
- const result = await fetchAndWritePrices(ticker, currencyConfig, "ledger/currencies", directory, endDate, defaultBackfillDate, backfill, priceFetcher);
17321
- results.push(result);
17274
+ const startDate = backfill ? currencyConfig.backfill_date || defaultBackfillDate : endDate;
17275
+ const cmdArgs = buildPricehistArgs(startDate, endDate, currencyConfig);
17276
+ const output = await priceFetcher(cmdArgs);
17277
+ const rawPriceLines = output.split(`
17278
+ `).filter((line) => line.startsWith("P "));
17279
+ if (rawPriceLines.length === 0) {
17280
+ results.push({
17281
+ ticker,
17282
+ error: `No price lines in pricehist output: ${output}`
17283
+ });
17284
+ continue;
17285
+ }
17286
+ const priceLines = filterAndSortPriceLinesByDateRange(rawPriceLines, startDate, endDate);
17287
+ if (priceLines.length === 0) {
17288
+ results.push({
17289
+ ticker,
17290
+ error: `No price data found within date range ${startDate} to ${endDate}`
17291
+ });
17292
+ continue;
17293
+ }
17294
+ const journalPath = path4.join(directory, "ledger", "currencies", currencyConfig.file);
17295
+ updatePriceJournal(journalPath, priceLines);
17296
+ const latestPriceLine = priceLines[priceLines.length - 1];
17297
+ results.push({
17298
+ ticker,
17299
+ priceLine: latestPriceLine,
17300
+ file: currencyConfig.file
17301
+ });
17322
17302
  } catch (err) {
17323
17303
  results.push({
17324
17304
  ticker,
@@ -17326,10 +17306,37 @@ async function fetchPrices(directory, agent, backfill, priceFetcher = defaultPri
17326
17306
  });
17327
17307
  }
17328
17308
  }
17329
- for (const [ticker, stockConfig] of Object.entries(config2.stocks)) {
17309
+ for (const ticker of config2.stocks ?? []) {
17330
17310
  try {
17331
- const result = await fetchAndWritePrices(ticker, stockConfig, "ledger/stocks", directory, endDate, defaultBackfillDate, backfill, priceFetcher);
17332
- results.push(result);
17311
+ const startDate = backfill ? defaultBackfillDate : endDate;
17312
+ const cmdArgs = ["fetch", "-o", "ledger", "-s", startDate, "-e", endDate, "yahoo", ticker];
17313
+ const output = await priceFetcher(cmdArgs);
17314
+ const rawPriceLines = output.split(`
17315
+ `).filter((line) => line.startsWith("P "));
17316
+ if (rawPriceLines.length === 0) {
17317
+ results.push({
17318
+ ticker,
17319
+ error: `No price lines in pricehist output: ${output}`
17320
+ });
17321
+ continue;
17322
+ }
17323
+ const priceLines = filterAndSortPriceLinesByDateRange(rawPriceLines, startDate, endDate).map(ensureQuotedSymbols);
17324
+ if (priceLines.length === 0) {
17325
+ results.push({
17326
+ ticker,
17327
+ error: `No price data found within date range ${startDate} to ${endDate}`
17328
+ });
17329
+ continue;
17330
+ }
17331
+ const file2 = `${ticker.toLowerCase()}.journal`;
17332
+ const journalPath = path4.join(directory, "ledger", "stocks", file2);
17333
+ updatePriceJournal(journalPath, priceLines);
17334
+ const latestPriceLine = priceLines[priceLines.length - 1];
17335
+ results.push({
17336
+ ticker,
17337
+ priceLine: latestPriceLine,
17338
+ file: file2
17339
+ });
17333
17340
  } catch (err) {
17334
17341
  results.push({
17335
17342
  ticker,
@@ -17339,15 +17346,15 @@ async function fetchPrices(directory, agent, backfill, priceFetcher = defaultPri
17339
17346
  }
17340
17347
  return buildSuccessResult(results, endDate, backfill);
17341
17348
  }
17342
- var fetch_prices_default = tool({
17349
+ var fetch_currency_prices_default = tool({
17343
17350
  description: "ACCOUNTANT AGENT ONLY: Fetches end-of-day prices for all configured currencies and stock tickers (from config/prices.yaml) and appends them to the corresponding price journals in ledger/currencies/ and ledger/stocks/.",
17344
17351
  args: {
17345
- backfill: tool.schema.boolean().optional().describe("If true, fetch history from each currency/stock's configured backfill_date (or Jan 1 of current year if not specified)")
17352
+ backfill: tool.schema.boolean().optional().describe("If true, fetch history from each currency's configured backfill_date (or Jan 1 of current year if not specified)")
17346
17353
  },
17347
17354
  async execute(params, context) {
17348
17355
  const { directory, agent } = context;
17349
17356
  const { backfill } = params;
17350
- return fetchPrices(directory, agent, backfill || false);
17357
+ return fetchCurrencyPrices(directory, agent, backfill || false);
17351
17358
  }
17352
17359
  });
17353
17360
  // src/tools/classify-statements.ts
@@ -27810,7 +27817,7 @@ var AccountantPlugin = async () => {
27810
27817
  const agent = loadAgent(AGENT_FILE);
27811
27818
  return {
27812
27819
  tool: {
27813
- "fetch-prices": fetch_prices_default,
27820
+ "fetch-currency-prices": fetch_currency_prices_default,
27814
27821
  "classify-statements": classify_statements_default,
27815
27822
  "import-statements": import_statements_default,
27816
27823
  "reconcile-statements": reconcile_statement_default,
@@ -1,6 +1,6 @@
1
- # fetch-prices Tool
1
+ # fetch-currency-prices Tool
2
2
 
3
- The `fetch-prices` tool fetches end-of-day currency exchange rates and stock prices, updating the price journals in `ledger/currencies/` and `ledger/stocks/`. It uses the external `pricehist` tool to fetch data from various sources (Yahoo Finance, CoinMarketCap, ECB, etc.).
3
+ The `fetch-currency-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
4
 
5
5
  This tool is **restricted to the accountant agent only**.
6
6
 
@@ -33,15 +33,15 @@ When fetching only yesterday's prices:
33
33
  "file": "usd.journal"
34
34
  },
35
35
  {
36
- "ticker": "AG.TO",
37
- "priceLine": "P 2026-02-21 \"AG.TO\" 10.07 CAD",
38
- "file": "ag.to.journal"
36
+ "ticker": "BTC",
37
+ "priceLine": "P 2026-02-21 BTC 52341.50 CHF",
38
+ "file": "btc.journal"
39
39
  }
40
40
  ]
41
41
  }
42
42
  ```
43
43
 
44
- **Note:** The `priceLine` shows the latest (most recent) price added. In daily mode, only one price per ticker is fetched. Stock symbols with dots are automatically quoted for hledger compatibility.
44
+ **Note:** The `priceLine` shows the latest (most recent) price added. In daily mode, only one price per currency is fetched.
45
45
 
46
46
  ### Backfill Success (backfill: true)
47
47
 
@@ -59,9 +59,9 @@ When fetching historical prices:
59
59
  "file": "eur.journal"
60
60
  },
61
61
  {
62
- "ticker": "DRLL",
63
- "priceLine": "P 2026-02-21 DRLL 28.77 USD",
64
- "file": "drll.journal"
62
+ "ticker": "USD",
63
+ "priceLine": "P 2026-02-21 USD 0.881 CHF",
64
+ "file": "usd.journal"
65
65
  }
66
66
  ]
67
67
  }
@@ -71,7 +71,7 @@ When fetching historical prices:
71
71
 
72
72
  ### Partial Failure
73
73
 
74
- When some tickers succeed and others fail:
74
+ When some currencies succeed and others fail:
75
75
 
76
76
  ```json
77
77
  {
@@ -96,7 +96,7 @@ When some tickers succeed and others fail:
96
96
  }
97
97
  ```
98
98
 
99
- The tool processes all currencies and stocks independently. Partial success is possible.
99
+ The tool processes all currencies independently. Partial success is possible.
100
100
 
101
101
  ### Configuration Error
102
102
 
@@ -126,8 +126,8 @@ When called by the wrong agent:
126
126
 
127
127
  **Behavior:**
128
128
 
129
- - Fetches only yesterday's price for each currency/stock
130
- - Fast execution (single date per ticker)
129
+ - Fetches only yesterday's price for each currency
130
+ - Fast execution (single date per currency)
131
131
  - Typical use: Daily or weekly routine updates
132
132
 
133
133
  **Date range:** Yesterday to yesterday
@@ -135,9 +135,9 @@ When called by the wrong agent:
135
135
  **Example:**
136
136
 
137
137
  ```
138
- fetch-prices()
138
+ fetch-currency-prices()
139
139
  # or
140
- fetch-prices(backfill: false)
140
+ fetch-currency-prices(backfill: false)
141
141
  ```
142
142
 
143
143
  **Use when:**
@@ -151,22 +151,22 @@ fetch-prices(backfill: false)
151
151
  **Behavior:**
152
152
 
153
153
  - Fetches historical prices from `backfill_date` to yesterday
154
- - Slower execution (multiple dates per ticker)
155
- - Typical use: Initial setup, adding new currency/stock, filling gaps
154
+ - Slower execution (multiple dates per currency)
155
+ - Typical use: Initial setup, adding new currency, filling gaps
156
156
 
157
- **Date range:** Per-ticker `backfill_date` (or default) to yesterday
157
+ **Date range:** Per-currency `backfill_date` (or default) to yesterday
158
158
 
159
159
  **Example:**
160
160
 
161
161
  ```
162
- fetch-prices(backfill: true)
162
+ fetch-currency-prices(backfill: true)
163
163
  ```
164
164
 
165
165
  **Use when:**
166
166
 
167
- - Adding a new currency or stock (populate full history)
167
+ - Adding a new currency (populate full history)
168
168
  - Fixing missing dates (fill gaps)
169
- - Initial repository setup (populate all prices)
169
+ - Initial repository setup (populate all currencies)
170
170
  - Recovering from extended outage
171
171
 
172
172
  ### Backfill Date Configuration
@@ -182,22 +182,12 @@ currencies:
182
182
  backfill_date: '2024-01-01' # Start from this date
183
183
  ```
184
184
 
185
- **Per-stock backfill_date:**
186
-
187
- ```yaml
188
- stocks:
189
- BOGO:
190
- pair: BOGO.V
191
- fmt_base: BOGO
192
- backfill_date: '2025-06-01'
193
- ```
194
-
195
185
  **Default backfill_date:**
196
186
 
197
- - If no `backfill_date` is configured: January 1st of current year
187
+ - If currency has no `backfill_date` configured: January 1st of current year
198
188
  - Example: In 2026, default is `2026-01-01`
199
189
 
200
- **Why per-ticker?**
190
+ **Why per-currency?**
201
191
 
202
192
  - Different assets have different availability histories
203
193
  - Cryptocurrencies may have shorter histories
@@ -228,7 +218,7 @@ The tool **always fetches up to yesterday**, never today.
228
218
 
229
219
  **Backfill mode:**
230
220
 
231
- - Start date = ticker's `backfill_date` (or default: Jan 1 of current year)
221
+ - Start date = currency's `backfill_date` (or default: Jan 1 of current year)
232
222
  - Fetches range from start to yesterday
233
223
 
234
224
  ### Date Filtering
@@ -309,12 +299,6 @@ P 2026-01-16 EUR 0.943 CHF
309
299
  P 2026-02-21 EUR 0.944 CHF
310
300
  ```
311
301
 
312
- ```
313
- # ledger/stocks/ag.to.journal
314
- P 2026-01-15 00:00:00 "AG.TO" 10.05 CAD
315
- P 2026-01-16 00:00:00 "AG.TO" 10.07 CAD
316
- ```
317
-
318
302
  ### Price Line Format
319
303
 
320
304
  **Format:** `P date commodity price base-currency`
@@ -323,40 +307,33 @@ P 2026-01-16 00:00:00 "AG.TO" 10.07 CAD
323
307
 
324
308
  - `P` = Price directive (hledger syntax)
325
309
  - `date` = YYYY-MM-DD (may include timestamp HH:MM:SS)
326
- - `commodity` = Currency/stock being priced (e.g., EUR, USD, BTC, "AG.TO")
327
- - `price` = Exchange rate or price value
328
- - `base-currency` = Base currency for conversion (e.g., CHF, CAD, USD)
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)
329
313
 
330
- **Examples:**
314
+ **Example:**
331
315
 
332
316
  ```
333
317
  P 2026-02-21 EUR 0.944 CHF
334
- P 2026-02-21 00:00:00 "AG.TO" 10.07 CAD
335
- P 2026-02-21 00:00:00 DRLL 28.77 USD
336
318
  ```
337
319
 
338
- ### Symbol Quoting
339
-
340
- Stock symbols containing dots (e.g., `AG.TO`, `BRC.V`) are automatically quoted with double quotes for hledger compatibility. Symbols without dots (e.g., `DRLL`, `GBTC`) remain unquoted.
320
+ Means: 1 EUR = 0.944 CHF on 2026-02-21
341
321
 
342
322
  ### File Locations
343
323
 
344
- - Currency price journals: `ledger/currencies/` (one file per currency)
345
- - Stock price journals: `ledger/stocks/` (one file per stock)
324
+ - All price journals stored in `ledger/currencies/`
325
+ - One file per currency (configured in `config/prices.yaml`)
346
326
  - Files updated in place (existing prices preserved, new ones added/merged)
347
327
 
348
328
  **Example structure:**
349
329
 
350
330
  ```
351
331
  ledger/
352
- ├── currencies/
353
- ├── eur-chf.journal
354
- ├── usd-chf.journal
355
- │ └── btc-chf.journal
356
- └── stocks/
357
- ├── ag.to.journal
358
- ├── drll.journal
359
- └── bogo.journal
332
+ └── currencies/
333
+ ├── eur.journal
334
+ ├── usd.journal
335
+ ├── btc.journal
336
+ └── eth.journal
360
337
  ```
361
338
 
362
339
  ## Typical Workflow
@@ -365,12 +342,11 @@ ledger/
365
342
 
366
343
  **Goal:** Keep prices current with daily/weekly updates
367
344
 
368
- 1. Run `fetch-prices()` (or `fetch-prices(backfill: false)`)
345
+ 1. Run `fetch-currency-prices()` (or `fetch-currency-prices(backfill: false)`)
369
346
  2. Check output for any errors
370
347
  3. Verify prices were added:
371
348
  ```bash
372
- tail -3 ledger/currencies/eur-chf.journal
373
- tail -3 ledger/stocks/ag.to.journal
349
+ tail -3 ledger/currencies/eur.journal
374
350
  ```
375
351
  4. Success: Latest prices now available for hledger
376
352
 
@@ -390,55 +366,39 @@ ledger/
390
366
  file: gbp.journal
391
367
  backfill_date: '2024-01-01'
392
368
  ```
393
- 2. Run `fetch-prices(backfill: true)` to fetch historical data
369
+ 2. Run `fetch-currency-prices(backfill: true)` to fetch historical data
394
370
  3. Check output and verify `ledger/currencies/gbp.journal` created
395
371
  4. Inspect file to confirm date range:
396
372
  ```bash
397
373
  head -3 ledger/currencies/gbp.journal
398
374
  tail -3 ledger/currencies/gbp.journal
399
375
  ```
400
- 5. Subsequent updates: use daily mode (`fetch-prices()`)
376
+ 5. Subsequent updates: use daily mode (`fetch-currency-prices()`)
401
377
 
402
- ### Scenario 3: Adding a New Stock
403
-
404
- **Goal:** Add a new stock ticker
405
-
406
- 1. Add stock to `config/prices.yaml`:
407
- ```yaml
408
- stocks:
409
- AAPL: # Defaults: source=yahoo, pair=AAPL, file=aapl.journal
410
- BOGO: # Custom pair and fmt_base for TSXV listing
411
- pair: BOGO.V
412
- fmt_base: BOGO
413
- ```
414
- 2. Run `fetch-prices(backfill: true)` to fetch historical data
415
- 3. Check output and verify `ledger/stocks/aapl.journal` created
416
- 4. Subsequent updates: use daily mode (`fetch-prices()`)
417
-
418
- ### Scenario 4: Fixing Missing Dates
378
+ ### Scenario 3: Fixing Missing Dates
419
379
 
420
380
  **Goal:** Fill gaps in existing price history
421
381
 
422
382
  1. Identify date gaps in price journals:
423
383
  ```bash
424
- cat ledger/currencies/eur-chf.journal
384
+ cat ledger/currencies/eur.journal
425
385
  # Notice: prices for Feb 15-20 are missing
426
386
  ```
427
- 2. Run `fetch-prices(backfill: true)` to fill gaps
387
+ 2. Run `fetch-currency-prices(backfill: true)` to fill gaps
428
388
  3. Deduplication ensures:
429
389
  - Existing prices preserved
430
390
  - Missing dates added
431
391
  - No duplicate entries
432
392
  4. Verify gaps are filled:
433
393
  ```bash
434
- cat ledger/currencies/eur-chf.journal | grep "2026-02"
394
+ cat ledger/currencies/eur.journal | grep "2026-02"
435
395
  ```
436
396
 
437
- ### Scenario 5: Handling Errors
397
+ ### Scenario 4: Handling Errors
438
398
 
439
399
  **Goal:** Recover from partial failures
440
400
 
441
- 1. Run `fetch-prices()`
401
+ 1. Run `fetch-currency-prices()`
442
402
  2. Output shows `success: false` with partial results:
443
403
  ```json
444
404
  {
@@ -449,7 +409,7 @@ ledger/
449
409
  ]
450
410
  }
451
411
  ```
452
- 3. Check error messages for each failed ticker
412
+ 3. Check error messages for each failed currency
453
413
  4. Common fixes:
454
414
  - **Network issues**: Retry later
455
415
  - **API rate limits**: Wait (usually resets hourly/daily) and retry
@@ -457,7 +417,7 @@ ledger/
457
417
  - **Missing pricehist**: Install external dependency
458
418
  5. Re-run tool (idempotent - safe to retry)
459
419
 
460
- **Note:** Successful tickers are already updated. Only failed tickers need retry.
420
+ **Note:** Successful currencies are already updated. Only failed currencies need retry.
461
421
 
462
422
  ## Configuration
463
423
 
@@ -468,23 +428,14 @@ ledger/
468
428
  ```yaml
469
429
  currencies:
470
430
  <TICKER>:
471
- source: <source-name> # Required: e.g., yahoo, coinmarketcap, ecb
472
- pair: <trading-pair> # Required: source-specific format
473
- file: <journal-filename> # Required: e.g., eur-chf.journal
474
- fmt_base: <base-currency> # Optional: e.g., CHF, USD
475
- backfill_date: <YYYY-MM-DD> # Optional: per-currency backfill start
476
-
477
- stocks:
478
- <TICKER>: # Null value → all defaults (source=yahoo, pair=key, file=key.lower.journal)
479
- <TICKER>:
480
- pair: <exchange-ticker> # Optional: override pair (e.g., BOGO.V for TSXV listing)
481
- fmt_base: <symbol> # Optional: rename symbol in output (e.g., BOGO)
482
- source: <source-name> # Optional: default yahoo
483
- file: <journal-filename> # Optional: default {key.lowercase}.journal
484
- backfill_date: <YYYY-MM-DD> # Optional: per-stock backfill start
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
485
436
  ```
486
437
 
487
- ### Currency Field Descriptions
438
+ ### Field Descriptions
488
439
 
489
440
  | Field | Required | Description |
490
441
  | --------------- | -------- | -------------------------------------------------------------- |
@@ -494,16 +445,6 @@ stocks:
494
445
  | `fmt_base` | No | Base currency for price notation (default: inferred from pair) |
495
446
  | `backfill_date` | No | Override default backfill start date for this currency |
496
447
 
497
- ### Stock Field Descriptions
498
-
499
- | Field | Default | Description |
500
- | --------------- | ---------------------------- | ---------------------------------------------------------------- |
501
- | `source` | `yahoo` | Price data source |
502
- | `pair` | the ticker key itself | Ticker/pair to fetch (useful when exchange ticker differs) |
503
- | `file` | `{lowercase key}.journal` | Journal filename in `ledger/stocks/` |
504
- | `fmt_base` | — | Symbol override in output (e.g., `BOGO` when pair is `BOGO.V`) |
505
- | `backfill_date` | — | Override default backfill start date for this stock |
506
-
507
448
  ### Configuration Examples
508
449
 
509
450
  **Fiat currencies (Yahoo Finance):**
@@ -553,17 +494,6 @@ currencies:
553
494
  file: eur.journal
554
495
  ```
555
496
 
556
- **Stocks (Yahoo Finance):**
557
-
558
- ```yaml
559
- stocks:
560
- AG.TO: # Defaults: yahoo, pair=AG.TO, file=ag.to.journal
561
- DRLL: # Defaults: yahoo, pair=DRLL, file=drll.journal
562
- BOGO: # Custom: fetches BOGO.V, outputs as BOGO
563
- pair: BOGO.V
564
- fmt_base: BOGO
565
- ```
566
-
567
497
  ### External Dependency: pricehist
568
498
 
569
499
  The tool uses the `pricehist` command-line tool to fetch price data.
@@ -590,19 +520,19 @@ The tool uses the `pricehist` command-line tool to fetch price data.
590
520
  | Configuration error | Missing or invalid `config/prices.yaml` | Ensure config file exists with proper YAML syntax and required fields |
591
521
  | Invalid date range | Start date after end date | Check `backfill_date` configuration; must be before yesterday |
592
522
  | Agent restriction | Called by wrong agent | Use `Task(subagent_type='accountant', prompt='update prices')` |
593
- | Permission error | Cannot write to journal files | Check file permissions on `ledger/currencies/` and `ledger/stocks/` dirs |
523
+ | Permission error | Cannot write to journal files | Check file permissions on `ledger/currencies/` directory |
594
524
  | Invalid source/pair | Source or pair format incorrect | Check `pricehist` docs for correct format for your source |
595
525
 
596
526
  ### Partial Failures
597
527
 
598
- The tool processes all currencies and stocks **independently**.
528
+ The tool processes all currencies **independently**.
599
529
 
600
530
  **Behavior:**
601
531
 
602
- - Each ticker is fetched and updated separately
603
- - Failure in one ticker doesn't affect others
604
- - Overall `success: false` if ANY ticker fails
605
- - Check `results` array for per-ticker status
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
606
536
 
607
537
  **Example:**
608
538
 
@@ -610,20 +540,19 @@ The tool processes all currencies and stocks **independently**.
610
540
  {
611
541
  "success": false,
612
542
  "results": [
613
- { "ticker": "EUR", "priceLine": "P 2026-02-21 EUR 0.944 CHF", "file": "eur-chf.journal" },
614
- { "ticker": "BTC", "error": "API rate limit exceeded" },
615
- { "ticker": "AG.TO", "priceLine": "P 2026-02-21 \"AG.TO\" 10.07 CAD", "file": "ag.to.journal" }
543
+ { "ticker": "EUR", "priceLine": "P 2026-02-21 EUR 0.944 CHF", "file": "eur.journal" },
544
+ { "ticker": "BTC", "error": "API rate limit exceeded" }
616
545
  ]
617
546
  }
618
547
  ```
619
548
 
620
- EUR and AG.TO succeeded (updated), BTC failed (not updated).
549
+ EUR succeeded (updated), BTC failed (not updated).
621
550
 
622
551
  **Recovery:**
623
552
 
624
553
  - Can re-run tool safely (idempotent)
625
- - Successful tickers won't re-fetch (deduplication handles this)
626
- - Only failed tickers will retry
554
+ - Successful currencies won't re-fetch (deduplication handles this)
555
+ - Only failed currencies will retry
627
556
 
628
557
  ### Debugging Tips
629
558
 
@@ -631,15 +560,12 @@ EUR and AG.TO succeeded (updated), BTC failed (not updated).
631
560
 
632
561
  ```bash
633
562
  pricehist fetch -o ledger -s 2026-02-21 -e 2026-02-21 yahoo EURCHF=X
634
- pricehist fetch -o ledger -s 2026-02-21 -e 2026-02-21 yahoo AG.TO
635
- pricehist fetch -o ledger -s 2026-02-21 -e 2026-02-21 yahoo BOGO.V --fmt-base BOGO
636
563
  ```
637
564
 
638
- **Check journal files:**
565
+ **Check journal file:**
639
566
 
640
567
  ```bash
641
- cat ledger/currencies/eur-chf.journal
642
- cat ledger/stocks/ag.to.journal
568
+ cat ledger/currencies/eur.journal
643
569
  ```
644
570
 
645
571
  **Check config syntax:**
@@ -652,5 +578,4 @@ cat config/prices.yaml
652
578
 
653
579
  ```bash
654
580
  ls -la ledger/currencies/
655
- ls -la ledger/stocks/
656
581
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzzle/opencode-accountant",
3
- "version": "0.14.0-next.1",
3
+ "version": "0.14.0",
4
4
  "description": "An OpenCode accounting agent, specialized in double-entry-bookkepping with hledger",
5
5
  "author": {
6
6
  "name": "ali bengali",