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