@fuzzle/opencode-accountant 0.4.2 → 0.4.3
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 +228 -677
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1548,18 +1548,18 @@ var require_brace_expansion = __commonJS((exports, module) => {
|
|
|
1548
1548
|
|
|
1549
1549
|
// node_modules/convert-csv-to-json/src/util/fileUtils.js
|
|
1550
1550
|
var require_fileUtils = __commonJS((exports, module) => {
|
|
1551
|
-
var
|
|
1551
|
+
var fs8 = __require("fs");
|
|
1552
1552
|
|
|
1553
1553
|
class FileUtils {
|
|
1554
1554
|
readFile(fileInputName, encoding) {
|
|
1555
|
-
return
|
|
1555
|
+
return fs8.readFileSync(fileInputName, encoding).toString();
|
|
1556
1556
|
}
|
|
1557
1557
|
readFileAsync(fileInputName, encoding = "utf8") {
|
|
1558
|
-
if (
|
|
1559
|
-
return
|
|
1558
|
+
if (fs8.promises && typeof fs8.promises.readFile === "function") {
|
|
1559
|
+
return fs8.promises.readFile(fileInputName, encoding).then((buf) => buf.toString());
|
|
1560
1560
|
}
|
|
1561
1561
|
return new Promise((resolve2, reject) => {
|
|
1562
|
-
|
|
1562
|
+
fs8.readFile(fileInputName, encoding, (err, data) => {
|
|
1563
1563
|
if (err) {
|
|
1564
1564
|
reject(err);
|
|
1565
1565
|
return;
|
|
@@ -1569,7 +1569,7 @@ var require_fileUtils = __commonJS((exports, module) => {
|
|
|
1569
1569
|
});
|
|
1570
1570
|
}
|
|
1571
1571
|
writeFile(json3, fileOutputName) {
|
|
1572
|
-
|
|
1572
|
+
fs8.writeFile(fileOutputName, json3, function(err) {
|
|
1573
1573
|
if (err) {
|
|
1574
1574
|
throw err;
|
|
1575
1575
|
} else {
|
|
@@ -1578,11 +1578,11 @@ var require_fileUtils = __commonJS((exports, module) => {
|
|
|
1578
1578
|
});
|
|
1579
1579
|
}
|
|
1580
1580
|
writeFileAsync(json3, fileOutputName) {
|
|
1581
|
-
if (
|
|
1582
|
-
return
|
|
1581
|
+
if (fs8.promises && typeof fs8.promises.writeFile === "function") {
|
|
1582
|
+
return fs8.promises.writeFile(fileOutputName, json3);
|
|
1583
1583
|
}
|
|
1584
1584
|
return new Promise((resolve2, reject) => {
|
|
1585
|
-
|
|
1585
|
+
fs8.writeFile(fileOutputName, json3, (err) => {
|
|
1586
1586
|
if (err)
|
|
1587
1587
|
return reject(err);
|
|
1588
1588
|
resolve2();
|
|
@@ -2147,7 +2147,7 @@ var require_convert_csv_to_json = __commonJS((exports) => {
|
|
|
2147
2147
|
});
|
|
2148
2148
|
|
|
2149
2149
|
// src/index.ts
|
|
2150
|
-
import { dirname as
|
|
2150
|
+
import { dirname as dirname5, join as join12 } from "path";
|
|
2151
2151
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2152
2152
|
|
|
2153
2153
|
// src/utils/agentLoader.ts
|
|
@@ -17479,8 +17479,8 @@ var fetch_currency_prices_default = tool({
|
|
|
17479
17479
|
}
|
|
17480
17480
|
});
|
|
17481
17481
|
// src/tools/classify-statements.ts
|
|
17482
|
-
import * as
|
|
17483
|
-
import * as
|
|
17482
|
+
import * as fs5 from "fs";
|
|
17483
|
+
import * as path6 from "path";
|
|
17484
17484
|
|
|
17485
17485
|
// src/utils/importConfig.ts
|
|
17486
17486
|
import * as fs3 from "fs";
|
|
@@ -17751,263 +17751,23 @@ function detectProvider(filename, content, config2) {
|
|
|
17751
17751
|
return null;
|
|
17752
17752
|
}
|
|
17753
17753
|
|
|
17754
|
-
// src/utils/
|
|
17755
|
-
import { spawnSync } from "child_process";
|
|
17756
|
-
|
|
17757
|
-
// node_modules/uuid/dist-node/stringify.js
|
|
17758
|
-
var byteToHex = [];
|
|
17759
|
-
for (let i2 = 0;i2 < 256; ++i2) {
|
|
17760
|
-
byteToHex.push((i2 + 256).toString(16).slice(1));
|
|
17761
|
-
}
|
|
17762
|
-
function unsafeStringify(arr, offset = 0) {
|
|
17763
|
-
return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
|
|
17764
|
-
}
|
|
17765
|
-
|
|
17766
|
-
// node_modules/uuid/dist-node/rng.js
|
|
17767
|
-
import { randomFillSync } from "crypto";
|
|
17768
|
-
var rnds8Pool = new Uint8Array(256);
|
|
17769
|
-
var poolPtr = rnds8Pool.length;
|
|
17770
|
-
function rng() {
|
|
17771
|
-
if (poolPtr > rnds8Pool.length - 16) {
|
|
17772
|
-
randomFillSync(rnds8Pool);
|
|
17773
|
-
poolPtr = 0;
|
|
17774
|
-
}
|
|
17775
|
-
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
|
17776
|
-
}
|
|
17777
|
-
|
|
17778
|
-
// node_modules/uuid/dist-node/native.js
|
|
17779
|
-
import { randomUUID } from "crypto";
|
|
17780
|
-
var native_default = { randomUUID };
|
|
17781
|
-
|
|
17782
|
-
// node_modules/uuid/dist-node/v4.js
|
|
17783
|
-
function _v4(options, buf, offset) {
|
|
17784
|
-
options = options || {};
|
|
17785
|
-
const rnds = options.random ?? options.rng?.() ?? rng();
|
|
17786
|
-
if (rnds.length < 16) {
|
|
17787
|
-
throw new Error("Random bytes length must be >= 16");
|
|
17788
|
-
}
|
|
17789
|
-
rnds[6] = rnds[6] & 15 | 64;
|
|
17790
|
-
rnds[8] = rnds[8] & 63 | 128;
|
|
17791
|
-
if (buf) {
|
|
17792
|
-
offset = offset || 0;
|
|
17793
|
-
if (offset < 0 || offset + 16 > buf.length) {
|
|
17794
|
-
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
|
|
17795
|
-
}
|
|
17796
|
-
for (let i2 = 0;i2 < 16; ++i2) {
|
|
17797
|
-
buf[offset + i2] = rnds[i2];
|
|
17798
|
-
}
|
|
17799
|
-
return buf;
|
|
17800
|
-
}
|
|
17801
|
-
return unsafeStringify(rnds);
|
|
17802
|
-
}
|
|
17803
|
-
function v4(options, buf, offset) {
|
|
17804
|
-
if (native_default.randomUUID && !buf && !options) {
|
|
17805
|
-
return native_default.randomUUID();
|
|
17806
|
-
}
|
|
17807
|
-
return _v4(options, buf, offset);
|
|
17808
|
-
}
|
|
17809
|
-
var v4_default = v4;
|
|
17810
|
-
// src/utils/worktreeManager.ts
|
|
17754
|
+
// src/utils/fileUtils.ts
|
|
17811
17755
|
import * as fs4 from "fs";
|
|
17812
17756
|
import * as path5 from "path";
|
|
17813
|
-
function execGit(args, cwd) {
|
|
17814
|
-
const result = spawnSync("git", args, { cwd, encoding: "utf-8" });
|
|
17815
|
-
if (result.status !== 0) {
|
|
17816
|
-
throw new Error(result.stderr || result.stdout || `git ${args[0]} failed`);
|
|
17817
|
-
}
|
|
17818
|
-
return (result.stdout || "").trim();
|
|
17819
|
-
}
|
|
17820
|
-
function execGitSafe(args, cwd) {
|
|
17821
|
-
const result = spawnSync("git", args, { cwd, encoding: "utf-8" });
|
|
17822
|
-
if (result.status !== 0) {
|
|
17823
|
-
return { success: false, output: result.stderr || result.stdout || `git ${args[0]} failed` };
|
|
17824
|
-
}
|
|
17825
|
-
return { success: true, output: (result.stdout || "").trim() };
|
|
17826
|
-
}
|
|
17827
|
-
function copyIncomingFiles(mainRepoPath, worktreePath) {
|
|
17828
|
-
const sourceDir = path5.join(mainRepoPath, "import/incoming");
|
|
17829
|
-
const targetDir = path5.join(worktreePath, "import/incoming");
|
|
17830
|
-
if (!fs4.existsSync(sourceDir)) {
|
|
17831
|
-
return;
|
|
17832
|
-
}
|
|
17833
|
-
fs4.mkdirSync(targetDir, { recursive: true });
|
|
17834
|
-
const entries = fs4.readdirSync(sourceDir, { withFileTypes: true });
|
|
17835
|
-
let copiedCount = 0;
|
|
17836
|
-
for (const entry of entries) {
|
|
17837
|
-
if (entry.isFile() && !entry.name.startsWith(".")) {
|
|
17838
|
-
const srcPath = path5.join(sourceDir, entry.name);
|
|
17839
|
-
const destPath = path5.join(targetDir, entry.name);
|
|
17840
|
-
fs4.copyFileSync(srcPath, destPath);
|
|
17841
|
-
copiedCount++;
|
|
17842
|
-
}
|
|
17843
|
-
}
|
|
17844
|
-
if (copiedCount > 0) {
|
|
17845
|
-
console.log(`[INFO] Copied ${copiedCount} file(s) from import/incoming/ to worktree`);
|
|
17846
|
-
}
|
|
17847
|
-
}
|
|
17848
|
-
function createImportWorktree(mainRepoPath, options = {}) {
|
|
17849
|
-
const baseDir = options.baseDir ?? "/tmp";
|
|
17850
|
-
const uuid3 = v4_default();
|
|
17851
|
-
const branch = `import-${uuid3}`;
|
|
17852
|
-
const worktreePath = path5.join(baseDir, `import-worktree-${uuid3}`);
|
|
17853
|
-
try {
|
|
17854
|
-
execGit(["rev-parse", "--git-dir"], mainRepoPath);
|
|
17855
|
-
} catch {
|
|
17856
|
-
throw new Error(`Not a git repository: ${mainRepoPath}`);
|
|
17857
|
-
}
|
|
17858
|
-
execGit(["branch", branch], mainRepoPath);
|
|
17859
|
-
try {
|
|
17860
|
-
execGit(["worktree", "add", worktreePath, branch], mainRepoPath);
|
|
17861
|
-
} catch (error45) {
|
|
17862
|
-
execGitSafe(["branch", "-D", branch], mainRepoPath);
|
|
17863
|
-
throw error45;
|
|
17864
|
-
}
|
|
17865
|
-
copyIncomingFiles(mainRepoPath, worktreePath);
|
|
17866
|
-
return {
|
|
17867
|
-
path: worktreePath,
|
|
17868
|
-
branch,
|
|
17869
|
-
uuid: uuid3,
|
|
17870
|
-
mainRepoPath
|
|
17871
|
-
};
|
|
17872
|
-
}
|
|
17873
|
-
function mergeWorktree(context, commitMessage) {
|
|
17874
|
-
const status = execGit(["status", "--porcelain"], context.path);
|
|
17875
|
-
if (status.length > 0) {
|
|
17876
|
-
execGit(["add", "-A"], context.path);
|
|
17877
|
-
execGit(["commit", "-m", commitMessage], context.path);
|
|
17878
|
-
}
|
|
17879
|
-
const currentBranch = execGit(["rev-parse", "--abbrev-ref", "HEAD"], context.mainRepoPath);
|
|
17880
|
-
execGit(["merge", "--no-ff", context.branch, "-m", commitMessage], context.mainRepoPath);
|
|
17881
|
-
if (currentBranch !== "main" && currentBranch !== "master") {
|
|
17882
|
-
execGit(["checkout", currentBranch], context.mainRepoPath);
|
|
17883
|
-
}
|
|
17884
|
-
}
|
|
17885
|
-
function removeWorktree(context, force = false) {
|
|
17886
|
-
const forceFlag = force ? "--force" : "";
|
|
17887
|
-
const args = ["worktree", "remove", context.path];
|
|
17888
|
-
if (forceFlag) {
|
|
17889
|
-
args.push(forceFlag);
|
|
17890
|
-
}
|
|
17891
|
-
const removeResult = execGitSafe(args, context.mainRepoPath);
|
|
17892
|
-
if (!removeResult.success) {
|
|
17893
|
-
if (!fs4.existsSync(context.path)) {} else {
|
|
17894
|
-
return { success: false, error: `Failed to remove worktree: ${removeResult.output}` };
|
|
17895
|
-
}
|
|
17896
|
-
}
|
|
17897
|
-
execGitSafe(["worktree", "prune"], context.mainRepoPath);
|
|
17898
|
-
const branchResult = execGitSafe(["branch", "-D", context.branch], context.mainRepoPath);
|
|
17899
|
-
if (!branchResult.success) {
|
|
17900
|
-
if (!branchResult.output.includes("not found")) {
|
|
17901
|
-
return { success: false, error: `Failed to delete branch: ${branchResult.output}` };
|
|
17902
|
-
}
|
|
17903
|
-
}
|
|
17904
|
-
return { success: true };
|
|
17905
|
-
}
|
|
17906
|
-
function isInWorktree(directory) {
|
|
17907
|
-
try {
|
|
17908
|
-
const gitDir = execGit(["rev-parse", "--git-dir"], directory);
|
|
17909
|
-
return gitDir.includes(".git/worktrees/");
|
|
17910
|
-
} catch {
|
|
17911
|
-
return false;
|
|
17912
|
-
}
|
|
17913
|
-
}
|
|
17914
|
-
async function withWorktree(directory, operation, options) {
|
|
17915
|
-
let createdWorktree = null;
|
|
17916
|
-
let operationSucceeded = false;
|
|
17917
|
-
const logger = options?.logger;
|
|
17918
|
-
const keepOnError = options?.keepOnError ?? true;
|
|
17919
|
-
try {
|
|
17920
|
-
logger?.logStep("Create Worktree", "start");
|
|
17921
|
-
createdWorktree = createImportWorktree(directory);
|
|
17922
|
-
logger?.logStep("Create Worktree", "success", `Path: ${createdWorktree.path}`);
|
|
17923
|
-
logger?.info(`Branch: ${createdWorktree.branch}`);
|
|
17924
|
-
logger?.info(`UUID: ${createdWorktree.uuid}`);
|
|
17925
|
-
const result = await operation(createdWorktree);
|
|
17926
|
-
operationSucceeded = true;
|
|
17927
|
-
return result;
|
|
17928
|
-
} finally {
|
|
17929
|
-
if (createdWorktree) {
|
|
17930
|
-
if (operationSucceeded) {
|
|
17931
|
-
logger?.logStep("Cleanup Worktree", "start");
|
|
17932
|
-
removeWorktree(createdWorktree, true);
|
|
17933
|
-
logger?.logStep("Cleanup Worktree", "success", "Worktree removed");
|
|
17934
|
-
} else if (!keepOnError) {
|
|
17935
|
-
logger?.warn("Operation failed, but keepOnError=false, removing worktree");
|
|
17936
|
-
removeWorktree(createdWorktree, true);
|
|
17937
|
-
} else {
|
|
17938
|
-
logger?.warn("Operation failed, worktree preserved for debugging");
|
|
17939
|
-
logger?.info(`Worktree path: ${createdWorktree.path}`);
|
|
17940
|
-
logger?.info(`To clean up manually: git worktree remove ${createdWorktree.path}`);
|
|
17941
|
-
logger?.info(`To list all worktrees: git worktree list`);
|
|
17942
|
-
}
|
|
17943
|
-
}
|
|
17944
|
-
}
|
|
17945
|
-
}
|
|
17946
|
-
|
|
17947
|
-
// src/utils/fileUtils.ts
|
|
17948
|
-
import * as fs5 from "fs";
|
|
17949
|
-
import * as path6 from "path";
|
|
17950
17757
|
function findCSVFiles(importsDir) {
|
|
17951
|
-
if (!
|
|
17758
|
+
if (!fs4.existsSync(importsDir)) {
|
|
17952
17759
|
return [];
|
|
17953
17760
|
}
|
|
17954
|
-
return
|
|
17955
|
-
const fullPath =
|
|
17956
|
-
return
|
|
17761
|
+
return fs4.readdirSync(importsDir).filter((file2) => file2.toLowerCase().endsWith(".csv")).filter((file2) => {
|
|
17762
|
+
const fullPath = path5.join(importsDir, file2);
|
|
17763
|
+
return fs4.statSync(fullPath).isFile();
|
|
17957
17764
|
});
|
|
17958
17765
|
}
|
|
17959
17766
|
function ensureDirectory(dirPath) {
|
|
17960
|
-
if (!
|
|
17961
|
-
|
|
17767
|
+
if (!fs4.existsSync(dirPath)) {
|
|
17768
|
+
fs4.mkdirSync(dirPath, { recursive: true });
|
|
17962
17769
|
}
|
|
17963
17770
|
}
|
|
17964
|
-
function syncCSVFilesToWorktree(mainRepoPath, worktreePath, importDir) {
|
|
17965
|
-
const result = {
|
|
17966
|
-
synced: [],
|
|
17967
|
-
errors: []
|
|
17968
|
-
};
|
|
17969
|
-
const mainImportDir = path6.join(mainRepoPath, importDir);
|
|
17970
|
-
const worktreeImportDir = path6.join(worktreePath, importDir);
|
|
17971
|
-
const csvFiles = findCSVFiles(mainImportDir);
|
|
17972
|
-
if (csvFiles.length === 0) {
|
|
17973
|
-
return result;
|
|
17974
|
-
}
|
|
17975
|
-
ensureDirectory(worktreeImportDir);
|
|
17976
|
-
for (const file2 of csvFiles) {
|
|
17977
|
-
try {
|
|
17978
|
-
const sourcePath = path6.join(mainImportDir, file2);
|
|
17979
|
-
const destPath = path6.join(worktreeImportDir, file2);
|
|
17980
|
-
fs5.copyFileSync(sourcePath, destPath);
|
|
17981
|
-
result.synced.push(file2);
|
|
17982
|
-
} catch (error45) {
|
|
17983
|
-
const errorMsg = error45 instanceof Error ? error45.message : String(error45);
|
|
17984
|
-
result.errors.push({ file: file2, error: errorMsg });
|
|
17985
|
-
}
|
|
17986
|
-
}
|
|
17987
|
-
return result;
|
|
17988
|
-
}
|
|
17989
|
-
function cleanupProcessedCSVFiles(mainRepoPath, importDir) {
|
|
17990
|
-
const result = {
|
|
17991
|
-
deleted: [],
|
|
17992
|
-
errors: []
|
|
17993
|
-
};
|
|
17994
|
-
const mainImportDir = path6.join(mainRepoPath, importDir);
|
|
17995
|
-
const csvFiles = findCSVFiles(mainImportDir);
|
|
17996
|
-
if (csvFiles.length === 0) {
|
|
17997
|
-
return result;
|
|
17998
|
-
}
|
|
17999
|
-
for (const file2 of csvFiles) {
|
|
18000
|
-
try {
|
|
18001
|
-
const filePath = path6.join(mainImportDir, file2);
|
|
18002
|
-
fs5.unlinkSync(filePath);
|
|
18003
|
-
result.deleted.push(file2);
|
|
18004
|
-
} catch (error45) {
|
|
18005
|
-
const errorMsg = error45 instanceof Error ? error45.message : String(error45);
|
|
18006
|
-
result.errors.push({ file: file2, error: errorMsg });
|
|
18007
|
-
}
|
|
18008
|
-
}
|
|
18009
|
-
return result;
|
|
18010
|
-
}
|
|
18011
17771
|
|
|
18012
17772
|
// src/tools/classify-statements.ts
|
|
18013
17773
|
function buildSuccessResult2(classified, unrecognized, message) {
|
|
@@ -18046,20 +17806,20 @@ function planMoves(csvFiles, importsDir, pendingDir, unrecognizedDir, config2) {
|
|
|
18046
17806
|
const plannedMoves = [];
|
|
18047
17807
|
const collisions = [];
|
|
18048
17808
|
for (const filename of csvFiles) {
|
|
18049
|
-
const sourcePath =
|
|
18050
|
-
const content =
|
|
17809
|
+
const sourcePath = path6.join(importsDir, filename);
|
|
17810
|
+
const content = fs5.readFileSync(sourcePath, "utf-8");
|
|
18051
17811
|
const detection = detectProvider(filename, content, config2);
|
|
18052
17812
|
let targetPath;
|
|
18053
17813
|
let targetFilename;
|
|
18054
17814
|
if (detection) {
|
|
18055
17815
|
targetFilename = detection.outputFilename || filename;
|
|
18056
|
-
const targetDir =
|
|
18057
|
-
targetPath =
|
|
17816
|
+
const targetDir = path6.join(pendingDir, detection.provider, detection.currency);
|
|
17817
|
+
targetPath = path6.join(targetDir, targetFilename);
|
|
18058
17818
|
} else {
|
|
18059
17819
|
targetFilename = filename;
|
|
18060
|
-
targetPath =
|
|
17820
|
+
targetPath = path6.join(unrecognizedDir, filename);
|
|
18061
17821
|
}
|
|
18062
|
-
if (
|
|
17822
|
+
if (fs5.existsSync(targetPath)) {
|
|
18063
17823
|
collisions.push({
|
|
18064
17824
|
filename,
|
|
18065
17825
|
existingPath: targetPath
|
|
@@ -18080,28 +17840,28 @@ function executeMoves(plannedMoves, config2, unrecognizedDir) {
|
|
|
18080
17840
|
const unrecognized = [];
|
|
18081
17841
|
for (const move of plannedMoves) {
|
|
18082
17842
|
if (move.detection) {
|
|
18083
|
-
const targetDir =
|
|
17843
|
+
const targetDir = path6.dirname(move.targetPath);
|
|
18084
17844
|
ensureDirectory(targetDir);
|
|
18085
|
-
|
|
17845
|
+
fs5.renameSync(move.sourcePath, move.targetPath);
|
|
18086
17846
|
classified.push({
|
|
18087
17847
|
filename: move.targetFilename,
|
|
18088
17848
|
originalFilename: move.detection.outputFilename ? move.filename : undefined,
|
|
18089
17849
|
provider: move.detection.provider,
|
|
18090
17850
|
currency: move.detection.currency,
|
|
18091
|
-
targetPath:
|
|
17851
|
+
targetPath: path6.join(config2.paths.pending, move.detection.provider, move.detection.currency, move.targetFilename)
|
|
18092
17852
|
});
|
|
18093
17853
|
} else {
|
|
18094
17854
|
ensureDirectory(unrecognizedDir);
|
|
18095
|
-
|
|
17855
|
+
fs5.renameSync(move.sourcePath, move.targetPath);
|
|
18096
17856
|
unrecognized.push({
|
|
18097
17857
|
filename: move.filename,
|
|
18098
|
-
targetPath:
|
|
17858
|
+
targetPath: path6.join(config2.paths.unrecognized, move.filename)
|
|
18099
17859
|
});
|
|
18100
17860
|
}
|
|
18101
17861
|
}
|
|
18102
17862
|
return { classified, unrecognized };
|
|
18103
17863
|
}
|
|
18104
|
-
async function classifyStatements(directory, agent, configLoader = loadImportConfig
|
|
17864
|
+
async function classifyStatements(directory, agent, configLoader = loadImportConfig) {
|
|
18105
17865
|
const restrictionError = checkAccountantAgent(agent, "classify statements", {
|
|
18106
17866
|
classified: [],
|
|
18107
17867
|
unrecognized: []
|
|
@@ -18109,9 +17869,6 @@ async function classifyStatements(directory, agent, configLoader = loadImportCon
|
|
|
18109
17869
|
if (restrictionError) {
|
|
18110
17870
|
return restrictionError;
|
|
18111
17871
|
}
|
|
18112
|
-
if (!worktreeChecker(directory)) {
|
|
18113
|
-
return buildErrorResult2("classify-statements must be run inside an import worktree", "Use import-pipeline tool to orchestrate the full workflow");
|
|
18114
|
-
}
|
|
18115
17872
|
let config2;
|
|
18116
17873
|
try {
|
|
18117
17874
|
config2 = configLoader(directory);
|
|
@@ -18119,9 +17876,9 @@ async function classifyStatements(directory, agent, configLoader = loadImportCon
|
|
|
18119
17876
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
18120
17877
|
return buildErrorResult2(errorMessage);
|
|
18121
17878
|
}
|
|
18122
|
-
const importsDir =
|
|
18123
|
-
const pendingDir =
|
|
18124
|
-
const unrecognizedDir =
|
|
17879
|
+
const importsDir = path6.join(directory, config2.paths.import);
|
|
17880
|
+
const pendingDir = path6.join(directory, config2.paths.pending);
|
|
17881
|
+
const unrecognizedDir = path6.join(directory, config2.paths.unrecognized);
|
|
18125
17882
|
const csvFiles = findCSVFiles(importsDir);
|
|
18126
17883
|
if (csvFiles.length === 0) {
|
|
18127
17884
|
return buildSuccessResult2([], [], `No CSV files found in ${config2.paths.import}`);
|
|
@@ -18142,8 +17899,8 @@ var classify_statements_default = tool({
|
|
|
18142
17899
|
}
|
|
18143
17900
|
});
|
|
18144
17901
|
// src/tools/import-statements.ts
|
|
18145
|
-
import * as
|
|
18146
|
-
import * as
|
|
17902
|
+
import * as fs9 from "fs";
|
|
17903
|
+
import * as path9 from "path";
|
|
18147
17904
|
|
|
18148
17905
|
// node_modules/minimatch/dist/esm/index.js
|
|
18149
17906
|
var import_brace_expansion = __toESM(require_brace_expansion(), 1);
|
|
@@ -18736,11 +18493,11 @@ var qmarksTestNoExtDot = ([$0]) => {
|
|
|
18736
18493
|
return (f) => f.length === len && f !== "." && f !== "..";
|
|
18737
18494
|
};
|
|
18738
18495
|
var defaultPlatform = typeof process === "object" && process ? typeof process.env === "object" && process.env && process.env.__MINIMATCH_TESTING_PLATFORM__ || process.platform : "posix";
|
|
18739
|
-
var
|
|
18496
|
+
var path7 = {
|
|
18740
18497
|
win32: { sep: "\\" },
|
|
18741
18498
|
posix: { sep: "/" }
|
|
18742
18499
|
};
|
|
18743
|
-
var sep = defaultPlatform === "win32" ?
|
|
18500
|
+
var sep = defaultPlatform === "win32" ? path7.win32.sep : path7.posix.sep;
|
|
18744
18501
|
minimatch.sep = sep;
|
|
18745
18502
|
var GLOBSTAR = Symbol("globstar **");
|
|
18746
18503
|
minimatch.GLOBSTAR = GLOBSTAR;
|
|
@@ -20474,7 +20231,7 @@ class LRUCache {
|
|
|
20474
20231
|
// node_modules/path-scurry/dist/esm/index.js
|
|
20475
20232
|
import { posix, win32 } from "path";
|
|
20476
20233
|
import { fileURLToPath } from "url";
|
|
20477
|
-
import { lstatSync, readdir as readdirCB, readdirSync as
|
|
20234
|
+
import { lstatSync, readdir as readdirCB, readdirSync as readdirSync3, readlinkSync, realpathSync as rps } from "fs";
|
|
20478
20235
|
import * as actualFS from "fs";
|
|
20479
20236
|
import { lstat, readdir, readlink, realpath } from "fs/promises";
|
|
20480
20237
|
|
|
@@ -21146,7 +20903,7 @@ var realpathSync = rps.native;
|
|
|
21146
20903
|
var defaultFS = {
|
|
21147
20904
|
lstatSync,
|
|
21148
20905
|
readdir: readdirCB,
|
|
21149
|
-
readdirSync:
|
|
20906
|
+
readdirSync: readdirSync3,
|
|
21150
20907
|
readlinkSync,
|
|
21151
20908
|
realpathSync,
|
|
21152
20909
|
promises: {
|
|
@@ -21345,12 +21102,12 @@ class PathBase {
|
|
|
21345
21102
|
childrenCache() {
|
|
21346
21103
|
return this.#children;
|
|
21347
21104
|
}
|
|
21348
|
-
resolve(
|
|
21349
|
-
if (!
|
|
21105
|
+
resolve(path8) {
|
|
21106
|
+
if (!path8) {
|
|
21350
21107
|
return this;
|
|
21351
21108
|
}
|
|
21352
|
-
const rootPath = this.getRootString(
|
|
21353
|
-
const dir =
|
|
21109
|
+
const rootPath = this.getRootString(path8);
|
|
21110
|
+
const dir = path8.substring(rootPath.length);
|
|
21354
21111
|
const dirParts = dir.split(this.splitSep);
|
|
21355
21112
|
const result = rootPath ? this.getRoot(rootPath).#resolveParts(dirParts) : this.#resolveParts(dirParts);
|
|
21356
21113
|
return result;
|
|
@@ -21879,8 +21636,8 @@ class PathWin32 extends PathBase {
|
|
|
21879
21636
|
newChild(name, type2 = UNKNOWN, opts = {}) {
|
|
21880
21637
|
return new PathWin32(name, type2, this.root, this.roots, this.nocase, this.childrenCache(), opts);
|
|
21881
21638
|
}
|
|
21882
|
-
getRootString(
|
|
21883
|
-
return win32.parse(
|
|
21639
|
+
getRootString(path8) {
|
|
21640
|
+
return win32.parse(path8).root;
|
|
21884
21641
|
}
|
|
21885
21642
|
getRoot(rootPath) {
|
|
21886
21643
|
rootPath = uncToDrive(rootPath.toUpperCase());
|
|
@@ -21906,8 +21663,8 @@ class PathPosix extends PathBase {
|
|
|
21906
21663
|
constructor(name, type2 = UNKNOWN, root, roots, nocase, children, opts) {
|
|
21907
21664
|
super(name, type2, root, roots, nocase, children, opts);
|
|
21908
21665
|
}
|
|
21909
|
-
getRootString(
|
|
21910
|
-
return
|
|
21666
|
+
getRootString(path8) {
|
|
21667
|
+
return path8.startsWith("/") ? "/" : "";
|
|
21911
21668
|
}
|
|
21912
21669
|
getRoot(_rootPath) {
|
|
21913
21670
|
return this.root;
|
|
@@ -21927,8 +21684,8 @@ class PathScurryBase {
|
|
|
21927
21684
|
#children;
|
|
21928
21685
|
nocase;
|
|
21929
21686
|
#fs;
|
|
21930
|
-
constructor(cwd = process.cwd(), pathImpl, sep2, { nocase, childrenCacheSize = 16 * 1024, fs:
|
|
21931
|
-
this.#fs = fsFromOption(
|
|
21687
|
+
constructor(cwd = process.cwd(), pathImpl, sep2, { nocase, childrenCacheSize = 16 * 1024, fs: fs6 = defaultFS } = {}) {
|
|
21688
|
+
this.#fs = fsFromOption(fs6);
|
|
21932
21689
|
if (cwd instanceof URL || cwd.startsWith("file://")) {
|
|
21933
21690
|
cwd = fileURLToPath(cwd);
|
|
21934
21691
|
}
|
|
@@ -21964,11 +21721,11 @@ class PathScurryBase {
|
|
|
21964
21721
|
}
|
|
21965
21722
|
this.cwd = prev;
|
|
21966
21723
|
}
|
|
21967
|
-
depth(
|
|
21968
|
-
if (typeof
|
|
21969
|
-
|
|
21724
|
+
depth(path8 = this.cwd) {
|
|
21725
|
+
if (typeof path8 === "string") {
|
|
21726
|
+
path8 = this.cwd.resolve(path8);
|
|
21970
21727
|
}
|
|
21971
|
-
return
|
|
21728
|
+
return path8.depth();
|
|
21972
21729
|
}
|
|
21973
21730
|
childrenCache() {
|
|
21974
21731
|
return this.#children;
|
|
@@ -22384,9 +22141,9 @@ class PathScurryBase {
|
|
|
22384
22141
|
process2();
|
|
22385
22142
|
return results;
|
|
22386
22143
|
}
|
|
22387
|
-
chdir(
|
|
22144
|
+
chdir(path8 = this.cwd) {
|
|
22388
22145
|
const oldCwd = this.cwd;
|
|
22389
|
-
this.cwd = typeof
|
|
22146
|
+
this.cwd = typeof path8 === "string" ? this.cwd.resolve(path8) : path8;
|
|
22390
22147
|
this.cwd[setAsCwd](oldCwd);
|
|
22391
22148
|
}
|
|
22392
22149
|
}
|
|
@@ -22404,8 +22161,8 @@ class PathScurryWin32 extends PathScurryBase {
|
|
|
22404
22161
|
parseRootPath(dir) {
|
|
22405
22162
|
return win32.parse(dir).root.toUpperCase();
|
|
22406
22163
|
}
|
|
22407
|
-
newRoot(
|
|
22408
|
-
return new PathWin32(this.rootPath, IFDIR, undefined, this.roots, this.nocase, this.childrenCache(), { fs:
|
|
22164
|
+
newRoot(fs6) {
|
|
22165
|
+
return new PathWin32(this.rootPath, IFDIR, undefined, this.roots, this.nocase, this.childrenCache(), { fs: fs6 });
|
|
22409
22166
|
}
|
|
22410
22167
|
isAbsolute(p) {
|
|
22411
22168
|
return p.startsWith("/") || p.startsWith("\\") || /^[a-z]:(\/|\\)/i.test(p);
|
|
@@ -22422,8 +22179,8 @@ class PathScurryPosix extends PathScurryBase {
|
|
|
22422
22179
|
parseRootPath(_dir) {
|
|
22423
22180
|
return "/";
|
|
22424
22181
|
}
|
|
22425
|
-
newRoot(
|
|
22426
|
-
return new PathPosix(this.rootPath, IFDIR, undefined, this.roots, this.nocase, this.childrenCache(), { fs:
|
|
22182
|
+
newRoot(fs6) {
|
|
22183
|
+
return new PathPosix(this.rootPath, IFDIR, undefined, this.roots, this.nocase, this.childrenCache(), { fs: fs6 });
|
|
22427
22184
|
}
|
|
22428
22185
|
isAbsolute(p) {
|
|
22429
22186
|
return p.startsWith("/");
|
|
@@ -22675,8 +22432,8 @@ class MatchRecord {
|
|
|
22675
22432
|
this.store.set(target, current === undefined ? n : n & current);
|
|
22676
22433
|
}
|
|
22677
22434
|
entries() {
|
|
22678
|
-
return [...this.store.entries()].map(([
|
|
22679
|
-
|
|
22435
|
+
return [...this.store.entries()].map(([path8, n]) => [
|
|
22436
|
+
path8,
|
|
22680
22437
|
!!(n & 2),
|
|
22681
22438
|
!!(n & 1)
|
|
22682
22439
|
]);
|
|
@@ -22879,9 +22636,9 @@ class GlobUtil {
|
|
|
22879
22636
|
signal;
|
|
22880
22637
|
maxDepth;
|
|
22881
22638
|
includeChildMatches;
|
|
22882
|
-
constructor(patterns,
|
|
22639
|
+
constructor(patterns, path8, opts) {
|
|
22883
22640
|
this.patterns = patterns;
|
|
22884
|
-
this.path =
|
|
22641
|
+
this.path = path8;
|
|
22885
22642
|
this.opts = opts;
|
|
22886
22643
|
this.#sep = !opts.posix && opts.platform === "win32" ? "\\" : "/";
|
|
22887
22644
|
this.includeChildMatches = opts.includeChildMatches !== false;
|
|
@@ -22900,11 +22657,11 @@ class GlobUtil {
|
|
|
22900
22657
|
});
|
|
22901
22658
|
}
|
|
22902
22659
|
}
|
|
22903
|
-
#ignored(
|
|
22904
|
-
return this.seen.has(
|
|
22660
|
+
#ignored(path8) {
|
|
22661
|
+
return this.seen.has(path8) || !!this.#ignore?.ignored?.(path8);
|
|
22905
22662
|
}
|
|
22906
|
-
#childrenIgnored(
|
|
22907
|
-
return !!this.#ignore?.childrenIgnored?.(
|
|
22663
|
+
#childrenIgnored(path8) {
|
|
22664
|
+
return !!this.#ignore?.childrenIgnored?.(path8);
|
|
22908
22665
|
}
|
|
22909
22666
|
pause() {
|
|
22910
22667
|
this.paused = true;
|
|
@@ -23117,8 +22874,8 @@ class GlobUtil {
|
|
|
23117
22874
|
|
|
23118
22875
|
class GlobWalker extends GlobUtil {
|
|
23119
22876
|
matches = new Set;
|
|
23120
|
-
constructor(patterns,
|
|
23121
|
-
super(patterns,
|
|
22877
|
+
constructor(patterns, path8, opts) {
|
|
22878
|
+
super(patterns, path8, opts);
|
|
23122
22879
|
}
|
|
23123
22880
|
matchEmit(e) {
|
|
23124
22881
|
this.matches.add(e);
|
|
@@ -23156,8 +22913,8 @@ class GlobWalker extends GlobUtil {
|
|
|
23156
22913
|
|
|
23157
22914
|
class GlobStream extends GlobUtil {
|
|
23158
22915
|
results;
|
|
23159
|
-
constructor(patterns,
|
|
23160
|
-
super(patterns,
|
|
22916
|
+
constructor(patterns, path8, opts) {
|
|
22917
|
+
super(patterns, path8, opts);
|
|
23161
22918
|
this.results = new Minipass({
|
|
23162
22919
|
signal: this.signal,
|
|
23163
22920
|
objectMode: true
|
|
@@ -23425,8 +23182,8 @@ var glob = Object.assign(glob_, {
|
|
|
23425
23182
|
glob.glob = glob;
|
|
23426
23183
|
|
|
23427
23184
|
// src/utils/rulesMatcher.ts
|
|
23428
|
-
import * as
|
|
23429
|
-
import * as
|
|
23185
|
+
import * as fs6 from "fs";
|
|
23186
|
+
import * as path8 from "path";
|
|
23430
23187
|
function parseSourceDirective(content) {
|
|
23431
23188
|
const match2 = content.match(/^source\s+([^\n#]+)/m);
|
|
23432
23189
|
if (!match2) {
|
|
@@ -23435,28 +23192,28 @@ function parseSourceDirective(content) {
|
|
|
23435
23192
|
return match2[1].trim();
|
|
23436
23193
|
}
|
|
23437
23194
|
function resolveSourcePath(sourcePath, rulesFilePath) {
|
|
23438
|
-
if (
|
|
23195
|
+
if (path8.isAbsolute(sourcePath)) {
|
|
23439
23196
|
return sourcePath;
|
|
23440
23197
|
}
|
|
23441
|
-
const rulesDir =
|
|
23442
|
-
return
|
|
23198
|
+
const rulesDir = path8.dirname(rulesFilePath);
|
|
23199
|
+
return path8.resolve(rulesDir, sourcePath);
|
|
23443
23200
|
}
|
|
23444
23201
|
function loadRulesMapping(rulesDir) {
|
|
23445
23202
|
const mapping = {};
|
|
23446
|
-
if (!
|
|
23203
|
+
if (!fs6.existsSync(rulesDir)) {
|
|
23447
23204
|
return mapping;
|
|
23448
23205
|
}
|
|
23449
|
-
const files =
|
|
23206
|
+
const files = fs6.readdirSync(rulesDir);
|
|
23450
23207
|
for (const file2 of files) {
|
|
23451
23208
|
if (!file2.endsWith(".rules")) {
|
|
23452
23209
|
continue;
|
|
23453
23210
|
}
|
|
23454
|
-
const rulesFilePath =
|
|
23455
|
-
const stat =
|
|
23211
|
+
const rulesFilePath = path8.join(rulesDir, file2);
|
|
23212
|
+
const stat = fs6.statSync(rulesFilePath);
|
|
23456
23213
|
if (!stat.isFile()) {
|
|
23457
23214
|
continue;
|
|
23458
23215
|
}
|
|
23459
|
-
const content =
|
|
23216
|
+
const content = fs6.readFileSync(rulesFilePath, "utf-8");
|
|
23460
23217
|
const sourcePath = parseSourceDirective(content);
|
|
23461
23218
|
if (!sourcePath) {
|
|
23462
23219
|
continue;
|
|
@@ -23470,22 +23227,22 @@ function findRulesForCsv(csvPath, mapping) {
|
|
|
23470
23227
|
if (mapping[csvPath]) {
|
|
23471
23228
|
return mapping[csvPath];
|
|
23472
23229
|
}
|
|
23473
|
-
const normalizedCsvPath =
|
|
23230
|
+
const normalizedCsvPath = path8.normalize(csvPath);
|
|
23474
23231
|
for (const [mappedCsv, rulesFile] of Object.entries(mapping)) {
|
|
23475
|
-
const normalizedMappedCsv =
|
|
23232
|
+
const normalizedMappedCsv = path8.normalize(mappedCsv);
|
|
23476
23233
|
if (normalizedMappedCsv === normalizedCsvPath) {
|
|
23477
23234
|
return rulesFile;
|
|
23478
23235
|
}
|
|
23479
23236
|
}
|
|
23480
23237
|
for (const [pattern, rulesFile] of Object.entries(mapping)) {
|
|
23481
|
-
if (minimatch(csvPath, pattern) || minimatch(normalizedCsvPath,
|
|
23238
|
+
if (minimatch(csvPath, pattern) || minimatch(normalizedCsvPath, path8.normalize(pattern))) {
|
|
23482
23239
|
return rulesFile;
|
|
23483
23240
|
}
|
|
23484
23241
|
}
|
|
23485
|
-
const csvBasename =
|
|
23242
|
+
const csvBasename = path8.basename(csvPath);
|
|
23486
23243
|
const matches = [];
|
|
23487
23244
|
for (const rulesFile of Object.values(mapping)) {
|
|
23488
|
-
const rulesBasename =
|
|
23245
|
+
const rulesBasename = path8.basename(rulesFile, ".rules");
|
|
23489
23246
|
if (csvBasename.startsWith(rulesBasename)) {
|
|
23490
23247
|
matches.push({ rulesFile, prefixLength: rulesBasename.length });
|
|
23491
23248
|
}
|
|
@@ -23616,7 +23373,7 @@ async function getAccountBalance(mainJournalPath, account, asOfDate, executor =
|
|
|
23616
23373
|
}
|
|
23617
23374
|
|
|
23618
23375
|
// src/utils/rulesParser.ts
|
|
23619
|
-
import * as
|
|
23376
|
+
import * as fs7 from "fs";
|
|
23620
23377
|
function parseSkipRows(rulesContent) {
|
|
23621
23378
|
const match2 = rulesContent.match(/^skip\s+(\d+)/m);
|
|
23622
23379
|
return match2 ? parseInt(match2[1], 10) : 0;
|
|
@@ -23682,7 +23439,7 @@ function parseAccount1(rulesContent) {
|
|
|
23682
23439
|
}
|
|
23683
23440
|
function getAccountFromRulesFile(rulesFilePath) {
|
|
23684
23441
|
try {
|
|
23685
|
-
const content =
|
|
23442
|
+
const content = fs7.readFileSync(rulesFilePath, "utf-8");
|
|
23686
23443
|
return parseAccount1(content);
|
|
23687
23444
|
} catch {
|
|
23688
23445
|
return null;
|
|
@@ -23702,7 +23459,7 @@ function parseRulesFile(rulesContent) {
|
|
|
23702
23459
|
|
|
23703
23460
|
// src/utils/csvParser.ts
|
|
23704
23461
|
var import_convert_csv_to_json = __toESM(require_convert_csv_to_json(), 1);
|
|
23705
|
-
import * as
|
|
23462
|
+
import * as fs8 from "fs";
|
|
23706
23463
|
|
|
23707
23464
|
// src/utils/balanceUtils.ts
|
|
23708
23465
|
function parseAmountValue(amountStr) {
|
|
@@ -23751,7 +23508,7 @@ function balancesMatch(balance1, balance2) {
|
|
|
23751
23508
|
|
|
23752
23509
|
// src/utils/csvParser.ts
|
|
23753
23510
|
function parseCsvFile(csvPath, config2) {
|
|
23754
|
-
const csvContent =
|
|
23511
|
+
const csvContent = fs8.readFileSync(csvPath, "utf-8");
|
|
23755
23512
|
const lines = csvContent.split(`
|
|
23756
23513
|
`);
|
|
23757
23514
|
const headerIndex = config2.skipRows;
|
|
@@ -23909,21 +23666,21 @@ function buildSuccessResult3(files, summary, message) {
|
|
|
23909
23666
|
});
|
|
23910
23667
|
}
|
|
23911
23668
|
function findCsvFromRulesFile(rulesFile) {
|
|
23912
|
-
const content =
|
|
23669
|
+
const content = fs9.readFileSync(rulesFile, "utf-8");
|
|
23913
23670
|
const match2 = content.match(/^source\s+([^\n#]+)/m);
|
|
23914
23671
|
if (!match2) {
|
|
23915
23672
|
return null;
|
|
23916
23673
|
}
|
|
23917
23674
|
const sourcePath = match2[1].trim();
|
|
23918
|
-
const rulesDir =
|
|
23919
|
-
const absolutePattern =
|
|
23675
|
+
const rulesDir = path9.dirname(rulesFile);
|
|
23676
|
+
const absolutePattern = path9.resolve(rulesDir, sourcePath);
|
|
23920
23677
|
const matches = glob.sync(absolutePattern);
|
|
23921
23678
|
if (matches.length === 0) {
|
|
23922
23679
|
return null;
|
|
23923
23680
|
}
|
|
23924
23681
|
matches.sort((a, b) => {
|
|
23925
|
-
const aStat =
|
|
23926
|
-
const bStat =
|
|
23682
|
+
const aStat = fs9.statSync(a);
|
|
23683
|
+
const bStat = fs9.statSync(b);
|
|
23927
23684
|
return bStat.mtime.getTime() - aStat.mtime.getTime();
|
|
23928
23685
|
});
|
|
23929
23686
|
return matches[0];
|
|
@@ -23931,7 +23688,7 @@ function findCsvFromRulesFile(rulesFile) {
|
|
|
23931
23688
|
async function executeImports(fileResults, directory, pendingDir, doneDir, hledgerExecutor) {
|
|
23932
23689
|
const importedFiles = [];
|
|
23933
23690
|
for (const fileResult of fileResults) {
|
|
23934
|
-
const rulesFile = fileResult.rulesFile ?
|
|
23691
|
+
const rulesFile = fileResult.rulesFile ? path9.join(directory, fileResult.rulesFile) : null;
|
|
23935
23692
|
if (!rulesFile)
|
|
23936
23693
|
continue;
|
|
23937
23694
|
const year = fileResult.transactionYear;
|
|
@@ -23963,7 +23720,7 @@ async function executeImports(fileResults, directory, pendingDir, doneDir, hledg
|
|
|
23963
23720
|
importedFiles.push(importedCsv);
|
|
23964
23721
|
}
|
|
23965
23722
|
}
|
|
23966
|
-
const mainJournalPath =
|
|
23723
|
+
const mainJournalPath = path9.join(directory, ".hledger.journal");
|
|
23967
23724
|
const validationResult = await validateLedger(mainJournalPath, hledgerExecutor);
|
|
23968
23725
|
if (!validationResult.valid) {
|
|
23969
23726
|
return {
|
|
@@ -23973,13 +23730,13 @@ async function executeImports(fileResults, directory, pendingDir, doneDir, hledg
|
|
|
23973
23730
|
};
|
|
23974
23731
|
}
|
|
23975
23732
|
for (const csvFile of importedFiles) {
|
|
23976
|
-
const relativePath =
|
|
23977
|
-
const destPath =
|
|
23978
|
-
const destDir =
|
|
23979
|
-
if (!
|
|
23980
|
-
|
|
23733
|
+
const relativePath = path9.relative(pendingDir, csvFile);
|
|
23734
|
+
const destPath = path9.join(doneDir, relativePath);
|
|
23735
|
+
const destDir = path9.dirname(destPath);
|
|
23736
|
+
if (!fs9.existsSync(destDir)) {
|
|
23737
|
+
fs9.mkdirSync(destDir, { recursive: true });
|
|
23981
23738
|
}
|
|
23982
|
-
|
|
23739
|
+
fs9.renameSync(csvFile, destPath);
|
|
23983
23740
|
}
|
|
23984
23741
|
return {
|
|
23985
23742
|
success: true,
|
|
@@ -23990,7 +23747,7 @@ async function processCsvFile(csvFile, rulesMapping, directory, hledgerExecutor)
|
|
|
23990
23747
|
const rulesFile = findRulesForCsv(csvFile, rulesMapping);
|
|
23991
23748
|
if (!rulesFile) {
|
|
23992
23749
|
return {
|
|
23993
|
-
csv:
|
|
23750
|
+
csv: path9.relative(directory, csvFile),
|
|
23994
23751
|
rulesFile: null,
|
|
23995
23752
|
totalTransactions: 0,
|
|
23996
23753
|
matchedTransactions: 0,
|
|
@@ -24001,8 +23758,8 @@ async function processCsvFile(csvFile, rulesMapping, directory, hledgerExecutor)
|
|
|
24001
23758
|
const result = await hledgerExecutor(["print", "-f", rulesFile]);
|
|
24002
23759
|
if (result.exitCode !== 0) {
|
|
24003
23760
|
return {
|
|
24004
|
-
csv:
|
|
24005
|
-
rulesFile:
|
|
23761
|
+
csv: path9.relative(directory, csvFile),
|
|
23762
|
+
rulesFile: path9.relative(directory, rulesFile),
|
|
24006
23763
|
totalTransactions: 0,
|
|
24007
23764
|
matchedTransactions: 0,
|
|
24008
23765
|
unknownPostings: [],
|
|
@@ -24016,8 +23773,8 @@ async function processCsvFile(csvFile, rulesMapping, directory, hledgerExecutor)
|
|
|
24016
23773
|
if (years.size > 1) {
|
|
24017
23774
|
const yearList = Array.from(years).sort().join(", ");
|
|
24018
23775
|
return {
|
|
24019
|
-
csv:
|
|
24020
|
-
rulesFile:
|
|
23776
|
+
csv: path9.relative(directory, csvFile),
|
|
23777
|
+
rulesFile: path9.relative(directory, rulesFile),
|
|
24021
23778
|
totalTransactions: transactionCount,
|
|
24022
23779
|
matchedTransactions: matchedCount,
|
|
24023
23780
|
unknownPostings: [],
|
|
@@ -24027,7 +23784,7 @@ async function processCsvFile(csvFile, rulesMapping, directory, hledgerExecutor)
|
|
|
24027
23784
|
const transactionYear = years.size === 1 ? Array.from(years)[0] : undefined;
|
|
24028
23785
|
if (unknownPostings.length > 0) {
|
|
24029
23786
|
try {
|
|
24030
|
-
const rulesContent =
|
|
23787
|
+
const rulesContent = fs9.readFileSync(rulesFile, "utf-8");
|
|
24031
23788
|
const rulesConfig = parseRulesFile(rulesContent);
|
|
24032
23789
|
const csvRows = parseCsvFile(csvFile, rulesConfig);
|
|
24033
23790
|
for (const posting of unknownPostings) {
|
|
@@ -24044,22 +23801,19 @@ async function processCsvFile(csvFile, rulesMapping, directory, hledgerExecutor)
|
|
|
24044
23801
|
}
|
|
24045
23802
|
}
|
|
24046
23803
|
return {
|
|
24047
|
-
csv:
|
|
24048
|
-
rulesFile:
|
|
23804
|
+
csv: path9.relative(directory, csvFile),
|
|
23805
|
+
rulesFile: path9.relative(directory, rulesFile),
|
|
24049
23806
|
totalTransactions: transactionCount,
|
|
24050
23807
|
matchedTransactions: matchedCount,
|
|
24051
23808
|
unknownPostings,
|
|
24052
23809
|
transactionYear
|
|
24053
23810
|
};
|
|
24054
23811
|
}
|
|
24055
|
-
async function importStatements(directory, agent, options, configLoader = loadImportConfig, hledgerExecutor = defaultHledgerExecutor
|
|
23812
|
+
async function importStatements(directory, agent, options, configLoader = loadImportConfig, hledgerExecutor = defaultHledgerExecutor) {
|
|
24056
23813
|
const restrictionError = checkAccountantAgent(agent, "import statements");
|
|
24057
23814
|
if (restrictionError) {
|
|
24058
23815
|
return restrictionError;
|
|
24059
23816
|
}
|
|
24060
|
-
if (!worktreeChecker(directory)) {
|
|
24061
|
-
return buildErrorResult3("import-statements must be run inside an import worktree", "Use import-pipeline tool to orchestrate the full workflow");
|
|
24062
|
-
}
|
|
24063
23817
|
let config2;
|
|
24064
23818
|
try {
|
|
24065
23819
|
config2 = configLoader(directory);
|
|
@@ -24067,9 +23821,9 @@ async function importStatements(directory, agent, options, configLoader = loadIm
|
|
|
24067
23821
|
const errorMessage = `Failed to load configuration: ${error45 instanceof Error ? error45.message : String(error45)}`;
|
|
24068
23822
|
return buildErrorResult3(errorMessage, 'Ensure config/import/providers.yaml exists with required paths including "rules"');
|
|
24069
23823
|
}
|
|
24070
|
-
const pendingDir =
|
|
24071
|
-
const rulesDir =
|
|
24072
|
-
const doneDir =
|
|
23824
|
+
const pendingDir = path9.join(directory, config2.paths.pending);
|
|
23825
|
+
const rulesDir = path9.join(directory, config2.paths.rules);
|
|
23826
|
+
const doneDir = path9.join(directory, config2.paths.done);
|
|
24073
23827
|
const rulesMapping = loadRulesMapping(rulesDir);
|
|
24074
23828
|
const csvFiles = findCsvFiles(pendingDir, options.provider, options.currency);
|
|
24075
23829
|
if (csvFiles.length === 0) {
|
|
@@ -24113,8 +23867,8 @@ async function importStatements(directory, agent, options, configLoader = loadIm
|
|
|
24113
23867
|
}
|
|
24114
23868
|
for (const [_rulesFile, matchingCSVs] of rulesFileToCSVs.entries()) {
|
|
24115
23869
|
matchingCSVs.sort((a, b) => {
|
|
24116
|
-
const aStat =
|
|
24117
|
-
const bStat =
|
|
23870
|
+
const aStat = fs9.statSync(a);
|
|
23871
|
+
const bStat = fs9.statSync(b);
|
|
24118
23872
|
return bStat.mtime.getTime() - aStat.mtime.getTime();
|
|
24119
23873
|
});
|
|
24120
23874
|
const newestCSV = matchingCSVs[0];
|
|
@@ -24203,7 +23957,9 @@ This tool processes CSV files in the pending import directory and uses hledger's
|
|
|
24203
23957
|
1. Run with checkOnly: true (or no args)
|
|
24204
23958
|
2. If unknowns found, add rules to the appropriate .rules file
|
|
24205
23959
|
3. Repeat until no unknowns
|
|
24206
|
-
4. Run with checkOnly: false to import
|
|
23960
|
+
4. Run with checkOnly: false to import
|
|
23961
|
+
|
|
23962
|
+
Note: This tool is typically called via import-pipeline for the full workflow.`,
|
|
24207
23963
|
args: {
|
|
24208
23964
|
provider: tool.schema.string().optional().describe('Filter by provider (e.g., "revolut", "ubs"). If omitted, process all providers.'),
|
|
24209
23965
|
currency: tool.schema.string().optional().describe('Filter by currency (e.g., "chf", "eur"). If omitted, process all currencies for the provider.'),
|
|
@@ -24219,23 +23975,14 @@ This tool processes CSV files in the pending import directory and uses hledger's
|
|
|
24219
23975
|
}
|
|
24220
23976
|
});
|
|
24221
23977
|
// src/tools/reconcile-statement.ts
|
|
24222
|
-
import * as
|
|
24223
|
-
import * as
|
|
23978
|
+
import * as fs10 from "fs";
|
|
23979
|
+
import * as path10 from "path";
|
|
24224
23980
|
function buildErrorResult4(params) {
|
|
24225
23981
|
return JSON.stringify({
|
|
24226
23982
|
success: false,
|
|
24227
23983
|
...params
|
|
24228
23984
|
});
|
|
24229
23985
|
}
|
|
24230
|
-
function validateWorktree(directory, worktreeChecker) {
|
|
24231
|
-
if (!worktreeChecker(directory)) {
|
|
24232
|
-
return buildErrorResult4({
|
|
24233
|
-
error: "reconcile-statement must be run inside an import worktree",
|
|
24234
|
-
hint: "Use import-pipeline tool to orchestrate the full workflow"
|
|
24235
|
-
});
|
|
24236
|
-
}
|
|
24237
|
-
return null;
|
|
24238
|
-
}
|
|
24239
23986
|
function loadConfiguration(directory, configLoader) {
|
|
24240
23987
|
try {
|
|
24241
23988
|
const config2 = configLoader(directory);
|
|
@@ -24262,14 +24009,14 @@ function findCsvToReconcile(doneDir, options) {
|
|
|
24262
24009
|
};
|
|
24263
24010
|
}
|
|
24264
24011
|
const csvFile = csvFiles[csvFiles.length - 1];
|
|
24265
|
-
const relativePath =
|
|
24012
|
+
const relativePath = path10.relative(path10.dirname(path10.dirname(doneDir)), csvFile);
|
|
24266
24013
|
return { csvFile, relativePath };
|
|
24267
24014
|
}
|
|
24268
24015
|
function determineClosingBalance(csvFile, config2, options, relativeCsvPath, rulesDir) {
|
|
24269
24016
|
let metadata;
|
|
24270
24017
|
try {
|
|
24271
|
-
const content =
|
|
24272
|
-
const filename =
|
|
24018
|
+
const content = fs10.readFileSync(csvFile, "utf-8");
|
|
24019
|
+
const filename = path10.basename(csvFile);
|
|
24273
24020
|
const detectionResult = detectProvider(filename, content, config2);
|
|
24274
24021
|
metadata = detectionResult?.metadata;
|
|
24275
24022
|
} catch {
|
|
@@ -24351,7 +24098,7 @@ function tryExtractClosingBalanceFromCSV(csvFile, rulesDir) {
|
|
|
24351
24098
|
if (!rulesFile) {
|
|
24352
24099
|
return null;
|
|
24353
24100
|
}
|
|
24354
|
-
const rulesContent =
|
|
24101
|
+
const rulesContent = fs10.readFileSync(rulesFile, "utf-8");
|
|
24355
24102
|
const rulesConfig = parseRulesFile(rulesContent);
|
|
24356
24103
|
const csvRows = parseCsvFile(csvFile, rulesConfig);
|
|
24357
24104
|
if (csvRows.length === 0) {
|
|
@@ -24408,23 +24155,19 @@ function tryExtractClosingBalanceFromCSV(csvFile, rulesDir) {
|
|
|
24408
24155
|
return null;
|
|
24409
24156
|
}
|
|
24410
24157
|
}
|
|
24411
|
-
async function reconcileStatement(directory, agent, options, configLoader = loadImportConfig, hledgerExecutor = defaultHledgerExecutor
|
|
24158
|
+
async function reconcileStatement(directory, agent, options, configLoader = loadImportConfig, hledgerExecutor = defaultHledgerExecutor) {
|
|
24412
24159
|
const restrictionError = checkAccountantAgent(agent, "reconcile statement");
|
|
24413
24160
|
if (restrictionError) {
|
|
24414
24161
|
return restrictionError;
|
|
24415
24162
|
}
|
|
24416
|
-
const worktreeError = validateWorktree(directory, worktreeChecker);
|
|
24417
|
-
if (worktreeError) {
|
|
24418
|
-
return worktreeError;
|
|
24419
|
-
}
|
|
24420
24163
|
const configResult = loadConfiguration(directory, configLoader);
|
|
24421
24164
|
if ("error" in configResult) {
|
|
24422
24165
|
return configResult.error;
|
|
24423
24166
|
}
|
|
24424
24167
|
const { config: config2 } = configResult;
|
|
24425
|
-
const doneDir =
|
|
24426
|
-
const rulesDir =
|
|
24427
|
-
const mainJournalPath =
|
|
24168
|
+
const doneDir = path10.join(directory, config2.paths.done);
|
|
24169
|
+
const rulesDir = path10.join(directory, config2.paths.rules);
|
|
24170
|
+
const mainJournalPath = path10.join(directory, ".hledger.journal");
|
|
24428
24171
|
const csvResult = findCsvToReconcile(doneDir, options);
|
|
24429
24172
|
if ("error" in csvResult) {
|
|
24430
24173
|
return csvResult.error;
|
|
@@ -24520,7 +24263,6 @@ var reconcile_statement_default = tool({
|
|
|
24520
24263
|
description: `ACCOUNTANT AGENT ONLY: Reconcile imported bank statement against closing balance.
|
|
24521
24264
|
|
|
24522
24265
|
This tool validates that the imported transactions result in the correct closing balance.
|
|
24523
|
-
It must be run inside an import worktree (use import-pipeline for the full workflow).
|
|
24524
24266
|
|
|
24525
24267
|
**Workflow:**
|
|
24526
24268
|
1. Finds the most recently imported CSV in the done directory
|
|
@@ -24553,17 +24295,16 @@ It must be run inside an import worktree (use import-pipeline for the full workf
|
|
|
24553
24295
|
}
|
|
24554
24296
|
});
|
|
24555
24297
|
// src/tools/import-pipeline.ts
|
|
24556
|
-
import * as
|
|
24557
|
-
import * as path13 from "path";
|
|
24298
|
+
import * as path12 from "path";
|
|
24558
24299
|
|
|
24559
24300
|
// src/utils/accountDeclarations.ts
|
|
24560
|
-
import * as
|
|
24301
|
+
import * as fs11 from "fs";
|
|
24561
24302
|
function extractAccountsFromRulesFile(rulesPath) {
|
|
24562
24303
|
const accounts = new Set;
|
|
24563
|
-
if (!
|
|
24304
|
+
if (!fs11.existsSync(rulesPath)) {
|
|
24564
24305
|
return accounts;
|
|
24565
24306
|
}
|
|
24566
|
-
const content =
|
|
24307
|
+
const content = fs11.readFileSync(rulesPath, "utf-8");
|
|
24567
24308
|
const lines = content.split(`
|
|
24568
24309
|
`);
|
|
24569
24310
|
for (const line of lines) {
|
|
@@ -24598,10 +24339,10 @@ function sortAccountDeclarations(accounts) {
|
|
|
24598
24339
|
return Array.from(accounts).sort((a, b) => a.localeCompare(b));
|
|
24599
24340
|
}
|
|
24600
24341
|
function ensureAccountDeclarations(yearJournalPath, accounts) {
|
|
24601
|
-
if (!
|
|
24342
|
+
if (!fs11.existsSync(yearJournalPath)) {
|
|
24602
24343
|
throw new Error(`Year journal not found: ${yearJournalPath}`);
|
|
24603
24344
|
}
|
|
24604
|
-
const content =
|
|
24345
|
+
const content = fs11.readFileSync(yearJournalPath, "utf-8");
|
|
24605
24346
|
const lines = content.split(`
|
|
24606
24347
|
`);
|
|
24607
24348
|
const existingAccounts = new Set;
|
|
@@ -24663,7 +24404,7 @@ function ensureAccountDeclarations(yearJournalPath, accounts) {
|
|
|
24663
24404
|
newContent.push("");
|
|
24664
24405
|
}
|
|
24665
24406
|
newContent.push(...otherLines);
|
|
24666
|
-
|
|
24407
|
+
fs11.writeFileSync(yearJournalPath, newContent.join(`
|
|
24667
24408
|
`));
|
|
24668
24409
|
return {
|
|
24669
24410
|
added: Array.from(missingAccounts).sort(),
|
|
@@ -24672,8 +24413,8 @@ function ensureAccountDeclarations(yearJournalPath, accounts) {
|
|
|
24672
24413
|
}
|
|
24673
24414
|
|
|
24674
24415
|
// src/utils/logger.ts
|
|
24675
|
-
import
|
|
24676
|
-
import
|
|
24416
|
+
import fs12 from "fs/promises";
|
|
24417
|
+
import path11 from "path";
|
|
24677
24418
|
|
|
24678
24419
|
class MarkdownLogger {
|
|
24679
24420
|
buffer = [];
|
|
@@ -24685,7 +24426,7 @@ class MarkdownLogger {
|
|
|
24685
24426
|
this.autoFlush = config2.autoFlush ?? true;
|
|
24686
24427
|
this.context = config2.context || {};
|
|
24687
24428
|
const filename = config2.filename || `import-${this.getTimestamp()}.md`;
|
|
24688
|
-
this.logPath =
|
|
24429
|
+
this.logPath = path11.join(config2.logDir, filename);
|
|
24689
24430
|
this.buffer.push(`# Import Pipeline Log`);
|
|
24690
24431
|
this.buffer.push(`**Started**: ${new Date().toLocaleString()}`);
|
|
24691
24432
|
this.buffer.push("");
|
|
@@ -24782,8 +24523,8 @@ class MarkdownLogger {
|
|
|
24782
24523
|
if (this.buffer.length === 0)
|
|
24783
24524
|
return;
|
|
24784
24525
|
try {
|
|
24785
|
-
await
|
|
24786
|
-
await
|
|
24526
|
+
await fs12.mkdir(path11.dirname(this.logPath), { recursive: true });
|
|
24527
|
+
await fs12.writeFile(this.logPath, this.buffer.join(`
|
|
24787
24528
|
`), "utf-8");
|
|
24788
24529
|
} catch {}
|
|
24789
24530
|
}
|
|
@@ -24810,7 +24551,7 @@ function createImportLogger(directory, worktreeId, provider) {
|
|
|
24810
24551
|
if (provider)
|
|
24811
24552
|
context.provider = provider;
|
|
24812
24553
|
const logger = createLogger({
|
|
24813
|
-
logDir:
|
|
24554
|
+
logDir: path11.join(directory, ".memory"),
|
|
24814
24555
|
autoFlush: true,
|
|
24815
24556
|
context
|
|
24816
24557
|
});
|
|
@@ -24850,51 +24591,8 @@ function buildErrorResult5(result, error45, hint) {
|
|
|
24850
24591
|
}
|
|
24851
24592
|
return JSON.stringify(result);
|
|
24852
24593
|
}
|
|
24853
|
-
function
|
|
24854
|
-
|
|
24855
|
-
const currencyStr = currency?.toUpperCase();
|
|
24856
|
-
const dateRange = fromDate && untilDate ? ` ${fromDate} to ${untilDate}` : "";
|
|
24857
|
-
const txStr = transactionCount > 0 ? ` (${transactionCount} transactions)` : "";
|
|
24858
|
-
const parts = ["Import:", providerStr];
|
|
24859
|
-
if (currencyStr) {
|
|
24860
|
-
parts.push(currencyStr);
|
|
24861
|
-
}
|
|
24862
|
-
return `${parts.join(" ")}${dateRange}${txStr}`;
|
|
24863
|
-
}
|
|
24864
|
-
function cleanupIncomingFiles(worktree, context) {
|
|
24865
|
-
const incomingDir = path13.join(worktree.mainRepoPath, "import/incoming");
|
|
24866
|
-
if (!fs14.existsSync(incomingDir)) {
|
|
24867
|
-
return;
|
|
24868
|
-
}
|
|
24869
|
-
const importStep = context.result.steps.import;
|
|
24870
|
-
if (!importStep?.success || !importStep.details) {
|
|
24871
|
-
return;
|
|
24872
|
-
}
|
|
24873
|
-
const importResult = importStep.details;
|
|
24874
|
-
if (!importResult.files || !Array.isArray(importResult.files)) {
|
|
24875
|
-
return;
|
|
24876
|
-
}
|
|
24877
|
-
let deletedCount = 0;
|
|
24878
|
-
for (const fileResult of importResult.files) {
|
|
24879
|
-
if (!fileResult.csv)
|
|
24880
|
-
continue;
|
|
24881
|
-
const filename = path13.basename(fileResult.csv);
|
|
24882
|
-
const filePath = path13.join(incomingDir, filename);
|
|
24883
|
-
if (fs14.existsSync(filePath)) {
|
|
24884
|
-
try {
|
|
24885
|
-
fs14.unlinkSync(filePath);
|
|
24886
|
-
deletedCount++;
|
|
24887
|
-
} catch (error45) {
|
|
24888
|
-
console.error(`[ERROR] Failed to delete ${filename}: ${error45 instanceof Error ? error45.message : String(error45)}`);
|
|
24889
|
-
}
|
|
24890
|
-
}
|
|
24891
|
-
}
|
|
24892
|
-
if (deletedCount > 0) {
|
|
24893
|
-
console.log(`[INFO] Cleaned up ${deletedCount} file(s) from import/incoming/`);
|
|
24894
|
-
}
|
|
24895
|
-
}
|
|
24896
|
-
async function executeClassifyStep(context, worktree, logger) {
|
|
24897
|
-
logger?.startSection("Step 2: Classify Transactions");
|
|
24594
|
+
async function executeClassifyStep(context, logger) {
|
|
24595
|
+
logger?.startSection("Step 1: Classify Transactions");
|
|
24898
24596
|
logger?.logStep("Classify", "start");
|
|
24899
24597
|
if (context.options.skipClassify) {
|
|
24900
24598
|
logger?.info("Classification skipped (skipClassify: true)");
|
|
@@ -24902,8 +24600,7 @@ async function executeClassifyStep(context, worktree, logger) {
|
|
|
24902
24600
|
logger?.endSection();
|
|
24903
24601
|
return;
|
|
24904
24602
|
}
|
|
24905
|
-
const
|
|
24906
|
-
const classifyResult = await classifyStatements(worktree.path, context.agent, context.configLoader, inWorktree);
|
|
24603
|
+
const classifyResult = await classifyStatements(context.directory, context.agent, context.configLoader);
|
|
24907
24604
|
const classifyParsed = JSON.parse(classifyResult);
|
|
24908
24605
|
const success2 = classifyParsed.success !== false;
|
|
24909
24606
|
let message = success2 ? "Classification complete" : "Classification had issues";
|
|
@@ -24920,12 +24617,12 @@ async function executeClassifyStep(context, worktree, logger) {
|
|
|
24920
24617
|
context.result.steps.classify = buildStepResult(success2, message, details);
|
|
24921
24618
|
logger?.endSection();
|
|
24922
24619
|
}
|
|
24923
|
-
async function executeAccountDeclarationsStep(context,
|
|
24924
|
-
logger?.startSection("Step
|
|
24620
|
+
async function executeAccountDeclarationsStep(context, logger) {
|
|
24621
|
+
logger?.startSection("Step 2: Check Account Declarations");
|
|
24925
24622
|
logger?.logStep("Check Accounts", "start");
|
|
24926
|
-
const config2 = context.configLoader(
|
|
24927
|
-
const pendingDir =
|
|
24928
|
-
const rulesDir =
|
|
24623
|
+
const config2 = context.configLoader(context.directory);
|
|
24624
|
+
const pendingDir = path12.join(context.directory, config2.paths.pending);
|
|
24625
|
+
const rulesDir = path12.join(context.directory, config2.paths.rules);
|
|
24929
24626
|
const csvFiles = findCsvFiles(pendingDir, context.options.provider, context.options.currency);
|
|
24930
24627
|
if (csvFiles.length === 0) {
|
|
24931
24628
|
context.result.steps.accountDeclarations = buildStepResult(true, "No CSV files to process", {
|
|
@@ -24956,7 +24653,7 @@ async function executeAccountDeclarationsStep(context, worktree, logger) {
|
|
|
24956
24653
|
context.result.steps.accountDeclarations = buildStepResult(true, "No accounts found in rules files", {
|
|
24957
24654
|
accountsAdded: [],
|
|
24958
24655
|
journalUpdated: "",
|
|
24959
|
-
rulesScanned: Array.from(matchedRulesFiles).map((f) =>
|
|
24656
|
+
rulesScanned: Array.from(matchedRulesFiles).map((f) => path12.relative(context.directory, f))
|
|
24960
24657
|
});
|
|
24961
24658
|
return;
|
|
24962
24659
|
}
|
|
@@ -24979,23 +24676,23 @@ async function executeAccountDeclarationsStep(context, worktree, logger) {
|
|
|
24979
24676
|
context.result.steps.accountDeclarations = buildStepResult(false, "Could not determine transaction year from CSV files", {
|
|
24980
24677
|
accountsAdded: [],
|
|
24981
24678
|
journalUpdated: "",
|
|
24982
|
-
rulesScanned: Array.from(matchedRulesFiles).map((f) =>
|
|
24679
|
+
rulesScanned: Array.from(matchedRulesFiles).map((f) => path12.relative(context.directory, f))
|
|
24983
24680
|
});
|
|
24984
24681
|
return;
|
|
24985
24682
|
}
|
|
24986
24683
|
let yearJournalPath;
|
|
24987
24684
|
try {
|
|
24988
|
-
yearJournalPath = ensureYearJournalExists(
|
|
24685
|
+
yearJournalPath = ensureYearJournalExists(context.directory, transactionYear);
|
|
24989
24686
|
} catch (error45) {
|
|
24990
24687
|
context.result.steps.accountDeclarations = buildStepResult(false, `Failed to create year journal: ${error45 instanceof Error ? error45.message : String(error45)}`, {
|
|
24991
24688
|
accountsAdded: [],
|
|
24992
24689
|
journalUpdated: "",
|
|
24993
|
-
rulesScanned: Array.from(matchedRulesFiles).map((f) =>
|
|
24690
|
+
rulesScanned: Array.from(matchedRulesFiles).map((f) => path12.relative(context.directory, f))
|
|
24994
24691
|
});
|
|
24995
24692
|
return;
|
|
24996
24693
|
}
|
|
24997
24694
|
const result = ensureAccountDeclarations(yearJournalPath, allAccounts);
|
|
24998
|
-
const message = result.added.length > 0 ? `Added ${result.added.length} account declaration(s) to ${
|
|
24695
|
+
const message = result.added.length > 0 ? `Added ${result.added.length} account declaration(s) to ${path12.relative(context.directory, yearJournalPath)}` : "All required accounts already declared";
|
|
24999
24696
|
logger?.logStep("Check Accounts", "success", message);
|
|
25000
24697
|
if (result.added.length > 0) {
|
|
25001
24698
|
for (const account of result.added) {
|
|
@@ -25004,20 +24701,19 @@ async function executeAccountDeclarationsStep(context, worktree, logger) {
|
|
|
25004
24701
|
}
|
|
25005
24702
|
context.result.steps.accountDeclarations = buildStepResult(true, message, {
|
|
25006
24703
|
accountsAdded: result.added,
|
|
25007
|
-
journalUpdated:
|
|
25008
|
-
rulesScanned: Array.from(matchedRulesFiles).map((f) =>
|
|
24704
|
+
journalUpdated: path12.relative(context.directory, yearJournalPath),
|
|
24705
|
+
rulesScanned: Array.from(matchedRulesFiles).map((f) => path12.relative(context.directory, f))
|
|
25009
24706
|
});
|
|
25010
24707
|
logger?.endSection();
|
|
25011
24708
|
}
|
|
25012
|
-
async function executeDryRunStep(context,
|
|
25013
|
-
logger?.startSection("Step
|
|
24709
|
+
async function executeDryRunStep(context, logger) {
|
|
24710
|
+
logger?.startSection("Step 3: Dry Run Import");
|
|
25014
24711
|
logger?.logStep("Dry Run", "start");
|
|
25015
|
-
const
|
|
25016
|
-
const dryRunResult = await importStatements(worktree.path, context.agent, {
|
|
24712
|
+
const dryRunResult = await importStatements(context.directory, context.agent, {
|
|
25017
24713
|
provider: context.options.provider,
|
|
25018
24714
|
currency: context.options.currency,
|
|
25019
24715
|
checkOnly: true
|
|
25020
|
-
}, context.configLoader, context.hledgerExecutor
|
|
24716
|
+
}, context.configLoader, context.hledgerExecutor);
|
|
25021
24717
|
const dryRunParsed = JSON.parse(dryRunResult);
|
|
25022
24718
|
const message = dryRunParsed.success ? `Dry run passed: ${dryRunParsed.summary?.totalTransactions || 0} transactions ready` : `Dry run failed: ${dryRunParsed.summary?.unknown || 0} unknown account(s)`;
|
|
25023
24719
|
logger?.logStep("Dry Run", dryRunParsed.success ? "success" : "error", message);
|
|
@@ -25041,15 +24737,14 @@ async function executeDryRunStep(context, worktree, logger) {
|
|
|
25041
24737
|
}
|
|
25042
24738
|
logger?.endSection();
|
|
25043
24739
|
}
|
|
25044
|
-
async function executeImportStep(context,
|
|
25045
|
-
logger?.startSection("Step
|
|
24740
|
+
async function executeImportStep(context, logger) {
|
|
24741
|
+
logger?.startSection("Step 4: Import Transactions");
|
|
25046
24742
|
logger?.logStep("Import", "start");
|
|
25047
|
-
const
|
|
25048
|
-
const importResult = await importStatements(worktree.path, context.agent, {
|
|
24743
|
+
const importResult = await importStatements(context.directory, context.agent, {
|
|
25049
24744
|
provider: context.options.provider,
|
|
25050
24745
|
currency: context.options.currency,
|
|
25051
24746
|
checkOnly: false
|
|
25052
|
-
}, context.configLoader, context.hledgerExecutor
|
|
24747
|
+
}, context.configLoader, context.hledgerExecutor);
|
|
25053
24748
|
const importParsed = JSON.parse(importResult);
|
|
25054
24749
|
const message = importParsed.success ? `Imported ${importParsed.summary?.totalTransactions || 0} transactions` : `Import failed: ${importParsed.error || "Unknown error"}`;
|
|
25055
24750
|
logger?.logStep("Import", importParsed.success ? "success" : "error", message);
|
|
@@ -25066,16 +24761,15 @@ async function executeImportStep(context, worktree, logger) {
|
|
|
25066
24761
|
}
|
|
25067
24762
|
logger?.endSection();
|
|
25068
24763
|
}
|
|
25069
|
-
async function executeReconcileStep(context,
|
|
25070
|
-
logger?.startSection("Step
|
|
24764
|
+
async function executeReconcileStep(context, logger) {
|
|
24765
|
+
logger?.startSection("Step 5: Reconcile Balance");
|
|
25071
24766
|
logger?.logStep("Reconcile", "start");
|
|
25072
|
-
const
|
|
25073
|
-
const reconcileResult = await reconcileStatement(worktree.path, context.agent, {
|
|
24767
|
+
const reconcileResult = await reconcileStatement(context.directory, context.agent, {
|
|
25074
24768
|
provider: context.options.provider,
|
|
25075
24769
|
currency: context.options.currency,
|
|
25076
24770
|
closingBalance: context.options.closingBalance,
|
|
25077
24771
|
account: context.options.account
|
|
25078
|
-
}, context.configLoader, context.hledgerExecutor
|
|
24772
|
+
}, context.configLoader, context.hledgerExecutor);
|
|
25079
24773
|
const reconcileParsed = JSON.parse(reconcileResult);
|
|
25080
24774
|
const message = reconcileParsed.success ? `Balance reconciled: ${reconcileParsed.actualBalance}` : `Balance mismatch: expected ${reconcileParsed.expectedBalance}, got ${reconcileParsed.actualBalance}`;
|
|
25081
24775
|
logger?.logStep("Reconcile", reconcileParsed.success ? "success" : "error", message);
|
|
@@ -25099,42 +24793,9 @@ async function executeReconcileStep(context, worktree, logger) {
|
|
|
25099
24793
|
}
|
|
25100
24794
|
logger?.endSection();
|
|
25101
24795
|
}
|
|
25102
|
-
async function executeMergeStep(context, worktree, logger) {
|
|
25103
|
-
logger?.startSection("Step 7: Merge to Main");
|
|
25104
|
-
logger?.logStep("Merge", "start");
|
|
25105
|
-
const importDetails = context.result.steps.import?.details;
|
|
25106
|
-
const reconcileDetails = context.result.steps.reconcile?.details;
|
|
25107
|
-
if (!importDetails || !reconcileDetails) {
|
|
25108
|
-
throw new Error("Import or reconcile step not completed before merge");
|
|
25109
|
-
}
|
|
25110
|
-
const commitInfo = {
|
|
25111
|
-
fromDate: reconcileDetails.metadata?.["from-date"],
|
|
25112
|
-
untilDate: reconcileDetails.metadata?.["until-date"]
|
|
25113
|
-
};
|
|
25114
|
-
const transactionCount = importDetails.summary?.totalTransactions || 0;
|
|
25115
|
-
const commitMessage = buildCommitMessage(context.options.provider, context.options.currency, commitInfo.fromDate, commitInfo.untilDate, transactionCount);
|
|
25116
|
-
try {
|
|
25117
|
-
logger?.info(`Commit message: "${commitMessage}"`);
|
|
25118
|
-
mergeWorktree(worktree, commitMessage);
|
|
25119
|
-
logger?.logStep("Merge", "success", "Merged to main branch");
|
|
25120
|
-
const mergeDetails = { commitMessage };
|
|
25121
|
-
context.result.steps.merge = buildStepResult(true, `Merged to main: "${commitMessage}"`, mergeDetails);
|
|
25122
|
-
cleanupIncomingFiles(worktree, context);
|
|
25123
|
-
logger?.endSection();
|
|
25124
|
-
} catch (error45) {
|
|
25125
|
-
logger?.logStep("Merge", "error");
|
|
25126
|
-
logger?.error("Merge to main branch failed", error45);
|
|
25127
|
-
logger?.endSection();
|
|
25128
|
-
const message = `Merge failed: ${error45 instanceof Error ? error45.message : String(error45)}`;
|
|
25129
|
-
context.result.steps.merge = buildStepResult(false, message);
|
|
25130
|
-
context.result.error = "Merge to main branch failed";
|
|
25131
|
-
throw new Error("Merge failed");
|
|
25132
|
-
}
|
|
25133
|
-
}
|
|
25134
24796
|
function handleNoTransactions(result) {
|
|
25135
24797
|
result.steps.import = buildStepResult(true, "No transactions to import");
|
|
25136
24798
|
result.steps.reconcile = buildStepResult(true, "Reconciliation skipped (no transactions)");
|
|
25137
|
-
result.steps.merge = buildStepResult(true, "Merge skipped (no changes)");
|
|
25138
24799
|
return buildSuccessResult4(result, "No transactions found to import");
|
|
25139
24800
|
}
|
|
25140
24801
|
async function importPipeline(directory, agent, options, configLoader = loadImportConfig, hledgerExecutor = defaultHledgerExecutor) {
|
|
@@ -25147,7 +24808,6 @@ async function importPipeline(directory, agent, options, configLoader = loadImpo
|
|
|
25147
24808
|
logger.info(`Provider filter: ${options.provider || "all"}`);
|
|
25148
24809
|
logger.info(`Currency filter: ${options.currency || "all"}`);
|
|
25149
24810
|
logger.info(`Skip classify: ${options.skipClassify || false}`);
|
|
25150
|
-
logger.info(`Keep worktree on error: ${options.keepWorktreeOnError ?? true}`);
|
|
25151
24811
|
logger.info("");
|
|
25152
24812
|
const result = {
|
|
25153
24813
|
success: false,
|
|
@@ -25162,176 +24822,68 @@ async function importPipeline(directory, agent, options, configLoader = loadImpo
|
|
|
25162
24822
|
result
|
|
25163
24823
|
};
|
|
25164
24824
|
try {
|
|
25165
|
-
|
|
25166
|
-
|
|
25167
|
-
|
|
25168
|
-
|
|
25169
|
-
|
|
25170
|
-
|
|
25171
|
-
|
|
25172
|
-
|
|
25173
|
-
|
|
25174
|
-
|
|
25175
|
-
|
|
25176
|
-
|
|
25177
|
-
|
|
25178
|
-
|
|
25179
|
-
|
|
25180
|
-
result.steps.sync = buildStepResult(true, "No CSV files to sync", {
|
|
25181
|
-
synced: []
|
|
25182
|
-
});
|
|
25183
|
-
} else if (syncResult.errors.length > 0) {
|
|
25184
|
-
logger.warn(`Synced ${syncResult.synced.length} file(s) with ${syncResult.errors.length} error(s)`);
|
|
25185
|
-
result.steps.sync = buildStepResult(true, `Synced ${syncResult.synced.length} file(s) with ${syncResult.errors.length} error(s)`, { synced: syncResult.synced, errors: syncResult.errors });
|
|
25186
|
-
} else {
|
|
25187
|
-
logger.logStep("Sync Files", "success", `Synced ${syncResult.synced.length} CSV file(s)`);
|
|
25188
|
-
for (const file2 of syncResult.synced) {
|
|
25189
|
-
logger.info(` - ${file2}`);
|
|
25190
|
-
}
|
|
25191
|
-
result.steps.sync = buildStepResult(true, `Synced ${syncResult.synced.length} CSV file(s) to worktree`, { synced: syncResult.synced });
|
|
25192
|
-
}
|
|
25193
|
-
logger.endSection();
|
|
25194
|
-
} catch (error45) {
|
|
25195
|
-
logger.logStep("Sync Files", "error");
|
|
25196
|
-
logger.error("Failed to sync CSV files", error45);
|
|
25197
|
-
logger.endSection();
|
|
25198
|
-
const errorMsg = error45 instanceof Error ? error45.message : String(error45);
|
|
25199
|
-
result.steps.sync = buildStepResult(false, `Failed to sync CSV files: ${errorMsg}`, { synced: [], errors: [{ file: "unknown", error: errorMsg }] });
|
|
25200
|
-
}
|
|
25201
|
-
try {
|
|
25202
|
-
await executeClassifyStep(context, worktree, logger);
|
|
25203
|
-
await executeAccountDeclarationsStep(context, worktree, logger);
|
|
25204
|
-
await executeDryRunStep(context, worktree, logger);
|
|
25205
|
-
await executeImportStep(context, worktree, logger);
|
|
25206
|
-
await executeReconcileStep(context, worktree, logger);
|
|
25207
|
-
try {
|
|
25208
|
-
const config2 = configLoader(directory);
|
|
25209
|
-
const cleanupResult = cleanupProcessedCSVFiles(directory, config2.paths.import);
|
|
25210
|
-
if (cleanupResult.deleted.length === 0 && cleanupResult.errors.length === 0) {
|
|
25211
|
-
result.steps.cleanup = buildStepResult(true, "No CSV files to cleanup", { csvCleanup: { deleted: [] } });
|
|
25212
|
-
} else if (cleanupResult.errors.length > 0) {
|
|
25213
|
-
result.steps.cleanup = buildStepResult(true, `Deleted ${cleanupResult.deleted.length} CSV file(s) with ${cleanupResult.errors.length} error(s)`, {
|
|
25214
|
-
csvCleanup: {
|
|
25215
|
-
deleted: cleanupResult.deleted,
|
|
25216
|
-
errors: cleanupResult.errors
|
|
25217
|
-
}
|
|
25218
|
-
});
|
|
25219
|
-
} else {
|
|
25220
|
-
result.steps.cleanup = buildStepResult(true, `Deleted ${cleanupResult.deleted.length} CSV file(s) from main repo`, { csvCleanup: { deleted: cleanupResult.deleted } });
|
|
25221
|
-
}
|
|
25222
|
-
} catch (error45) {
|
|
25223
|
-
const errorMsg = error45 instanceof Error ? error45.message : String(error45);
|
|
25224
|
-
result.steps.cleanup = buildStepResult(false, `Failed to cleanup CSV files: ${errorMsg}`, {
|
|
25225
|
-
csvCleanup: {
|
|
25226
|
-
deleted: [],
|
|
25227
|
-
errors: [{ file: "unknown", error: errorMsg }]
|
|
25228
|
-
}
|
|
25229
|
-
});
|
|
25230
|
-
}
|
|
25231
|
-
await executeMergeStep(context, worktree, logger);
|
|
25232
|
-
const existingCleanup = result.steps.cleanup;
|
|
25233
|
-
if (existingCleanup) {
|
|
25234
|
-
existingCleanup.message += ", worktree cleaned up";
|
|
25235
|
-
existingCleanup.details = {
|
|
25236
|
-
...existingCleanup.details,
|
|
25237
|
-
cleanedAfterSuccess: true
|
|
25238
|
-
};
|
|
25239
|
-
}
|
|
25240
|
-
const transactionCount = context.result.steps.import?.details?.summary?.totalTransactions || 0;
|
|
25241
|
-
logger.startSection("Summary");
|
|
25242
|
-
logger.info(`\u2705 Import completed successfully`);
|
|
25243
|
-
logger.info(`Total transactions imported: ${transactionCount}`);
|
|
25244
|
-
if (context.result.steps.reconcile?.details?.actualBalance) {
|
|
25245
|
-
logger.info(`Balance reconciliation: \u2705 Matched (${context.result.steps.reconcile.details.actualBalance})`);
|
|
25246
|
-
}
|
|
25247
|
-
logger.info(`Log file: ${logger.getLogPath()}`);
|
|
25248
|
-
logger.endSection();
|
|
25249
|
-
return buildSuccessResult4(result, `Successfully imported ${transactionCount} transaction(s)`);
|
|
25250
|
-
} catch (error45) {
|
|
25251
|
-
const worktreePath = context.result.steps.worktree?.details?.path;
|
|
25252
|
-
const keepWorktree = options.keepWorktreeOnError ?? true;
|
|
25253
|
-
logger.error("Pipeline step failed", error45);
|
|
25254
|
-
if (keepWorktree && worktreePath) {
|
|
25255
|
-
logger.warn(`Worktree preserved at: ${worktreePath}`);
|
|
25256
|
-
logger.info(`To continue manually: cd ${worktreePath}`);
|
|
25257
|
-
logger.info(`To clean up: git worktree remove ${worktreePath}`);
|
|
25258
|
-
}
|
|
25259
|
-
logger.info(`Log file: ${logger.getLogPath()}`);
|
|
25260
|
-
result.steps.cleanup = buildStepResult(true, keepWorktree ? `Worktree preserved for debugging (CSV files preserved for retry)` : "Worktree cleaned up after failure (CSV files preserved for retry)", {
|
|
25261
|
-
cleanedAfterFailure: !keepWorktree,
|
|
25262
|
-
worktreePreserved: keepWorktree,
|
|
25263
|
-
worktreePath,
|
|
25264
|
-
preserveReason: keepWorktree ? "error occurred" : undefined,
|
|
25265
|
-
csvCleanup: { deleted: [] }
|
|
25266
|
-
});
|
|
25267
|
-
if (error45 instanceof NoTransactionsError) {
|
|
25268
|
-
return handleNoTransactions(result);
|
|
25269
|
-
}
|
|
25270
|
-
if (!result.error) {
|
|
25271
|
-
result.error = error45 instanceof Error ? error45.message : String(error45);
|
|
25272
|
-
}
|
|
25273
|
-
return buildErrorResult5(result, result.error, result.hint);
|
|
25274
|
-
}
|
|
25275
|
-
}, {
|
|
25276
|
-
keepOnError: options.keepWorktreeOnError ?? true,
|
|
25277
|
-
logger
|
|
25278
|
-
});
|
|
24825
|
+
await executeClassifyStep(context, logger);
|
|
24826
|
+
await executeAccountDeclarationsStep(context, logger);
|
|
24827
|
+
await executeDryRunStep(context, logger);
|
|
24828
|
+
await executeImportStep(context, logger);
|
|
24829
|
+
await executeReconcileStep(context, logger);
|
|
24830
|
+
const transactionCount = context.result.steps.import?.details?.summary?.totalTransactions || 0;
|
|
24831
|
+
logger.startSection("Summary");
|
|
24832
|
+
logger.info(`Import completed successfully`);
|
|
24833
|
+
logger.info(`Total transactions imported: ${transactionCount}`);
|
|
24834
|
+
if (context.result.steps.reconcile?.details?.actualBalance) {
|
|
24835
|
+
logger.info(`Balance: ${context.result.steps.reconcile.details.actualBalance}`);
|
|
24836
|
+
}
|
|
24837
|
+
logger.info(`Log file: ${logger.getLogPath()}`);
|
|
24838
|
+
logger.endSection();
|
|
24839
|
+
return buildSuccessResult4(result, `Successfully imported ${transactionCount} transaction(s)`);
|
|
25279
24840
|
} catch (error45) {
|
|
25280
|
-
logger.error("Pipeline failed", error45);
|
|
25281
|
-
|
|
25282
|
-
|
|
25283
|
-
|
|
24841
|
+
logger.error("Pipeline step failed", error45);
|
|
24842
|
+
logger.info(`Log file: ${logger.getLogPath()}`);
|
|
24843
|
+
if (error45 instanceof NoTransactionsError) {
|
|
24844
|
+
return handleNoTransactions(result);
|
|
24845
|
+
}
|
|
24846
|
+
if (!result.error) {
|
|
24847
|
+
result.error = error45 instanceof Error ? error45.message : String(error45);
|
|
24848
|
+
}
|
|
24849
|
+
return buildErrorResult5(result, result.error, result.hint);
|
|
25284
24850
|
} finally {
|
|
25285
24851
|
logger.endSection();
|
|
25286
24852
|
await logger.flush();
|
|
25287
24853
|
}
|
|
25288
24854
|
}
|
|
25289
24855
|
var import_pipeline_default = tool({
|
|
25290
|
-
description: `ACCOUNTANT AGENT ONLY: Complete import pipeline with
|
|
24856
|
+
description: `ACCOUNTANT AGENT ONLY: Complete import pipeline with balance reconciliation.
|
|
25291
24857
|
|
|
25292
|
-
This tool orchestrates the full import workflow
|
|
24858
|
+
This tool orchestrates the full import workflow:
|
|
25293
24859
|
|
|
25294
24860
|
**Pipeline Steps:**
|
|
25295
|
-
1. **
|
|
25296
|
-
2. **
|
|
24861
|
+
1. **Classify**: Moves CSVs from import/incoming to import/pending (optional, skip with skipClassify)
|
|
24862
|
+
2. **Account Declarations**: Ensures all required accounts are declared in year journal
|
|
25297
24863
|
3. **Dry Run**: Validates all transactions have known accounts
|
|
25298
|
-
4. **Import**: Imports transactions to the journal
|
|
24864
|
+
4. **Import**: Imports transactions to the journal (moves CSVs to import/done)
|
|
25299
24865
|
5. **Reconcile**: Validates closing balance matches CSV metadata
|
|
25300
|
-
6. **Merge**: Merges worktree to main with --no-ff
|
|
25301
|
-
7. **Cleanup**: Removes worktree (or preserves on error)
|
|
25302
24866
|
|
|
25303
|
-
**
|
|
25304
|
-
- All changes
|
|
25305
|
-
- If any step fails,
|
|
25306
|
-
-
|
|
25307
|
-
- Atomic commit with merge --no-ff preserves history
|
|
25308
|
-
|
|
25309
|
-
**Worktree Cleanup:**
|
|
25310
|
-
- On success: Worktree is always cleaned up
|
|
25311
|
-
- On error (default): Worktree is kept at /tmp/import-worktree-<uuid> for debugging
|
|
25312
|
-
- On error (--keepWorktreeOnError false): Worktree is removed (old behavior)
|
|
25313
|
-
- Manual cleanup: git worktree remove /tmp/import-worktree-<uuid>
|
|
25314
|
-
- Auto cleanup: System reboot (worktrees are in /tmp)
|
|
24867
|
+
**Important:**
|
|
24868
|
+
- All changes remain uncommitted in your working directory
|
|
24869
|
+
- If any step fails, changes remain in place for inspection
|
|
24870
|
+
- CSV files move from incoming/ \u2192 pending/ \u2192 done/ during the process
|
|
25315
24871
|
|
|
25316
24872
|
**Logging:**
|
|
25317
|
-
- All operations
|
|
25318
|
-
- Log includes
|
|
25319
|
-
- Log path is included in tool output for easy access
|
|
25320
|
-
- NO console output (avoids polluting OpenCode TUI)
|
|
24873
|
+
- All operations logged to .memory/import-<timestamp>.md
|
|
24874
|
+
- Log includes command output, timing, and error details
|
|
25321
24875
|
|
|
25322
24876
|
**Usage:**
|
|
25323
|
-
- Basic: import-pipeline (processes all
|
|
24877
|
+
- Basic: import-pipeline (processes all CSVs in incoming/)
|
|
25324
24878
|
- Filtered: import-pipeline --provider ubs --currency chf
|
|
25325
|
-
-
|
|
25326
|
-
- Skip classify: import-pipeline --skipClassify true
|
|
25327
|
-
- Always cleanup: import-pipeline --keepWorktreeOnError false`,
|
|
24879
|
+
- Manual balance: import-pipeline --closingBalance "CHF 1234.56"
|
|
24880
|
+
- Skip classify: import-pipeline --skipClassify true`,
|
|
25328
24881
|
args: {
|
|
25329
24882
|
provider: tool.schema.string().optional().describe('Filter by provider (e.g., "ubs", "revolut")'),
|
|
25330
24883
|
currency: tool.schema.string().optional().describe('Filter by currency (e.g., "chf", "eur")'),
|
|
25331
24884
|
closingBalance: tool.schema.string().optional().describe("Manual closing balance override (if not in CSV metadata)"),
|
|
25332
24885
|
account: tool.schema.string().optional().describe("Manual account override (auto-detected from rules file if not provided)"),
|
|
25333
|
-
skipClassify: tool.schema.boolean().optional().describe("Skip the classify step (default: false)")
|
|
25334
|
-
keepWorktreeOnError: tool.schema.boolean().optional().describe("Keep worktree on error for debugging (default: true)")
|
|
24886
|
+
skipClassify: tool.schema.boolean().optional().describe("Skip the classify step (default: false)")
|
|
25335
24887
|
},
|
|
25336
24888
|
async execute(params, context) {
|
|
25337
24889
|
const { directory, agent } = context;
|
|
@@ -25340,22 +24892,21 @@ This tool orchestrates the full import workflow in an isolated git worktree:
|
|
|
25340
24892
|
currency: params.currency,
|
|
25341
24893
|
closingBalance: params.closingBalance,
|
|
25342
24894
|
account: params.account,
|
|
25343
|
-
skipClassify: params.skipClassify
|
|
25344
|
-
keepWorktreeOnError: params.keepWorktreeOnError
|
|
24895
|
+
skipClassify: params.skipClassify
|
|
25345
24896
|
});
|
|
25346
24897
|
}
|
|
25347
24898
|
});
|
|
25348
24899
|
// src/tools/init-directories.ts
|
|
25349
|
-
import * as
|
|
25350
|
-
import * as
|
|
24900
|
+
import * as fs13 from "fs";
|
|
24901
|
+
import * as path13 from "path";
|
|
25351
24902
|
async function initDirectories(directory) {
|
|
25352
24903
|
try {
|
|
25353
24904
|
const config2 = loadImportConfig(directory);
|
|
25354
24905
|
const directoriesCreated = [];
|
|
25355
24906
|
const gitkeepFiles = [];
|
|
25356
|
-
const importBase =
|
|
25357
|
-
if (!
|
|
25358
|
-
|
|
24907
|
+
const importBase = path13.join(directory, "import");
|
|
24908
|
+
if (!fs13.existsSync(importBase)) {
|
|
24909
|
+
fs13.mkdirSync(importBase, { recursive: true });
|
|
25359
24910
|
directoriesCreated.push("import");
|
|
25360
24911
|
}
|
|
25361
24912
|
const pathsToCreate = [
|
|
@@ -25365,20 +24916,20 @@ async function initDirectories(directory) {
|
|
|
25365
24916
|
{ key: "unrecognized", path: config2.paths.unrecognized }
|
|
25366
24917
|
];
|
|
25367
24918
|
for (const { path: dirPath } of pathsToCreate) {
|
|
25368
|
-
const fullPath =
|
|
25369
|
-
if (!
|
|
25370
|
-
|
|
24919
|
+
const fullPath = path13.join(directory, dirPath);
|
|
24920
|
+
if (!fs13.existsSync(fullPath)) {
|
|
24921
|
+
fs13.mkdirSync(fullPath, { recursive: true });
|
|
25371
24922
|
directoriesCreated.push(dirPath);
|
|
25372
24923
|
}
|
|
25373
|
-
const gitkeepPath =
|
|
25374
|
-
if (!
|
|
25375
|
-
|
|
25376
|
-
gitkeepFiles.push(
|
|
24924
|
+
const gitkeepPath = path13.join(fullPath, ".gitkeep");
|
|
24925
|
+
if (!fs13.existsSync(gitkeepPath)) {
|
|
24926
|
+
fs13.writeFileSync(gitkeepPath, "");
|
|
24927
|
+
gitkeepFiles.push(path13.join(dirPath, ".gitkeep"));
|
|
25377
24928
|
}
|
|
25378
24929
|
}
|
|
25379
|
-
const gitignorePath =
|
|
24930
|
+
const gitignorePath = path13.join(importBase, ".gitignore");
|
|
25380
24931
|
let gitignoreCreated = false;
|
|
25381
|
-
if (!
|
|
24932
|
+
if (!fs13.existsSync(gitignorePath)) {
|
|
25382
24933
|
const gitignoreContent = `# Ignore CSV/PDF files in temporary directories
|
|
25383
24934
|
/incoming/*.csv
|
|
25384
24935
|
/incoming/*.pdf
|
|
@@ -25396,7 +24947,7 @@ async function initDirectories(directory) {
|
|
|
25396
24947
|
.DS_Store
|
|
25397
24948
|
Thumbs.db
|
|
25398
24949
|
`;
|
|
25399
|
-
|
|
24950
|
+
fs13.writeFileSync(gitignorePath, gitignoreContent);
|
|
25400
24951
|
gitignoreCreated = true;
|
|
25401
24952
|
}
|
|
25402
24953
|
const parts = [];
|
|
@@ -25472,8 +25023,8 @@ You can now drop CSV files into import/incoming/ and run import-pipeline.`);
|
|
|
25472
25023
|
}
|
|
25473
25024
|
});
|
|
25474
25025
|
// src/index.ts
|
|
25475
|
-
var __dirname2 =
|
|
25476
|
-
var AGENT_FILE =
|
|
25026
|
+
var __dirname2 = dirname5(fileURLToPath3(import.meta.url));
|
|
25027
|
+
var AGENT_FILE = join12(__dirname2, "..", "agent", "accountant.md");
|
|
25477
25028
|
var AccountantPlugin = async () => {
|
|
25478
25029
|
const agent = loadAgent(AGENT_FILE);
|
|
25479
25030
|
return {
|