@fuzzle/opencode-accountant 0.4.6-next.1 → 0.4.6

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/dist/index.js CHANGED
@@ -4222,6 +4222,606 @@ var require_brace_expansion = __commonJS((exports, module) => {
4222
4222
  }
4223
4223
  });
4224
4224
 
4225
+ // node_modules/convert-csv-to-json/src/util/fileUtils.js
4226
+ var require_fileUtils = __commonJS((exports, module) => {
4227
+ var fs8 = __require("fs");
4228
+
4229
+ class FileUtils {
4230
+ readFile(fileInputName, encoding) {
4231
+ return fs8.readFileSync(fileInputName, encoding).toString();
4232
+ }
4233
+ readFileAsync(fileInputName, encoding = "utf8") {
4234
+ if (fs8.promises && typeof fs8.promises.readFile === "function") {
4235
+ return fs8.promises.readFile(fileInputName, encoding).then((buf) => buf.toString());
4236
+ }
4237
+ return new Promise((resolve2, reject) => {
4238
+ fs8.readFile(fileInputName, encoding, (err, data) => {
4239
+ if (err) {
4240
+ reject(err);
4241
+ return;
4242
+ }
4243
+ resolve2(data.toString());
4244
+ });
4245
+ });
4246
+ }
4247
+ writeFile(json3, fileOutputName) {
4248
+ fs8.writeFile(fileOutputName, json3, function(err) {
4249
+ if (err) {
4250
+ throw err;
4251
+ } else {
4252
+ console.log("File saved: " + fileOutputName);
4253
+ }
4254
+ });
4255
+ }
4256
+ writeFileAsync(json3, fileOutputName) {
4257
+ if (fs8.promises && typeof fs8.promises.writeFile === "function") {
4258
+ return fs8.promises.writeFile(fileOutputName, json3);
4259
+ }
4260
+ return new Promise((resolve2, reject) => {
4261
+ fs8.writeFile(fileOutputName, json3, (err) => {
4262
+ if (err)
4263
+ return reject(err);
4264
+ resolve2();
4265
+ });
4266
+ });
4267
+ }
4268
+ }
4269
+ module.exports = new FileUtils;
4270
+ });
4271
+
4272
+ // node_modules/convert-csv-to-json/src/util/stringUtils.js
4273
+ var require_stringUtils = __commonJS((exports, module) => {
4274
+ class StringUtils {
4275
+ static PATTERNS = {
4276
+ INTEGER: /^-?\d+$/,
4277
+ FLOAT: /^-?\d*\.\d+$/,
4278
+ WHITESPACE: /\s/g
4279
+ };
4280
+ static BOOLEAN_VALUES = {
4281
+ TRUE: "true",
4282
+ FALSE: "false"
4283
+ };
4284
+ trimPropertyName(shouldTrimAll, propertyName) {
4285
+ if (!propertyName) {
4286
+ return "";
4287
+ }
4288
+ return shouldTrimAll ? propertyName.replace(StringUtils.PATTERNS.WHITESPACE, "") : propertyName.trim();
4289
+ }
4290
+ getValueFormatByType(value) {
4291
+ if (this.isEmpty(value)) {
4292
+ return String();
4293
+ }
4294
+ if (this.isBoolean(value)) {
4295
+ return this.convertToBoolean(value);
4296
+ }
4297
+ if (this.isInteger(value)) {
4298
+ return this.convertInteger(value);
4299
+ }
4300
+ if (this.isFloat(value)) {
4301
+ return this.convertFloat(value);
4302
+ }
4303
+ return String(value);
4304
+ }
4305
+ hasContent(values = []) {
4306
+ return Array.isArray(values) && values.some((value) => Boolean(value));
4307
+ }
4308
+ isEmpty(value) {
4309
+ return value === undefined || value === "";
4310
+ }
4311
+ isBoolean(value) {
4312
+ const normalizedValue = value.toLowerCase();
4313
+ return normalizedValue === StringUtils.BOOLEAN_VALUES.TRUE || normalizedValue === StringUtils.BOOLEAN_VALUES.FALSE;
4314
+ }
4315
+ isInteger(value) {
4316
+ return StringUtils.PATTERNS.INTEGER.test(value);
4317
+ }
4318
+ isFloat(value) {
4319
+ return StringUtils.PATTERNS.FLOAT.test(value);
4320
+ }
4321
+ hasLeadingZero(value) {
4322
+ const isPositiveWithLeadingZero = value.length > 1 && value[0] === "0";
4323
+ const isNegativeWithLeadingZero = value.length > 2 && value[0] === "-" && value[1] === "0";
4324
+ return isPositiveWithLeadingZero || isNegativeWithLeadingZero;
4325
+ }
4326
+ convertToBoolean(value) {
4327
+ return JSON.parse(value.toLowerCase());
4328
+ }
4329
+ convertInteger(value) {
4330
+ if (this.hasLeadingZero(value)) {
4331
+ return String(value);
4332
+ }
4333
+ const num = Number(value);
4334
+ return Number.isSafeInteger(num) ? num : String(value);
4335
+ }
4336
+ convertFloat(value) {
4337
+ const num = Number(value);
4338
+ return Number.isFinite(num) ? num : String(value);
4339
+ }
4340
+ }
4341
+ module.exports = new StringUtils;
4342
+ });
4343
+
4344
+ // node_modules/convert-csv-to-json/src/util/jsonUtils.js
4345
+ var require_jsonUtils = __commonJS((exports, module) => {
4346
+ class JsonUtil {
4347
+ validateJson(json3) {
4348
+ try {
4349
+ JSON.parse(json3);
4350
+ } catch (err) {
4351
+ throw Error(`Parsed csv has generated an invalid json!!!
4352
+ ` + err);
4353
+ }
4354
+ }
4355
+ }
4356
+ module.exports = new JsonUtil;
4357
+ });
4358
+
4359
+ // node_modules/convert-csv-to-json/src/csvToJson.js
4360
+ var require_csvToJson = __commonJS((exports, module) => {
4361
+ var fileUtils = require_fileUtils();
4362
+ var stringUtils = require_stringUtils();
4363
+ var jsonUtils = require_jsonUtils();
4364
+ var newLine = /\r?\n/;
4365
+ var defaultFieldDelimiter = ";";
4366
+
4367
+ class CsvToJson {
4368
+ formatValueByType(active) {
4369
+ this.printValueFormatByType = active;
4370
+ return this;
4371
+ }
4372
+ supportQuotedField(active) {
4373
+ this.isSupportQuotedField = active;
4374
+ return this;
4375
+ }
4376
+ fieldDelimiter(delimiter) {
4377
+ this.delimiter = delimiter;
4378
+ return this;
4379
+ }
4380
+ trimHeaderFieldWhiteSpace(active) {
4381
+ this.isTrimHeaderFieldWhiteSpace = active;
4382
+ return this;
4383
+ }
4384
+ indexHeader(indexHeaderValue) {
4385
+ if (isNaN(indexHeaderValue)) {
4386
+ throw new Error("The index Header must be a Number!");
4387
+ }
4388
+ this.indexHeaderValue = indexHeaderValue;
4389
+ return this;
4390
+ }
4391
+ parseSubArray(delimiter = "*", separator = ",") {
4392
+ this.parseSubArrayDelimiter = delimiter;
4393
+ this.parseSubArraySeparator = separator;
4394
+ }
4395
+ encoding(encoding) {
4396
+ this.encoding = encoding;
4397
+ return this;
4398
+ }
4399
+ generateJsonFileFromCsv(fileInputName, fileOutputName) {
4400
+ let jsonStringified = this.getJsonFromCsvStringified(fileInputName);
4401
+ fileUtils.writeFile(jsonStringified, fileOutputName);
4402
+ }
4403
+ getJsonFromCsvStringified(fileInputName) {
4404
+ let json3 = this.getJsonFromCsv(fileInputName);
4405
+ let jsonStringified = JSON.stringify(json3, undefined, 1);
4406
+ jsonUtils.validateJson(jsonStringified);
4407
+ return jsonStringified;
4408
+ }
4409
+ getJsonFromCsv(fileInputName) {
4410
+ let parsedCsv = fileUtils.readFile(fileInputName, this.encoding);
4411
+ return this.csvToJson(parsedCsv);
4412
+ }
4413
+ csvStringToJson(csvString) {
4414
+ return this.csvToJson(csvString);
4415
+ }
4416
+ csvStringToJsonStringified(csvString) {
4417
+ let json3 = this.csvStringToJson(csvString);
4418
+ let jsonStringified = JSON.stringify(json3, undefined, 1);
4419
+ jsonUtils.validateJson(jsonStringified);
4420
+ return jsonStringified;
4421
+ }
4422
+ csvToJson(parsedCsv) {
4423
+ this.validateInputConfig();
4424
+ let lines = parsedCsv.split(newLine);
4425
+ let fieldDelimiter = this.getFieldDelimiter();
4426
+ let index = this.getIndexHeader();
4427
+ let headers;
4428
+ if (this.isSupportQuotedField) {
4429
+ headers = this.split(lines[index]);
4430
+ } else {
4431
+ headers = lines[index].split(fieldDelimiter);
4432
+ }
4433
+ while (!stringUtils.hasContent(headers) && index <= lines.length) {
4434
+ index = index + 1;
4435
+ headers = lines[index].split(fieldDelimiter);
4436
+ }
4437
+ let jsonResult = [];
4438
+ for (let i2 = index + 1;i2 < lines.length; i2++) {
4439
+ let currentLine;
4440
+ if (this.isSupportQuotedField) {
4441
+ currentLine = this.split(lines[i2]);
4442
+ } else {
4443
+ currentLine = lines[i2].split(fieldDelimiter);
4444
+ }
4445
+ if (stringUtils.hasContent(currentLine)) {
4446
+ jsonResult.push(this.buildJsonResult(headers, currentLine));
4447
+ }
4448
+ }
4449
+ return jsonResult;
4450
+ }
4451
+ getFieldDelimiter() {
4452
+ if (this.delimiter) {
4453
+ return this.delimiter;
4454
+ }
4455
+ return defaultFieldDelimiter;
4456
+ }
4457
+ getIndexHeader() {
4458
+ if (this.indexHeaderValue !== null && !isNaN(this.indexHeaderValue)) {
4459
+ return this.indexHeaderValue;
4460
+ }
4461
+ return 0;
4462
+ }
4463
+ buildJsonResult(headers, currentLine) {
4464
+ let jsonObject = {};
4465
+ for (let j = 0;j < headers.length; j++) {
4466
+ let propertyName = stringUtils.trimPropertyName(this.isTrimHeaderFieldWhiteSpace, headers[j]);
4467
+ let value = currentLine[j];
4468
+ if (this.isParseSubArray(value)) {
4469
+ value = this.buildJsonSubArray(value);
4470
+ }
4471
+ if (this.printValueFormatByType && !Array.isArray(value)) {
4472
+ value = stringUtils.getValueFormatByType(currentLine[j]);
4473
+ }
4474
+ jsonObject[propertyName] = value;
4475
+ }
4476
+ return jsonObject;
4477
+ }
4478
+ buildJsonSubArray(value) {
4479
+ let extractedValues = value.substring(value.indexOf(this.parseSubArrayDelimiter) + 1, value.lastIndexOf(this.parseSubArrayDelimiter));
4480
+ extractedValues.trim();
4481
+ value = extractedValues.split(this.parseSubArraySeparator);
4482
+ if (this.printValueFormatByType) {
4483
+ for (let i2 = 0;i2 < value.length; i2++) {
4484
+ value[i2] = stringUtils.getValueFormatByType(value[i2]);
4485
+ }
4486
+ }
4487
+ return value;
4488
+ }
4489
+ isParseSubArray(value) {
4490
+ if (this.parseSubArrayDelimiter) {
4491
+ if (value && (value.indexOf(this.parseSubArrayDelimiter) === 0 && value.lastIndexOf(this.parseSubArrayDelimiter) === value.length - 1)) {
4492
+ return true;
4493
+ }
4494
+ }
4495
+ return false;
4496
+ }
4497
+ validateInputConfig() {
4498
+ if (this.isSupportQuotedField) {
4499
+ if (this.getFieldDelimiter() === '"') {
4500
+ throw new Error('When SupportQuotedFields is enabled you cannot defined the field delimiter as quote -> ["]');
4501
+ }
4502
+ if (this.parseSubArraySeparator === '"') {
4503
+ throw new Error('When SupportQuotedFields is enabled you cannot defined the field parseSubArraySeparator as quote -> ["]');
4504
+ }
4505
+ if (this.parseSubArrayDelimiter === '"') {
4506
+ throw new Error('When SupportQuotedFields is enabled you cannot defined the field parseSubArrayDelimiter as quote -> ["]');
4507
+ }
4508
+ }
4509
+ }
4510
+ hasQuotes(line) {
4511
+ return line.includes('"');
4512
+ }
4513
+ split(line) {
4514
+ if (line.length == 0) {
4515
+ return [];
4516
+ }
4517
+ let delim = this.getFieldDelimiter();
4518
+ let subSplits = [""];
4519
+ if (this.hasQuotes(line)) {
4520
+ let chars = line.split("");
4521
+ let subIndex = 0;
4522
+ let startQuote = false;
4523
+ let isDouble = false;
4524
+ chars.forEach((c, i2, arr) => {
4525
+ if (isDouble) {
4526
+ subSplits[subIndex] += c;
4527
+ isDouble = false;
4528
+ return;
4529
+ }
4530
+ if (c != '"' && c != delim) {
4531
+ subSplits[subIndex] += c;
4532
+ } else if (c == delim && startQuote) {
4533
+ subSplits[subIndex] += c;
4534
+ } else if (c == delim) {
4535
+ subIndex++;
4536
+ subSplits[subIndex] = "";
4537
+ return;
4538
+ } else {
4539
+ if (arr[i2 + 1] === '"') {
4540
+ isDouble = true;
4541
+ } else {
4542
+ if (!startQuote) {
4543
+ startQuote = true;
4544
+ } else {
4545
+ startQuote = false;
4546
+ }
4547
+ }
4548
+ }
4549
+ });
4550
+ if (startQuote) {
4551
+ throw new Error("Row contains mismatched quotes!");
4552
+ }
4553
+ return subSplits;
4554
+ } else {
4555
+ return line.split(delim);
4556
+ }
4557
+ }
4558
+ }
4559
+ module.exports = new CsvToJson;
4560
+ });
4561
+
4562
+ // node_modules/convert-csv-to-json/src/csvToJsonAsync.js
4563
+ var require_csvToJsonAsync = __commonJS((exports, module) => {
4564
+ var fileUtils = require_fileUtils();
4565
+ var csvToJson = require_csvToJson();
4566
+
4567
+ class CsvToJsonAsync {
4568
+ constructor() {
4569
+ this.csvToJson = csvToJson;
4570
+ }
4571
+ formatValueByType(active) {
4572
+ this.csvToJson.formatValueByType(active);
4573
+ return this;
4574
+ }
4575
+ supportQuotedField(active) {
4576
+ this.csvToJson.supportQuotedField(active);
4577
+ return this;
4578
+ }
4579
+ fieldDelimiter(delimiter) {
4580
+ this.csvToJson.fieldDelimiter(delimiter);
4581
+ return this;
4582
+ }
4583
+ trimHeaderFieldWhiteSpace(active) {
4584
+ this.csvToJson.trimHeaderFieldWhiteSpace(active);
4585
+ return this;
4586
+ }
4587
+ indexHeader(indexHeader) {
4588
+ this.csvToJson.indexHeader(indexHeader);
4589
+ return this;
4590
+ }
4591
+ parseSubArray(delimiter = "*", separator = ",") {
4592
+ this.csvToJson.parseSubArray(delimiter, separator);
4593
+ return this;
4594
+ }
4595
+ encoding(encoding) {
4596
+ this.csvToJson.encoding = encoding;
4597
+ return this;
4598
+ }
4599
+ async generateJsonFileFromCsv(fileInputName, fileOutputName) {
4600
+ const jsonStringified = await this.getJsonFromCsvStringified(fileInputName);
4601
+ await fileUtils.writeFileAsync(jsonStringified, fileOutputName);
4602
+ }
4603
+ async getJsonFromCsvStringified(fileInputName) {
4604
+ const json3 = await this.getJsonFromCsvAsync(fileInputName);
4605
+ return JSON.stringify(json3, undefined, 1);
4606
+ }
4607
+ async getJsonFromCsvAsync(inputFileNameOrCsv, options = {}) {
4608
+ if (inputFileNameOrCsv === null || inputFileNameOrCsv === undefined) {
4609
+ throw new Error("inputFileNameOrCsv is not defined!!!");
4610
+ }
4611
+ if (options.raw) {
4612
+ if (inputFileNameOrCsv === "") {
4613
+ return [];
4614
+ }
4615
+ return this.csvToJson.csvToJson(inputFileNameOrCsv);
4616
+ }
4617
+ const parsedCsv = await fileUtils.readFileAsync(inputFileNameOrCsv, this.csvToJson.encoding || "utf8");
4618
+ return this.csvToJson.csvToJson(parsedCsv);
4619
+ }
4620
+ csvStringToJsonAsync(csvString, options = { raw: true }) {
4621
+ return this.getJsonFromCsvAsync(csvString, options);
4622
+ }
4623
+ async csvStringToJsonStringifiedAsync(csvString) {
4624
+ const json3 = await this.csvStringToJsonAsync(csvString);
4625
+ return JSON.stringify(json3, undefined, 1);
4626
+ }
4627
+ }
4628
+ module.exports = new CsvToJsonAsync;
4629
+ });
4630
+
4631
+ // node_modules/convert-csv-to-json/src/browserApi.js
4632
+ var require_browserApi = __commonJS((exports, module) => {
4633
+ var csvToJson = require_csvToJson();
4634
+
4635
+ class BrowserApi {
4636
+ constructor() {
4637
+ this.csvToJson = csvToJson;
4638
+ }
4639
+ formatValueByType(active = true) {
4640
+ this.csvToJson.formatValueByType(active);
4641
+ return this;
4642
+ }
4643
+ supportQuotedField(active = false) {
4644
+ this.csvToJson.supportQuotedField(active);
4645
+ return this;
4646
+ }
4647
+ fieldDelimiter(delimiter) {
4648
+ this.csvToJson.fieldDelimiter(delimiter);
4649
+ return this;
4650
+ }
4651
+ trimHeaderFieldWhiteSpace(active = false) {
4652
+ this.csvToJson.trimHeaderFieldWhiteSpace(active);
4653
+ return this;
4654
+ }
4655
+ indexHeader(index) {
4656
+ this.csvToJson.indexHeader(index);
4657
+ return this;
4658
+ }
4659
+ parseSubArray(delimiter = "*", separator = ",") {
4660
+ this.csvToJson.parseSubArray(delimiter, separator);
4661
+ return this;
4662
+ }
4663
+ csvStringToJson(csvString) {
4664
+ if (csvString === undefined || csvString === null) {
4665
+ throw new Error("csvString is not defined!!!");
4666
+ }
4667
+ return this.csvToJson.csvToJson(csvString);
4668
+ }
4669
+ csvStringToJsonStringified(csvString) {
4670
+ if (csvString === undefined || csvString === null) {
4671
+ throw new Error("csvString is not defined!!!");
4672
+ }
4673
+ return this.csvToJson.csvStringToJsonStringified(csvString);
4674
+ }
4675
+ csvStringToJsonAsync(csvString) {
4676
+ return Promise.resolve(this.csvStringToJson(csvString));
4677
+ }
4678
+ csvStringToJsonStringifiedAsync(csvString) {
4679
+ return Promise.resolve(this.csvStringToJsonStringified(csvString));
4680
+ }
4681
+ parseFile(file2, options = {}) {
4682
+ if (!file2) {
4683
+ return Promise.reject(new Error("file is not defined!!!"));
4684
+ }
4685
+ return new Promise((resolve2, reject) => {
4686
+ if (typeof FileReader === "undefined") {
4687
+ reject(new Error("FileReader is not available in this environment"));
4688
+ return;
4689
+ }
4690
+ const reader = new FileReader;
4691
+ reader.onerror = () => reject(reader.error || new Error("Failed to read file"));
4692
+ reader.onload = () => {
4693
+ try {
4694
+ const text = reader.result;
4695
+ const result = this.csvToJson.csvToJson(String(text));
4696
+ resolve2(result);
4697
+ } catch (err) {
4698
+ reject(err);
4699
+ }
4700
+ };
4701
+ if (options.encoding) {
4702
+ reader.readAsText(file2, options.encoding);
4703
+ } else {
4704
+ reader.readAsText(file2);
4705
+ }
4706
+ });
4707
+ }
4708
+ }
4709
+ module.exports = new BrowserApi;
4710
+ });
4711
+
4712
+ // node_modules/convert-csv-to-json/index.js
4713
+ var require_convert_csv_to_json = __commonJS((exports) => {
4714
+ var csvToJson = require_csvToJson();
4715
+ var encodingOps = {
4716
+ utf8: "utf8",
4717
+ ucs2: "ucs2",
4718
+ utf16le: "utf16le",
4719
+ latin1: "latin1",
4720
+ ascii: "ascii",
4721
+ base64: "base64",
4722
+ hex: "hex"
4723
+ };
4724
+ exports.formatValueByType = function(active = true) {
4725
+ csvToJson.formatValueByType(active);
4726
+ return this;
4727
+ };
4728
+ exports.supportQuotedField = function(active = false) {
4729
+ csvToJson.supportQuotedField(active);
4730
+ return this;
4731
+ };
4732
+ exports.fieldDelimiter = function(delimiter) {
4733
+ csvToJson.fieldDelimiter(delimiter);
4734
+ return this;
4735
+ };
4736
+ exports.trimHeaderFieldWhiteSpace = function(active = false) {
4737
+ csvToJson.trimHeaderFieldWhiteSpace(active);
4738
+ return this;
4739
+ };
4740
+ exports.indexHeader = function(index) {
4741
+ csvToJson.indexHeader(index);
4742
+ return this;
4743
+ };
4744
+ exports.parseSubArray = function(delimiter, separator) {
4745
+ csvToJson.parseSubArray(delimiter, separator);
4746
+ return this;
4747
+ };
4748
+ exports.customEncoding = function(encoding) {
4749
+ csvToJson.encoding = encoding;
4750
+ return this;
4751
+ };
4752
+ exports.utf8Encoding = function utf8Encoding() {
4753
+ csvToJson.encoding = encodingOps.utf8;
4754
+ return this;
4755
+ };
4756
+ exports.ucs2Encoding = function() {
4757
+ csvToJson.encoding = encodingOps.ucs2;
4758
+ return this;
4759
+ };
4760
+ exports.utf16leEncoding = function() {
4761
+ csvToJson.encoding = encodingOps.utf16le;
4762
+ return this;
4763
+ };
4764
+ exports.latin1Encoding = function() {
4765
+ csvToJson.encoding = encodingOps.latin1;
4766
+ return this;
4767
+ };
4768
+ exports.asciiEncoding = function() {
4769
+ csvToJson.encoding = encodingOps.ascii;
4770
+ return this;
4771
+ };
4772
+ exports.base64Encoding = function() {
4773
+ this.csvToJson = encodingOps.base64;
4774
+ return this;
4775
+ };
4776
+ exports.hexEncoding = function() {
4777
+ this.csvToJson = encodingOps.hex;
4778
+ return this;
4779
+ };
4780
+ exports.generateJsonFileFromCsv = function(inputFileName, outputFileName) {
4781
+ if (!inputFileName) {
4782
+ throw new Error("inputFileName is not defined!!!");
4783
+ }
4784
+ if (!outputFileName) {
4785
+ throw new Error("outputFileName is not defined!!!");
4786
+ }
4787
+ csvToJson.generateJsonFileFromCsv(inputFileName, outputFileName);
4788
+ };
4789
+ exports.getJsonFromCsv = function(inputFileName) {
4790
+ if (!inputFileName) {
4791
+ throw new Error("inputFileName is not defined!!!");
4792
+ }
4793
+ return csvToJson.getJsonFromCsv(inputFileName);
4794
+ };
4795
+ var csvToJsonAsync = require_csvToJsonAsync();
4796
+ Object.assign(exports, {
4797
+ getJsonFromCsvAsync: function(input, options) {
4798
+ return csvToJsonAsync.getJsonFromCsvAsync(input, options);
4799
+ },
4800
+ csvStringToJsonAsync: function(input, options) {
4801
+ return csvToJsonAsync.csvStringToJsonAsync(input, options);
4802
+ },
4803
+ csvStringToJsonStringifiedAsync: function(input) {
4804
+ return csvToJsonAsync.csvStringToJsonStringifiedAsync(input);
4805
+ },
4806
+ generateJsonFileFromCsvAsync: function(input, output) {
4807
+ return csvToJsonAsync.generateJsonFileFromCsv(input, output);
4808
+ }
4809
+ });
4810
+ exports.csvStringToJson = function(csvString) {
4811
+ return csvToJson.csvStringToJson(csvString);
4812
+ };
4813
+ exports.csvStringToJsonStringified = function(csvString) {
4814
+ if (csvString === undefined || csvString === null) {
4815
+ throw new Error("csvString is not defined!!!");
4816
+ }
4817
+ return csvToJson.csvStringToJsonStringified(csvString);
4818
+ };
4819
+ exports.jsonToCsv = function(inputFileName, outputFileName) {
4820
+ csvToJson.generateJsonFileFromCsv(inputFileName, outputFileName);
4821
+ };
4822
+ exports.browser = require_browserApi();
4823
+ });
4824
+
4225
4825
  // src/utils/accountSuggester.ts
