neuronix-node 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import type { TaskInput, TaskOutput } from "./index.js";
2
2
  /**
3
3
  * File processor handler.
4
- * Receives a file's content, parses it, determines what to do,
5
- * then routes to the appropriate specialized handler.
4
+ * Receives a file's content, parses it, determines the best handler,
5
+ * converts CSV data into the right format, and routes it.
6
6
  */
7
7
  export declare function handleFileProcess(task: TaskInput): Promise<TaskOutput>;
@@ -5,11 +5,18 @@ const index_js_1 = require("../parsers/index.js");
5
5
  const chart_js_1 = require("./chart.js");
6
6
  const expense_js_1 = require("./expense.js");
7
7
  const pnl_js_1 = require("./pnl.js");
8
- const invoice_js_1 = require("./invoice.js");
8
+ const ar_aging_js_1 = require("./ar-aging.js");
9
+ const ap_aging_js_1 = require("./ap-aging.js");
10
+ const bank_reconciliation_js_1 = require("./bank-reconciliation.js");
11
+ const budget_vs_actuals_js_1 = require("./budget-vs-actuals.js");
12
+ const payroll_js_1 = require("./payroll.js");
13
+ const sales_tax_js_1 = require("./sales-tax.js");
14
+ const depreciation_js_1 = require("./depreciation.js");
15
+ const department_spending_js_1 = require("./department-spending.js");
9
16
  /**
10
17
  * File processor handler.
11
- * Receives a file's content, parses it, determines what to do,
12
- * then routes to the appropriate specialized handler.
18
+ * Receives a file's content, parses it, determines the best handler,
19
+ * converts CSV data into the right format, and routes it.
13
20
  */
