@fuzzle/opencode-accountant 0.0.8 → 0.0.9-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +6 -2
  2. package/dist/index.js +49 -55
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -115,7 +115,9 @@ providers:
115
115
 
116
116
  ubs:
117
117
  detect:
118
- - header: 'Trade date,Trade time,Booking date,Value date,Currency,Debit,Credit,Individual amount,Balance,Transaction no.,Description1,Description2,Description3,Footnotes'
118
+ # Note: UBS exports have a trailing semicolon in the header row, which creates
119
+ # an empty field when parsed. The header must include a trailing comma to match.
120
+ - header: 'Trade date,Trade time,Booking date,Value date,Currency,Debit,Credit,Individual amount,Balance,Transaction no.,Description1,Description2,Description3,Footnotes,'
119
121
  currencyField: Currency
120
122
  skipRows: 9
121
123
  delimiter: ';'
@@ -147,7 +149,7 @@ providers:
147
149
  | Field | Required | Description |
148
150
  | ----------------- | -------- | ---------------------------------------------------------- |
149
151
  | `filenamePattern` | No | Regex pattern to match against filename |
150
- | `header` | Yes | Expected CSV header row (exact match) |
152
+ | `header` | Yes | Expected CSV header row (comma-separated, exact match)\* |
151
153
  | `currencyField` | Yes | Column name containing the currency/symbol |
152
154
  | `skipRows` | No | Number of rows to skip before header (default: 0) |
153
155
  | `delimiter` | No | CSV delimiter character (default: `,`) |
@@ -155,6 +157,8 @@ providers:
155
157
  | `metadata` | No | Array of metadata extraction rules (see below) |
156
158
  | `currencies` | Yes | Map of raw currency values to normalized folder names |
157
159
 
160
+ \* **Note on trailing delimiters:** If the CSV header row ends with a trailing delimiter (e.g., `Field1;Field2;`), this creates an empty field when parsed. The `header` config must include a trailing comma to account for this (e.g., `Field1,Field2,`).
161
+
158
162
  **Metadata Extraction Rules:**
159
163
 
160
164
  | Field | Required | Description |
package/dist/index.js CHANGED
@@ -1341,7 +1341,7 @@ var require_papaparse = __commonJS((exports, module) => {
1341
1341
  });
1342
1342
 
1343
1343
  // src/index.ts
1344
- import { dirname, join as join5 } from "path";
1344
+ import { dirname as dirname2, join as join5 } from "path";
1345
1345
  import { fileURLToPath } from "url";
1346
1346
 
1347
1347
  // src/utils/agentLoader.ts
@@ -16848,35 +16848,6 @@ function findCSVFiles(importsDir) {
16848
16848
  return fs4.statSync(fullPath).isFile();
16849
16849
  });
16850
16850
  }
