@fuzzle/opencode-accountant 0.1.2 → 0.1.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 +185 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1941,7 +1941,7 @@ var require_convert_csv_to_json = __commonJS((exports) => {
|
|
|
1941
1941
|
});
|
|
1942
1942
|
|
|
1943
1943
|
// src/index.ts
|
|
1944
|
-
import { dirname as dirname6, join as
|
|
1944
|
+
import { dirname as dirname6, join as join13 } from "path";
|
|
1945
1945
|
import { fileURLToPath } from "url";
|
|
1946
1946
|
|
|
1947
1947
|
// src/utils/agentLoader.ts
|
|
@@ -17614,6 +17614,27 @@ function execGitSafe(args, cwd) {
|
|
|
17614
17614
|
}
|
|
17615
17615
|
return { success: true, output: (result.stdout || "").trim() };
|
|
17616
17616
|
}
|
|
17617
|
+
function copyIncomingFiles(mainRepoPath, worktreePath) {
|
|
17618
|
+
const sourceDir = path5.join(mainRepoPath, "import/incoming");
|
|
17619
|
+
const targetDir = path5.join(worktreePath, "import/incoming");
|
|
17620
|
+
if (!fs4.existsSync(sourceDir)) {
|
|
17621
|
+
return;
|
|
17622
|
+
}
|
|
17623
|
+
fs4.mkdirSync(targetDir, { recursive: true });
|
|
17624
|
+
const entries = fs4.readdirSync(sourceDir, { withFileTypes: true });
|
|
17625
|
+
let copiedCount = 0;
|
|
17626
|
+
for (const entry of entries) {
|
|
17627
|
+
if (entry.isFile() && !entry.name.startsWith(".")) {
|
|
17628
|
+
const srcPath = path5.join(sourceDir, entry.name);
|
|
17629
|
+
const destPath = path5.join(targetDir, entry.name);
|
|
17630
|
+
fs4.copyFileSync(srcPath, destPath);
|
|
17631
|
+
copiedCount++;
|
|
17632
|
+
}
|
|
17633
|
+
}
|
|
17634
|
+
if (copiedCount > 0) {
|
|
17635
|
+
console.log(`[INFO] Copied ${copiedCount} file(s) from import/incoming/ to worktree`);
|
|
17636
|
+
}
|
|
17637
|
+
}
|
|
17617
17638
|
function createImportWorktree(mainRepoPath, options = {}) {
|
|
17618
17639
|
const baseDir = options.baseDir ?? "/tmp";
|
|
17619
17640
|
const uuid3 = v4_default();
|
|
@@ -17631,6 +17652,7 @@ function createImportWorktree(mainRepoPath, options = {}) {
|
|
|
17631
17652
|
execGitSafe(["branch", "-D", branch], mainRepoPath);
|
|
17632
17653
|
throw error45;
|
|
17633
17654
|
}
|
|
17655
|
+
copyIncomingFiles(mainRepoPath, worktreePath);
|
|
17634
17656
|
return {
|
|
17635
17657
|
path: worktreePath,
|
|
17636
17658
|
branch,
|
|
@@ -18829,6 +18851,8 @@ It must be run inside an import worktree (use import-pipeline for the full workf
|
|
|
18829
18851
|
}
|
|
18830
18852
|
});
|
|
18831
18853
|
// src/tools/import-pipeline.ts
|
|
18854
|
+
import * as fs12 from "fs";
|
|
18855
|
+
import * as path11 from "path";
|
|
18832
18856
|
class NoTransactionsError extends Error {
|
|
18833
18857
|
constructor() {
|
|
18834
18858
|
super("No transactions to import");
|
|
@@ -18866,6 +18890,38 @@ function buildCommitMessage(provider, currency, fromDate, untilDate, transaction
|
|
|
18866
18890
|
}
|
|
18867
18891
|
return `${parts.join(" ")}${dateRange}${txStr}`;
|
|
18868
18892
|
}
|
|
18893
|
+
function cleanupIncomingFiles(worktree, context) {
|
|
18894
|
+
const incomingDir = path11.join(worktree.mainRepoPath, "import/incoming");
|
|
18895
|
+
if (!fs12.existsSync(incomingDir)) {
|
|
18896
|
+
return;
|
|
18897
|
+
}
|
|
18898
|
+
const importStep = context.result.steps.import;
|
|
18899
|
+
if (!importStep?.success || !importStep.details) {
|
|
18900
|
+
return;
|
|
18901
|
+
}
|
|
18902
|
+
const importResult = importStep.details;
|
|
18903
|
+
if (!importResult.files || !Array.isArray(importResult.files)) {
|
|
18904
|
+
return;
|
|
18905
|
+
}
|
|
18906
|
+
let deletedCount = 0;
|
|
18907
|
+
for (const fileResult of importResult.files) {
|
|
18908
|
+
if (!fileResult.csv)
|
|
18909
|
+
continue;
|
|
18910
|
+
const filename = path11.basename(fileResult.csv);
|
|
18911
|
+
const filePath = path11.join(incomingDir, filename);
|
|
18912
|
+
if (fs12.existsSync(filePath)) {
|
|
18913
|
+
try {
|
|
18914
|
+
fs12.unlinkSync(filePath);
|
|
18915
|
+
deletedCount++;
|
|
18916
|
+
} catch (error45) {
|
|
18917
|
+
console.error(`[ERROR] Failed to delete ${filename}: ${error45 instanceof Error ? error45.message : String(error45)}`);
|
|
18918
|
+
}
|
|
18919
|
+
}
|
|
18920
|
+
}
|
|
18921
|
+
if (deletedCount > 0) {
|
|
18922
|
+
console.log(`[INFO] Cleaned up ${deletedCount} file(s) from import/incoming/`);
|
|
18923
|
+
}
|
|
18924
|
+
}
|
|
18869
18925
|
async function executeClassifyStep(context, worktree) {
|
|
18870
18926
|
if (context.options.skipClassify) {
|
|
18871
18927
|
context.result.steps.classify = buildStepResult(true, "Classification skipped (skipClassify: true)");
|
|
@@ -18966,6 +19022,7 @@ async function executeMergeStep(context, worktree) {
|
|
|
18966
19022
|
mergeWorktree(worktree, commitMessage);
|
|
18967
19023
|
const mergeDetails = { commitMessage };
|
|
18968
19024
|
context.result.steps.merge = buildStepResult(true, `Merged to main: "${commitMessage}"`, mergeDetails);
|
|
19025
|
+
cleanupIncomingFiles(worktree, context);
|
|
18969
19026
|
} catch (error45) {
|
|
18970
19027
|
const message = `Merge failed: ${error45 instanceof Error ? error45.message : String(error45)}`;
|
|
18971
19028
|
context.result.steps.merge = buildStepResult(false, message);
|
|
@@ -19074,9 +19131,135 @@ This tool orchestrates the full import workflow in an isolated git worktree:
|
|
|
19074
19131
|
});
|
|
19075
19132
|
}
|
|
19076
19133
|
});
|
|
19134
|
+
// src/tools/init-directories.ts
|
|
19135
|
+
import * as fs13 from "fs";
|
|
19136
|
+
import * as path12 from "path";
|
|
19137
|
+
async function initDirectories(directory) {
|
|
19138
|
+
try {
|
|
19139
|
+
const config2 = loadImportConfig(directory);
|
|
19140
|
+
const directoriesCreated = [];
|
|
19141
|
+
const gitkeepFiles = [];
|
|
19142
|
+
const importBase = path12.join(directory, "import");
|
|
19143
|
+
if (!fs13.existsSync(importBase)) {
|
|
19144
|
+
fs13.mkdirSync(importBase, { recursive: true });
|
|
19145
|
+
directoriesCreated.push("import");
|
|
19146
|
+
}
|
|
19147
|
+
const pathsToCreate = [
|
|
19148
|
+
{ key: "import", path: config2.paths.import },
|
|
19149
|
+
{ key: "pending", path: config2.paths.pending },
|
|
19150
|
+
{ key: "done", path: config2.paths.done },
|
|
19151
|
+
{ key: "unrecognized", path: config2.paths.unrecognized }
|
|
19152
|
+
];
|
|
19153
|
+
for (const { path: dirPath } of pathsToCreate) {
|
|
19154
|
+
const fullPath = path12.join(directory, dirPath);
|
|
19155
|
+
if (!fs13.existsSync(fullPath)) {
|
|
19156
|
+
fs13.mkdirSync(fullPath, { recursive: true });
|
|
19157
|
+
directoriesCreated.push(dirPath);
|
|
19158
|
+
}
|
|
19159
|
+
const gitkeepPath = path12.join(fullPath, ".gitkeep");
|
|
19160
|
+
if (!fs13.existsSync(gitkeepPath)) {
|
|
19161
|
+
fs13.writeFileSync(gitkeepPath, "");
|
|
19162
|
+
gitkeepFiles.push(path12.join(dirPath, ".gitkeep"));
|
|
19163
|
+
}
|
|
19164
|
+
}
|
|
19165
|
+
const gitignorePath = path12.join(importBase, ".gitignore");
|
|
19166
|
+
let gitignoreCreated = false;
|
|
19167
|
+
if (!fs13.existsSync(gitignorePath)) {
|
|
19168
|
+
const gitignoreContent = `# Ignore CSV/PDF files in temporary directories
|
|
19169
|
+
/incoming/*.csv
|
|
19170
|
+
/incoming/*.pdf
|
|
19171
|
+
/pending/**/*.csv
|
|
19172
|
+
/pending/**/*.pdf
|
|
19173
|
+
/unrecognized/**/*.csv
|
|
19174
|
+
/unrecognized/**/*.pdf
|
|
19175
|
+
|
|
19176
|
+
# Track processed files in done/ (audit trail)
|
|
19177
|
+
# No ignore rule needed - tracked by default
|
|
19178
|
+
|
|
19179
|
+
# Ignore temporary files
|
|
19180
|
+
*.tmp
|
|
19181
|
+
*.temp
|
|
19182
|
+
.DS_Store
|
|
19183
|
+
Thumbs.db
|
|
19184
|
+
`;
|
|
19185
|
+
fs13.writeFileSync(gitignorePath, gitignoreContent);
|
|
19186
|
+
gitignoreCreated = true;
|
|
19187
|
+
}
|
|
19188
|
+
const parts = [];
|
|
19189
|
+
if (directoriesCreated.length > 0) {
|
|
19190
|
+
parts.push(`Created ${directoriesCreated.length} director${directoriesCreated.length === 1 ? "y" : "ies"}`);
|
|
19191
|
+
}
|
|
19192
|
+
if (gitkeepFiles.length > 0) {
|
|
19193
|
+
parts.push(`added ${gitkeepFiles.length} .gitkeep file${gitkeepFiles.length === 1 ? "" : "s"}`);
|
|
19194
|
+
}
|
|
19195
|
+
if (gitignoreCreated) {
|
|
19196
|
+
parts.push("created .gitignore");
|
|
19197
|
+
}
|
|
19198
|
+
const message = parts.length > 0 ? `Import directory structure initialized: ${parts.join(", ")}` : "Import directory structure already exists (no changes needed)";
|
|
19199
|
+
return {
|
|
19200
|
+
success: true,
|
|
19201
|
+
directoriesCreated,
|
|
19202
|
+
gitkeepFiles,
|
|
19203
|
+
gitignoreCreated,
|
|
19204
|
+
message
|
|
19205
|
+
};
|
|
19206
|
+
} catch (error45) {
|
|
19207
|
+
return {
|
|
19208
|
+
success: false,
|
|
19209
|
+
directoriesCreated: [],
|
|
19210
|
+
gitkeepFiles: [],
|
|
19211
|
+
gitignoreCreated: false,
|
|
19212
|
+
error: error45 instanceof Error ? error45.message : String(error45),
|
|
19213
|
+
message: "Failed to initialize import directory structure"
|
|
19214
|
+
};
|
|
19215
|
+
}
|
|
19216
|
+
}
|
|
19217
|
+
var init_directories_default = tool({
|
|
19218
|
+
description: "ACCOUNTANT AGENT ONLY: Initialize the import directory structure needed for processing bank statements. Creates import/incoming, import/pending, import/done, and import/unrecognized directories with .gitkeep files and appropriate .gitignore rules. Reads directory paths from config/import/providers.yaml. Safe to run multiple times (idempotent).",
|
|
19219
|
+
args: {},
|
|
19220
|
+
async execute(_params, context) {
|
|
19221
|
+
const restrictionError = checkAccountantAgent(context.agent, "init directories");
|
|
19222
|
+
if (restrictionError) {
|
|
19223
|
+
throw new Error(restrictionError);
|
|
19224
|
+
}
|
|
19225
|
+
const { directory } = context;
|
|
19226
|
+
const result = await initDirectories(directory);
|
|
19227
|
+
if (!result.success) {
|
|
19228
|
+
return `Error: ${result.error}
|
|
19229
|
+
|
|
19230
|
+
${result.message}`;
|
|
19231
|
+
}
|
|
19232
|
+
const output = [];
|
|
19233
|
+
output.push(result.message || "");
|
|
19234
|
+
if (result.directoriesCreated.length > 0) {
|
|
19235
|
+
output.push(`
|
|
19236
|
+
Directories created:`);
|
|
19237
|
+
for (const dir of result.directoriesCreated) {
|
|
19238
|
+
output.push(` - ${dir}`);
|
|
19239
|
+
}
|
|
19240
|
+
}
|
|
19241
|
+
if (result.gitkeepFiles.length > 0) {
|
|
19242
|
+
output.push(`
|
|
19243
|
+
.gitkeep files added:`);
|
|
19244
|
+
for (const file2 of result.gitkeepFiles) {
|
|
19245
|
+
output.push(` - ${file2}`);
|
|
19246
|
+
}
|
|
19247
|
+
}
|
|
19248
|
+
if (result.gitignoreCreated) {
|
|
19249
|
+
output.push(`
|
|
19250
|
+
Created import/.gitignore with rules to:`);
|
|
19251
|
+
output.push(" - Ignore CSV/PDF files in incoming/, pending/, unrecognized/");
|
|
19252
|
+
output.push(" - Track processed files in done/ for audit trail");
|
|
19253
|
+
}
|
|
19254
|
+
output.push(`
|
|
19255
|
+
You can now drop CSV files into import/incoming/ and run import-pipeline.`);
|
|
19256
|
+
return output.join(`
|
|
19257
|
+
`);
|
|
19258
|
+
}
|
|
19259
|
+
});
|
|
19077
19260
|
// src/index.ts
|
|
19078
19261
|
var __dirname2 = dirname6(fileURLToPath(import.meta.url));
|
|
19079
|
-
var AGENT_FILE =
|
|
19262
|
+
var AGENT_FILE = join13(__dirname2, "..", "agent", "accountant.md");
|
|
19080
19263
|
var AccountantPlugin = async () => {
|
|
19081
19264
|
const agent = loadAgent(AGENT_FILE);
|
|
19082
19265
|
return {
|