14
21
  async function handleFileProcess(task) {
15
22
  const start = Date.now();
@@ -17,141 +24,358 @@ async function handleFileProcess(task) {
17
24
  const fileName = payload.file_name || "unknown";
18
25
  const fileType = payload.file_type || "unknown";
19
26
  const content = payload.file_content || "";
20
- // Parse the file
27
+ // Parse the file and detect type
21
28
  const parsed = (0, index_js_1.parseFile)(fileName, fileType, content);
22
- // If there are suggested actions, auto-execute the best one
23
- if (parsed.suggestedActions.length > 0) {
24
- const bestAction = parsed.suggestedActions[0];
25
- // Build input from parsed data
26
- if (bestAction === "expense_report" && parsed.type === "csv") {
27
- const csvData = parsed.data;
28
- const expenses = csvToExpenses(csvData);
29
- if (expenses.length > 0) {
30
- const result = await (0, expense_js_1.handleExpenseReport)({
29
+ if (parsed.suggestedActions.length === 0 || parsed.type !== "csv") {
30
+ return {
31
+ text: `Parsed "${fileName}" (${fileType}). Could not determine the appropriate handler.\n\n${JSON.stringify(parsed.data, null, 2)}`,
32
+ parsed_file: parsed,
33
+ duration_ms: Date.now() - start,
34
+ };
35
+ }
36
+ const bestAction = parsed.suggestedActions[0];
37
+ const csvData = parsed.data;
38
+ const headers = csvData.headers.map(h => h.toLowerCase());
39
+ try {
40
+ let result;
41
+ switch (bestAction) {
42
+ case "expense_report": {
43
+ const expenses = csvToExpenses(csvData);
44
+ if (expenses.length === 0)
45
+ break;
46
+ result = await (0, expense_js_1.handleExpenseReport)({
31
47
  type: "expense_report",
32
48
  input_payload: { expenses, merged_from: payload.merged_from },
33
49
  });
34
- return {
35
- ...result,
36
- parsed_file: { type: parsed.type, rows: csvData.rowCount, columns: csvData.headers.length },
37
- auto_action: bestAction,
38
- duration_ms: Date.now() - start,
39
- };
50
+ return withMeta(result, parsed, bestAction, start);
40
51
  }
41
- }
42
- if (bestAction === "chart" && parsed.type === "csv") {
43
- const csvData = parsed.data;
44
- const chartInput = csvToChart(csvData, fileName);
45
- if (chartInput) {
46
- const result = await (0, chart_js_1.handleChart)({
47
- type: "chart",
48
- input_payload: chartInput,
52
+ case "ar_aging": {
53
+ const invoices = csvToInvoices(csvData);
54
+ if (invoices.length === 0)
55
+ break;
56
+ result = await (0, ar_aging_js_1.handleARAging)({
57
+ type: "ar_aging",
58
+ input_payload: { invoices },
49
59
  });
50
- return {
51
- ...result,
52
- parsed_file: { type: parsed.type, rows: csvData.rowCount, columns: csvData.headers.length },
53
- auto_action: bestAction,
54
- duration_ms: Date.now() - start,
55
- };
60
+ return withMeta(result, parsed, bestAction, start);
56
61
  }
57
- }
58
- if (bestAction === "pnl" && parsed.type === "csv") {
59
- const csvData = parsed.data;
60
- const pnlInput = csvToPnl(csvData);
61
- if (pnlInput) {
62
- const result = await (0, pnl_js_1.handlePnl)({
62
+ case "ap_aging": {
63
+ const bills = csvToBills(csvData);
64
+ if (bills.length === 0)
65
+ break;
66
+ result = await (0, ap_aging_js_1.handleAPAging)({
67
+ type: "ap_aging",
68
+ input_payload: { bills },
69
+ });
70
+ return withMeta(result, parsed, bestAction, start);
71
+ }
72
+ case "bank_reconciliation": {
73
+ const bankTxns = csvToBankTransactions(csvData);
74
+ if (bankTxns.length === 0)
75
+ break;
76
+ result = await (0, bank_reconciliation_js_1.handleBankReconciliation)({
77
+ type: "bank_reconciliation",
78
+ input_payload: { bank_transactions: bankTxns },
79
+ });
80
+ return withMeta(result, parsed, bestAction, start);
81
+ }
82
+ case "budget_vs_actuals": {
83
+ const lines = csvToBudget(csvData);
84
+ if (lines.length === 0)
85
+ break;
86
+ result = await (0, budget_vs_actuals_js_1.handleBudgetVsActuals)({
87
+ type: "budget_vs_actuals",
88
+ input_payload: { lines },
89
+ });
90
+ return withMeta(result, parsed, bestAction, start);
91
+ }
92
+ case "payroll": {
93
+ const employees = csvToPayroll(csvData);
94
+ if (employees.length === 0)
95
+ break;
96
+ result = await (0, payroll_js_1.handlePayroll)({
97
+ type: "payroll",
98
+ input_payload: { employees },
99
+ });
100
+ return withMeta(result, parsed, bestAction, start);
101
+ }
102
+ case "sales_tax": {
103
+ const transactions = csvToSalesTax(csvData);
104
+ if (transactions.length === 0)
105
+ break;
106
+ result = await (0, sales_tax_js_1.handleSalesTax)({
107
+ type: "sales_tax",
108
+ input_payload: { transactions },
109
+ });
110
+ return withMeta(result, parsed, bestAction, start);
111
+ }
112
+ case "depreciation": {
113
+ const assets = csvToAssets(csvData);
114
+ if (assets.length === 0)
115
+ break;
116
+ result = await (0, depreciation_js_1.handleDepreciation)({
117
+ type: "depreciation",
118
+ input_payload: { assets },
119
+ });
120
+ return withMeta(result, parsed, bestAction, start);
121
+ }
122
+ case "department_spending": {
123
+ const expenses = csvToDeptExpenses(csvData);
124
+ if (expenses.length === 0)
125
+ break;
126
+ result = await (0, department_spending_js_1.handleDepartmentSpending)({
127
+ type: "department_spending",
128
+ input_payload: { expenses },
129
+ });
130
+ return withMeta(result, parsed, bestAction, start);
131
+ }
132
+ case "pnl": {
133
+ const pnlInput = csvToPnl(csvData);
134
+ if (!pnlInput)
135
+ break;
136
+ result = await (0, pnl_js_1.handlePnl)({
63
137
  type: "pnl",
64
138
  input_payload: pnlInput,
65
139
  });
66
- return {
67
- ...result,
68
- parsed_file: { type: parsed.type, rows: csvData.rowCount, columns: csvData.headers.length },
69
- auto_action: bestAction,
70
- duration_ms: Date.now() - start,
71
- };
140
+ return withMeta(result, parsed, bestAction, start);
72
141
  }
142
+ case "chart": {
143
+ const chartInput = csvToChart(csvData, fileName);
144
+ if (!chartInput)
145
+ break;
146
+ result = await (0, chart_js_1.handleChart)({
147
+ type: "chart",
148
+ input_payload: chartInput,
149
+ });
150
+ return withMeta(result, parsed, bestAction, start);
151
+ }
152
+ default:
153
+ break;
73
154
  }
74
- if (bestAction === "invoice") {
75
- const result = await (0, invoice_js_1.handleInvoice)({
76
- type: "invoice",
77
- input_payload: {},
78
- });
79
- return {
80
- ...result,
81
- parsed_file: { type: parsed.type },
82
- auto_action: bestAction,
83
- duration_ms: Date.now() - start,
84
- };
85
- }
86
155
  }
87
- // Default: return parsed summary
88
- const durationMs = Date.now() - start;
156
+ catch (err) {
157
+ return {
158
+ text: `Error processing "${fileName}" as ${bestAction}: ${err}`,
159
+ parsed_file: parsed,
160
+ auto_action: bestAction,
161
+ error: String(err),
162
+ duration_ms: Date.now() - start,
163
+ };
164
+ }
165
+ // Fallback: return parsed data with a chart
166
+ const chartInput = csvToChart(csvData, fileName);
167
+ if (chartInput) {
168
+ const result = await (0, chart_js_1.handleChart)({ type: "chart", input_payload: chartInput });
169
+ return withMeta(result, parsed, "chart", start);
170
+ }
89
171
  return {
90
- text: `Parsed "${fileName}" (${fileType}).\n\n${JSON.stringify(parsed.data, null, 2)}`,
172
+ text: `Parsed "${fileName}" ${csvData.rowCount} rows, ${csvData.headers.length} columns.\n\nHeaders: ${csvData.headers.join(", ")}`,
91
173
  parsed_file: parsed,
92
- suggested_actions: parsed.suggestedActions,
93
- duration_ms: durationMs,
174
+ duration_ms: Date.now() - start,
94
175
  };
95
176
  }
96
- /**
97
- * Convert CSV data into expense objects for the expense handler.
98
- */
177
+ function withMeta(result, parsed, action, start) {
178
+ const csv = parsed.data;
179
+ return {
180
+ ...result,
181
+ parsed_file: { type: parsed.type, rows: csv.rowCount || 0, columns: csv.headers?.length || 0 },
182
+ auto_action: action,
183
+ duration_ms: Date.now() - start,
184
+ };
185
+ }
186
+ // ═══════════════════════════════════════════════════════════
187
+ // CSV → Handler Data Converters
188
+ // ═══════════════════════════════════════════════════════════
189
+ function findCol(headers, ...patterns) {
190
+ for (const p of patterns) {
191
+ const idx = headers.findIndex(h => p.test(h));
192
+ if (idx >= 0)
193
+ return idx;
194
+ }
195
+ return -1;
196
+ }
197
+ function parseNum(val) {
198
+ return parseFloat((val || "0").replace(/[$,]/g, "")) || 0;
199
+ }
99
200
  function csvToExpenses(csv) {
100
- const headersLower = csv.headers.map((h) => h.toLowerCase());
101
- // Find relevant columns
102
- const descCol = headersLower.findIndex((h) => /description|name|item|memo/.test(h));
103
- const amountCol = headersLower.findIndex((h) => /amount|cost|price|total|debit/.test(h));
104
- const categoryCol = headersLower.findIndex((h) => /category|type|class|department/.test(h));
105
- const dateCol = headersLower.findIndex((h) => /date/.test(h));
201
+ const h = csv.headers.map(h => h.toLowerCase());
202
+ const descCol = findCol(h, /description|name|item|memo/);
203
+ const amountCol = findCol(h, /amount|cost|price|total/);
204
+ const categoryCol = findCol(h, /category|type|class|department/);
205
+ const dateCol = findCol(h, /date/);
106
206
  if (amountCol === -1)
107
207
  return [];
108
- return csv.rows.map((row) => ({
109
- description: descCol >= 0 ? row[descCol] : "Item",
110
- amount: parseFloat((row[amountCol] || "0").replace(/[$,]/g, "")) || 0,
111
- category: categoryCol >= 0 ? row[categoryCol] : "Uncategorized",
112
- date: dateCol >= 0 ? row[dateCol] : undefined,
113
- })).filter((e) => e.amount > 0);
208
+ return csv.rows.map(r => ({
209
+ description: descCol >= 0 ? r[descCol] : "Item",
210
+ amount: parseNum(r[amountCol]),
211
+ category: categoryCol >= 0 ? r[categoryCol] : "Uncategorized",
212
+ date: dateCol >= 0 ? r[dateCol] : undefined,
213
+ })).filter(e => e.amount > 0);
114
214
  }
115
- /**
116
- * Convert CSV data into chart parameters.
117
- */
118
- function csvToChart(csv, fileName) {
119
- if (csv.summary.numericColumns.length === 0)
120
- return null;
121
- const headersLower = csv.headers.map((h) => h.toLowerCase());
122
- const labelCol = headersLower.findIndex((h) => /name|description|category|date|month|item|label/.test(h));
123
- const numCol = csv.headers.findIndex((h) => csv.columnTypes[h] === "number" || csv.columnTypes[h] === "currency");
124
- if (numCol === -1)
125
- return null;
126
- const labels = labelCol >= 0
127
- ? csv.rows.map((r) => r[labelCol]).slice(0, 20)
128
- : csv.rows.map((_, i) => `Row ${i + 1}`).slice(0, 20);
129
- const data = csv.rows
130
- .map((r) => parseFloat((r[numCol] || "0").replace(/[$,]/g, "")))
131
- .filter((n) => !isNaN(n))
132
- .slice(0, 20);
133
- return {
134
- chart_type: data.length > 8 ? "line" : "bar",
135
- title: fileName.replace(/\.[^.]+$/, "").replace(/[_-]/g, " "),
136
- labels,
137
- datasets: [{ label: csv.headers[numCol], data }],
138
- };
215
+ function csvToInvoices(csv) {
216
+ const h = csv.headers.map(h => h.toLowerCase());
217
+ const customerCol = findCol(h, /customer|client|bill.?to/);
218
+ const invNumCol = findCol(h, /invoice|inv/);
219
+ const amountCol = findCol(h, /amount|total/);
220
+ const dateIssuedCol = findCol(h, /date.?issued|issue.?date|^date$/);
221
+ const dateDueCol = findCol(h, /due.?date|payment.?due/);
222
+ const statusCol = findCol(h, /status/);
223
+ const paidCol = findCol(h, /paid|payment/);
224
+ if (amountCol === -1)
225
+ return [];
226
+ return csv.rows.map(r => {
227
+ const status = statusCol >= 0 ? r[statusCol]?.toLowerCase() || "" : "";
228
+ const paid = paidCol >= 0 ? parseNum(r[paidCol]) : (status === "paid" ? parseNum(r[amountCol]) : 0);
229
+ return {
230
+ customer: customerCol >= 0 ? r[customerCol] : "Unknown",
231
+ invoice_number: invNumCol >= 0 ? r[invNumCol] : undefined,
232
+ amount: parseNum(r[amountCol]),
233
+ date_issued: dateIssuedCol >= 0 ? r[dateIssuedCol] : new Date().toISOString().split("T")[0],
234
+ date_due: dateDueCol >= 0 ? r[dateDueCol] : new Date().toISOString().split("T")[0],
235
+ amount_paid: paid,
236
+ };
237
+ }).filter(e => e.amount > 0);
238
+ }
239
+ function csvToBills(csv) {
240
+ const h = csv.headers.map(h => h.toLowerCase());
241
+ const vendorCol = findCol(h, /vendor|supplier|payee/);
242
+ const billNumCol = findCol(h, /bill|inv/);
243
+ const amountCol = findCol(h, /amount|total/);
244
+ const dateRecCol = findCol(h, /date.?received|receive|^date$/);
245
+ const dateDueCol = findCol(h, /due.?date/);
246
+ const paidCol = findCol(h, /paid|payment/);
247
+ if (amountCol === -1)
248
+ return [];
249
+ return csv.rows.map(r => ({
250
+ vendor: vendorCol >= 0 ? r[vendorCol] : "Unknown",
251
+ bill_number: billNumCol >= 0 ? r[billNumCol] : undefined,
252
+ amount: parseNum(r[amountCol]),
253
+ date_received: dateRecCol >= 0 ? r[dateRecCol] : new Date().toISOString().split("T")[0],
254
+ date_due: dateDueCol >= 0 ? r[dateDueCol] : new Date().toISOString().split("T")[0],
255
+ amount_paid: paidCol >= 0 ? parseNum(r[paidCol]) : 0,
256
+ })).filter(e => e.amount > 0);
257
+ }
258
+ function csvToBankTransactions(csv) {
259
+ const h = csv.headers.map(h => h.toLowerCase());
260
+ const dateCol = findCol(h, /date/);
261
+ const descCol = findCol(h, /description|desc|memo|payee/);
262
+ const amountCol = findCol(h, /amount|total/);
263
+ const typeCol = findCol(h, /^type$/);
264
+ const refCol = findCol(h, /reference|ref|check/);
265
+ if (amountCol === -1)
266
+ return [];
267
+ return csv.rows.map(r => {
268
+ const amount = parseNum(r[amountCol]);
269
+ let type = amount >= 0 ? "credit" : "debit";
270
+ if (typeCol >= 0) {
271
+ const t = (r[typeCol] || "").toLowerCase();
272
+ if (t === "debit")
273
+ type = "debit";
274
+ else if (t === "credit")
275
+ type = "credit";
276
+ }
277
+ return {
278
+ date: dateCol >= 0 ? r[dateCol] : "",
279
+ description: descCol >= 0 ? r[descCol] : "",
280
+ amount: Math.abs(amount),
281
+ type,
282
+ reference: refCol >= 0 ? r[refCol] : undefined,
283
+ };
284
+ }).filter(t => t.amount > 0);
285
+ }
286
+ function csvToBudget(csv) {
287
+ const h = csv.headers.map(h => h.toLowerCase());
288
+ const catCol = findCol(h, /category|item|line|department|account/);
289
+ const budgetCol = findCol(h, /budget|planned|forecast/);
290
+ const actualCol = findCol(h, /actual|spent|real/);
291
+ if (budgetCol === -1 || actualCol === -1)
292
+ return [];
293
+ return csv.rows.map(r => ({
294
+ category: catCol >= 0 ? r[catCol] : "Item",
295
+ budget: parseNum(r[budgetCol]),
296
+ actual: parseNum(r[actualCol]),
297
+ })).filter(l => l.budget > 0 || l.actual > 0);
298
+ }
299
+ function csvToPayroll(csv) {
300
+ const h = csv.headers.map(h => h.toLowerCase());
301
+ const nameCol = findCol(h, /employee|name/);
302
+ const typeCol = findCol(h, /^type$/);
303
+ const rateCol = findCol(h, /rate|salary|wage|pay/);
304
+ const hoursCol = findCol(h, /hours/);
305
+ const deptCol = findCol(h, /department|dept/);
306
+ if (nameCol === -1 || rateCol === -1)
307
+ return [];
308
+ return csv.rows.map(r => {
309
+ const type = typeCol >= 0 ? (r[typeCol]?.toLowerCase().includes("hourly") ? "hourly" : "salary") : "salary";
310
+ return {
311
+ name: r[nameCol] || "Employee",
312
+ type: type,
313
+ rate: parseNum(r[rateCol]),
314
+ hours: hoursCol >= 0 && r[hoursCol] ? parseNum(r[hoursCol]) : undefined,
315
+ };
316
+ }).filter(e => e.rate > 0);
317
+ }
318
+ function csvToSalesTax(csv) {
319
+ const h = csv.headers.map(h => h.toLowerCase());
320
+ const dateCol = findCol(h, /date/);
321
+ const descCol = findCol(h, /description|item/);
322
+ const amountCol = findCol(h, /amount|total|sales/);
323
+ const taxRateCol = findCol(h, /tax.?rate/);
324
+ const stateCol = findCol(h, /state/);
325
+ const taxableCol = findCol(h, /taxable/);
326
+ if (amountCol === -1)
327
+ return [];
328
+ return csv.rows.map(r => ({
329
+ date: dateCol >= 0 ? r[dateCol] : "",
330
+ description: descCol >= 0 ? r[descCol] : "Sale",
331
+ amount: parseNum(r[amountCol]),
332
+ tax_rate: taxRateCol >= 0 ? parseNum(r[taxRateCol]) : 0,
333
+ state: stateCol >= 0 ? r[stateCol] : undefined,
334
+ taxable: taxableCol >= 0 ? /yes|true|1/i.test(r[taxableCol]) : true,
335
+ })).filter(t => t.amount > 0);
336
+ }
337
+ function csvToAssets(csv) {
338
+ const h = csv.headers.map(h => h.toLowerCase());
339
+ const nameCol = findCol(h, /name|asset|description/);
340
+ const costCol = findCol(h, /cost|price|value/);
341
+ const salvageCol = findCol(h, /salvage|residual/);
342
+ const lifeCol = findCol(h, /useful.?life|lifespan|years/);
343
+ const acquiredCol = findCol(h, /acquired|purchase|date/);
344
+ if (costCol === -1)
345
+ return [];
346
+ return csv.rows.map(r => ({
347
+ name: nameCol >= 0 ? r[nameCol] : "Asset",
348
+ cost: parseNum(r[costCol]),
349
+ salvage_value: salvageCol >= 0 ? parseNum(r[salvageCol]) : 0,
350
+ useful_life_years: lifeCol >= 0 ? parseNum(r[lifeCol]) : 5,
351
+ date_acquired: acquiredCol >= 0 ? r[acquiredCol] : "2024-01-01",
352
+ })).filter(a => a.cost > 0);
353
+ }
354
+ function csvToDeptExpenses(csv) {
355
+ const h = csv.headers.map(h => h.toLowerCase());
356
+ const deptCol = findCol(h, /department|dept|team|division/);
357
+ const catCol = findCol(h, /category|type|class/);
358
+ const amountCol = findCol(h, /amount|cost|total/);
359
+ if (deptCol === -1 || amountCol === -1)
360
+ return [];
361
+ return csv.rows.map(r => ({
362
+ department: r[deptCol] || "General",
363
+ category: catCol >= 0 ? r[catCol] : "General",
364
+ amount: parseNum(r[amountCol]),
365
+ })).filter(e => e.amount > 0);
139
366
  }
140
- /**
141
- * Convert CSV data into P&L parameters.
142
- */
143
367
  function csvToPnl(csv) {
144
- const headersLower = csv.headers.map((h) => h.toLowerCase());
145
- const descCol = headersLower.findIndex((h) => /description|name|item/.test(h));
146
- const amountCol = headersLower.findIndex((h) => /amount|total/.test(h));
147
- const typeCol = headersLower.findIndex((h) => /type|category/.test(h));
368
+ const h = csv.headers.map(h => h.toLowerCase());
369
+ const descCol = findCol(h, /description|name|item/);
370
+ const amountCol = findCol(h, /amount|total/);
371
+ const typeCol = findCol(h, /type|category/);
148
372
  if (amountCol === -1)
149
373
  return null;
150
374
  const revenue = [];
151
375
  const expenses = [];
152
376
  for (const row of csv.rows) {
153
377
  const desc = descCol >= 0 ? row[descCol] : "Item";
154
- const amount = Math.abs(parseFloat((row[amountCol] || "0").replace(/[$,]/g, "")));
378
+ const amount = Math.abs(parseNum(row[amountCol]));
155
379
  const type = typeCol >= 0 ? row[typeCol].toLowerCase() : "";
156
380
  if (amount === 0)
157
381
  continue;
@@ -166,3 +390,23 @@ function csvToPnl(csv) {
166
390
  return null;
167
391
  return { revenue, expenses };
168
392
  }
393
+ function csvToChart(csv, fileName) {
394
+ if (csv.summary.numericColumns.length === 0)
395
+ return null;
396
+ const h = csv.headers.map(h => h.toLowerCase());
397
+ const labelCol = findCol(h, /name|description|category|month|item|label|employee/);
398
+ const numCol = csv.headers.findIndex(hdr => csv.columnTypes[hdr] === "number" || csv.columnTypes[hdr] === "currency");
399
+ if (numCol === -1)
400
+ return null;
401
+ const labels = labelCol >= 0 ? csv.rows.map(r => r[labelCol]).slice(0, 20) : csv.rows.map((_, i) => `Row ${i + 1}`).slice(0, 20);
402
+ const data = csv.rows.map(r => parseNum(r[numCol])).filter(n => !isNaN(n)).slice(0, 20);
403
+ // Pick chart type: line only for time-series data, bar for everything else
404
+ const isTimeSeries = labelCol >= 0 && csv.columnTypes[csv.headers[labelCol]] === "date";
405
+ const chartType = isTimeSeries ? "line" : "bar";
406
+ return {
407
+ chart_type: chartType,
408
+ title: fileName.replace(/\.[^.]+$/, "").replace(/[_-]/g, " "),
409
+ labels,
410
+ datasets: [{ label: csv.headers[numCol], data }],
411
+ };
412
+ }
package/dist/index.js CHANGED
@@ -8,6 +8,10 @@ const models_js_1 = require("./models.js");
8
8
  const inference_js_1 = require("./inference.js");
9
9
  const index_js_1 = require("./handlers/index.js");
10
10
  const updater_js_1 = require("./updater.js");
11
+ const resource_limiter_js_1 = require("./security/resource-limiter.js");
12
+ const audit_log_js_1 = require("./security/audit-log.js");
13
+ const task_verifier_js_1 = require("./security/task-verifier.js");
14
+ const sandbox_js_1 = require("./security/sandbox.js");
11
15
  // ── Helpers ──────────────────────────────────────────────────
12
16
  function log(msg) {
13
17
  const ts = new Date().toLocaleTimeString();
@@ -74,7 +78,35 @@ async function authenticate(config) {
74
78
  // ── Main ─────────────────────────────────────────────────────
75
79
  async function main() {
76
80
  banner();
81
+ // ── Security: Initialize audit log ──
82
+ (0, audit_log_js_1.initAuditLog)();
77
83
  const config = (0, config_js_1.loadConfig)();
84
+ // ── Security: Verify sandbox ──
85
+ log("Running security checks...");
86
+ const sandbox = (0, sandbox_js_1.verifySandbox)();
87
+ if (sandbox.safe) {
88
+ log("Security: All checks passed ✓");
89
+ }
90
+ else {
91
+ for (const issue of sandbox.issues) {
92
+ log(`Security warning: ${issue}`);
93
+ }
94
+ }
95
+ // ── Security: Show security report if requested ──
96
+ if (process.argv.includes("--security-report")) {
97
+ const report = (0, sandbox_js_1.getSecurityReport)();
98
+ console.log("\n ── Security Report ──");
99
+ console.log(` Sandbox: ${report.sandbox_status}`);
100
+ console.log(` File Access: ${report.file_access}`);
101
+ console.log(` Network Access: ${report.network_access}`);
102
+ console.log(` Process Isolation: ${report.process_isolation}`);
103
+ console.log(` Data Handling: ${report.data_handling}`);
104
+ console.log(" Recommendations:");
105
+ for (const rec of report.recommendations)
106
+ console.log(` - ${rec}`);
107
+ console.log("");
108
+ process.exit(0);
109
+ }
78
110
  // Check for updates
79
111
  log("Checking for updates...");
80
112
  try {
@@ -189,6 +221,27 @@ async function main() {
189
221
  if (result.task) {
190
222
  const task = result.task;
191
223
  log(`Task received: ${task.id.slice(0, 8)}... [${task.type}] model=${task.model || "auto"}`);
224
+ // ── Security: Validate task before execution ──
225
+ const validation = (0, task_verifier_js_1.validateTask)(task);
226
+ if (!validation.valid) {
227
+ log(`Task REJECTED: ${validation.reason}`);
228
+ (0, audit_log_js_1.logSecurityEvent)("TASK_REJECTED", `${task.id}: ${validation.reason}`);
229
+ await (0, api_js_1.completeTask)(config, task.id, "failed", { error: `Security: ${validation.reason}` }, 0);
230
+ continue;
231
+ }
232
+ for (const warn of validation.warnings) {
233
+ log(`Task warning: ${warn}`);
234
+ }
235
+ // ── Security: Check resource limits ──
236
+ const resources = await (0, resource_limiter_js_1.checkResources)();
237
+ if (!resources.allowed) {
238
+ log(`Task deferred: ${resources.reason}`);
239
+ // Don't complete as failed — let another node pick it up
240
+ continue;
241
+ }
242
+ // ── Security: Log task receipt ──
243
+ (0, audit_log_js_1.logTaskReceived)(task.id, task.type, task.model || "auto");
244
+ (0, resource_limiter_js_1.taskStarted)();
192
245
  // Set node to busy
193
246
  await (0, api_js_1.sendHeartbeat)(config, "busy");
194
247
  const taskStart = Date.now();
@@ -243,7 +296,14 @@ async function main() {
243
296
  duration_ms: durationMs,
244
297
  };
245
298
  }
246
- await (0, api_js_1.completeTask)(config, task.id, "completed", outputPayload, durationMs);
299
+ // ── Security: Sanitize output ──
300
+ const safeOutput = (0, sandbox_js_1.sanitizeOutput)(outputPayload);
301
+ await (0, api_js_1.completeTask)(config, task.id, "completed", safeOutput, durationMs);
302
+ // ── Security: Log completion ──
303
+ const outputSize = JSON.stringify(safeOutput).length;
304
+ const outputType = safeOutput.output_csv ? "csv" : safeOutput.image_base64 ? "image" : safeOutput.invoice_html ? "html" : "text";
305
+ (0, audit_log_js_1.logTaskCompleted)(task.id, task.type, durationMs, outputType, outputSize);
306
+ (0, resource_limiter_js_1.taskFinished)();
247
307
  tasksCompleted++;
248
308
  totalEarned += task.cost_usd || 0;
249
309
  log(`Task ${task.id.slice(0, 8)}... [${task.type}] completed in ${durationMs}ms (+$${(task.cost_usd || 0).toFixed(4)})`);
@@ -251,6 +311,9 @@ async function main() {
251
311
  catch (inferErr) {
252
312
  const elapsed = Date.now() - taskStart;
253
313
  tasksFailed++;
314
+ // ── Security: Log failure ──
315
+ (0, audit_log_js_1.logTaskFailed)(task.id, task.type, String(inferErr), elapsed);
316
+ (0, resource_limiter_js_1.taskFinished)();
254
317
  log(`Task ${task.id.slice(0, 8)}... failed after ${elapsed}ms: ${inferErr}`);
255
318
  await (0, api_js_1.completeTask)(config, task.id, "failed", {
256
319
  error: String(inferErr),
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * File parser registry.
3
- * Detects file type and extracts structured data.
3
+ * Detects file type, extracts structured data, and determines the best handler.
4
4
  */
5
5
  import { parseCSV, type CSVData } from "./csv.js";
6
6
  export interface ParsedFile {
@@ -10,8 +10,7 @@ export interface ParsedFile {
10
10
  suggestedActions: string[];
11
11
  }
12
12
  /**
13
- * Parse file content based on its type.
14
- * Returns structured data + suggested actions the bot can take.
13
+ * Parse file content and intelligently detect what kind of financial data it contains.
15
14
  */
16
15
  export declare function parseFile(fileName: string, fileType: string, content: string | Buffer): ParsedFile;
17
16
  export { parseCSV, type CSVData };