16851
- function checkPendingFiles(directory, pendingBasePath) {
16852
- const pendingDir = path4.join(directory, pendingBasePath);
16853
- const pendingFiles = [];
16854
- if (!fs4.existsSync(pendingDir)) {
16855
- return [];
16856
- }
16857
- const providers = fs4.readdirSync(pendingDir);
16858
- for (const provider of providers) {
16859
- const providerPath = path4.join(pendingDir, provider);
16860
- if (!fs4.statSync(providerPath).isDirectory())
16861
- continue;
16862
- const currencies = fs4.readdirSync(providerPath);
16863
- for (const currency of currencies) {
16864
- const currencyPath = path4.join(providerPath, currency);
16865
- if (!fs4.statSync(currencyPath).isDirectory())
16866
- continue;
16867
- const files = fs4.readdirSync(currencyPath).filter((f) => f.toLowerCase().endsWith(".csv"));
16868
- for (const file2 of files) {
16869
- pendingFiles.push({
16870
- provider,
16871
- currency,
16872
- filename: file2,
16873
- path: path4.join(currencyPath, file2)
16874
- });
16875
- }
16876
- }
16877
- }
16878
- return pendingFiles;
16879
- }
16880
16851
  function ensureDirectory(dirPath) {
16881
16852
  if (!fs4.existsSync(dirPath)) {
16882
16853
  fs4.mkdirSync(dirPath, { recursive: true });
@@ -16907,16 +16878,6 @@ async function classifyStatementsCore(directory, agent, configLoader = loadImpor
16907
16878
  const importsDir = path4.join(directory, config2.paths.import);
16908
16879
  const pendingDir = path4.join(directory, config2.paths.pending);
16909
16880
  const unrecognizedDir = path4.join(directory, config2.paths.unrecognized);
16910
- const pendingFiles = checkPendingFiles(directory, config2.paths.pending);
16911
- if (pendingFiles.length > 0) {
16912
- return JSON.stringify({
16913
- success: false,
16914
- error: `Found ${pendingFiles.length} pending file(s) that must be processed before classifying new statements.`,
16915
- pendingFiles,
16916
- classified: [],
16917
- unrecognized: []
16918
- });
16919
- }
16920
16881
  const csvFiles = findCSVFiles(importsDir);
16921
16882
  if (csvFiles.length === 0) {
16922
16883
  return JSON.stringify({
@@ -16926,32 +16887,65 @@ async function classifyStatementsCore(directory, agent, configLoader = loadImpor
16926
16887
  message: `No CSV files found in ${config2.paths.import}`
16927
16888
  });
16928
16889
  }
16929
- const classified = [];
16930
- const unrecognized = [];
16890
+ const plannedMoves = [];
16891
+ const collisions = [];
16931
16892
  for (const filename of csvFiles) {
16932
16893
  const sourcePath = path4.join(importsDir, filename);
16933
16894
  const content = fs4.readFileSync(sourcePath, "utf-8");
16934
16895
  const detection = detectProvider(filename, content, config2);
16896
+ let targetPath;
16897
+ let targetFilename;
16935
16898
  if (detection) {
16936
- const targetFilename = detection.outputFilename || filename;
16899
+ targetFilename = detection.outputFilename || filename;
16937
16900
  const targetDir = path4.join(pendingDir, detection.provider, detection.currency);
16901
+ targetPath = path4.join(targetDir, targetFilename);
16902
+ } else {
16903
+ targetFilename = filename;
16904
+ targetPath = path4.join(unrecognizedDir, filename);
16905
+ }
16906
+ if (fs4.existsSync(targetPath)) {
16907
+ collisions.push({
16908
+ filename,
16909
+ existingPath: targetPath
16910
+ });
16911
+ }
16912
+ plannedMoves.push({
16913
+ filename,
16914
+ sourcePath,
16915
+ targetPath,
16916
+ targetFilename,
16917
+ detection
16918
+ });
16919
+ }
16920
+ if (collisions.length > 0) {
16921
+ return JSON.stringify({
16922
+ success: false,
16923
+ error: `Cannot classify: ${collisions.length} file(s) would overwrite existing pending files.`,
16924
+ collisions,
16925
+ classified: [],
16926
+ unrecognized: []
16927
+ });
16928
+ }
16929
+ const classified = [];
16930
+ const unrecognized = [];
16931
+ for (const move of plannedMoves) {
16932
+ if (move.detection) {
16933
+ const targetDir = path4.dirname(move.targetPath);
16938
16934
  ensureDirectory(targetDir);
16939
- const targetPath = path4.join(targetDir, targetFilename);
16940
- fs4.renameSync(sourcePath, targetPath);
16935
+ fs4.renameSync(move.sourcePath, move.targetPath);
16941
16936
  classified.push({
16942
- filename: targetFilename,
16943
- originalFilename: detection.outputFilename ? filename : undefined,
16944
- provider: detection.provider,
16945
- currency: detection.currency,
16946
- targetPath: path4.join(config2.paths.pending, detection.provider, detection.currency, targetFilename)
16937
+ filename: move.targetFilename,
16938
+ originalFilename: move.detection.outputFilename ? move.filename : undefined,
16939
+ provider: move.detection.provider,
16940
+ currency: move.detection.currency,
16941
+ targetPath: path4.join(config2.paths.pending, move.detection.provider, move.detection.currency, move.targetFilename)
16947
16942
  });
16948
16943
  } else {
16949
16944
  ensureDirectory(unrecognizedDir);
16950
- const targetPath = path4.join(unrecognizedDir, filename);
16951
- fs4.renameSync(sourcePath, targetPath);
16945
+ fs4.renameSync(move.sourcePath, move.targetPath);
16952
16946
  unrecognized.push({
16953
- filename,
16954
- targetPath: path4.join(config2.paths.unrecognized, filename)
16947
+ filename: move.filename,
16948
+ targetPath: path4.join(config2.paths.unrecognized, move.filename)
16955
16949
  });
16956
16950
  }
16957
16951
  }
@@ -16975,7 +16969,7 @@ var classify_statements_default = tool({
16975
16969
  }
16976
16970
  });
16977
16971
  // src/index.ts
16978
- var __dirname2 = dirname(fileURLToPath(import.meta.url));
16972
+ var __dirname2 = dirname2(fileURLToPath(import.meta.url));
16979
16973
  var AGENT_FILE = join5(__dirname2, "..", "agent", "accountant.md");
16980
16974
  var AccountantPlugin = async () => {
16981
16975
  const agent = loadAgent(AGENT_FILE);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzzle/opencode-accountant",
3
- "version": "0.0.8",
3
+ "version": "0.0.9-next.1",
4
4
  "description": "An OpenCode accounting agent, specialized in double-entry-bookkepping with hledger",
5
5
  "author": {
6
6
  "name": "ali bengali",