@fuzzle/opencode-accountant 0.5.0 → 0.5.1

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.
Files changed (2) hide show
  1. package/dist/index.js +133 -141
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2676,12 +2676,12 @@ var init_js_yaml = __esm(() => {
2676
2676
  });
2677
2677
 
2678
2678
  // src/utils/agentLoader.ts
2679
- import { existsSync, readFileSync } from "fs";
2679
+ import * as fs from "fs";
2680
2680
  function loadAgent(filePath) {
2681
- if (!existsSync(filePath)) {
2681
+ if (!fs.existsSync(filePath)) {
2682
2682
  return null;
2683
2683
  }
2684
- const content = readFileSync(filePath, "utf-8");
2684
+ const content = fs.readFileSync(filePath, "utf-8");
2685
2685
  const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
2686
2686
  if (!match) {
2687
2687
  throw new Error(`Invalid frontmatter format in ${filePath}`);
@@ -4231,7 +4231,7 @@ __export(exports_accountSuggester, {
4231
4231
  extractRulePatternsFromFile: () => extractRulePatternsFromFile,
4232
4232
  clearSuggestionCache: () => clearSuggestionCache
4233
4233
  });
4234
- import * as fs13 from "fs";
4234
+ import * as fs14 from "fs";
4235
4235
  import * as crypto from "crypto";
4236
4236
  function clearSuggestionCache() {
4237
4237
  Object.keys(suggestionCache).forEach((key) => delete suggestionCache[key]);
@@ -4240,11 +4240,11 @@ function hashTransaction(posting) {
4240
4240
  const data = `${posting.description}|${posting.amount}|${posting.account}`;
4241
4241
  return crypto.createHash("md5").update(data).digest("hex");
4242
4242
  }
4243
- async function loadExistingAccounts(yearJournalPath) {
4244
- if (!fs13.existsSync(yearJournalPath)) {
4243
+ function loadExistingAccounts(yearJournalPath) {
4244
+ if (!fs14.existsSync(yearJournalPath)) {
4245
4245
  return [];
4246
4246
  }
4247
- const content = fs13.readFileSync(yearJournalPath, "utf-8");
4247
+ const content = fs14.readFileSync(yearJournalPath, "utf-8");
4248
4248
  const lines = content.split(`
4249
4249
  `);
4250
4250
  const accounts = [];
@@ -4259,11 +4259,11 @@ async function loadExistingAccounts(yearJournalPath) {
4259
4259
  }
4260
4260
  return accounts.sort();
4261
4261
  }
4262
- async function extractRulePatternsFromFile(rulesPath) {
4263
- if (!fs13.existsSync(rulesPath)) {
4262
+ function extractRulePatternsFromFile(rulesPath) {
4263
+ if (!fs14.existsSync(rulesPath)) {
4264
4264
  return [];
4265
4265
  }
4266
- const content = fs13.readFileSync(rulesPath, "utf-8");
4266
+ const content = fs14.readFileSync(rulesPath, "utf-8");
4267
4267
  const lines = content.split(`
4268
4268
  `);
4269
4269
  const patterns = [];
@@ -16844,16 +16844,16 @@ function checkAccountantAgent(agent, toolPrompt, additionalFields) {
16844
16844
 
16845
16845
  // src/utils/yamlLoader.ts
16846
16846
  init_js_yaml();
16847
- import * as fs from "fs";
16847
+ import * as fs2 from "fs";
16848
16848
  import * as path from "path";
16849
16849
  function loadYamlConfig(directory, configFile, validator, notFoundMessage) {
16850
16850
  const configPath = path.join(directory, configFile);
16851
- if (!fs.existsSync(configPath)) {
16851
+ if (!fs2.existsSync(configPath)) {
16852
16852
  throw new Error(notFoundMessage || `Configuration file not found: ${configFile}. Please create this file to configure the feature.`);
16853
16853
  }
16854
16854
  let parsed;
16855
16855
  try {
16856
- const content = fs.readFileSync(configPath, "utf-8");
16856
+ const content = fs2.readFileSync(configPath, "utf-8");
16857
16857
  parsed = jsYaml.load(content);
16858
16858
  } catch (err) {
16859
16859
  if (err instanceof jsYaml.YAMLException) {
@@ -16910,14 +16910,14 @@ function loadPricesConfig(directory) {
16910
16910
  }
16911
16911
 
16912
16912
  // src/utils/journalUtils.ts
16913
- import * as fs3 from "fs";
16913
+ import * as fs4 from "fs";
16914
16914
  import * as path3 from "path";
16915
16915
 
16916
16916
  // src/utils/fileUtils.ts
16917
- import * as fs2 from "fs";
16917
+ import * as fs3 from "fs";
16918
16918
  import * as path2 from "path";
16919
16919
  function findCsvFiles(baseDir, options = {}) {
16920
- if (!fs2.existsSync(baseDir)) {
16920
+ if (!fs3.existsSync(baseDir)) {
16921
16921
  return [];
16922
16922
  }
16923
16923
  let searchDir = baseDir;
@@ -16927,13 +16927,13 @@ function findCsvFiles(baseDir, options = {}) {
16927
16927
  searchDir = path2.join(searchDir, options.subsubdir);
16928
16928
  }
16929
16929
  }
16930
- if (!fs2.existsSync(searchDir)) {
16930
+ if (!fs3.existsSync(searchDir)) {
16931
16931
  return [];
16932
16932
  }
16933
16933
  const csvFiles = [];
16934
16934
  if (options.recursive) {
16935
16935
  let scanDirectory = function(dir) {
16936
- const entries = fs2.readdirSync(dir, { withFileTypes: true });
16936
+ const entries = fs3.readdirSync(dir, { withFileTypes: true });
16937
16937
  for (const entry of entries) {
16938
16938
  const fullPath = path2.join(dir, entry.name);
16939
16939
  if (entry.isDirectory()) {
@@ -16945,12 +16945,12 @@ function findCsvFiles(baseDir, options = {}) {
16945
16945
  };
16946
16946
  scanDirectory(searchDir);
16947
16947
  } else {
16948
- const entries = fs2.readdirSync(searchDir);
16948
+ const entries = fs3.readdirSync(searchDir);
16949
16949
  for (const name of entries) {
16950
16950
  if (!name.toLowerCase().endsWith(".csv"))
16951
16951
  continue;
16952
16952
  const fullPath = path2.join(searchDir, name);
16953
- if (fs2.statSync(fullPath).isFile()) {
16953
+ if (fs3.statSync(fullPath).isFile()) {
16954
16954
  csvFiles.push(options.fullPaths ? fullPath : name);
16955
16955
  }
16956
16956
  }
@@ -16958,8 +16958,8 @@ function findCsvFiles(baseDir, options = {}) {
16958
16958
  return csvFiles.sort();
16959
16959
  }
16960
16960
  function ensureDirectory(dirPath) {
16961
- if (!fs2.existsSync(dirPath)) {
16962
- fs2.mkdirSync(dirPath, { recursive: true });
16961
+ if (!fs3.existsSync(dirPath)) {
16962
+ fs3.mkdirSync(dirPath, { recursive: true });
16963
16963
  }
16964
16964
  }
16965
16965
 
@@ -16969,8 +16969,8 @@ function extractDateFromPriceLine(line) {
16969
16969
  }
16970
16970
  function updatePriceJournal(journalPath, newPriceLines) {
16971
16971
  let existingLines = [];
16972
- if (fs3.existsSync(journalPath)) {
16973
- existingLines = fs3.readFileSync(journalPath, "utf-8").split(`
16972
+ if (fs4.existsSync(journalPath)) {
16973
+ existingLines = fs4.readFileSync(journalPath, "utf-8").split(`
16974
16974
  `).filter((line) => line.trim() !== "");
16975
16975
  }
16976
16976
  const priceMap = new Map;
@@ -16985,7 +16985,7 @@ function updatePriceJournal(journalPath, newPriceLines) {
16985
16985
  priceMap.set(date5, line);
16986
16986
  }
16987
16987
  const sortedLines = Array.from(priceMap.entries()).sort((a, b) => a[0].localeCompare(b[0])).map(([, line]) => line);
16988
- fs3.writeFileSync(journalPath, sortedLines.join(`
16988
+ fs4.writeFileSync(journalPath, sortedLines.join(`
16989
16989
  `) + `
16990
16990
  `);
16991
16991
  }
@@ -16993,17 +16993,15 @@ function ensureYearJournalExists(directory, year) {
16993
16993
  const ledgerDir = path3.join(directory, "ledger");
16994
16994
  const yearJournalPath = path3.join(ledgerDir, `${year}.journal`);
16995
16995
  const mainJournalPath = path3.join(directory, ".hledger.journal");
16996
- if (!fs3.existsSync(ledgerDir)) {
16997
- fs3.mkdirSync(ledgerDir, { recursive: true });
16998
- }
16999
- if (!fs3.existsSync(yearJournalPath)) {
17000
- fs3.writeFileSync(yearJournalPath, `; ${year} transactions
16996
+ ensureDirectory(ledgerDir);
16997
+ if (!fs4.existsSync(yearJournalPath)) {
16998
+ fs4.writeFileSync(yearJournalPath, `; ${year} transactions
17001
16999
  `);
17002
17000
  }
17003
- if (!fs3.existsSync(mainJournalPath)) {
17001
+ if (!fs4.existsSync(mainJournalPath)) {
17004
17002
  throw new Error(`.hledger.journal not found at ${mainJournalPath}. Create it first with appropriate includes.`);
17005
17003
  }
17006
- const mainJournalContent = fs3.readFileSync(mainJournalPath, "utf-8");
17004
+ const mainJournalContent = fs4.readFileSync(mainJournalPath, "utf-8");
17007
17005
  const includeDirective = `include ledger/${year}.journal`;
17008
17006
  const lines = mainJournalContent.split(`
17009
17007
  `);
@@ -17015,7 +17013,7 @@ function ensureYearJournalExists(directory, year) {
17015
17013
  const newContent = mainJournalContent.trimEnd() + `
17016
17014
  ` + includeDirective + `
17017
17015
  `;
17018
- fs3.writeFileSync(mainJournalPath, newContent);
17016
+ fs4.writeFileSync(mainJournalPath, newContent);
17019
17017
  }
17020
17018
  return yearJournalPath;
17021
17019
  }
@@ -17035,6 +17033,30 @@ function getNextDay(dateStr) {
17035
17033
  return formatDateISO(date5);
17036
17034
  }
17037
17035
 
17036
+ // src/utils/resultHelpers.ts
17037
+ function buildToolErrorResult(error45, hint, extra) {
17038
+ const result = {
17039
+ success: false,
17040
+ error: error45
17041
+ };
17042
+ if (hint) {
17043
+ result.hint = hint;
17044
+ }
17045
+ if (extra) {
17046
+ Object.assign(result, extra);
17047
+ }
17048
+ return JSON.stringify(result);
17049
+ }
17050
+ function buildToolSuccessResult(data) {
17051
+ const result = {
17052
+ success: true
17053
+ };
17054
+ if (data) {
17055
+ Object.assign(result, data);
17056
+ }
17057
+ return JSON.stringify(result);
17058
+ }
17059
+
17038
17060
  // src/tools/fetch-currency-prices.ts
17039
17061
  async function defaultPriceFetcher(cmdArgs) {
17040
17062
  const result = await $`pricehist ${cmdArgs}`.quiet();
@@ -17058,10 +17080,10 @@ function buildPricehistArgs(startDate, endDate, currencyConfig) {
17058
17080
  return cmdArgs;
17059
17081
  }
17060
17082
  function buildErrorResult(error45) {
17061
- return JSON.stringify({ error: error45 });
17083
+ return buildToolErrorResult(error45);
17062
17084
  }
17063
17085
  function buildSuccessResult(results, endDate, backfill) {
17064
- return JSON.stringify({
17086
+ return buildToolSuccessResult({
17065
17087
  success: results.every((r) => !("error" in r)),
17066
17088
  endDate,
17067
17089
  backfill,
@@ -17150,7 +17172,7 @@ var fetch_currency_prices_default = tool({
17150
17172
  }
17151
17173
  });
17152
17174
  // src/tools/classify-statements.ts
17153
- import * as fs5 from "fs";
17175
+ import * as fs6 from "fs";
17154
17176
  import * as path6 from "path";
17155
17177
 
17156
17178
  // src/utils/importConfig.ts
@@ -17405,17 +17427,14 @@ function detectProvider(filename, content, config2) {
17405
17427
  }
17406
17428
 
17407
17429
  // src/utils/importContext.ts
17408
- import * as fs4 from "fs";
17430
+ import * as fs5 from "fs";
17409
17431
  import * as path5 from "path";
17410
17432
  import { randomUUID } from "crypto";
17411
17433
  function getContextPath(directory, contextId) {
17412
17434
  return path5.join(directory, ".memory", `${contextId}.json`);
17413
17435
  }
17414
17436
  function ensureMemoryDir(directory) {
17415
- const memoryDir = path5.join(directory, ".memory");
17416
- if (!fs4.existsSync(memoryDir)) {
17417
- fs4.mkdirSync(memoryDir, { recursive: true });
17418
- }
17437
+ ensureDirectory(path5.join(directory, ".memory"));
17419
17438
  }
17420
17439
  function createContext(directory, params) {
17421
17440
  const now = new Date().toISOString();
@@ -17437,7 +17456,7 @@ function createContext(directory, params) {
17437
17456
  };
17438
17457
  ensureMemoryDir(directory);
17439
17458
  const contextPath = getContextPath(directory, context.id);
17440
- fs4.writeFileSync(contextPath, JSON.stringify(context, null, 2), "utf-8");
17459
+ fs5.writeFileSync(contextPath, JSON.stringify(context, null, 2), "utf-8");
17441
17460
  return context;
17442
17461
  }
17443
17462
  function validateContext(context, contextId) {
@@ -17456,10 +17475,10 @@ function validateContext(context, contextId) {
17456
17475
  }
17457
17476
  function loadContext(directory, contextId) {
17458
17477
  const contextPath = getContextPath(directory, contextId);
17459
- if (!fs4.existsSync(contextPath)) {
17478
+ if (!fs5.existsSync(contextPath)) {
17460
17479
  throw new Error(`Context not found: ${contextId}`);
17461
17480
  }
17462
- const content = fs4.readFileSync(contextPath, "utf-8");
17481
+ const content = fs5.readFileSync(contextPath, "utf-8");
17463
17482
  let context;
17464
17483
  try {
17465
17484
  context = JSON.parse(content);
@@ -17479,14 +17498,13 @@ function updateContext(directory, contextId, updates) {
17479
17498
  updatedAt: new Date().toISOString()
17480
17499
  };
17481
17500
  const contextPath = getContextPath(directory, contextId);
17482
- fs4.writeFileSync(contextPath, JSON.stringify(updatedContext, null, 2), "utf-8");
17501
+ fs5.writeFileSync(contextPath, JSON.stringify(updatedContext, null, 2), "utf-8");
17483
17502
  return updatedContext;
17484
17503
  }
17485
17504
 
17486
17505
  // src/tools/classify-statements.ts
17487
17506
  function buildSuccessResult2(classified, unrecognized, message) {
17488
- return JSON.stringify({
17489
- success: true,
17507
+ return buildToolSuccessResult({
17490
17508
  classified,
17491
17509
  unrecognized,
17492
17510
  message,
@@ -17498,19 +17516,14 @@ function buildSuccessResult2(classified, unrecognized, message) {
17498
17516
  });
17499
17517
  }
17500
17518
  function buildErrorResult2(error45, hint) {
17501
- return JSON.stringify({
17502
- success: false,
17503
- error: error45,
17504
- hint,
17519
+ return buildToolErrorResult(error45, hint, {
17505
17520
  classified: [],
17506
17521
  unrecognized: []
17507
17522
  });
17508
17523
  }
17509
17524
  function buildCollisionError(collisions) {
17510
17525
  const error45 = `Cannot classify: ${collisions.length} file(s) would overwrite existing pending files.`;
17511
- return JSON.stringify({
17512
- success: false,
17513
- error: error45,
17526
+ return buildToolErrorResult(error45, undefined, {
17514
17527
  collisions,
17515
17528
  classified: [],
17516
17529
  unrecognized: []
@@ -17521,7 +17534,7 @@ function planMoves(csvFiles, importsDir, pendingDir, unrecognizedDir, config2) {
17521
17534
  const collisions = [];
17522
17535
  for (const filename of csvFiles) {
17523
17536
  const sourcePath = path6.join(importsDir, filename);
17524
- const content = fs5.readFileSync(sourcePath, "utf-8");
17537
+ const content = fs6.readFileSync(sourcePath, "utf-8");
17525
17538
  const detection = detectProvider(filename, content, config2);
17526
17539
  let targetPath;
17527
17540
  let targetFilename;
@@ -17533,7 +17546,7 @@ function planMoves(csvFiles, importsDir, pendingDir, unrecognizedDir, config2) {
17533
17546
  targetFilename = filename;
17534
17547
  targetPath = path6.join(unrecognizedDir, filename);
17535
17548
  }
17536
- if (fs5.existsSync(targetPath)) {
17549
+ if (fs6.existsSync(targetPath)) {
17537
17550
  collisions.push({
17538
17551
  filename,
17539
17552
  existingPath: targetPath
@@ -17569,7 +17582,7 @@ function executeMoves(plannedMoves, config2, unrecognizedDir, directory) {
17569
17582
  if (move.detection) {
17570
17583
  const targetDir = path6.dirname(move.targetPath);
17571
17584
  ensureDirectory(targetDir);
17572
- fs5.renameSync(move.sourcePath, move.targetPath);
17585
+ fs6.renameSync(move.sourcePath, move.targetPath);
17573
17586
  const targetPath = path6.join(config2.paths.pending, move.detection.provider, move.detection.currency, move.targetFilename);
17574
17587
  const metadata = extractMetadata2(move.detection);
17575
17588
  const context = createContext(directory, {
@@ -17594,7 +17607,7 @@ function executeMoves(plannedMoves, config2, unrecognizedDir, directory) {
17594
17607
  });
17595
17608
  } else {
17596
17609
  ensureDirectory(unrecognizedDir);
17597
- fs5.renameSync(move.sourcePath, move.targetPath);
17610
+ fs6.renameSync(move.sourcePath, move.targetPath);
17598
17611
  unrecognized.push({
17599
17612
  filename: move.filename,
17600
17613
  targetPath: path6.join(config2.paths.unrecognized, move.filename)
@@ -17649,7 +17662,7 @@ For each CSV file:
17649
17662
  }
17650
17663
  });
17651
17664
  // src/tools/import-statements.ts
17652
- import * as fs9 from "fs";
17665
+ import * as fs10 from "fs";
17653
17666
  import * as path9 from "path";
17654
17667
 
17655
17668
  // node_modules/minimatch/dist/esm/index.js
@@ -21434,8 +21447,8 @@ class PathScurryBase {
21434
21447
  #children;
21435
21448
  nocase;
21436
21449
  #fs;
21437
- constructor(cwd = process.cwd(), pathImpl, sep2, { nocase, childrenCacheSize = 16 * 1024, fs: fs6 = defaultFS } = {}) {
21438
- this.#fs = fsFromOption(fs6);
21450
+ constructor(cwd = process.cwd(), pathImpl, sep2, { nocase, childrenCacheSize = 16 * 1024, fs: fs7 = defaultFS } = {}) {
21451
+ this.#fs = fsFromOption(fs7);
21439
21452
  if (cwd instanceof URL || cwd.startsWith("file://")) {
21440
21453
  cwd = fileURLToPath(cwd);
21441
21454
  }
@@ -21911,8 +21924,8 @@ class PathScurryWin32 extends PathScurryBase {
21911
21924
  parseRootPath(dir) {
21912
21925
  return win32.parse(dir).root.toUpperCase();
21913
21926
  }
21914
- newRoot(fs6) {
21915
- return new PathWin32(this.rootPath, IFDIR, undefined, this.roots, this.nocase, this.childrenCache(), { fs: fs6 });
21927
+ newRoot(fs7) {
21928
+ return new PathWin32(this.rootPath, IFDIR, undefined, this.roots, this.nocase, this.childrenCache(), { fs: fs7 });
21916
21929
  }
21917
21930
  isAbsolute(p) {
21918
21931
  return p.startsWith("/") || p.startsWith("\\") || /^[a-z]:(\/|\\)/i.test(p);
@@ -21929,8 +21942,8 @@ class PathScurryPosix extends PathScurryBase {
21929
21942
  parseRootPath(_dir) {
21930
21943
  return "/";
21931
21944
  }
21932
- newRoot(fs6) {
21933
- return new PathPosix(this.rootPath, IFDIR, undefined, this.roots, this.nocase, this.childrenCache(), { fs: fs6 });
21945
+ newRoot(fs7) {
21946
+ return new PathPosix(this.rootPath, IFDIR, undefined, this.roots, this.nocase, this.childrenCache(), { fs: fs7 });
21934
21947
  }
21935
21948
  isAbsolute(p) {
21936
21949
  return p.startsWith("/");
@@ -22932,7 +22945,7 @@ var glob = Object.assign(glob_, {
22932
22945
  glob.glob = glob;
22933
22946
 
22934
22947
  // src/utils/rulesMatcher.ts
22935
- import * as fs6 from "fs";
22948
+ import * as fs7 from "fs";
22936
22949
  import * as path8 from "path";
22937
22950
  function parseSourceDirective(content) {
22938
22951
  const match2 = content.match(/^source\s+([^\n#]+)/m);
@@ -22950,22 +22963,22 @@ function resolveSourcePath(sourcePath, rulesFilePath) {
22950
22963
  }
22951
22964
  function loadRulesMapping(rulesDir) {
22952
22965
  const mapping = {};
22953
- if (!fs6.existsSync(rulesDir)) {
22966
+ if (!fs7.existsSync(rulesDir)) {
22954
22967
  return mapping;
22955
22968
  }
22956
- const files = fs6.readdirSync(rulesDir);
22969
+ const files = fs7.readdirSync(rulesDir);
22957
22970
  for (const file2 of files) {
22958
22971
  if (!file2.endsWith(".rules")) {
22959
22972
  continue;
22960
22973
  }
22961
22974
  const rulesFilePath = path8.join(rulesDir, file2);
22962
- const stat = fs6.statSync(rulesFilePath);
22975
+ const stat = fs7.statSync(rulesFilePath);
22963
22976
  if (!stat.isFile()) {
22964
22977
  continue;
22965
22978
  }
22966
22979
  let content;
22967
22980
  try {
22968
- content = fs6.readFileSync(rulesFilePath, "utf-8");
22981
+ content = fs7.readFileSync(rulesFilePath, "utf-8");
22969
22982
  } catch {
22970
22983
  continue;
22971
22984
  }
@@ -23140,7 +23153,7 @@ async function getAccountBalance(mainJournalPath, account, asOfDate, executor =
23140
23153
  }
23141
23154
 
23142
23155
  // src/utils/rulesParser.ts
23143
- import * as fs7 from "fs";
23156
+ import * as fs8 from "fs";
23144
23157
  function parseSkipRows(rulesContent) {
23145
23158
  const match2 = rulesContent.match(/^skip\s+(\d+)/m);
23146
23159
  return match2 ? parseInt(match2[1], 10) : 0;
@@ -23206,7 +23219,7 @@ function parseAccount1(rulesContent) {
23206
23219
  }
23207
23220
  function getAccountFromRulesFile(rulesFilePath) {
23208
23221
  try {
23209
- const content = fs7.readFileSync(rulesFilePath, "utf-8");
23222
+ const content = fs8.readFileSync(rulesFilePath, "utf-8");
23210
23223
  return parseAccount1(content);
23211
23224
  } catch {
23212
23225
  return null;
@@ -23226,7 +23239,7 @@ function parseRulesFile(rulesContent) {
23226
23239
 
23227
23240
  // src/utils/csvParser.ts
23228
23241
  var import_papaparse2 = __toESM(require_papaparse(), 1);
23229
- import * as fs8 from "fs";
23242
+ import * as fs9 from "fs";
23230
23243
 
23231
23244
  // src/utils/balanceUtils.ts
23232
23245
  function parseAmountValue(amountStr) {
@@ -23276,7 +23289,7 @@ function balancesMatch(balance1, balance2) {
23276
23289
  // src/utils/csvParser.ts
23277
23290
  var AMOUNT_MATCH_TOLERANCE = 0.001;
23278
23291
  function parseCsvFile(csvPath, config2) {
23279
- const csvContent = fs8.readFileSync(csvPath, "utf-8");
23292
+ const csvContent = fs9.readFileSync(csvPath, "utf-8");
23280
23293
  const lines = csvContent.split(`
23281
23294
  `);
23282
23295
  const headerIndex = config2.skipRows;
@@ -23414,10 +23427,7 @@ function findMatchingCsvRow(posting, csvRows, config2) {
23414
23427
 
23415
23428
  // src/tools/import-statements.ts
23416
23429
  function buildErrorResult3(error45, hint) {
23417
- const result = {
23418
- success: false,
23419
- error: error45,
23420
- hint,
23430
+ return buildToolErrorResult(error45, hint, {
23421
23431
  files: [],
23422
23432
  summary: {
23423
23433
  filesProcessed: 0,
@@ -23427,28 +23437,16 @@ function buildErrorResult3(error45, hint) {
23427
23437
  matched: 0,
23428
23438
  unknown: 0
23429
23439
  }
23430
- };
23431
- return JSON.stringify(result);
23440
+ });
23432
23441
  }
23433
23442
  function buildErrorResultWithDetails(error45, files, summary, hint) {
23434
- return JSON.stringify({
23435
- success: false,
23436
- error: error45,
23437
- hint,
23438
- files,
23439
- summary
23440
- });
23443
+ return buildToolErrorResult(error45, hint, { files, summary });
23441
23444
  }
23442
23445
  function buildSuccessResult3(files, summary, message) {
23443
- return JSON.stringify({
23444
- success: true,
23445
- files,
23446
- summary,
23447
- message
23448
- });
23446
+ return buildToolSuccessResult({ files, summary, message });
23449
23447
  }
23450
23448
  function findCsvFromRulesFile(rulesFile) {
23451
- const content = fs9.readFileSync(rulesFile, "utf-8");
23449
+ const content = fs10.readFileSync(rulesFile, "utf-8");
23452
23450
  const match2 = content.match(/^source\s+([^\n#]+)/m);
23453
23451
  if (!match2) {
23454
23452
  return null;
@@ -23461,8 +23459,8 @@ function findCsvFromRulesFile(rulesFile) {
23461
23459
  return null;
23462
23460
  }
23463
23461
  matches.sort((a, b) => {
23464
- const aStat = fs9.statSync(a);
23465
- const bStat = fs9.statSync(b);
23462
+ const aStat = fs10.statSync(a);
23463
+ const bStat = fs10.statSync(b);
23466
23464
  return bStat.mtime.getTime() - aStat.mtime.getTime();
23467
23465
  });
23468
23466
  return matches[0];
@@ -23515,10 +23513,8 @@ async function executeImports(fileResults, directory, pendingDir, doneDir, hledg
23515
23513
  const relativePath = path9.relative(pendingDir, csvFile);
23516
23514
  const destPath = path9.join(doneDir, relativePath);
23517
23515
  const destDir = path9.dirname(destPath);
23518
- if (!fs9.existsSync(destDir)) {
23519
- fs9.mkdirSync(destDir, { recursive: true });
23520
- }
23521
- fs9.renameSync(csvFile, destPath);
23516
+ ensureDirectory(destDir);
23517
+ fs10.renameSync(csvFile, destPath);
23522
23518
  }
23523
23519
  return {
23524
23520
  success: true,
@@ -23566,7 +23562,7 @@ async function processCsvFile(csvFile, rulesMapping, directory, hledgerExecutor)
23566
23562
  const transactionYear = years.size === 1 ? Array.from(years)[0] : undefined;
23567
23563
  if (unknownPostings.length > 0) {
23568
23564
  try {
23569
- const rulesContent = fs9.readFileSync(rulesFile, "utf-8");
23565
+ const rulesContent = fs10.readFileSync(rulesFile, "utf-8");
23570
23566
  const rulesConfig = parseRulesFile(rulesContent);
23571
23567
  const csvRows = parseCsvFile(csvFile, rulesConfig);
23572
23568
  for (const posting of unknownPostings) {
@@ -23609,7 +23605,7 @@ async function importStatements(directory, agent, options, configLoader = loadIm
23609
23605
  const rulesMapping = loadRulesMapping(rulesDir);
23610
23606
  const importContext = loadContext(directory, options.contextId);
23611
23607
  const csvPath = path9.join(directory, importContext.filePath);
23612
- if (!fs9.existsSync(csvPath)) {
23608
+ if (!fs10.existsSync(csvPath)) {
23613
23609
  return buildErrorResult3(`CSV file not found: ${importContext.filePath}`, "The file may have been moved or deleted");
23614
23610
  }
23615
23611
  const csvFiles = [csvPath];
@@ -23644,8 +23640,8 @@ async function importStatements(directory, agent, options, configLoader = loadIm
23644
23640
  }
23645
23641
  for (const [_rulesFile, matchingCSVs] of rulesFileToCSVs.entries()) {
23646
23642
  matchingCSVs.sort((a, b) => {
23647
- const aStat = fs9.statSync(a);
23648
- const bStat = fs9.statSync(b);
23643
+ const aStat = fs10.statSync(a);
23644
+ const bStat = fs10.statSync(b);
23649
23645
  return bStat.mtime.getTime() - aStat.mtime.getTime();
23650
23646
  });
23651
23647
  const newestCSV = matchingCSVs[0];
@@ -23760,22 +23756,18 @@ Note: This tool is typically called via import-pipeline for the full workflow.`,
23760
23756
  }
23761
23757
  });
23762
23758
  // src/tools/reconcile-statement.ts
23763
- import * as fs10 from "fs";
23759
+ import * as fs11 from "fs";
23764
23760
  import * as path10 from "path";
23765
23761
  function buildErrorResult4(params) {
23766
- const result = {
23767
- success: false,
23762
+ return buildToolErrorResult(params.error, params.hint, {
23768
23763
  account: params.account ?? "",
23769
23764
  expectedBalance: params.expectedBalance ?? "",
23770
23765
  actualBalance: params.actualBalance ?? "",
23771
23766
  lastTransactionDate: params.lastTransactionDate ?? "",
23772
23767
  csvFile: params.csvFile ?? "",
23773
23768
  difference: params.difference,
23774
- metadata: params.metadata,
23775
- error: params.error,
23776
- hint: params.hint
23777
- };
23778
- return JSON.stringify(result);
23769
+ metadata: params.metadata
23770
+ });
23779
23771
  }
23780
23772
  function loadConfiguration(directory, configLoader) {
23781
23773
  try {
@@ -23792,7 +23784,7 @@ function loadConfiguration(directory, configLoader) {
23792
23784
  }
23793
23785
  function verifyCsvExists(directory, importContext) {
23794
23786
  const csvFile = path10.join(directory, importContext.filePath);
23795
- if (!fs10.existsSync(csvFile)) {
23787
+ if (!fs11.existsSync(csvFile)) {
23796
23788
  return {
23797
23789
  error: buildErrorResult4({
23798
23790
  error: `CSV file not found: ${importContext.filePath}`,
@@ -23830,7 +23822,7 @@ function getBalanceFromCsvAnalysis(csvFile, rulesDir) {
23830
23822
  }
23831
23823
  function extractCsvMetadata(csvFile, config2) {
23832
23824
  try {
23833
- const content = fs10.readFileSync(csvFile, "utf-8");
23825
+ const content = fs11.readFileSync(csvFile, "utf-8");
23834
23826
  const filename = path10.basename(csvFile);
23835
23827
  const detectionResult = detectProvider(filename, content, config2);
23836
23828
  if (detectionResult?.metadata) {
@@ -23908,7 +23900,7 @@ function tryExtractClosingBalanceFromCSV(csvFile, rulesDir) {
23908
23900
  if (!rulesFile) {
23909
23901
  return null;
23910
23902
  }
23911
- const rulesContent = fs10.readFileSync(rulesFile, "utf-8");
23903
+ const rulesContent = fs11.readFileSync(rulesFile, "utf-8");
23912
23904
  const rulesConfig = parseRulesFile(rulesContent);
23913
23905
  const csvRows = parseCsvFile(csvFile, rulesConfig);
23914
23906
  if (csvRows.length === 0) {
@@ -24116,13 +24108,13 @@ Note: This tool requires a contextId from a prior classify/import step.`,
24116
24108
  import * as path12 from "path";
24117
24109
 
24118
24110
  // src/utils/accountDeclarations.ts
24119
- import * as fs11 from "fs";
24111
+ import * as fs12 from "fs";
24120
24112
  function extractAccountsFromRulesFile(rulesPath) {
24121
24113
  const accounts = new Set;
24122
- if (!fs11.existsSync(rulesPath)) {
24114
+ if (!fs12.existsSync(rulesPath)) {
24123
24115
  return accounts;
24124
24116
  }
24125
- const content = fs11.readFileSync(rulesPath, "utf-8");
24117
+ const content = fs12.readFileSync(rulesPath, "utf-8");
24126
24118
  const lines = content.split(`
24127
24119
  `);
24128
24120
  for (const line of lines) {
@@ -24157,10 +24149,10 @@ function sortAccountDeclarations(accounts) {
24157
24149
  return Array.from(accounts).sort((a, b) => a.localeCompare(b));
24158
24150
  }
24159
24151
  function ensureAccountDeclarations(yearJournalPath, accounts) {
24160
- if (!fs11.existsSync(yearJournalPath)) {
24152
+ if (!fs12.existsSync(yearJournalPath)) {
24161
24153
  throw new Error(`Year journal not found: ${yearJournalPath}`);
24162
24154
  }
24163
- const content = fs11.readFileSync(yearJournalPath, "utf-8");
24155
+ const content = fs12.readFileSync(yearJournalPath, "utf-8");
24164
24156
  const lines = content.split(`
24165
24157
  `);
24166
24158
  const existingAccounts = new Set;
@@ -24222,7 +24214,7 @@ function ensureAccountDeclarations(yearJournalPath, accounts) {
24222
24214
  newContent.push("");
24223
24215
  }
24224
24216
  newContent.push(...otherLines);
24225
- fs11.writeFileSync(yearJournalPath, newContent.join(`
24217
+ fs12.writeFileSync(yearJournalPath, newContent.join(`
24226
24218
  `));
24227
24219
  return {
24228
24220
  added: Array.from(missingAccounts).sort(),
@@ -24231,7 +24223,7 @@ function ensureAccountDeclarations(yearJournalPath, accounts) {
24231
24223
  }
24232
24224
 
24233
24225
  // src/utils/logger.ts
24234
- import fs12 from "fs/promises";
24226
+ import fs13 from "fs/promises";
24235
24227
  import path11 from "path";
24236
24228
  var LOG_LINE_LIMIT = 50;
24237
24229
 
@@ -24346,8 +24338,8 @@ class MarkdownLogger {
24346
24338
  if (this.buffer.length === 0)
24347
24339
  return;
24348
24340
  try {
24349
- await fs12.mkdir(path11.dirname(this.logPath), { recursive: true });
24350
- await fs12.writeFile(this.logPath, this.buffer.join(`
24341
+ await fs13.mkdir(path11.dirname(this.logPath), { recursive: true });
24342
+ await fs13.writeFile(this.logPath, this.buffer.join(`
24351
24343
  `), "utf-8");
24352
24344
  } catch {}
24353
24345
  }
@@ -24401,12 +24393,12 @@ function buildStepResult(success2, message, details) {
24401
24393
  }
24402
24394
  return result;
24403
24395
  }
24404
- function buildSuccessResult4(result, summary) {
24396
+ function buildPipelineSuccessResult(result, summary) {
24405
24397
  result.success = true;
24406
24398
  result.summary = summary;
24407
24399
  return JSON.stringify(result);
24408
24400
  }
24409
- function buildErrorResult5(result, error45, hint) {
24401
+ function buildPipelineErrorResult(result, error45, hint) {
24410
24402
  result.success = false;
24411
24403
  result.error = error45;
24412
24404
  if (hint) {
@@ -24586,9 +24578,9 @@ async function executeDryRunStep(context, contextId, logger) {
24586
24578
  } catch {}
24587
24579
  }
24588
24580
  const suggestionContext = {
24589
- existingAccounts: yearJournalPath ? await loadExistingAccounts2(yearJournalPath) : [],
24581
+ existingAccounts: yearJournalPath ? loadExistingAccounts2(yearJournalPath) : [],
24590
24582
  rulesFilePath: firstRulesFile,
24591
- existingRules: firstRulesFile ? await extractRulePatternsFromFile2(firstRulesFile) : undefined,
24583
+ existingRules: firstRulesFile ? extractRulePatternsFromFile2(firstRulesFile) : undefined,
24592
24584
  yearJournalPath,
24593
24585
  logger
24594
24586
  };
@@ -24730,7 +24722,7 @@ async function executeReconcileStep(context, contextId, logger) {
24730
24722
  function handleNoTransactions(result) {
24731
24723
  result.steps.import = buildStepResult(true, "No transactions to import");
24732
24724
  result.steps.reconcile = buildStepResult(true, "Reconciliation skipped (no transactions)");
24733
- return buildSuccessResult4(result, "No transactions found to import");
24725
+ return buildPipelineSuccessResult(result, "No transactions found to import");
24734
24726
  }
24735
24727
  async function importPipeline(directory, agent, options, configLoader = loadImportConfig, hledgerExecutor = defaultHledgerExecutor) {
24736
24728
  const restrictionError = checkAccountantAgent(agent, "import pipeline");
@@ -24759,7 +24751,7 @@ async function importPipeline(directory, agent, options, configLoader = loadImpo
24759
24751
  const contextIds = await executeClassifyStep(context, logger);
24760
24752
  if (contextIds.length === 0) {
24761
24753
  logger.info("No files classified, nothing to import");
24762
- return buildSuccessResult4(result, "No files to import");
24754
+ return buildPipelineSuccessResult(result, "No files to import");
24763
24755
  }
24764
24756
  let totalTransactions = 0;
24765
24757
  for (const contextId of contextIds) {
@@ -24780,7 +24772,7 @@ async function importPipeline(directory, agent, options, configLoader = loadImpo
24780
24772
  }
24781
24773
  logger.info(`Log file: ${logger.getLogPath()}`);
24782
24774
  logger.endSection();
24783
- return buildSuccessResult4(result, `Successfully imported ${totalTransactions} transaction(s) from ${contextIds.length} file(s)`);
24775
+ return buildPipelineSuccessResult(result, `Successfully imported ${totalTransactions} transaction(s) from ${contextIds.length} file(s)`);
24784
24776
  } catch (error45) {
24785
24777
  logger.error("Pipeline step failed", error45);
24786
24778
  logger.info(`Log file: ${logger.getLogPath()}`);
@@ -24790,7 +24782,7 @@ async function importPipeline(directory, agent, options, configLoader = loadImpo
24790
24782
  if (!result.error) {
24791
24783
  result.error = error45 instanceof Error ? error45.message : String(error45);
24792
24784
  }
24793
- return buildErrorResult5(result, result.error, result.hint);
24785
+ return buildPipelineErrorResult(result, result.error, result.hint);
24794
24786
  } finally {
24795
24787
  logger.endSection();
24796
24788
  await logger.flush();
@@ -24841,7 +24833,7 @@ This tool orchestrates the full import workflow:
24841
24833
  }
24842
24834
  });
24843
24835
  // src/tools/init-directories.ts
24844
- import * as fs14 from "fs";
24836
+ import * as fs15 from "fs";
24845
24837
  import * as path13 from "path";
24846
24838
  async function initDirectories(directory) {
24847
24839
  try {
@@ -24849,8 +24841,8 @@ async function initDirectories(directory) {
24849
24841
  const directoriesCreated = [];
24850
24842
  const gitkeepFiles = [];
24851
24843
  const importBase = path13.join(directory, "import");
24852
- if (!fs14.existsSync(importBase)) {
24853
- fs14.mkdirSync(importBase, { recursive: true });
24844
+ if (!fs15.existsSync(importBase)) {
24845
+ fs15.mkdirSync(importBase, { recursive: true });
24854
24846
  directoriesCreated.push("import");
24855
24847
  }
24856
24848
  const pathsToCreate = [
@@ -24861,19 +24853,19 @@ async function initDirectories(directory) {
24861
24853
  ];
24862
24854
  for (const { path: dirPath } of pathsToCreate) {
24863
24855
  const fullPath = path13.join(directory, dirPath);
24864
- if (!fs14.existsSync(fullPath)) {
24865
- fs14.mkdirSync(fullPath, { recursive: true });
24856
+ if (!fs15.existsSync(fullPath)) {
24857
+ fs15.mkdirSync(fullPath, { recursive: true });
24866
24858
  directoriesCreated.push(dirPath);
24867
24859
  }
24868
24860
  const gitkeepPath = path13.join(fullPath, ".gitkeep");
24869
- if (!fs14.existsSync(gitkeepPath)) {
24870
- fs14.writeFileSync(gitkeepPath, "");
24861
+ if (!fs15.existsSync(gitkeepPath)) {
24862
+ fs15.writeFileSync(gitkeepPath, "");
24871
24863
  gitkeepFiles.push(path13.join(dirPath, ".gitkeep"));
24872
24864
  }
24873
24865
  }
24874
24866
  const gitignorePath = path13.join(importBase, ".gitignore");
24875
24867
  let gitignoreCreated = false;
24876
- if (!fs14.existsSync(gitignorePath)) {
24868
+ if (!fs15.existsSync(gitignorePath)) {
24877
24869
  const gitignoreContent = `# Ignore CSV/PDF files in temporary directories
24878
24870
  /incoming/*.csv
24879
24871
  /incoming/*.pdf
@@ -24891,7 +24883,7 @@ async function initDirectories(directory) {
24891
24883
  .DS_Store
24892
24884
  Thumbs.db
24893
24885
  `;
24894
- fs14.writeFileSync(gitignorePath, gitignoreContent);
24886
+ fs15.writeFileSync(gitignorePath, gitignoreContent);
24895
24887
  gitignoreCreated = true;
24896
24888
  }
24897
24889
  const parts = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzzle/opencode-accountant",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "An OpenCode accounting agent, specialized in double-entry-bookkepping with hledger",
5
5
  "author": {
6
6
  "name": "ali bengali",