@fuzzle/opencode-accountant 0.5.0 → 0.5.1-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +155 -175
- 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 * as fs from "fs";
|
|
2680
2680
|
function loadAgent(filePath) {
|
|
2681
|
-
if (!existsSync(filePath)) {
|
|
2681
|
+
if (!fs.existsSync(filePath)) {
|
|
2682
2682
|
return null;
|
|
2683
2683
|
}
|
|
2684
|
-
const content = readFileSync(filePath, "utf-8");
|
|
2684
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
2685
2685
|
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
2686
2686
|
if (!match) {
|
|
2687
2687
|
throw new Error(`Invalid frontmatter format in ${filePath}`);
|
|
@@ -4231,7 +4231,7 @@ __export(exports_accountSuggester, {
|
|
|
4231
4231
|
extractRulePatternsFromFile: () => extractRulePatternsFromFile,
|
|
4232
4232
|
clearSuggestionCache: () => clearSuggestionCache
|
|
4233
4233
|
});
|
|
4234
|
-
import * as
|
|
4234
|
+
import * as fs14 from "fs";
|
|
4235
4235
|
import * as crypto from "crypto";
|
|
4236
4236
|
function clearSuggestionCache() {
|
|
4237
4237
|
Object.keys(suggestionCache).forEach((key) => delete suggestionCache[key]);
|
|
@@ -4240,11 +4240,11 @@ function hashTransaction(posting) {
|
|
|
4240
4240
|
const data = `${posting.description}|${posting.amount}|${posting.account}`;
|
|
4241
4241
|
return crypto.createHash("md5").update(data).digest("hex");
|
|
4242
4242
|
}
|
|
4243
|
-
|
|
4244
|
-
if (!
|
|
4243
|
+
function loadExistingAccounts(yearJournalPath) {
|
|
4244
|
+
if (!fs14.existsSync(yearJournalPath)) {
|
|
4245
4245
|
return [];
|
|
4246
4246
|
}
|
|
4247
|
-
const content =
|
|
4247
|
+
const content = fs14.readFileSync(yearJournalPath, "utf-8");
|
|
4248
4248
|
const lines = content.split(`
|
|
4249
4249
|
`);
|
|
4250
4250
|
const accounts = [];
|
|
@@ -4259,11 +4259,11 @@ async function loadExistingAccounts(yearJournalPath) {
|
|
|
4259
4259
|
}
|
|
4260
4260
|
return accounts.sort();
|
|
4261
4261
|
}
|
|
4262
|
-
|
|
4263
|
-
if (!
|
|
4262
|
+
function extractRulePatternsFromFile(rulesPath) {
|
|
4263
|
+
if (!fs14.existsSync(rulesPath)) {
|
|
4264
4264
|
return [];
|
|
4265
4265
|
}
|
|
4266
|
-
const content =
|
|
4266
|
+
const content = fs14.readFileSync(rulesPath, "utf-8");
|
|
4267
4267
|
const lines = content.split(`
|
|
4268
4268
|
`);
|
|
4269
4269
|
const patterns = [];
|
|
@@ -16844,16 +16844,16 @@ function checkAccountantAgent(agent, toolPrompt, additionalFields) {
|
|
|
16844
16844
|
|
|
16845
16845
|
// src/utils/yamlLoader.ts
|
|
16846
16846
|
init_js_yaml();
|
|
16847
|
-
import * as
|
|
16847
|
+
import * as fs2 from "fs";
|
|
16848
16848
|
import * as path from "path";
|
|
16849
16849
|
function loadYamlConfig(directory, configFile, validator, notFoundMessage) {
|
|
16850
16850
|
const configPath = path.join(directory, configFile);
|
|
16851
|
-
if (!
|
|
16851
|
+
if (!fs2.existsSync(configPath)) {
|
|
16852
16852
|
throw new Error(notFoundMessage || `Configuration file not found: ${configFile}. Please create this file to configure the feature.`);
|
|
16853
16853
|
}
|
|
16854
16854
|
let parsed;
|
|
16855
16855
|
try {
|
|
16856
|
-
const content =
|
|
16856
|
+
const content = fs2.readFileSync(configPath, "utf-8");
|
|
16857
16857
|
parsed = jsYaml.load(content);
|
|
16858
16858
|
} catch (err) {
|
|
16859
16859
|
if (err instanceof jsYaml.YAMLException) {
|
|
@@ -16910,14 +16910,14 @@ function loadPricesConfig(directory) {
|
|
|
16910
16910
|
}
|
|
16911
16911
|
|
|
16912
16912
|
// src/utils/journalUtils.ts
|
|
16913
|
-
import * as
|
|
16913
|
+
import * as fs4 from "fs";
|
|
16914
16914
|
import * as path3 from "path";
|
|
16915
16915
|
|
|
16916
16916
|
// src/utils/fileUtils.ts
|
|
16917
|
-
import * as
|
|
16917
|
+
import * as fs3 from "fs";
|
|
16918
16918
|
import * as path2 from "path";
|
|
16919
16919
|
function findCsvFiles(baseDir, options = {}) {
|
|
16920
|
-
if (!
|
|
16920
|
+
if (!fs3.existsSync(baseDir)) {
|
|
16921
16921
|
return [];
|
|
16922
16922
|
}
|
|
16923
16923
|
let searchDir = baseDir;
|
|
@@ -16927,13 +16927,13 @@ function findCsvFiles(baseDir, options = {}) {
|
|
|
16927
16927
|
searchDir = path2.join(searchDir, options.subsubdir);
|
|
16928
16928
|
}
|
|
16929
16929
|
}
|
|
16930
|
-
if (!
|
|
16930
|
+
if (!fs3.existsSync(searchDir)) {
|
|
16931
16931
|
return [];
|
|
16932
16932
|
}
|
|
16933
16933
|
const csvFiles = [];
|
|
16934
16934
|
if (options.recursive) {
|
|
16935
16935
|
let scanDirectory = function(dir) {
|
|
16936
|
-
const entries =
|
|
16936
|
+
const entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
16937
16937
|
for (const entry of entries) {
|
|
16938
16938
|
const fullPath = path2.join(dir, entry.name);
|
|
16939
16939
|
if (entry.isDirectory()) {
|
|
@@ -16945,12 +16945,12 @@ function findCsvFiles(baseDir, options = {}) {
|
|
|
16945
16945
|
};
|
|
16946
16946
|
scanDirectory(searchDir);
|
|
16947
16947
|
} else {
|
|
16948
|
-
const entries =
|
|
16948
|
+
const entries = fs3.readdirSync(searchDir);
|
|
16949
16949
|
for (const name of entries) {
|
|
16950
16950
|
if (!name.toLowerCase().endsWith(".csv"))
|
|
16951
16951
|
continue;
|
|
16952
16952
|
const fullPath = path2.join(searchDir, name);
|
|
16953
|
-
if (
|
|
16953
|
+
if (fs3.statSync(fullPath).isFile()) {
|
|
16954
16954
|
csvFiles.push(options.fullPaths ? fullPath : name);
|
|
16955
16955
|
}
|
|
16956
16956
|
}
|
|
@@ -16958,8 +16958,8 @@ function findCsvFiles(baseDir, options = {}) {
|
|
|
16958
16958
|
return csvFiles.sort();
|
|
16959
16959
|
}
|
|
16960
16960
|
function ensureDirectory(dirPath) {
|
|
16961
|
-
if (!
|
|
16962
|
-
|
|
16961
|
+
if (!fs3.existsSync(dirPath)) {
|
|
16962
|
+
fs3.mkdirSync(dirPath, { recursive: true });
|
|
16963
16963
|
}
|
|
16964
16964
|
}
|
|
16965
16965
|
|
|
@@ -16969,8 +16969,8 @@ function extractDateFromPriceLine(line) {
|
|
|
16969
16969
|
}
|
|
16970
16970
|
function updatePriceJournal(journalPath, newPriceLines) {
|
|
16971
16971
|
let existingLines = [];
|
|
16972
|
-
if (
|
|
16973
|
-
existingLines =
|
|
16972
|
+
if (fs4.existsSync(journalPath)) {
|
|
16973
|
+
existingLines = fs4.readFileSync(journalPath, "utf-8").split(`
|
|
16974
16974
|
`).filter((line) => line.trim() !== "");
|
|
16975
16975
|
}
|
|
16976
16976
|
const priceMap = new Map;
|
|
@@ -16985,7 +16985,7 @@ function updatePriceJournal(journalPath, newPriceLines) {
|
|
|
16985
16985
|
priceMap.set(date5, line);
|
|
16986
16986
|
}
|
|
16987
16987
|
const sortedLines = Array.from(priceMap.entries()).sort((a, b) => a[0].localeCompare(b[0])).map(([, line]) => line);
|
|
16988
|
-
|
|
16988
|
+
fs4.writeFileSync(journalPath, sortedLines.join(`
|
|
16989
16989
|
`) + `
|
|
16990
16990
|
`);
|
|
16991
16991
|
}
|
|
@@ -16993,17 +16993,15 @@ function ensureYearJournalExists(directory, year) {
|
|
|
16993
16993
|
const ledgerDir = path3.join(directory, "ledger");
|
|
16994
16994
|
const yearJournalPath = path3.join(ledgerDir, `${year}.journal`);
|
|
16995
16995
|
const mainJournalPath = path3.join(directory, ".hledger.journal");
|
|
16996
|
-
|
|
16997
|
-
|
|
16998
|
-
|
|
16999
|
-
if (!fs3.existsSync(yearJournalPath)) {
|
|
17000
|
-
fs3.writeFileSync(yearJournalPath, `; ${year} transactions
|
|
16996
|
+
ensureDirectory(ledgerDir);
|
|
16997
|
+
if (!fs4.existsSync(yearJournalPath)) {
|
|
16998
|
+
fs4.writeFileSync(yearJournalPath, `; ${year} transactions
|
|
17001
16999
|
`);
|
|
17002
17000
|
}
|
|
17003
|
-
if (!
|
|
17001
|
+
if (!fs4.existsSync(mainJournalPath)) {
|
|
17004
17002
|
throw new Error(`.hledger.journal not found at ${mainJournalPath}. Create it first with appropriate includes.`);
|
|
17005
17003
|
}
|
|
17006
|
-
const mainJournalContent =
|
|
17004
|
+
const mainJournalContent = fs4.readFileSync(mainJournalPath, "utf-8");
|
|
17007
17005
|
const includeDirective = `include ledger/${year}.journal`;
|
|
17008
17006
|
const lines = mainJournalContent.split(`
|
|
17009
17007
|
`);
|
|
@@ -17015,7 +17013,7 @@ function ensureYearJournalExists(directory, year) {
|
|
|
17015
17013
|
const newContent = mainJournalContent.trimEnd() + `
|
|
17016
17014
|
` + includeDirective + `
|
|
17017
17015
|
`;
|
|
17018
|
-
|
|
17016
|
+
fs4.writeFileSync(mainJournalPath, newContent);
|
|
17019
17017
|
}
|
|
17020
17018
|
return yearJournalPath;
|
|
17021
17019
|
}
|
|
@@ -17035,6 +17033,30 @@ function getNextDay(dateStr) {
|
|
|
17035
17033
|
return formatDateISO(date5);
|
|
17036
17034
|
}
|
|
17037
17035
|
|
|
17036
|
+
// src/utils/resultHelpers.ts
|
|
17037
|
+
function buildToolErrorResult(error45, hint, extra) {
|
|
17038
|
+
const result = {
|
|
17039
|
+
success: false,
|
|
17040
|
+
error: error45
|
|
17041
|
+
};
|
|
17042
|
+
if (hint) {
|
|
17043
|
+
result.hint = hint;
|
|
17044
|
+
}
|
|
17045
|
+
if (extra) {
|
|
17046
|
+
Object.assign(result, extra);
|
|
17047
|
+
}
|
|
17048
|
+
return JSON.stringify(result);
|
|
17049
|
+
}
|
|
17050
|
+
function buildToolSuccessResult(data) {
|
|
17051
|
+
const result = {
|
|
17052
|
+
success: true
|
|
17053
|
+
};
|
|
17054
|
+
if (data) {
|
|
17055
|
+
Object.assign(result, data);
|
|
17056
|
+
}
|
|
17057
|
+
return JSON.stringify(result);
|
|
17058
|
+
}
|
|
17059
|
+
|
|
17038
17060
|
// src/tools/fetch-currency-prices.ts
|
|
17039
17061
|
async function defaultPriceFetcher(cmdArgs) {
|
|
17040
17062
|
const result = await $`pricehist ${cmdArgs}`.quiet();
|
|
@@ -17057,11 +17079,8 @@ function buildPricehistArgs(startDate, endDate, currencyConfig) {
|
|
|
17057
17079
|
}
|
|
17058
17080
|
return cmdArgs;
|
|
17059
17081
|
}
|
|
17060
|
-
function buildErrorResult(error45) {
|
|
17061
|
-
return JSON.stringify({ error: error45 });
|
|
17062
|
-
}
|
|
17063
17082
|
function buildSuccessResult(results, endDate, backfill) {
|
|
17064
|
-
return
|
|
17083
|
+
return buildToolSuccessResult({
|
|
17065
17084
|
success: results.every((r) => !("error" in r)),
|
|
17066
17085
|
endDate,
|
|
17067
17086
|
backfill,
|
|
@@ -17094,7 +17113,7 @@ async function fetchCurrencyPrices(directory, agent, backfill, priceFetcher = de
|
|
|
17094
17113
|
config2 = configLoader(directory);
|
|
17095
17114
|
} catch (err) {
|
|
17096
17115
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
17097
|
-
return
|
|
17116
|
+
return buildToolErrorResult(errorMessage);
|
|
17098
17117
|
}
|
|
17099
17118
|
const endDate = getYesterday();
|
|
17100
17119
|
const defaultBackfillDate = getDefaultBackfillDate();
|
|
@@ -17150,7 +17169,7 @@ var fetch_currency_prices_default = tool({
|
|
|
17150
17169
|
}
|
|
17151
17170
|
});
|
|
17152
17171
|
// src/tools/classify-statements.ts
|
|
17153
|
-
import * as
|
|
17172
|
+
import * as fs6 from "fs";
|
|
17154
17173
|
import * as path6 from "path";
|
|
17155
17174
|
|
|
17156
17175
|
// src/utils/importConfig.ts
|
|
@@ -17405,18 +17424,12 @@ function detectProvider(filename, content, config2) {
|
|
|
17405
17424
|
}
|
|
17406
17425
|
|
|
17407
17426
|
// src/utils/importContext.ts
|
|
17408
|
-
import * as
|
|
17427
|
+
import * as fs5 from "fs";
|
|
17409
17428
|
import * as path5 from "path";
|
|
17410
17429
|
import { randomUUID } from "crypto";
|
|
17411
17430
|
function getContextPath(directory, contextId) {
|
|
17412
17431
|
return path5.join(directory, ".memory", `${contextId}.json`);
|
|
17413
17432
|
}
|
|
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
17433
|
function createContext(directory, params) {
|
|
17421
17434
|
const now = new Date().toISOString();
|
|
17422
17435
|
const context = {
|
|
@@ -17435,9 +17448,9 @@ function createContext(directory, params) {
|
|
|
17435
17448
|
closingBalance: params.closingBalance,
|
|
17436
17449
|
account: params.account
|
|
17437
17450
|
};
|
|
17438
|
-
|
|
17451
|
+
ensureDirectory(path5.join(directory, ".memory"));
|
|
17439
17452
|
const contextPath = getContextPath(directory, context.id);
|
|
17440
|
-
|
|
17453
|
+
fs5.writeFileSync(contextPath, JSON.stringify(context, null, 2), "utf-8");
|
|
17441
17454
|
return context;
|
|
17442
17455
|
}
|
|
17443
17456
|
function validateContext(context, contextId) {
|
|
@@ -17456,10 +17469,10 @@ function validateContext(context, contextId) {
|
|
|
17456
17469
|
}
|
|
17457
17470
|
function loadContext(directory, contextId) {
|
|
17458
17471
|
const contextPath = getContextPath(directory, contextId);
|
|
17459
|
-
if (!
|
|
17472
|
+
if (!fs5.existsSync(contextPath)) {
|
|
17460
17473
|
throw new Error(`Context not found: ${contextId}`);
|
|
17461
17474
|
}
|
|
17462
|
-
const content =
|
|
17475
|
+
const content = fs5.readFileSync(contextPath, "utf-8");
|
|
17463
17476
|
let context;
|
|
17464
17477
|
try {
|
|
17465
17478
|
context = JSON.parse(content);
|
|
@@ -17479,14 +17492,13 @@ function updateContext(directory, contextId, updates) {
|
|
|
17479
17492
|
updatedAt: new Date().toISOString()
|
|
17480
17493
|
};
|
|
17481
17494
|
const contextPath = getContextPath(directory, contextId);
|
|
17482
|
-
|
|
17495
|
+
fs5.writeFileSync(contextPath, JSON.stringify(updatedContext, null, 2), "utf-8");
|
|
17483
17496
|
return updatedContext;
|
|
17484
17497
|
}
|
|
17485
17498
|
|
|
17486
17499
|
// src/tools/classify-statements.ts
|
|
17487
17500
|
function buildSuccessResult2(classified, unrecognized, message) {
|
|
17488
|
-
return
|
|
17489
|
-
success: true,
|
|
17501
|
+
return buildToolSuccessResult({
|
|
17490
17502
|
classified,
|
|
17491
17503
|
unrecognized,
|
|
17492
17504
|
message,
|
|
@@ -17497,31 +17509,12 @@ function buildSuccessResult2(classified, unrecognized, message) {
|
|
|
17497
17509
|
}
|
|
17498
17510
|
});
|
|
17499
17511
|
}
|
|
17500
|
-
function buildErrorResult2(error45, hint) {
|
|
17501
|
-
return JSON.stringify({
|
|
17502
|
-
success: false,
|
|
17503
|
-
error: error45,
|
|
17504
|
-
hint,
|
|
17505
|
-
classified: [],
|
|
17506
|
-
unrecognized: []
|
|
17507
|
-
});
|
|
17508
|
-
}
|
|
17509
|
-
function buildCollisionError(collisions) {
|
|
17510
|
-
const error45 = `Cannot classify: ${collisions.length} file(s) would overwrite existing pending files.`;
|
|
17511
|
-
return JSON.stringify({
|
|
17512
|
-
success: false,
|
|
17513
|
-
error: error45,
|
|
17514
|
-
collisions,
|
|
17515
|
-
classified: [],
|
|
17516
|
-
unrecognized: []
|
|
17517
|
-
});
|
|
17518
|
-
}
|
|
17519
17512
|
function planMoves(csvFiles, importsDir, pendingDir, unrecognizedDir, config2) {
|
|
17520
17513
|
const plannedMoves = [];
|
|
17521
17514
|
const collisions = [];
|
|
17522
17515
|
for (const filename of csvFiles) {
|
|
17523
17516
|
const sourcePath = path6.join(importsDir, filename);
|
|
17524
|
-
const content =
|
|
17517
|
+
const content = fs6.readFileSync(sourcePath, "utf-8");
|
|
17525
17518
|
const detection = detectProvider(filename, content, config2);
|
|
17526
17519
|
let targetPath;
|
|
17527
17520
|
let targetFilename;
|
|
@@ -17533,7 +17526,7 @@ function planMoves(csvFiles, importsDir, pendingDir, unrecognizedDir, config2) {
|
|
|
17533
17526
|
targetFilename = filename;
|
|
17534
17527
|
targetPath = path6.join(unrecognizedDir, filename);
|
|
17535
17528
|
}
|
|
17536
|
-
if (
|
|
17529
|
+
if (fs6.existsSync(targetPath)) {
|
|
17537
17530
|
collisions.push({
|
|
17538
17531
|
filename,
|
|
17539
17532
|
existingPath: targetPath
|
|
@@ -17569,7 +17562,7 @@ function executeMoves(plannedMoves, config2, unrecognizedDir, directory) {
|
|
|
17569
17562
|
if (move.detection) {
|
|
17570
17563
|
const targetDir = path6.dirname(move.targetPath);
|
|
17571
17564
|
ensureDirectory(targetDir);
|
|
17572
|
-
|
|
17565
|
+
fs6.renameSync(move.sourcePath, move.targetPath);
|
|
17573
17566
|
const targetPath = path6.join(config2.paths.pending, move.detection.provider, move.detection.currency, move.targetFilename);
|
|
17574
17567
|
const metadata = extractMetadata2(move.detection);
|
|
17575
17568
|
const context = createContext(directory, {
|
|
@@ -17594,7 +17587,7 @@ function executeMoves(plannedMoves, config2, unrecognizedDir, directory) {
|
|
|
17594
17587
|
});
|
|
17595
17588
|
} else {
|
|
17596
17589
|
ensureDirectory(unrecognizedDir);
|
|
17597
|
-
|
|
17590
|
+
fs6.renameSync(move.sourcePath, move.targetPath);
|
|
17598
17591
|
unrecognized.push({
|
|
17599
17592
|
filename: move.filename,
|
|
17600
17593
|
targetPath: path6.join(config2.paths.unrecognized, move.filename)
|
|
@@ -17616,7 +17609,10 @@ async function classifyStatements(directory, agent, configLoader = loadImportCon
|
|
|
17616
17609
|
config2 = configLoader(directory);
|
|
17617
17610
|
} catch (err) {
|
|
17618
17611
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
17619
|
-
return
|
|
17612
|
+
return buildToolErrorResult(errorMessage, undefined, {
|
|
17613
|
+
classified: [],
|
|
17614
|
+
unrecognized: []
|
|
17615
|
+
});
|
|
17620
17616
|
}
|
|
17621
17617
|
const importsDir = path6.join(directory, config2.paths.import);
|
|
17622
17618
|
const pendingDir = path6.join(directory, config2.paths.pending);
|
|
@@ -17627,7 +17623,12 @@ async function classifyStatements(directory, agent, configLoader = loadImportCon
|
|
|
17627
17623
|
}
|
|
17628
17624
|
const { plannedMoves, collisions } = planMoves(csvFiles, importsDir, pendingDir, unrecognizedDir, config2);
|
|
17629
17625
|
if (collisions.length > 0) {
|
|
17630
|
-
|
|
17626
|
+
const error45 = `Cannot classify: ${collisions.length} file(s) would overwrite existing pending files.`;
|
|
17627
|
+
return buildToolErrorResult(error45, undefined, {
|
|
17628
|
+
collisions,
|
|
17629
|
+
classified: [],
|
|
17630
|
+
unrecognized: []
|
|
17631
|
+
});
|
|
17631
17632
|
}
|
|
17632
17633
|
const { classified, unrecognized } = executeMoves(plannedMoves, config2, unrecognizedDir, directory);
|
|
17633
17634
|
return buildSuccessResult2(classified, unrecognized);
|
|
@@ -17649,7 +17650,7 @@ For each CSV file:
|
|
|
17649
17650
|
}
|
|
17650
17651
|
});
|
|
17651
17652
|
// src/tools/import-statements.ts
|
|
17652
|
-
import * as
|
|
17653
|
+
import * as fs10 from "fs";
|
|
17653
17654
|
import * as path9 from "path";
|
|
17654
17655
|
|
|
17655
17656
|
// node_modules/minimatch/dist/esm/index.js
|
|
@@ -21434,8 +21435,8 @@ class PathScurryBase {
|
|
|
21434
21435
|
#children;
|
|
21435
21436
|
nocase;
|
|
21436
21437
|
#fs;
|
|
21437
|
-
constructor(cwd = process.cwd(), pathImpl, sep2, { nocase, childrenCacheSize = 16 * 1024, fs:
|
|
21438
|
-
this.#fs = fsFromOption(
|
|
21438
|
+
constructor(cwd = process.cwd(), pathImpl, sep2, { nocase, childrenCacheSize = 16 * 1024, fs: fs7 = defaultFS } = {}) {
|
|
21439
|
+
this.#fs = fsFromOption(fs7);
|
|
21439
21440
|
if (cwd instanceof URL || cwd.startsWith("file://")) {
|
|
21440
21441
|
cwd = fileURLToPath(cwd);
|
|
21441
21442
|
}
|
|
@@ -21911,8 +21912,8 @@ class PathScurryWin32 extends PathScurryBase {
|
|
|
21911
21912
|
parseRootPath(dir) {
|
|
21912
21913
|
return win32.parse(dir).root.toUpperCase();
|
|
21913
21914
|
}
|
|
21914
|
-
newRoot(
|
|
21915
|
-
return new PathWin32(this.rootPath, IFDIR, undefined, this.roots, this.nocase, this.childrenCache(), { fs:
|
|
21915
|
+
newRoot(fs7) {
|
|
21916
|
+
return new PathWin32(this.rootPath, IFDIR, undefined, this.roots, this.nocase, this.childrenCache(), { fs: fs7 });
|
|
21916
21917
|
}
|
|
21917
21918
|
isAbsolute(p) {
|
|
21918
21919
|
return p.startsWith("/") || p.startsWith("\\") || /^[a-z]:(\/|\\)/i.test(p);
|
|
@@ -21929,8 +21930,8 @@ class PathScurryPosix extends PathScurryBase {
|
|
|
21929
21930
|
parseRootPath(_dir) {
|
|
21930
21931
|
return "/";
|
|
21931
21932
|
}
|
|
21932
|
-
newRoot(
|
|
21933
|
-
return new PathPosix(this.rootPath, IFDIR, undefined, this.roots, this.nocase, this.childrenCache(), { fs:
|
|
21933
|
+
newRoot(fs7) {
|
|
21934
|
+
return new PathPosix(this.rootPath, IFDIR, undefined, this.roots, this.nocase, this.childrenCache(), { fs: fs7 });
|
|
21934
21935
|
}
|
|
21935
21936
|
isAbsolute(p) {
|
|
21936
21937
|
return p.startsWith("/");
|
|
@@ -22932,7 +22933,7 @@ var glob = Object.assign(glob_, {
|
|
|
22932
22933
|
glob.glob = glob;
|
|
22933
22934
|
|
|
22934
22935
|
// src/utils/rulesMatcher.ts
|
|
22935
|
-
import * as
|
|
22936
|
+
import * as fs7 from "fs";
|
|
22936
22937
|
import * as path8 from "path";
|
|
22937
22938
|
function parseSourceDirective(content) {
|
|
22938
22939
|
const match2 = content.match(/^source\s+([^\n#]+)/m);
|
|
@@ -22950,22 +22951,22 @@ function resolveSourcePath(sourcePath, rulesFilePath) {
|
|
|
22950
22951
|
}
|
|
22951
22952
|
function loadRulesMapping(rulesDir) {
|
|
22952
22953
|
const mapping = {};
|
|
22953
|
-
if (!
|
|
22954
|
+
if (!fs7.existsSync(rulesDir)) {
|
|
22954
22955
|
return mapping;
|
|
22955
22956
|
}
|
|
22956
|
-
const files =
|
|
22957
|
+
const files = fs7.readdirSync(rulesDir);
|
|
22957
22958
|
for (const file2 of files) {
|
|
22958
22959
|
if (!file2.endsWith(".rules")) {
|
|
22959
22960
|
continue;
|
|
22960
22961
|
}
|
|
22961
22962
|
const rulesFilePath = path8.join(rulesDir, file2);
|
|
22962
|
-
const stat =
|
|
22963
|
+
const stat = fs7.statSync(rulesFilePath);
|
|
22963
22964
|
if (!stat.isFile()) {
|
|
22964
22965
|
continue;
|
|
22965
22966
|
}
|
|
22966
22967
|
let content;
|
|
22967
22968
|
try {
|
|
22968
|
-
content =
|
|
22969
|
+
content = fs7.readFileSync(rulesFilePath, "utf-8");
|
|
22969
22970
|
} catch {
|
|
22970
22971
|
continue;
|
|
22971
22972
|
}
|
|
@@ -23140,7 +23141,7 @@ async function getAccountBalance(mainJournalPath, account, asOfDate, executor =
|
|
|
23140
23141
|
}
|
|
23141
23142
|
|
|
23142
23143
|
// src/utils/rulesParser.ts
|
|
23143
|
-
import * as
|
|
23144
|
+
import * as fs8 from "fs";
|
|
23144
23145
|
function parseSkipRows(rulesContent) {
|
|
23145
23146
|
const match2 = rulesContent.match(/^skip\s+(\d+)/m);
|
|
23146
23147
|
return match2 ? parseInt(match2[1], 10) : 0;
|
|
@@ -23206,7 +23207,7 @@ function parseAccount1(rulesContent) {
|
|
|
23206
23207
|
}
|
|
23207
23208
|
function getAccountFromRulesFile(rulesFilePath) {
|
|
23208
23209
|
try {
|
|
23209
|
-
const content =
|
|
23210
|
+
const content = fs8.readFileSync(rulesFilePath, "utf-8");
|
|
23210
23211
|
return parseAccount1(content);
|
|
23211
23212
|
} catch {
|
|
23212
23213
|
return null;
|
|
@@ -23226,7 +23227,7 @@ function parseRulesFile(rulesContent) {
|
|
|
23226
23227
|
|
|
23227
23228
|
// src/utils/csvParser.ts
|
|
23228
23229
|
var import_papaparse2 = __toESM(require_papaparse(), 1);
|
|
23229
|
-
import * as
|
|
23230
|
+
import * as fs9 from "fs";
|
|
23230
23231
|
|
|
23231
23232
|
// src/utils/balanceUtils.ts
|
|
23232
23233
|
function parseAmountValue(amountStr) {
|
|
@@ -23276,7 +23277,7 @@ function balancesMatch(balance1, balance2) {
|
|
|
23276
23277
|
// src/utils/csvParser.ts
|
|
23277
23278
|
var AMOUNT_MATCH_TOLERANCE = 0.001;
|
|
23278
23279
|
function parseCsvFile(csvPath, config2) {
|
|
23279
|
-
const csvContent =
|
|
23280
|
+
const csvContent = fs9.readFileSync(csvPath, "utf-8");
|
|
23280
23281
|
const lines = csvContent.split(`
|
|
23281
23282
|
`);
|
|
23282
23283
|
const headerIndex = config2.skipRows;
|
|
@@ -23413,11 +23414,8 @@ function findMatchingCsvRow(posting, csvRows, config2) {
|
|
|
23413
23414
|
}
|
|
23414
23415
|
|
|
23415
23416
|
// src/tools/import-statements.ts
|
|
23416
|
-
function
|
|
23417
|
-
|
|
23418
|
-
success: false,
|
|
23419
|
-
error: error45,
|
|
23420
|
-
hint,
|
|
23417
|
+
function buildErrorResult(error45, hint) {
|
|
23418
|
+
return buildToolErrorResult(error45, hint, {
|
|
23421
23419
|
files: [],
|
|
23422
23420
|
summary: {
|
|
23423
23421
|
filesProcessed: 0,
|
|
@@ -23427,28 +23425,16 @@ function buildErrorResult3(error45, hint) {
|
|
|
23427
23425
|
matched: 0,
|
|
23428
23426
|
unknown: 0
|
|
23429
23427
|
}
|
|
23430
|
-
};
|
|
23431
|
-
return JSON.stringify(result);
|
|
23428
|
+
});
|
|
23432
23429
|
}
|
|
23433
23430
|
function buildErrorResultWithDetails(error45, files, summary, hint) {
|
|
23434
|
-
return
|
|
23435
|
-
success: false,
|
|
23436
|
-
error: error45,
|
|
23437
|
-
hint,
|
|
23438
|
-
files,
|
|
23439
|
-
summary
|
|
23440
|
-
});
|
|
23431
|
+
return buildToolErrorResult(error45, hint, { files, summary });
|
|
23441
23432
|
}
|
|
23442
23433
|
function buildSuccessResult3(files, summary, message) {
|
|
23443
|
-
return
|
|
23444
|
-
success: true,
|
|
23445
|
-
files,
|
|
23446
|
-
summary,
|
|
23447
|
-
message
|
|
23448
|
-
});
|
|
23434
|
+
return buildToolSuccessResult({ files, summary, message });
|
|
23449
23435
|
}
|
|
23450
23436
|
function findCsvFromRulesFile(rulesFile) {
|
|
23451
|
-
const content =
|
|
23437
|
+
const content = fs10.readFileSync(rulesFile, "utf-8");
|
|
23452
23438
|
const match2 = content.match(/^source\s+([^\n#]+)/m);
|
|
23453
23439
|
if (!match2) {
|
|
23454
23440
|
return null;
|
|
@@ -23461,8 +23447,8 @@ function findCsvFromRulesFile(rulesFile) {
|
|
|
23461
23447
|
return null;
|
|
23462
23448
|
}
|
|
23463
23449
|
matches.sort((a, b) => {
|
|
23464
|
-
const aStat =
|
|
23465
|
-
const bStat =
|
|
23450
|
+
const aStat = fs10.statSync(a);
|
|
23451
|
+
const bStat = fs10.statSync(b);
|
|
23466
23452
|
return bStat.mtime.getTime() - aStat.mtime.getTime();
|
|
23467
23453
|
});
|
|
23468
23454
|
return matches[0];
|
|
@@ -23515,10 +23501,8 @@ async function executeImports(fileResults, directory, pendingDir, doneDir, hledg
|
|
|
23515
23501
|
const relativePath = path9.relative(pendingDir, csvFile);
|
|
23516
23502
|
const destPath = path9.join(doneDir, relativePath);
|
|
23517
23503
|
const destDir = path9.dirname(destPath);
|
|
23518
|
-
|
|
23519
|
-
|
|
23520
|
-
}
|
|
23521
|
-
fs9.renameSync(csvFile, destPath);
|
|
23504
|
+
ensureDirectory(destDir);
|
|
23505
|
+
fs10.renameSync(csvFile, destPath);
|
|
23522
23506
|
}
|
|
23523
23507
|
return {
|
|
23524
23508
|
success: true,
|
|
@@ -23566,7 +23550,7 @@ async function processCsvFile(csvFile, rulesMapping, directory, hledgerExecutor)
|
|
|
23566
23550
|
const transactionYear = years.size === 1 ? Array.from(years)[0] : undefined;
|
|
23567
23551
|
if (unknownPostings.length > 0) {
|
|
23568
23552
|
try {
|
|
23569
|
-
const rulesContent =
|
|
23553
|
+
const rulesContent = fs10.readFileSync(rulesFile, "utf-8");
|
|
23570
23554
|
const rulesConfig = parseRulesFile(rulesContent);
|
|
23571
23555
|
const csvRows = parseCsvFile(csvFile, rulesConfig);
|
|
23572
23556
|
for (const posting of unknownPostings) {
|
|
@@ -23601,7 +23585,7 @@ async function importStatements(directory, agent, options, configLoader = loadIm
|
|
|
23601
23585
|
config2 = configLoader(directory);
|
|
23602
23586
|
} catch (error45) {
|
|
23603
23587
|
const errorMessage = `Failed to load configuration: ${error45 instanceof Error ? error45.message : String(error45)}`;
|
|
23604
|
-
return
|
|
23588
|
+
return buildErrorResult(errorMessage, 'Ensure config/import/providers.yaml exists with required paths including "rules"');
|
|
23605
23589
|
}
|
|
23606
23590
|
const pendingDir = path9.join(directory, config2.paths.pending);
|
|
23607
23591
|
const rulesDir = path9.join(directory, config2.paths.rules);
|
|
@@ -23609,8 +23593,8 @@ async function importStatements(directory, agent, options, configLoader = loadIm
|
|
|
23609
23593
|
const rulesMapping = loadRulesMapping(rulesDir);
|
|
23610
23594
|
const importContext = loadContext(directory, options.contextId);
|
|
23611
23595
|
const csvPath = path9.join(directory, importContext.filePath);
|
|
23612
|
-
if (!
|
|
23613
|
-
return
|
|
23596
|
+
if (!fs10.existsSync(csvPath)) {
|
|
23597
|
+
return buildErrorResult(`CSV file not found: ${importContext.filePath}`, "The file may have been moved or deleted");
|
|
23614
23598
|
}
|
|
23615
23599
|
const csvFiles = [csvPath];
|
|
23616
23600
|
const fileResults = [];
|
|
@@ -23644,8 +23628,8 @@ async function importStatements(directory, agent, options, configLoader = loadIm
|
|
|
23644
23628
|
}
|
|
23645
23629
|
for (const [_rulesFile, matchingCSVs] of rulesFileToCSVs.entries()) {
|
|
23646
23630
|
matchingCSVs.sort((a, b) => {
|
|
23647
|
-
const aStat =
|
|
23648
|
-
const bStat =
|
|
23631
|
+
const aStat = fs10.statSync(a);
|
|
23632
|
+
const bStat = fs10.statSync(b);
|
|
23649
23633
|
return bStat.mtime.getTime() - aStat.mtime.getTime();
|
|
23650
23634
|
});
|
|
23651
23635
|
const newestCSV = matchingCSVs[0];
|
|
@@ -23760,22 +23744,18 @@ Note: This tool is typically called via import-pipeline for the full workflow.`,
|
|
|
23760
23744
|
}
|
|
23761
23745
|
});
|
|
23762
23746
|
// src/tools/reconcile-statement.ts
|
|
23763
|
-
import * as
|
|
23747
|
+
import * as fs11 from "fs";
|
|
23764
23748
|
import * as path10 from "path";
|
|
23765
|
-
function
|
|
23766
|
-
|
|
23767
|
-
success: false,
|
|
23749
|
+
function buildErrorResult2(params) {
|
|
23750
|
+
return buildToolErrorResult(params.error, params.hint, {
|
|
23768
23751
|
account: params.account ?? "",
|
|
23769
23752
|
expectedBalance: params.expectedBalance ?? "",
|
|
23770
23753
|
actualBalance: params.actualBalance ?? "",
|
|
23771
23754
|
lastTransactionDate: params.lastTransactionDate ?? "",
|
|
23772
23755
|
csvFile: params.csvFile ?? "",
|
|
23773
23756
|
difference: params.difference,
|
|
23774
|
-
metadata: params.metadata
|
|
23775
|
-
|
|
23776
|
-
hint: params.hint
|
|
23777
|
-
};
|
|
23778
|
-
return JSON.stringify(result);
|
|
23757
|
+
metadata: params.metadata
|
|
23758
|
+
});
|
|
23779
23759
|
}
|
|
23780
23760
|
function loadConfiguration(directory, configLoader) {
|
|
23781
23761
|
try {
|
|
@@ -23783,7 +23763,7 @@ function loadConfiguration(directory, configLoader) {
|
|
|
23783
23763
|
return { config: config2 };
|
|
23784
23764
|
} catch (error45) {
|
|
23785
23765
|
return {
|
|
23786
|
-
error:
|
|
23766
|
+
error: buildErrorResult2({
|
|
23787
23767
|
error: `Failed to load configuration: ${error45 instanceof Error ? error45.message : String(error45)}`,
|
|
23788
23768
|
hint: "Ensure config/import/providers.yaml exists"
|
|
23789
23769
|
})
|
|
@@ -23792,9 +23772,9 @@ function loadConfiguration(directory, configLoader) {
|
|
|
23792
23772
|
}
|
|
23793
23773
|
function verifyCsvExists(directory, importContext) {
|
|
23794
23774
|
const csvFile = path10.join(directory, importContext.filePath);
|
|
23795
|
-
if (!
|
|
23775
|
+
if (!fs11.existsSync(csvFile)) {
|
|
23796
23776
|
return {
|
|
23797
|
-
error:
|
|
23777
|
+
error: buildErrorResult2({
|
|
23798
23778
|
error: `CSV file not found: ${importContext.filePath}`,
|
|
23799
23779
|
hint: `The file may have been moved or deleted. Context ID: ${importContext.id}`
|
|
23800
23780
|
})
|
|
@@ -23830,7 +23810,7 @@ function getBalanceFromCsvAnalysis(csvFile, rulesDir) {
|
|
|
23830
23810
|
}
|
|
23831
23811
|
function extractCsvMetadata(csvFile, config2) {
|
|
23832
23812
|
try {
|
|
23833
|
-
const content =
|
|
23813
|
+
const content = fs11.readFileSync(csvFile, "utf-8");
|
|
23834
23814
|
const filename = path10.basename(csvFile);
|
|
23835
23815
|
const detectionResult = detectProvider(filename, content, config2);
|
|
23836
23816
|
if (detectionResult?.metadata) {
|
|
@@ -23861,7 +23841,7 @@ function determineClosingBalance(csvFile, config2, importContext, manualClosingB
|
|
|
23861
23841
|
const exampleBalance = `${currency} <amount>`;
|
|
23862
23842
|
const retryCmd = buildRetryCommand(importContext.id, exampleBalance);
|
|
23863
23843
|
return {
|
|
23864
|
-
error:
|
|
23844
|
+
error: buildErrorResult2({
|
|
23865
23845
|
csvFile: relativeCsvPath,
|
|
23866
23846
|
error: "No closing balance found in CSV metadata or data",
|
|
23867
23847
|
hint: `Provide closingBalance parameter manually. Example retry: ${retryCmd}`,
|
|
@@ -23891,7 +23871,7 @@ function determineAccount(csvFile, rulesDir, importContext, manualAccount, relat
|
|
|
23891
23871
|
if (!account) {
|
|
23892
23872
|
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:...")}`;
|
|
23893
23873
|
return {
|
|
23894
|
-
error:
|
|
23874
|
+
error: buildErrorResult2({
|
|
23895
23875
|
csvFile: relativeCsvPath,
|
|
23896
23876
|
error: "Could not determine account from rules file",
|
|
23897
23877
|
hint: rulesHint,
|
|
@@ -23908,7 +23888,7 @@ function tryExtractClosingBalanceFromCSV(csvFile, rulesDir) {
|
|
|
23908
23888
|
if (!rulesFile) {
|
|
23909
23889
|
return null;
|
|
23910
23890
|
}
|
|
23911
|
-
const rulesContent =
|
|
23891
|
+
const rulesContent = fs11.readFileSync(rulesFile, "utf-8");
|
|
23912
23892
|
const rulesConfig = parseRulesFile(rulesContent);
|
|
23913
23893
|
const csvRows = parseCsvFile(csvFile, rulesConfig);
|
|
23914
23894
|
if (csvRows.length === 0) {
|
|
@@ -23974,7 +23954,7 @@ async function reconcileStatement(directory, agent, options, configLoader = load
|
|
|
23974
23954
|
try {
|
|
23975
23955
|
importContext = loadContext(directory, options.contextId);
|
|
23976
23956
|
} catch {
|
|
23977
|
-
return
|
|
23957
|
+
return buildErrorResult2({
|
|
23978
23958
|
error: `Failed to load import context: ${options.contextId}`,
|
|
23979
23959
|
hint: "Ensure the context ID is valid and the context file exists in .memory/"
|
|
23980
23960
|
});
|
|
@@ -24003,7 +23983,7 @@ async function reconcileStatement(directory, agent, options, configLoader = load
|
|
|
24003
23983
|
const { account } = accountResult;
|
|
24004
23984
|
const lastTransactionDate = await getLastTransactionDate(mainJournalPath, account, hledgerExecutor);
|
|
24005
23985
|
if (!lastTransactionDate) {
|
|
24006
|
-
return
|
|
23986
|
+
return buildErrorResult2({
|
|
24007
23987
|
csvFile: relativeCsvPath,
|
|
24008
23988
|
account,
|
|
24009
23989
|
error: "No transactions found for account",
|
|
@@ -24013,7 +23993,7 @@ async function reconcileStatement(directory, agent, options, configLoader = load
|
|
|
24013
23993
|
}
|
|
24014
23994
|
const actualBalance = await getAccountBalance(mainJournalPath, account, lastTransactionDate, hledgerExecutor);
|
|
24015
23995
|
if (actualBalance === null) {
|
|
24016
|
-
return
|
|
23996
|
+
return buildErrorResult2({
|
|
24017
23997
|
csvFile: relativeCsvPath,
|
|
24018
23998
|
account,
|
|
24019
23999
|
lastTransactionDate,
|
|
@@ -24026,7 +24006,7 @@ async function reconcileStatement(directory, agent, options, configLoader = load
|
|
|
24026
24006
|
try {
|
|
24027
24007
|
doBalancesMatch = balancesMatch(closingBalance, actualBalance);
|
|
24028
24008
|
} catch (error45) {
|
|
24029
|
-
return
|
|
24009
|
+
return buildErrorResult2({
|
|
24030
24010
|
csvFile: relativeCsvPath,
|
|
24031
24011
|
account,
|
|
24032
24012
|
lastTransactionDate,
|
|
@@ -24055,7 +24035,7 @@ async function reconcileStatement(directory, agent, options, configLoader = load
|
|
|
24055
24035
|
try {
|
|
24056
24036
|
difference = calculateDifference(closingBalance, actualBalance);
|
|
24057
24037
|
} catch (error45) {
|
|
24058
|
-
return
|
|
24038
|
+
return buildErrorResult2({
|
|
24059
24039
|
csvFile: relativeCsvPath,
|
|
24060
24040
|
account,
|
|
24061
24041
|
lastTransactionDate,
|
|
@@ -24065,7 +24045,7 @@ async function reconcileStatement(directory, agent, options, configLoader = load
|
|
|
24065
24045
|
metadata
|
|
24066
24046
|
});
|
|
24067
24047
|
}
|
|
24068
|
-
return
|
|
24048
|
+
return buildErrorResult2({
|
|
24069
24049
|
csvFile: relativeCsvPath,
|
|
24070
24050
|
account,
|
|
24071
24051
|
lastTransactionDate,
|
|
@@ -24116,13 +24096,13 @@ Note: This tool requires a contextId from a prior classify/import step.`,
|
|
|
24116
24096
|
import * as path12 from "path";
|
|
24117
24097
|
|
|
24118
24098
|
// src/utils/accountDeclarations.ts
|
|
24119
|
-
import * as
|
|
24099
|
+
import * as fs12 from "fs";
|
|
24120
24100
|
function extractAccountsFromRulesFile(rulesPath) {
|
|
24121
24101
|
const accounts = new Set;
|
|
24122
|
-
if (!
|
|
24102
|
+
if (!fs12.existsSync(rulesPath)) {
|
|
24123
24103
|
return accounts;
|
|
24124
24104
|
}
|
|
24125
|
-
const content =
|
|
24105
|
+
const content = fs12.readFileSync(rulesPath, "utf-8");
|
|
24126
24106
|
const lines = content.split(`
|
|
24127
24107
|
`);
|
|
24128
24108
|
for (const line of lines) {
|
|
@@ -24157,10 +24137,10 @@ function sortAccountDeclarations(accounts) {
|
|
|
24157
24137
|
return Array.from(accounts).sort((a, b) => a.localeCompare(b));
|
|
24158
24138
|
}
|
|
24159
24139
|
function ensureAccountDeclarations(yearJournalPath, accounts) {
|
|
24160
|
-
if (!
|
|
24140
|
+
if (!fs12.existsSync(yearJournalPath)) {
|
|
24161
24141
|
throw new Error(`Year journal not found: ${yearJournalPath}`);
|
|
24162
24142
|
}
|
|
24163
|
-
const content =
|
|
24143
|
+
const content = fs12.readFileSync(yearJournalPath, "utf-8");
|
|
24164
24144
|
const lines = content.split(`
|
|
24165
24145
|
`);
|
|
24166
24146
|
const existingAccounts = new Set;
|
|
@@ -24222,7 +24202,7 @@ function ensureAccountDeclarations(yearJournalPath, accounts) {
|
|
|
24222
24202
|
newContent.push("");
|
|
24223
24203
|
}
|
|
24224
24204
|
newContent.push(...otherLines);
|
|
24225
|
-
|
|
24205
|
+
fs12.writeFileSync(yearJournalPath, newContent.join(`
|
|
24226
24206
|
`));
|
|
24227
24207
|
return {
|
|
24228
24208
|
added: Array.from(missingAccounts).sort(),
|
|
@@ -24231,7 +24211,7 @@ function ensureAccountDeclarations(yearJournalPath, accounts) {
|
|
|
24231
24211
|
}
|
|
24232
24212
|
|
|
24233
24213
|
// src/utils/logger.ts
|
|
24234
|
-
import
|
|
24214
|
+
import fs13 from "fs/promises";
|
|
24235
24215
|
import path11 from "path";
|
|
24236
24216
|
var LOG_LINE_LIMIT = 50;
|
|
24237
24217
|
|
|
@@ -24346,8 +24326,8 @@ class MarkdownLogger {
|
|
|
24346
24326
|
if (this.buffer.length === 0)
|
|
24347
24327
|
return;
|
|
24348
24328
|
try {
|
|
24349
|
-
await
|
|
24350
|
-
await
|
|
24329
|
+
await fs13.mkdir(path11.dirname(this.logPath), { recursive: true });
|
|
24330
|
+
await fs13.writeFile(this.logPath, this.buffer.join(`
|
|
24351
24331
|
`), "utf-8");
|
|
24352
24332
|
} catch {}
|
|
24353
24333
|
}
|
|
@@ -24401,12 +24381,12 @@ function buildStepResult(success2, message, details) {
|
|
|
24401
24381
|
}
|
|
24402
24382
|
return result;
|
|
24403
24383
|
}
|
|
24404
|
-
function
|
|
24384
|
+
function buildPipelineSuccessResult(result, summary) {
|
|
24405
24385
|
result.success = true;
|
|
24406
24386
|
result.summary = summary;
|
|
24407
24387
|
return JSON.stringify(result);
|
|
24408
24388
|
}
|
|
24409
|
-
function
|
|
24389
|
+
function buildPipelineErrorResult(result, error45, hint) {
|
|
24410
24390
|
result.success = false;
|
|
24411
24391
|
result.error = error45;
|
|
24412
24392
|
if (hint) {
|
|
@@ -24586,9 +24566,9 @@ async function executeDryRunStep(context, contextId, logger) {
|
|
|
24586
24566
|
} catch {}
|
|
24587
24567
|
}
|
|
24588
24568
|
const suggestionContext = {
|
|
24589
|
-
existingAccounts: yearJournalPath ?
|
|
24569
|
+
existingAccounts: yearJournalPath ? loadExistingAccounts2(yearJournalPath) : [],
|
|
24590
24570
|
rulesFilePath: firstRulesFile,
|
|
24591
|
-
existingRules: firstRulesFile ?
|
|
24571
|
+
existingRules: firstRulesFile ? extractRulePatternsFromFile2(firstRulesFile) : undefined,
|
|
24592
24572
|
yearJournalPath,
|
|
24593
24573
|
logger
|
|
24594
24574
|
};
|
|
@@ -24730,7 +24710,7 @@ async function executeReconcileStep(context, contextId, logger) {
|
|
|
24730
24710
|
function handleNoTransactions(result) {
|
|
24731
24711
|
result.steps.import = buildStepResult(true, "No transactions to import");
|
|
24732
24712
|
result.steps.reconcile = buildStepResult(true, "Reconciliation skipped (no transactions)");
|
|
24733
|
-
return
|
|
24713
|
+
return buildPipelineSuccessResult(result, "No transactions found to import");
|
|
24734
24714
|
}
|
|
24735
24715
|
async function importPipeline(directory, agent, options, configLoader = loadImportConfig, hledgerExecutor = defaultHledgerExecutor) {
|
|
24736
24716
|
const restrictionError = checkAccountantAgent(agent, "import pipeline");
|
|
@@ -24759,7 +24739,7 @@ async function importPipeline(directory, agent, options, configLoader = loadImpo
|
|
|
24759
24739
|
const contextIds = await executeClassifyStep(context, logger);
|
|
24760
24740
|
if (contextIds.length === 0) {
|
|
24761
24741
|
logger.info("No files classified, nothing to import");
|
|
24762
|
-
return
|
|
24742
|
+
return buildPipelineSuccessResult(result, "No files to import");
|
|
24763
24743
|
}
|
|
24764
24744
|
let totalTransactions = 0;
|
|
24765
24745
|
for (const contextId of contextIds) {
|
|
@@ -24780,7 +24760,7 @@ async function importPipeline(directory, agent, options, configLoader = loadImpo
|
|
|
24780
24760
|
}
|
|
24781
24761
|
logger.info(`Log file: ${logger.getLogPath()}`);
|
|
24782
24762
|
logger.endSection();
|
|
24783
|
-
return
|
|
24763
|
+
return buildPipelineSuccessResult(result, `Successfully imported ${totalTransactions} transaction(s) from ${contextIds.length} file(s)`);
|
|
24784
24764
|
} catch (error45) {
|
|
24785
24765
|
logger.error("Pipeline step failed", error45);
|
|
24786
24766
|
logger.info(`Log file: ${logger.getLogPath()}`);
|
|
@@ -24790,7 +24770,7 @@ async function importPipeline(directory, agent, options, configLoader = loadImpo
|
|
|
24790
24770
|
if (!result.error) {
|
|
24791
24771
|
result.error = error45 instanceof Error ? error45.message : String(error45);
|
|
24792
24772
|
}
|
|
24793
|
-
return
|
|
24773
|
+
return buildPipelineErrorResult(result, result.error, result.hint);
|
|
24794
24774
|
} finally {
|
|
24795
24775
|
logger.endSection();
|
|
24796
24776
|
await logger.flush();
|
|
@@ -24841,7 +24821,7 @@ This tool orchestrates the full import workflow:
|
|
|
24841
24821
|
}
|
|
24842
24822
|
});
|
|
24843
24823
|
// src/tools/init-directories.ts
|
|
24844
|
-
import * as
|
|
24824
|
+
import * as fs15 from "fs";
|
|
24845
24825
|
import * as path13 from "path";
|
|
24846
24826
|
async function initDirectories(directory) {
|
|
24847
24827
|
try {
|
|
@@ -24849,8 +24829,8 @@ async function initDirectories(directory) {
|
|
|
24849
24829
|
const directoriesCreated = [];
|
|
24850
24830
|
const gitkeepFiles = [];
|
|
24851
24831
|
const importBase = path13.join(directory, "import");
|
|
24852
|
-
if (!
|
|
24853
|
-
|
|
24832
|
+
if (!fs15.existsSync(importBase)) {
|
|
24833
|
+
fs15.mkdirSync(importBase, { recursive: true });
|
|
24854
24834
|
directoriesCreated.push("import");
|
|
24855
24835
|
}
|
|
24856
24836
|
const pathsToCreate = [
|
|
@@ -24861,19 +24841,19 @@ async function initDirectories(directory) {
|
|
|
24861
24841
|
];
|
|
24862
24842
|
for (const { path: dirPath } of pathsToCreate) {
|
|
24863
24843
|
const fullPath = path13.join(directory, dirPath);
|
|
24864
|
-
if (!
|
|
24865
|
-
|
|
24844
|
+
if (!fs15.existsSync(fullPath)) {
|
|
24845
|
+
fs15.mkdirSync(fullPath, { recursive: true });
|
|
24866
24846
|
directoriesCreated.push(dirPath);
|
|
24867
24847
|
}
|
|
24868
24848
|
const gitkeepPath = path13.join(fullPath, ".gitkeep");
|
|
24869
|
-
if (!
|
|
24870
|
-
|
|
24849
|
+
if (!fs15.existsSync(gitkeepPath)) {
|
|
24850
|
+
fs15.writeFileSync(gitkeepPath, "");
|
|
24871
24851
|
gitkeepFiles.push(path13.join(dirPath, ".gitkeep"));
|
|
24872
24852
|
}
|
|
24873
24853
|
}
|
|
24874
24854
|
const gitignorePath = path13.join(importBase, ".gitignore");
|
|
24875
24855
|
let gitignoreCreated = false;
|
|
24876
|
-
if (!
|
|
24856
|
+
if (!fs15.existsSync(gitignorePath)) {
|
|
24877
24857
|
const gitignoreContent = `# Ignore CSV/PDF files in temporary directories
|
|
24878
24858
|
/incoming/*.csv
|
|
24879
24859
|
/incoming/*.pdf
|
|
@@ -24891,7 +24871,7 @@ async function initDirectories(directory) {
|
|
|
24891
24871
|
.DS_Store
|
|
24892
24872
|
Thumbs.db
|
|
24893
24873
|
`;
|
|
24894
|
-
|
|
24874
|
+
fs15.writeFileSync(gitignorePath, gitignoreContent);
|
|
24895
24875
|
gitignoreCreated = true;
|
|
24896
24876
|
}
|
|
24897
24877
|
const parts = [];
|