4226
4826
  var exports_accountSuggester = {};
4227
4827
  __export(exports_accountSuggester, {
@@ -4314,7 +4914,7 @@ function buildBatchSuggestionPrompt(postings, context) {
4314
4914
  prompt += `## Example Classification Patterns from Rules
4315
4915
 
4316
4916
  `;
4317
- const sampleSize = Math.min(EXAMPLE_PATTERN_SAMPLE_SIZE, context.existingRules.length);
4917
+ const sampleSize = Math.min(10, context.existingRules.length);
4318
4918
  for (let i2 = 0;i2 < sampleSize; i2++) {
4319
4919
  const pattern = context.existingRules[i2];
4320
4920
  prompt += `- If description matches "${pattern.condition}" \u2192 ${pattern.account}
@@ -4492,7 +5092,7 @@ function generateMockSuggestions(postings) {
4492
5092
  });
4493
5093
  return response;
4494
5094
  }
4495
- var EXAMPLE_PATTERN_SAMPLE_SIZE = 10, suggestionCache;
5095
+ var suggestionCache;
4496
5096
  var init_accountSuggester = __esm(() => {
4497
5097
  init_agentLoader();
4498
5098
  suggestionCache = {};
@@ -4500,7 +5100,7 @@ var init_accountSuggester = __esm(() => {
4500
5100
 
4501
5101
  // src/index.ts
4502
5102
  init_agentLoader();
4503
- import { dirname as dirname4, join as join12 } from "path";
5103
+ import { dirname as dirname5, join as join12 } from "path";
4504
5104
  import { fileURLToPath as fileURLToPath3 } from "url";
4505
5105
 
4506
5106
  // node_modules/zod/v4/classic/external.js
@@ -16825,7 +17425,7 @@ function tool(input) {
16825
17425
  tool.schema = exports_external;
16826
17426
  // src/tools/fetch-currency-prices.ts
16827
17427
  var {$ } = globalThis.Bun;
16828
- import * as path4 from "path";
17428
+ import * as path3 from "path";
16829
17429
 
16830
17430
  // src/utils/agentRestriction.ts
16831
17431
  function checkAccountantAgent(agent, toolPrompt, additionalFields) {
@@ -16842,32 +17442,10 @@ function checkAccountantAgent(agent, toolPrompt, additionalFields) {
16842
17442
  return JSON.stringify(errorResponse);
16843
17443
  }
16844
17444
 
16845
- // src/utils/yamlLoader.ts
17445
+ // src/utils/pricesConfig.ts
16846
17446
  init_js_yaml();
16847
17447
  import * as fs from "fs";
16848
17448
  import * as path from "path";
16849
- function loadYamlConfig(directory, configFile, validator, notFoundMessage) {
16850
- const configPath = path.join(directory, configFile);
16851
- if (!fs.existsSync(configPath)) {
16852
- throw new Error(notFoundMessage || `Configuration file not found: ${configFile}. Please create this file to configure the feature.`);
16853
- }
16854
- let parsed;
16855
- try {
16856
- const content = fs.readFileSync(configPath, "utf-8");
16857
- parsed = jsYaml.load(content);
16858
- } catch (err) {
16859
- if (err instanceof jsYaml.YAMLException) {
16860
- throw new Error(`Failed to parse ${configFile}: ${err.message}`);
16861
- }
16862
- throw err;
16863
- }
16864
- if (typeof parsed !== "object" || parsed === null) {
16865
- throw new Error(`Invalid config: ${configFile} must contain a YAML object`);
16866
- }
16867
- return validator(parsed);
16868
- }
16869
-
16870
- // src/utils/pricesConfig.ts
16871
17449
  var CONFIG_FILE = "config/prices.yaml";
16872
17450
  var REQUIRED_CURRENCY_FIELDS = ["source", "pair", "file"];
16873
17451
  function getDefaultBackfillDate() {
@@ -16893,84 +17471,48 @@ function validateCurrencyConfig(name, config2) {
16893
17471
  };
16894
17472
  }
16895
17473
  function loadPricesConfig(directory) {
16896
- return loadYamlConfig(directory, CONFIG_FILE, (parsedObj) => {
16897
- if (!parsedObj.currencies || typeof parsedObj.currencies !== "object") {
16898
- throw new Error(`Invalid config: 'currencies' section is required`);
16899
- }
16900
- const currenciesObj = parsedObj.currencies;
16901
- if (Object.keys(currenciesObj).length === 0) {
16902
- throw new Error(`Invalid config: 'currencies' section must contain at least one currency`);
16903
- }
16904
- const currencies = {};
16905
- for (const [name, config2] of Object.entries(currenciesObj)) {
16906
- currencies[name] = validateCurrencyConfig(name, config2);
16907
- }
16908
- return { currencies };
16909
- }, `Configuration file not found: ${CONFIG_FILE}. Please refer to the plugin's GitHub repository for setup instructions.`);
16910
- }
16911
-
16912
- // src/utils/journalUtils.ts
16913
- import * as fs3 from "fs";
16914
- import * as path3 from "path";
16915
-
16916
- // src/utils/fileUtils.ts
16917
- import * as fs2 from "fs";
16918
- import * as path2 from "path";
16919
- function findCsvFiles(baseDir, options = {}) {
16920
- if (!fs2.existsSync(baseDir)) {
16921
- return [];
17474
+ const configPath = path.join(directory, CONFIG_FILE);
17475
+ if (!fs.existsSync(configPath)) {
17476
+ throw new Error(`Configuration file not found: ${CONFIG_FILE}. Please refer to the plugin's GitHub repository for setup instructions.`);
16922
17477
  }
16923
- let searchDir = baseDir;
16924
- if (options.subdir) {
16925
- searchDir = path2.join(searchDir, options.subdir);
16926
- if (options.subsubdir) {
16927
- searchDir = path2.join(searchDir, options.subsubdir);
17478
+ let parsed;
17479
+ try {
17480
+ const content = fs.readFileSync(configPath, "utf-8");
17481
+ parsed = jsYaml.load(content);
17482
+ } catch (err) {
17483
+ if (err instanceof jsYaml.YAMLException) {
17484
+ throw new Error(`Failed to parse ${CONFIG_FILE}: ${err.message}`);
16928
17485
  }
17486
+ throw err;
16929
17487
  }
16930
- if (!fs2.existsSync(searchDir)) {
16931
- return [];
17488
+ if (typeof parsed !== "object" || parsed === null) {
17489
+ throw new Error(`Invalid config: ${CONFIG_FILE} must contain a YAML object`);
16932
17490
  }
16933
- const csvFiles = [];
16934
- if (options.recursive) {
16935
- let scanDirectory = function(dir) {
16936
- const entries = fs2.readdirSync(dir, { withFileTypes: true });
16937
- for (const entry of entries) {
16938
- const fullPath = path2.join(dir, entry.name);
16939
- if (entry.isDirectory()) {
16940
- scanDirectory(fullPath);
16941
- } else if (entry.isFile() && entry.name.toLowerCase().endsWith(".csv")) {
16942
- csvFiles.push(options.fullPaths ? fullPath : entry.name);
16943
- }
16944
- }
16945
- };
16946
- scanDirectory(searchDir);
16947
- } else {
16948
- const entries = fs2.readdirSync(searchDir);
16949
- for (const name of entries) {
16950
- if (!name.toLowerCase().endsWith(".csv"))
16951
- continue;
16952
- const fullPath = path2.join(searchDir, name);
16953
- if (fs2.statSync(fullPath).isFile()) {
16954
- csvFiles.push(options.fullPaths ? fullPath : name);
16955
- }
16956
- }
17491
+ const parsedObj = parsed;
17492
+ if (!parsedObj.currencies || typeof parsedObj.currencies !== "object") {
17493
+ throw new Error(`Invalid config: 'currencies' section is required`);
16957
17494
  }
16958
- return csvFiles.sort();
16959
- }
16960
- function ensureDirectory(dirPath) {
16961
- if (!fs2.existsSync(dirPath)) {
16962
- fs2.mkdirSync(dirPath, { recursive: true });
17495
+ const currenciesObj = parsedObj.currencies;
17496
+ if (Object.keys(currenciesObj).length === 0) {
17497
+ throw new Error(`Invalid config: 'currencies' section must contain at least one currency`);
17498
+ }
17499
+ const currencies = {};
17500
+ for (const [name, config2] of Object.entries(currenciesObj)) {
17501
+ currencies[name] = validateCurrencyConfig(name, config2);
16963
17502
  }
17503
+ return { currencies };
16964
17504
  }
16965
17505
 
16966
17506
  // src/utils/journalUtils.ts
17507
+ import * as fs2 from "fs";
17508
+ import * as path2 from "path";
16967
17509
  function extractDateFromPriceLine(line) {
16968
17510
  return line.split(" ")[1];
16969
17511
  }
16970
17512
  function updatePriceJournal(journalPath, newPriceLines) {
16971
17513
  let existingLines = [];
16972
- if (fs3.existsSync(journalPath)) {
16973
- existingLines = fs3.readFileSync(journalPath, "utf-8").split(`
17514
+ if (fs2.existsSync(journalPath)) {
17515
+ existingLines = fs2.readFileSync(journalPath, "utf-8").split(`
16974
17516
  `).filter((line) => line.trim() !== "");
16975
17517
  }
16976
17518
  const priceMap = new Map;
@@ -16985,25 +17527,54 @@ function updatePriceJournal(journalPath, newPriceLines) {
16985
17527
  priceMap.set(date5, line);
16986
17528
  }
16987
17529
  const sortedLines = Array.from(priceMap.entries()).sort((a, b) => a[0].localeCompare(b[0])).map(([, line]) => line);
16988
- fs3.writeFileSync(journalPath, sortedLines.join(`
17530
+ fs2.writeFileSync(journalPath, sortedLines.join(`
16989
17531
  `) + `
16990
17532
  `);
16991
17533
  }
17534
+ function findCsvFiles(directory, provider, currency) {
17535
+ const csvFiles = [];
17536
+ if (!fs2.existsSync(directory)) {
17537
+ return csvFiles;
17538
+ }
17539
+ let searchPath = directory;
17540
+ if (provider) {
17541
+ searchPath = path2.join(searchPath, provider);
17542
+ if (currency) {
17543
+ searchPath = path2.join(searchPath, currency);
17544
+ }
17545
+ }
17546
+ if (!fs2.existsSync(searchPath)) {
17547
+ return csvFiles;
17548
+ }
17549
+ function scanDirectory(dir) {
17550
+ const entries = fs2.readdirSync(dir, { withFileTypes: true });
17551
+ for (const entry of entries) {
17552
+ const fullPath = path2.join(dir, entry.name);
17553
+ if (entry.isDirectory()) {
17554
+ scanDirectory(fullPath);
17555
+ } else if (entry.isFile() && entry.name.endsWith(".csv")) {
17556
+ csvFiles.push(fullPath);
17557
+ }
17558
+ }
17559
+ }
17560
+ scanDirectory(searchPath);
17561
+ return csvFiles.sort();
17562
+ }
16992
17563
  function ensureYearJournalExists(directory, year) {
16993
- const ledgerDir = path3.join(directory, "ledger");
16994
- const yearJournalPath = path3.join(ledgerDir, `${year}.journal`);
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
17564
+ const ledgerDir = path2.join(directory, "ledger");
17565
+ const yearJournalPath = path2.join(ledgerDir, `${year}.journal`);
17566
+ const mainJournalPath = path2.join(directory, ".hledger.journal");
17567
+ if (!fs2.existsSync(ledgerDir)) {
17568
+ fs2.mkdirSync(ledgerDir, { recursive: true });
17569
+ }
17570
+ if (!fs2.existsSync(yearJournalPath)) {
17571
+ fs2.writeFileSync(yearJournalPath, `; ${year} transactions
17001
17572
  `);
17002
17573
  }
17003
- if (!fs3.existsSync(mainJournalPath)) {
17574
+ if (!fs2.existsSync(mainJournalPath)) {
17004
17575
  throw new Error(`.hledger.journal not found at ${mainJournalPath}. Create it first with appropriate includes.`);
17005
17576
  }
17006
- const mainJournalContent = fs3.readFileSync(mainJournalPath, "utf-8");
17577
+ const mainJournalContent = fs2.readFileSync(mainJournalPath, "utf-8");
17007
17578
  const includeDirective = `include ledger/${year}.journal`;
17008
17579
  const lines = mainJournalContent.split(`
17009
17580
  `);
@@ -17015,7 +17586,7 @@ function ensureYearJournalExists(directory, year) {
17015
17586
  const newContent = mainJournalContent.trimEnd() + `
17016
17587
  ` + includeDirective + `
17017
17588
  `;
17018
- fs3.writeFileSync(mainJournalPath, newContent);
17589
+ fs2.writeFileSync(mainJournalPath, newContent);
17019
17590
  }
17020
17591
  return yearJournalPath;
17021
17592
  }
@@ -17077,7 +17648,7 @@ function parsePriceLine(line) {
17077
17648
  formattedLine: line
17078
17649
  };
17079
17650
  }
17080
- function filterAndSortPriceLinesByDateRange(priceLines, startDate, endDate) {
17651
+ function filterPriceLinesByDateRange(priceLines, startDate, endDate) {
17081
17652
  return priceLines.map(parsePriceLine).filter((parsed) => {
17082
17653
  if (!parsed)
17083
17654
  return false;
@@ -17113,7 +17684,7 @@ async function fetchCurrencyPrices(directory, agent, backfill, priceFetcher = de
17113
17684
  });
17114
17685
  continue;
17115
17686
  }
17116
- const priceLines = filterAndSortPriceLinesByDateRange(rawPriceLines, startDate, endDate);
17687
+ const priceLines = filterPriceLinesByDateRange(rawPriceLines, startDate, endDate);
17117
17688
  if (priceLines.length === 0) {
17118
17689
  results.push({
17119
17690
  ticker,
@@ -17121,7 +17692,7 @@ async function fetchCurrencyPrices(directory, agent, backfill, priceFetcher = de
17121
17692
  });
17122
17693
  continue;
17123
17694
  }
17124
- const journalPath = path4.join(directory, "ledger", "currencies", currencyConfig.file);
17695
+ const journalPath = path3.join(directory, "ledger", "currencies", currencyConfig.file);
17125
17696
  updatePriceJournal(journalPath, priceLines);
17126
17697
  const latestPriceLine = priceLines[priceLines.length - 1];
17127
17698
  results.push({
@@ -17154,6 +17725,9 @@ import * as fs5 from "fs";
17154
17725
  import * as path6 from "path";
17155
17726
 
17156
17727
  // src/utils/importConfig.ts
17728
+ init_js_yaml();
17729
+ import * as fs3 from "fs";
17730
+ import * as path4 from "path";
17157
17731
  var CONFIG_FILE2 = "config/import/providers.yaml";
17158
17732
  var REQUIRED_PATH_FIELDS = [
17159
17733
  "import",
@@ -17276,27 +17850,43 @@ function validateProviderConfig(name, config2) {
17276
17850
  return { detect, currencies };
17277
17851
  }
17278
17852
  function loadImportConfig(directory) {
17279
- return loadYamlConfig(directory, CONFIG_FILE2, (parsedObj) => {
17280
- if (!parsedObj.paths) {
17281
- throw new Error("Invalid config: 'paths' section is required");
17282
- }
17283
- const paths = validatePaths(parsedObj.paths);
17284
- if (!parsedObj.providers || typeof parsedObj.providers !== "object") {
17285
- throw new Error("Invalid config: 'providers' section is required");
17286
- }
17287
- const providersObj = parsedObj.providers;
17288
- if (Object.keys(providersObj).length === 0) {
17289
- throw new Error("Invalid config: 'providers' section must contain at least one provider");
17290
- }
17291
- const providers = {};
17292
- for (const [name, config2] of Object.entries(providersObj)) {
17293
- providers[name] = validateProviderConfig(name, config2);
17294
- }
17295
- if (!paths.logs) {
17296
- paths.logs = ".memory";
17853
+ const configPath = path4.join(directory, CONFIG_FILE2);
17854
+ if (!fs3.existsSync(configPath)) {
17855
+ throw new Error(`Configuration file not found: ${CONFIG_FILE2}. Please create this file to configure statement imports.`);
17856
+ }
17857
+ let parsed;
17858
+ try {
17859
+ const content = fs3.readFileSync(configPath, "utf-8");
17860
+ parsed = jsYaml.load(content);
17861
+ } catch (err) {
17862
+ if (err instanceof jsYaml.YAMLException) {
17863
+ throw new Error(`Failed to parse ${CONFIG_FILE2}: ${err.message}`);
17297
17864
  }
17298
- return { paths, providers };
17299
- }, `Configuration file not found: ${CONFIG_FILE2}. Please create this file to configure statement imports.`);
17865
+ throw err;
17866
+ }
17867
+ if (typeof parsed !== "object" || parsed === null) {
17868
+ throw new Error(`Invalid config: ${CONFIG_FILE2} must contain a YAML object`);
17869
+ }
17870
+ const parsedObj = parsed;
17871
+ if (!parsedObj.paths) {
17872
+ throw new Error("Invalid config: 'paths' section is required");
17873
+ }
17874
+ const paths = validatePaths(parsedObj.paths);
17875
+ if (!parsedObj.providers || typeof parsedObj.providers !== "object") {
17876
+ throw new Error("Invalid config: 'providers' section is required");
17877
+ }
17878
+ const providersObj = parsedObj.providers;
17879
+ if (Object.keys(providersObj).length === 0) {
17880
+ throw new Error("Invalid config: 'providers' section must contain at least one provider");
17881
+ }
17882
+ const providers = {};
17883
+ for (const [name, config2] of Object.entries(providersObj)) {
17884
+ providers[name] = validateProviderConfig(name, config2);
17885
+ }
17886
+ if (!paths.logs) {
17887
+ paths.logs = ".memory";
17888
+ }
17889
+ return { paths, providers };
17300
17890
  }
17301
17891
 
17302
17892
  // src/utils/providerDetector.ts
@@ -17404,83 +17994,22 @@ function detectProvider(filename, content, config2) {
17404
17994
  return null;
17405
17995
  }
17406
17996
 
17407
- // src/utils/importContext.ts
17997
+ // src/utils/fileUtils.ts
17408
17998
  import * as fs4 from "fs";
17409
17999
  import * as path5 from "path";
17410
- import { randomUUID } from "crypto";
17411
- function getContextPath(directory, contextId) {
17412
- return path5.join(directory, ".memory", `${contextId}.json`);
17413
- }
17414
- function ensureMemoryDir(directory) {
17415
- const memoryDir = path5.join(directory, ".memory");
17416
- if (!fs4.existsSync(memoryDir)) {
17417
- fs4.mkdirSync(memoryDir, { recursive: true });
17418
- }
17419
- }
17420
- function createContext(directory, params) {
17421
- const now = new Date().toISOString();
17422
- const context = {
17423
- id: randomUUID(),
17424
- createdAt: now,
17425
- updatedAt: now,
17426
- filename: params.filename,
17427
- filePath: params.filePath,
17428
- provider: params.provider,
17429
- currency: params.currency,
17430
- accountNumber: params.accountNumber,
17431
- originalFilename: params.originalFilename,
17432
- fromDate: params.fromDate,
17433
- untilDate: params.untilDate,
17434
- openingBalance: params.openingBalance,
17435
- closingBalance: params.closingBalance,
17436
- account: params.account
17437
- };
17438
- ensureMemoryDir(directory);
17439
- const contextPath = getContextPath(directory, context.id);
17440
- fs4.writeFileSync(contextPath, JSON.stringify(context, null, 2), "utf-8");
17441
- return context;
17442
- }
17443
- function validateContext(context, contextId) {
17444
- const requiredFields = [
17445
- "id",
17446
- "filename",
17447
- "filePath",
17448
- "provider",
17449
- "currency"
17450
- ];
17451
- for (const field of requiredFields) {
17452
- if (!context[field]) {
17453
- throw new Error(`Invalid context ${contextId}: missing required field '${field}'`);
17454
- }
18000
+ function findCSVFiles(importsDir) {
18001
+ if (!fs4.existsSync(importsDir)) {
18002
+ return [];
17455
18003
  }
18004
+ return fs4.readdirSync(importsDir).filter((file2) => file2.toLowerCase().endsWith(".csv")).filter((file2) => {
18005
+ const fullPath = path5.join(importsDir, file2);
18006
+ return fs4.statSync(fullPath).isFile();
18007
+ });
17456
18008
  }
17457
- function loadContext(directory, contextId) {
17458
- const contextPath = getContextPath(directory, contextId);
17459
- if (!fs4.existsSync(contextPath)) {
17460
- throw new Error(`Context not found: ${contextId}`);
17461
- }
17462
- const content = fs4.readFileSync(contextPath, "utf-8");
17463
- let context;
17464
- try {
17465
- context = JSON.parse(content);
17466
- } catch (err) {
17467
- throw new Error(`Malformed context file ${contextId}: ${err instanceof Error ? err.message : String(err)}`);
18009
+ function ensureDirectory(dirPath) {
18010
+ if (!fs4.existsSync(dirPath)) {
18011
+ fs4.mkdirSync(dirPath, { recursive: true });
17468
18012
  }
17469
- validateContext(context, contextId);
17470
- return context;
17471
- }
17472
- function updateContext(directory, contextId, updates) {
17473
- const context = loadContext(directory, contextId);
17474
- const updatedContext = {
17475
- ...context,
17476
- ...updates,
17477
- id: context.id,
17478
- createdAt: context.createdAt,
17479
- updatedAt: new Date().toISOString()
17480
- };
17481
- const contextPath = getContextPath(directory, contextId);
17482
- fs4.writeFileSync(contextPath, JSON.stringify(updatedContext, null, 2), "utf-8");
17483
- return updatedContext;
17484
18013
  }
17485
18014
 
17486
18015
  // src/tools/classify-statements.ts
@@ -17549,20 +18078,7 @@ function planMoves(csvFiles, importsDir, pendingDir, unrecognizedDir, config2) {
17549
18078
  }
17550
18079
  return { plannedMoves, collisions };
17551
18080
  }
17552
- function extractMetadata2(detection) {
17553
- const metadata = detection.metadata;
17554
- if (!metadata) {
17555
- return {};
17556
- }
17557
- return {
17558
- accountNumber: metadata["account-number"],
17559
- fromDate: metadata["from-date"],
17560
- untilDate: metadata["until-date"],
17561
- openingBalance: metadata["opening-balance"],
17562
- closingBalance: metadata["closing-balance"]
17563
- };
17564
- }
17565
- function executeMoves(plannedMoves, config2, unrecognizedDir, directory) {
18081
+ function executeMoves(plannedMoves, config2, unrecognizedDir) {
17566
18082
  const classified = [];
17567
18083
  const unrecognized = [];
17568
18084
  for (const move of plannedMoves) {
@@ -17570,27 +18086,12 @@ function executeMoves(plannedMoves, config2, unrecognizedDir, directory) {
17570
18086
  const targetDir = path6.dirname(move.targetPath);
17571
18087
  ensureDirectory(targetDir);
17572
18088
  fs5.renameSync(move.sourcePath, move.targetPath);
17573
- const targetPath = path6.join(config2.paths.pending, move.detection.provider, move.detection.currency, move.targetFilename);
17574
- const metadata = extractMetadata2(move.detection);
17575
- const context = createContext(directory, {
17576
- filename: move.targetFilename,
17577
- filePath: targetPath,
17578
- provider: move.detection.provider,
17579
- currency: move.detection.currency,
17580
- originalFilename: move.detection.outputFilename ? move.filename : undefined,
17581
- accountNumber: metadata.accountNumber,
17582
- fromDate: metadata.fromDate,
17583
- untilDate: metadata.untilDate,
17584
- openingBalance: metadata.openingBalance,
17585
- closingBalance: metadata.closingBalance
17586
- });
17587
18089
  classified.push({
17588
18090
  filename: move.targetFilename,
17589
18091
  originalFilename: move.detection.outputFilename ? move.filename : undefined,
17590
18092
  provider: move.detection.provider,
17591
18093
  currency: move.detection.currency,
17592
- targetPath,
17593
- contextId: context.id
18094
+ targetPath: path6.join(config2.paths.pending, move.detection.provider, move.detection.currency, move.targetFilename)
17594
18095
  });
17595
18096
  } else {
17596
18097
  ensureDirectory(unrecognizedDir);
@@ -17621,7 +18122,7 @@ async function classifyStatements(directory, agent, configLoader = loadImportCon
17621
18122
  const importsDir = path6.join(directory, config2.paths.import);
17622
18123
  const pendingDir = path6.join(directory, config2.paths.pending);
17623
18124
  const unrecognizedDir = path6.join(directory, config2.paths.unrecognized);
17624
- const csvFiles = findCsvFiles(importsDir);
18125
+ const csvFiles = findCSVFiles(importsDir);
17625
18126
  if (csvFiles.length === 0) {
17626
18127
  return buildSuccessResult2([], [], `No CSV files found in ${config2.paths.import}`);
17627
18128
  }
@@ -17629,19 +18130,11 @@ async function classifyStatements(directory, agent, configLoader = loadImportCon
17629
18130
  if (collisions.length > 0) {
17630
18131
  return buildCollisionError(collisions);
17631
18132
  }
17632
- const { classified, unrecognized } = executeMoves(plannedMoves, config2, unrecognizedDir, directory);
18133
+ const { classified, unrecognized } = executeMoves(plannedMoves, config2, unrecognizedDir);
17633
18134
  return buildSuccessResult2(classified, unrecognized);
17634
18135
  }
17635
18136
  var classify_statements_default = tool({
17636
- description: `ACCOUNTANT AGENT ONLY: Classifies bank statement CSV files from the imports directory by detecting their provider and currency, then moves them to the appropriate pending import directories.
17637
-
17638
- For each CSV file:
17639
- - Detects the provider and currency using rules from providers.yaml
17640
- - Creates an import context (.memory/{uuid}.json) to track the file through the pipeline
17641
- - Extracts metadata (account number, dates, balances) from CSV headers
17642
- - Moves the file to import/pending/{provider}/{currency}/
17643
- - Checks for file collisions before any moves (atomic: all-or-nothing)
17644
- - Unrecognized files are moved to import/unrecognized/`,
18137
+ description: "ACCOUNTANT AGENT ONLY: Classifies bank statement CSV files from the imports directory by detecting their provider and currency, then moves them to the appropriate pending import directories.",
17645
18138
  args: {},
17646
18139
  async execute(_params, context) {
17647
18140
  const { directory, agent } = context;
@@ -19981,7 +20474,7 @@ class LRUCache {
19981
20474
  // node_modules/path-scurry/dist/esm/index.js
19982
20475
  import { posix, win32 } from "path";
19983
20476
  import { fileURLToPath } from "url";
19984
- import { lstatSync, readdir as readdirCB, readdirSync as readdirSync2, readlinkSync, realpathSync as rps } from "fs";
20477
+ import { lstatSync, readdir as readdirCB, readdirSync as readdirSync3, readlinkSync, realpathSync as rps } from "fs";
19985
20478
  import * as actualFS from "fs";
19986
20479
  import { lstat, readdir, readlink, realpath } from "fs/promises";
19987
20480
 
@@ -20653,7 +21146,7 @@ var realpathSync = rps.native;
20653
21146
  var defaultFS = {
20654
21147
  lstatSync,
20655
21148
  readdir: readdirCB,
20656
- readdirSync: readdirSync2,
21149
+ readdirSync: readdirSync3,
20657
21150
  readlinkSync,
20658
21151
  realpathSync,
20659
21152
  promises: {
@@ -22963,12 +23456,7 @@ function loadRulesMapping(rulesDir) {
22963
23456
  if (!stat.isFile()) {
22964
23457
  continue;
22965
23458
  }
22966
- let content;
22967
- try {
22968
- content = fs6.readFileSync(rulesFilePath, "utf-8");
22969
- } catch {
22970
- continue;
22971
- }
23459
+ const content = fs6.readFileSync(rulesFilePath, "utf-8");
22972
23460
  const sourcePath = parseSourceDirective(content);
22973
23461
  if (!sourcePath) {
22974
23462
  continue;
@@ -23011,30 +23499,18 @@ function findRulesForCsv(csvPath, mapping) {
23011
23499
 
23012
23500
  // src/utils/hledgerExecutor.ts
23013
23501
  var {$: $2 } = globalThis.Bun;
23014
- var STDERR_TRUNCATE_LENGTH = 500;
23015
23502
  async function defaultHledgerExecutor(cmdArgs) {
23016
23503
  try {
23017
23504
  const result = await $2`hledger ${cmdArgs}`.quiet().nothrow();
23018
- const stdout = result.stdout.toString();
23019
- const stderr = result.stderr.toString();
23020
- if (result.exitCode !== 0 && stderr) {
23021
- process.stderr.write(`[hledger] command failed (exit ${result.exitCode}): hledger ${cmdArgs.join(" ")}
23022
- `);
23023
- process.stderr.write(`[hledger] stderr: ${stderr.slice(0, STDERR_TRUNCATE_LENGTH)}
23024
- `);
23025
- }
23026
23505
  return {
23027
- stdout,
23028
- stderr,
23506
+ stdout: result.stdout.toString(),
23507
+ stderr: result.stderr.toString(),
23029
23508
  exitCode: result.exitCode
23030
23509
  };
23031
23510
  } catch (error45) {
23032
- const errorMessage = error45 instanceof Error ? error45.message : String(error45);
23033
- process.stderr.write(`[hledger] exception: ${errorMessage}
23034
- `);
23035
23511
  return {
23036
23512
  stdout: "",
23037
- stderr: errorMessage,
23513
+ stderr: error45 instanceof Error ? error45.message : String(error45),
23038
23514
  exitCode: 1
23039
23515
  };
23040
23516
  }
@@ -23225,7 +23701,7 @@ function parseRulesFile(rulesContent) {
23225
23701
  }
23226
23702
 
23227
23703
  // src/utils/csvParser.ts
23228
- var import_papaparse2 = __toESM(require_papaparse(), 1);
23704
+ var import_convert_csv_to_json = __toESM(require_convert_csv_to_json(), 1);
23229
23705
  import * as fs8 from "fs";
23230
23706
 
23231
23707
  // src/utils/balanceUtils.ts
@@ -23274,7 +23750,6 @@ function balancesMatch(balance1, balance2) {
23274
23750
  }
23275
23751
 
23276
23752
  // src/utils/csvParser.ts
23277
- var AMOUNT_MATCH_TOLERANCE = 0.001;
23278
23753
  function parseCsvFile(csvPath, config2) {
23279
23754
  const csvContent = fs8.readFileSync(csvPath, "utf-8");
23280
23755
  const lines = csvContent.split(`
@@ -23283,26 +23758,22 @@ function parseCsvFile(csvPath, config2) {
23283
23758
  if (headerIndex >= lines.length) {
23284
23759
  return [];
23285
23760
  }
23286
- const csvWithHeader = lines.slice(headerIndex).join(`
23761
+ const headerLine = lines[headerIndex];
23762
+ const dataLines = lines.slice(headerIndex + 1).filter((line) => line.trim() !== "");
23763
+ const csvWithHeader = [headerLine, ...dataLines].join(`
23287
23764
  `);
23288
- const useFieldNames = config2.fieldNames.length > 0;
23289
- const result = import_papaparse2.default.parse(csvWithHeader, {
23290
- header: !useFieldNames,
23291
- delimiter: config2.separator,
23292
- skipEmptyLines: true
23293
- });
23294
- if (useFieldNames) {
23295
- const rawRows = result.data;
23296
- const dataRows = rawRows.slice(1);
23297
- return dataRows.map((values) => {
23298
- const row = {};
23299
- for (let i2 = 0;i2 < config2.fieldNames.length && i2 < values.length; i2++) {
23300
- row[config2.fieldNames[i2]] = values[i2];
23301
- }
23302
- return row;
23303
- });
23765
+ const rawRows = import_convert_csv_to_json.default.indexHeader(0).fieldDelimiter(config2.separator).supportQuotedField(true).csvStringToJson(csvWithHeader);
23766
+ const fieldNames = config2.fieldNames.length > 0 ? config2.fieldNames : Object.keys(rawRows[0] || {});
23767
+ const mappedRows = [];
23768
+ for (const parsedRow of rawRows) {
23769
+ const row = {};
23770
+ const values = Object.values(parsedRow);
23771
+ for (let i2 = 0;i2 < fieldNames.length && i2 < values.length; i2++) {
23772
+ row[fieldNames[i2]] = values[i2];
23773
+ }
23774
+ mappedRows.push(row);
23304
23775
  }
23305
- return result.data;
23776
+ return mappedRows;
23306
23777
  }
23307
23778
  function getRowAmount(row, amountFields) {
23308
23779
  if (amountFields.single) {
@@ -23380,7 +23851,7 @@ function findMatchingCsvRow(posting, csvRows, config2) {
23380
23851
  const rowAmount = getRowAmount(row, config2.amountFields);
23381
23852
  if (rowDate !== posting.date)
23382
23853
  return false;
23383
- if (Math.abs(rowAmount - postingAmount) > AMOUNT_MATCH_TOLERANCE)
23854
+ if (Math.abs(rowAmount - postingAmount) > 0.001)
23384
23855
  return false;
23385
23856
  return true;
23386
23857
  });
@@ -23414,21 +23885,11 @@ function findMatchingCsvRow(posting, csvRows, config2) {
23414
23885
 
23415
23886
  // src/tools/import-statements.ts
23416
23887
  function buildErrorResult3(error45, hint) {
23417
- const result = {
23888
+ return JSON.stringify({
23418
23889
  success: false,
23419
23890
  error: error45,
23420
- hint,
23421
- files: [],
23422
- summary: {
23423
- filesProcessed: 0,
23424
- filesWithErrors: 0,
23425
- filesWithoutRules: 0,
23426
- totalTransactions: 0,
23427
- matched: 0,
23428
- unknown: 0
23429
- }
23430
- };
23431
- return JSON.stringify(result);
23891
+ hint
23892
+ });
23432
23893
  }
23433
23894
  function buildErrorResultWithDetails(error45, files, summary, hint) {
23434
23895
  return JSON.stringify({
@@ -23607,12 +24068,17 @@ async function importStatements(directory, agent, options, configLoader = loadIm
23607
24068
  const rulesDir = path9.join(directory, config2.paths.rules);
23608
24069
  const doneDir = path9.join(directory, config2.paths.done);
23609
24070
  const rulesMapping = loadRulesMapping(rulesDir);
23610
- const importContext = loadContext(directory, options.contextId);
23611
- const csvPath = path9.join(directory, importContext.filePath);
23612
- if (!fs9.existsSync(csvPath)) {
23613
- return buildErrorResult3(`CSV file not found: ${importContext.filePath}`, "The file may have been moved or deleted");
24071
+ const csvFiles = findCsvFiles(pendingDir, options.provider, options.currency);
24072
+ if (csvFiles.length === 0) {
24073
+ return buildSuccessResult3([], {
24074
+ filesProcessed: 0,
24075
+ filesWithErrors: 0,
24076
+ filesWithoutRules: 0,
24077
+ totalTransactions: 0,
24078
+ matched: 0,
24079
+ unknown: 0
24080
+ }, "No CSV files found to process");
23614
24081
  }
23615
- const csvFiles = [csvPath];
23616
24082
  const fileResults = [];
23617
24083
  let totalTransactions = 0;
23618
24084
  let totalMatched = 0;
@@ -23703,16 +24169,6 @@ async function importStatements(directory, agent, options, configLoader = loadIm
23703
24169
  unknown: totalUnknown
23704
24170
  }, importResult.hint);
23705
24171
  }
23706
- if (fileResults.length > 0) {
23707
- const firstResult = fileResults[0];
23708
- const newFilePath = path9.join(config2.paths.done, path9.relative(pendingDir, path9.join(directory, firstResult.csv)));
23709
- updateContext(directory, options.contextId, {
23710
- filePath: newFilePath,
23711
- rulesFile: firstResult.rulesFile || undefined,
23712
- yearJournal: firstResult.transactionYear ? `ledger/${firstResult.transactionYear}.journal` : undefined,
23713
- transactionCount: firstResult.totalTransactions
23714
- });
23715
- }
23716
24172
  return buildSuccessResult3(fileResults.map((f) => ({
23717
24173
  ...f,
23718
24174
  imported: true
@@ -23748,13 +24204,15 @@ This tool processes CSV files in the pending import directory and uses hledger's
23748
24204
 
23749
24205
  Note: This tool is typically called via import-pipeline for the full workflow.`,
23750
24206
  args: {
23751
- contextId: tool.schema.string().describe("Context ID from classify step. Used to locate the specific CSV file to process."),
24207
+ provider: tool.schema.string().optional().describe('Filter by provider (e.g., "revolut", "ubs"). If omitted, process all providers.'),
24208
+ currency: tool.schema.string().optional().describe('Filter by currency (e.g., "chf", "eur"). If omitted, process all currencies for the provider.'),
23752
24209
  checkOnly: tool.schema.boolean().optional().describe("If true (default), only check for unknown accounts without importing. Set to false to perform actual import.")
23753
24210
  },
23754
24211
  async execute(params, context) {
23755
24212
  const { directory, agent } = context;
23756
24213
  return importStatements(directory, agent, {
23757
- contextId: params.contextId,
24214
+ provider: params.provider,
24215
+ currency: params.currency,
23758
24216
  checkOnly: params.checkOnly
23759
24217
  });
23760
24218
  }
@@ -23763,19 +24221,10 @@ Note: This tool is typically called via import-pipeline for the full workflow.`,
23763
24221
  import * as fs10 from "fs";
23764
24222
  import * as path10 from "path";
23765
24223
  function buildErrorResult4(params) {
23766
- const result = {
24224
+ return JSON.stringify({
23767
24225
  success: false,
23768
- account: params.account ?? "",
23769
- expectedBalance: params.expectedBalance ?? "",
23770
- actualBalance: params.actualBalance ?? "",
23771
- lastTransactionDate: params.lastTransactionDate ?? "",
23772
- csvFile: params.csvFile ?? "",
23773
- difference: params.difference,
23774
- metadata: params.metadata,
23775
- error: params.error,
23776
- hint: params.hint
23777
- };
23778
- return JSON.stringify(result);
24226
+ ...params
24227
+ });
23779
24228
  }
23780
24229
  function loadConfiguration(directory, configLoader) {
23781
24230
  try {
@@ -23790,87 +24239,69 @@ function loadConfiguration(directory, configLoader) {
23790
24239
  };
23791
24240
  }
23792
24241
  }
23793
- function verifyCsvExists(directory, importContext) {
23794
- const csvFile = path10.join(directory, importContext.filePath);
23795
- if (!fs10.existsSync(csvFile)) {
24242
+ function findCsvToReconcile(doneDir, options) {
24243
+ const csvFiles = findCsvFiles(doneDir, options.provider, options.currency);
24244
+ if (csvFiles.length === 0) {
24245
+ const providerFilter = options.provider ? ` --provider=${options.provider}` : "";
24246
+ const currencyFilter = options.currency ? ` --currency=${options.currency}` : "";
23796
24247
  return {
23797
24248
  error: buildErrorResult4({
23798
- error: `CSV file not found: ${importContext.filePath}`,
23799
- hint: `The file may have been moved or deleted. Context ID: ${importContext.id}`
24249
+ error: `No CSV files found in ${doneDir}`,
24250
+ hint: `Run: import-statements${providerFilter}${currencyFilter}`
23800
24251
  })
23801
24252
  };
23802
24253
  }
23803
- return { csvFile, relativePath: importContext.filePath };
23804
- }
23805
- function getBalanceFromContext(importContext) {
23806
- if (!importContext.closingBalance)
23807
- return;
23808
- let balance = importContext.closingBalance;
23809
- if (importContext.currency && !balance.includes(importContext.currency.toUpperCase())) {
23810
- balance = `${importContext.currency.toUpperCase()} ${balance}`;
23811
- }
23812
- return balance;
23813
- }
23814
- function getBalanceFromCsvMetadata(metadata) {
23815
- if (!metadata?.["closing-balance"])
23816
- return;
23817
- let balance = metadata["closing-balance"];
23818
- const currency = metadata.currency;
23819
- if (currency && balance && !balance.includes(currency)) {
23820
- balance = `${currency} ${balance}`;
23821
- }
23822
- return balance;
23823
- }
23824
- function getBalanceFromCsvAnalysis(csvFile, rulesDir) {
23825
- const csvAnalysis = tryExtractClosingBalanceFromCSV(csvFile, rulesDir);
23826
- if (csvAnalysis && csvAnalysis.confidence === "high") {
23827
- return csvAnalysis.balance;
23828
- }
23829
- return;
24254
+ const csvFile = csvFiles[csvFiles.length - 1];
24255
+ const relativePath = path10.relative(path10.dirname(path10.dirname(doneDir)), csvFile);
24256
+ return { csvFile, relativePath };
23830
24257
  }
23831
- function extractCsvMetadata(csvFile, config2) {
24258
+ function determineClosingBalance(csvFile, config2, options, relativeCsvPath, rulesDir) {
24259
+ let metadata;
23832
24260
  try {
23833
24261
  const content = fs10.readFileSync(csvFile, "utf-8");
23834
24262
  const filename = path10.basename(csvFile);
23835
24263
  const detectionResult = detectProvider(filename, content, config2);
23836
- if (detectionResult?.metadata) {
23837
- const m = detectionResult.metadata;
23838
- return {
23839
- "from-date": m["from-date"],
23840
- "until-date": m["until-date"],
23841
- "opening-balance": m["opening-balance"],
23842
- "closing-balance": m["closing-balance"],
23843
- currency: m["currency"],
23844
- "account-number": m["account-number"]
23845
- };
24264
+ metadata = detectionResult?.metadata;
24265
+ } catch {
24266
+ metadata = undefined;
24267
+ }
24268
+ let closingBalance = options.closingBalance;
24269
+ if (!closingBalance && metadata?.["closing-balance"]) {
24270
+ const closingBalanceValue = metadata["closing-balance"];
24271
+ const currency = metadata.currency;
24272
+ closingBalance = closingBalanceValue;
24273
+ if (currency && closingBalance && !closingBalance.includes(currency)) {
24274
+ closingBalance = `${currency} ${closingBalance}`;
23846
24275
  }
23847
- } catch {}
23848
- return;
23849
- }
23850
- function determineClosingBalance(csvFile, config2, importContext, manualClosingBalance, relativeCsvPath, rulesDir) {
23851
- const metadata = extractCsvMetadata(csvFile, config2);
23852
- const closingBalance = manualClosingBalance || getBalanceFromContext(importContext) || getBalanceFromCsvMetadata(metadata);
23853
- if (closingBalance) {
23854
- return { closingBalance, metadata };
23855
24276
  }
23856
- const analysisBalance = getBalanceFromCsvAnalysis(csvFile, rulesDir);
23857
- if (analysisBalance) {
23858
- return { closingBalance: analysisBalance, metadata, fromCSVAnalysis: true };
24277
+ if (!closingBalance) {
24278
+ const csvAnalysis = tryExtractClosingBalanceFromCSV(csvFile, rulesDir);
24279
+ if (csvAnalysis && csvAnalysis.confidence === "high") {
24280
+ closingBalance = csvAnalysis.balance;
24281
+ return { closingBalance, metadata, fromCSVAnalysis: true };
24282
+ }
23859
24283
  }
23860
- const currency = importContext.currency?.toUpperCase() || "CHF";
23861
- const exampleBalance = `${currency} <amount>`;
23862
- const retryCmd = buildRetryCommand(importContext.id, exampleBalance);
23863
- return {
23864
- error: buildErrorResult4({
23865
- csvFile: relativeCsvPath,
23866
- error: "No closing balance found in CSV metadata or data",
23867
- hint: `Provide closingBalance parameter manually. Example retry: ${retryCmd}`,
23868
- metadata
23869
- })
23870
- };
24284
+ if (!closingBalance) {
24285
+ const retryCmd = buildRetryCommand(options, "CHF 2324.79", options.account);
24286
+ return {
24287
+ error: buildErrorResult4({
24288
+ csvFile: relativeCsvPath,
24289
+ error: "No closing balance found in CSV metadata or data",
24290
+ hint: `Provide closingBalance parameter manually. Example retry: ${retryCmd}`,
24291
+ metadata
24292
+ })
24293
+ };
24294
+ }
24295
+ return { closingBalance, metadata };
23871
24296
  }
23872
- function buildRetryCommand(contextId, closingBalance, account) {
23873
- const parts = ["reconcile-statement", `--contextId ${contextId}`];
24297
+ function buildRetryCommand(options, closingBalance, account) {
24298
+ const parts = ["import-pipeline"];
24299
+ if (options.provider) {
24300
+ parts.push(`--provider ${options.provider}`);
24301
+ }
24302
+ if (options.currency) {
24303
+ parts.push(`--currency ${options.currency}`);
24304
+ }
23874
24305
  if (closingBalance) {
23875
24306
  parts.push(`--closingBalance "${closingBalance}"`);
23876
24307
  }
@@ -23879,17 +24310,19 @@ function buildRetryCommand(contextId, closingBalance, account) {
23879
24310
  }
23880
24311
  return parts.join(" ");
23881
24312
  }
23882
- function determineAccount(csvFile, rulesDir, importContext, manualAccount, relativeCsvPath, metadata) {
23883
- let account = manualAccount;
23884
- const rulesMapping = loadRulesMapping(rulesDir);
23885
- const rulesFile = findRulesForCsv(csvFile, rulesMapping);
24313
+ function determineAccount(csvFile, rulesDir, options, relativeCsvPath, metadata) {
24314
+ let account = options.account;
23886
24315
  if (!account) {
24316
+ const rulesMapping = loadRulesMapping(rulesDir);
24317
+ const rulesFile = findRulesForCsv(csvFile, rulesMapping);
23887
24318
  if (rulesFile) {
23888
24319
  account = getAccountFromRulesFile(rulesFile) ?? undefined;
23889
24320
  }
23890
24321
  }
23891
24322
  if (!account) {
23892
- const rulesHint = rulesFile ? `Add 'account1 assets:bank:...' to ${rulesFile} or retry with: ${buildRetryCommand(importContext.id, undefined, "assets:bank:...")}` : `Create a rules file in ${rulesDir} with 'account1' directive or retry with: ${buildRetryCommand(importContext.id, undefined, "assets:bank:...")}`;
24323
+ const rulesMapping = loadRulesMapping(rulesDir);
24324
+ const rulesFile = findRulesForCsv(csvFile, rulesMapping);
24325
+ const rulesHint = rulesFile ? `Add 'account1 assets:bank:...' to ${rulesFile} or retry with: ${buildRetryCommand(options, undefined, "assets:bank:...")}` : `Create a rules file in ${rulesDir} with 'account1' directive or retry with: ${buildRetryCommand(options, undefined, "assets:bank:...")}`;
23893
24326
  return {
23894
24327
  error: buildErrorResult4({
23895
24328
  csvFile: relativeCsvPath,
@@ -23970,33 +24403,25 @@ async function reconcileStatement(directory, agent, options, configLoader = load
23970
24403
  if (restrictionError) {
23971
24404
  return restrictionError;
23972
24405
  }
23973
- let importContext;
23974
- try {
23975
- importContext = loadContext(directory, options.contextId);
23976
- } catch {
23977
- return buildErrorResult4({
23978
- error: `Failed to load import context: ${options.contextId}`,
23979
- hint: "Ensure the context ID is valid and the context file exists in .memory/"
23980
- });
23981
- }
23982
24406
  const configResult = loadConfiguration(directory, configLoader);
23983
24407
  if ("error" in configResult) {
23984
24408
  return configResult.error;
23985
24409
  }
23986
24410
  const { config: config2 } = configResult;
24411
+ const doneDir = path10.join(directory, config2.paths.done);
23987
24412
  const rulesDir = path10.join(directory, config2.paths.rules);
23988
24413
  const mainJournalPath = path10.join(directory, ".hledger.journal");
23989
- const csvResult = verifyCsvExists(directory, importContext);
24414
+ const csvResult = findCsvToReconcile(doneDir, options);
23990
24415
  if ("error" in csvResult) {
23991
24416
  return csvResult.error;
23992
24417
  }
23993
24418
  const { csvFile, relativePath: relativeCsvPath } = csvResult;
23994
- const balanceResult = determineClosingBalance(csvFile, config2, importContext, options.closingBalance, relativeCsvPath, rulesDir);
24419
+ const balanceResult = determineClosingBalance(csvFile, config2, options, relativeCsvPath, rulesDir);
23995
24420
  if ("error" in balanceResult) {
23996
24421
  return balanceResult.error;
23997
24422
  }
23998
24423
  const { closingBalance, metadata, fromCSVAnalysis } = balanceResult;
23999
- const accountResult = determineAccount(csvFile, rulesDir, importContext, options.account, relativeCsvPath, metadata);
24424
+ const accountResult = determineAccount(csvFile, rulesDir, options, relativeCsvPath, metadata);
24000
24425
  if ("error" in accountResult) {
24001
24426
  return accountResult.error;
24002
24427
  }
@@ -24083,30 +24508,30 @@ var reconcile_statement_default = tool({
24083
24508
  This tool validates that the imported transactions result in the correct closing balance.
24084
24509
 
24085
24510
  **Workflow:**
24086
- 1. Loads import context to find the CSV file
24087
- 2. Extracts closing balance from context/CSV metadata (or uses manual override)
24511
+ 1. Finds the most recently imported CSV in the done directory
24512
+ 2. Extracts closing balance from CSV metadata (or uses manual override)
24088
24513
  3. Determines the account from the matching rules file (or uses manual override)
24089
24514
  4. Queries hledger for the actual balance as of the last transaction date
24090
24515
  5. Compares expected vs actual balance
24091
24516
 
24092
24517
  **Balance Sources:**
24093
- - Automatic: From import context or CSV header metadata (e.g., UBS files have "Closing balance:" row)
24518
+ - Automatic: Extracted from CSV header metadata (e.g., UBS files have "Closing balance:" row)
24094
24519
  - Manual: Provided via closingBalance parameter (required for providers like Revolut)
24095
24520
 
24096
24521
  **Account Detection:**
24097
24522
  - Automatic: Parsed from account1 directive in matching rules file
24098
- - Manual: Provided via account parameter
24099
-
24100
- Note: This tool requires a contextId from a prior classify/import step.`,
24523
+ - Manual: Provided via account parameter`,
24101
24524
  args: {
24102
- contextId: tool.schema.string().describe("Context ID from classify/import step (required)"),
24525
+ provider: tool.schema.string().optional().describe('Filter by provider (e.g., "ubs", "revolut")'),
24526
+ currency: tool.schema.string().optional().describe('Filter by currency (e.g., "chf", "eur")'),
24103
24527
  closingBalance: tool.schema.string().optional().describe('Manual closing balance (e.g., "CHF 2324.79"). Required if not in CSV metadata.'),
24104
24528
  account: tool.schema.string().optional().describe('Manual account (e.g., "assets:bank:ubs:checking"). Auto-detected from rules file if not provided.')
24105
24529
  },
24106
24530
  async execute(params, context) {
24107
24531
  const { directory, agent } = context;
24108
24532
  return reconcileStatement(directory, agent, {
24109
- contextId: params.contextId,
24533
+ provider: params.provider,
24534
+ currency: params.currency,
24110
24535
  closingBalance: params.closingBalance,
24111
24536
  account: params.account
24112
24537
  });
@@ -24233,7 +24658,6 @@ function ensureAccountDeclarations(yearJournalPath, accounts) {
24233
24658
  // src/utils/logger.ts
24234
24659
  import fs12 from "fs/promises";
24235
24660
  import path11 from "path";
24236
- var LOG_LINE_LIMIT = 50;
24237
24661
 
24238
24662
  class MarkdownLogger {
24239
24663
  buffer = [];
@@ -24241,7 +24665,6 @@ class MarkdownLogger {
24241
24665
  context = {};
24242
24666
  autoFlush;
24243
24667
  sectionDepth = 0;
24244
- pendingFlush = null;
24245
24668
  constructor(config2) {
24246
24669
  this.autoFlush = config2.autoFlush ?? true;
24247
24670
  this.context = config2.context || {};
@@ -24316,9 +24739,9 @@ class MarkdownLogger {
24316
24739
  this.buffer.push("");
24317
24740
  const lines = output.trim().split(`
24318
24741
  `);
24319
- if (lines.length > LOG_LINE_LIMIT) {
24320
- this.buffer.push(...lines.slice(0, LOG_LINE_LIMIT));
24321
- this.buffer.push(`... (${lines.length - LOG_LINE_LIMIT} more lines omitted)`);
24742
+ if (lines.length > 50) {
24743
+ this.buffer.push(...lines.slice(0, 50));
24744
+ this.buffer.push(`... (${lines.length - 50} more lines omitted)`);
24322
24745
  } else {
24323
24746
  this.buffer.push(output.trim());
24324
24747
  }
@@ -24340,9 +24763,6 @@ class MarkdownLogger {
24340
24763
  this.context[key] = value;
24341
24764
  }
24342
24765
  async flush() {
24343
- if (this.pendingFlush) {
24344
- await this.pendingFlush;
24345
- }
24346
24766
  if (this.buffer.length === 0)
24347
24767
  return;
24348
24768
  try {
@@ -24355,7 +24775,7 @@ class MarkdownLogger {
24355
24775
  return this.logPath;
24356
24776
  }
24357
24777
  flushAsync() {
24358
- this.pendingFlush = this.flush().catch(() => {});
24778
+ this.flush().catch(() => {});
24359
24779
  }
24360
24780
  getTimestamp() {
24361
24781
  return new Date().toISOString().replace(/:/g, "-").split(".")[0];
@@ -24421,28 +24841,16 @@ async function executeClassifyStep(context, logger) {
24421
24841
  logger?.info("Classification skipped (skipClassify: true)");
24422
24842
  context.result.steps.classify = buildStepResult(true, "Classification skipped (skipClassify: true)");
24423
24843
  logger?.endSection();
24424
- return [];
24844
+ return;
24425
24845
  }
24426
24846
  const classifyResult = await classifyStatements(context.directory, context.agent, context.configLoader);
24427
24847
  const classifyParsed = JSON.parse(classifyResult);
24428
24848
  const success2 = classifyParsed.success !== false;
24429
- const contextIds = [];
24430
- if (classifyParsed.classified?.length > 0) {
24431
- for (const file2 of classifyParsed.classified) {
24432
- if (file2.contextId) {
24433
- contextIds.push(file2.contextId);
24434
- }
24435
- }
24436
- }
24437
24849
  let message = success2 ? "Classification complete" : "Classification had issues";
24438
24850
  if (classifyParsed.unrecognized?.length > 0) {
24439
24851
  message = `Classification complete with ${classifyParsed.unrecognized.length} unrecognized file(s)`;
24440
24852
  logger?.warn(`${classifyParsed.unrecognized.length} unrecognized file(s)`);
24441
24853
  }
24442
- if (contextIds.length > 0) {
24443
- message += ` (${contextIds.length} context(s) created)`;
24444
- logger?.info(`Created ${contextIds.length} import context(s)`);
24445
- }
24446
24854
  logger?.logStep("Classify", success2 ? "success" : "error", message);
24447
24855
  const details = {
24448
24856
  success: success2,
@@ -24450,18 +24858,23 @@ async function executeClassifyStep(context, logger) {
24450
24858
  classified: classifyParsed
24451
24859
  };
24452
24860
  context.result.steps.classify = buildStepResult(success2, message, details);
24453
- context.result.contexts = contextIds;
24454
24861
  logger?.endSection();
24455
- return contextIds;
24456
24862
  }
24457
- async function executeAccountDeclarationsStep(context, contextId, logger) {
24863
+ async function executeAccountDeclarationsStep(context, logger) {
24458
24864
  logger?.startSection("Step 2: Check Account Declarations");
24459
24865
  logger?.logStep("Check Accounts", "start");
24460
24866
  const config2 = context.configLoader(context.directory);
24867
+ const pendingDir = path12.join(context.directory, config2.paths.pending);
24461
24868
  const rulesDir = path12.join(context.directory, config2.paths.rules);
24462
- const importCtx = loadContext(context.directory, contextId);
24463
- const csvPath = path12.join(context.directory, importCtx.filePath);
24464
- const csvFiles = [csvPath];
24869
+ const csvFiles = findCsvFiles(pendingDir, context.options.provider, context.options.currency);
24870
+ if (csvFiles.length === 0) {
24871
+ context.result.steps.accountDeclarations = buildStepResult(true, "No CSV files to process", {
24872
+ accountsAdded: [],
24873
+ journalUpdated: "",
24874
+ rulesScanned: []
24875
+ });
24876
+ return;
24877
+ }
24465
24878
  const rulesMapping = loadRulesMapping(rulesDir);
24466
24879
  const matchedRulesFiles = new Set;
24467
24880
  for (const csvFile of csvFiles) {
@@ -24536,11 +24949,12 @@ async function executeAccountDeclarationsStep(context, contextId, logger) {
24536
24949
  });
24537
24950
  logger?.endSection();
24538
24951
  }
24539
- async function executeDryRunStep(context, contextId, logger) {
24952
+ async function executeDryRunStep(context, logger) {
24540
24953
  logger?.startSection("Step 3: Dry Run Import");
24541
24954
  logger?.logStep("Dry Run", "start");
24542
24955
  const dryRunResult = await importStatements(context.directory, context.agent, {
24543
- contextId,
24956
+ provider: context.options.provider,
24957
+ currency: context.options.currency,
24544
24958
  checkOnly: true
24545
24959
  }, context.configLoader, context.hledgerExecutor);
24546
24960
  const dryRunParsed = JSON.parse(dryRunResult);
@@ -24565,25 +24979,30 @@ async function executeDryRunStep(context, contextId, logger) {
24565
24979
  extractRulePatternsFromFile: extractRulePatternsFromFile2
24566
24980
  } = await Promise.resolve().then(() => (init_accountSuggester(), exports_accountSuggester));
24567
24981
  const config2 = context.configLoader(context.directory);
24982
+ const pendingDir = path12.join(context.directory, config2.paths.pending);
24568
24983
  const rulesDir = path12.join(context.directory, config2.paths.rules);
24569
- const importCtx = loadContext(context.directory, contextId);
24570
- const csvPath = path12.join(context.directory, importCtx.filePath);
24984
+ const csvFiles = findCsvFiles(pendingDir, context.options.provider, context.options.currency);
24571
24985
  const rulesMapping = loadRulesMapping(rulesDir);
24572
24986
  let yearJournalPath;
24573
24987
  let firstRulesFile;
24574
- const rulesFile = findRulesForCsv(csvPath, rulesMapping);
24575
- if (rulesFile) {
24576
- firstRulesFile = rulesFile;
24577
- try {
24578
- const result = await context.hledgerExecutor(["print", "-f", rulesFile]);
24579
- if (result.exitCode === 0) {
24580
- const years = extractTransactionYears(result.stdout);
24581
- if (years.size > 0) {
24582
- const transactionYear = Array.from(years)[0];
24583
- yearJournalPath = ensureYearJournalExists(context.directory, transactionYear);
24988
+ for (const csvFile of csvFiles) {
24989
+ const rulesFile = findRulesForCsv(csvFile, rulesMapping);
24990
+ if (rulesFile) {
24991
+ firstRulesFile = rulesFile;
24992
+ try {
24993
+ const result = await context.hledgerExecutor(["print", "-f", rulesFile]);
24994
+ if (result.exitCode === 0) {
24995
+ const years = extractTransactionYears(result.stdout);
24996
+ if (years.size > 0) {
24997
+ const transactionYear = Array.from(years)[0];
24998
+ yearJournalPath = ensureYearJournalExists(context.directory, transactionYear);
24999
+ break;
25000
+ }
24584
25001
  }
25002
+ } catch {
25003
+ continue;
24585
25004
  }
24586
- } catch {}
25005
+ }
24587
25006
  }
24588
25007
  const suggestionContext = {
24589
25008
  existingAccounts: yearJournalPath ? await loadExistingAccounts2(yearJournalPath) : [],
@@ -24656,12 +25075,12 @@ function formatUnknownPostingsLog(postings) {
24656
25075
  `;
24657
25076
  return log;
24658
25077
  }
24659
- async function executeImportStep(context, contextId, logger) {
24660
- const importContext = loadContext(context.directory, contextId);
24661
- logger?.startSection(`Step 4: Import Transactions (${importContext.accountNumber || contextId})`);
25078
+ async function executeImportStep(context, logger) {
25079
+ logger?.startSection("Step 4: Import Transactions");
24662
25080
  logger?.logStep("Import", "start");
24663
25081
  const importResult = await importStatements(context.directory, context.agent, {
24664
- contextId,
25082
+ provider: context.options.provider,
25083
+ currency: context.options.currency,
24665
25084
  checkOnly: false
24666
25085
  }, context.configLoader, context.hledgerExecutor);
24667
25086
  const importParsed = JSON.parse(importResult);
@@ -24672,13 +25091,6 @@ async function executeImportStep(context, contextId, logger) {
24672
25091
  summary: importParsed.summary,
24673
25092
  error: importParsed.error
24674
25093
  });
24675
- if (importParsed.success) {
24676
- updateContext(context.directory, contextId, {
24677
- rulesFile: importParsed.files?.[0]?.rulesFile,
24678
- yearJournal: importParsed.files?.[0]?.yearJournal,
24679
- transactionCount: importParsed.summary?.totalTransactions
24680
- });
24681
- }
24682
25094
  if (!importParsed.success) {
24683
25095
  logger?.error("Import failed", new Error(importParsed.error || "Unknown error"));
24684
25096
  logger?.endSection();
@@ -24687,12 +25099,12 @@ async function executeImportStep(context, contextId, logger) {
24687
25099
  }
24688
25100
  logger?.endSection();
24689
25101
  }
24690
- async function executeReconcileStep(context, contextId, logger) {
24691
- const importContext = loadContext(context.directory, contextId);
24692
- logger?.startSection(`Step 5: Reconcile Balance (${importContext.accountNumber || contextId})`);
25102
+ async function executeReconcileStep(context, logger) {
25103
+ logger?.startSection("Step 5: Reconcile Balance");
24693
25104
  logger?.logStep("Reconcile", "start");
24694
25105
  const reconcileResult = await reconcileStatement(context.directory, context.agent, {
24695
- contextId,
25106
+ provider: context.options.provider,
25107
+ currency: context.options.currency,
24696
25108
  closingBalance: context.options.closingBalance,
24697
25109
  account: context.options.account
24698
25110
  }, context.configLoader, context.hledgerExecutor);
@@ -24710,14 +25122,6 @@ async function executeReconcileStep(context, contextId, logger) {
24710
25122
  metadata: reconcileParsed.metadata,
24711
25123
  error: reconcileParsed.error
24712
25124
  });
24713
- if (reconcileParsed.success) {
24714
- updateContext(context.directory, contextId, {
24715
- reconciledAccount: reconcileParsed.account,
24716
- actualBalance: reconcileParsed.actualBalance,
24717
- lastTransactionDate: reconcileParsed.lastTransactionDate,
24718
- reconciled: true
24719
- });
24720
- }
24721
25125
  if (!reconcileParsed.success) {
24722
25126
  logger?.error("Reconciliation failed", new Error(reconcileParsed.error || "Balance mismatch"));
24723
25127
  logger?.endSection();
@@ -24756,31 +25160,21 @@ async function importPipeline(directory, agent, options, configLoader = loadImpo
24756
25160
  result
24757
25161
  };
24758
25162
  try {
24759
- const contextIds = await executeClassifyStep(context, logger);
24760
- if (contextIds.length === 0) {
24761
- logger.info("No files classified, nothing to import");
24762
- return buildSuccessResult4(result, "No files to import");
24763
- }
24764
- let totalTransactions = 0;
24765
- for (const contextId of contextIds) {
24766
- const importContext = loadContext(context.directory, contextId);
24767
- logger.info(`Processing: ${importContext.filename} (${importContext.accountNumber || "unknown account"})`);
24768
- await executeAccountDeclarationsStep(context, contextId, logger);
24769
- await executeDryRunStep(context, contextId, logger);
24770
- await executeImportStep(context, contextId, logger);
24771
- await executeReconcileStep(context, contextId, logger);
24772
- totalTransactions += context.result.steps.import?.details?.summary?.totalTransactions || 0;
24773
- }
25163
+ await executeClassifyStep(context, logger);
25164
+ await executeAccountDeclarationsStep(context, logger);
25165
+ await executeDryRunStep(context, logger);
25166
+ await executeImportStep(context, logger);
25167
+ await executeReconcileStep(context, logger);
25168
+ const transactionCount = context.result.steps.import?.details?.summary?.totalTransactions || 0;
24774
25169
  logger.startSection("Summary");
24775
25170
  logger.info(`Import completed successfully`);
24776
- logger.info(`Contexts processed: ${contextIds.length}`);
24777
- logger.info(`Total transactions imported: ${totalTransactions}`);
25171
+ logger.info(`Total transactions imported: ${transactionCount}`);
24778
25172
  if (context.result.steps.reconcile?.details?.actualBalance) {
24779
25173
  logger.info(`Balance: ${context.result.steps.reconcile.details.actualBalance}`);
24780
25174
  }
24781
25175
  logger.info(`Log file: ${logger.getLogPath()}`);
24782
25176
  logger.endSection();
24783
- return buildSuccessResult4(result, `Successfully imported ${totalTransactions} transaction(s) from ${contextIds.length} file(s)`);
25177
+ return buildSuccessResult4(result, `Successfully imported ${transactionCount} transaction(s)`);
24784
25178
  } catch (error45) {
24785
25179
  logger.error("Pipeline step failed", error45);
24786
25180
  logger.info(`Log file: ${logger.getLogPath()}`);
@@ -24967,7 +25361,7 @@ You can now drop CSV files into import/incoming/ and run import-pipeline.`);
24967
25361
  }
24968
25362
  });
24969
25363
  // src/index.ts
24970
- var __dirname2 = dirname4(fileURLToPath3(import.meta.url));
25364
+ var __dirname2 = dirname5(fileURLToPath3(import.meta.url));
24971
25365
  var AGENT_FILE = join12(__dirname2, "..", "agent", "accountant.md");
24972
25366
  var AccountantPlugin = async () => {
24973
25367
  const agent = loadAgent(AGENT_FILE);