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.
- package/dist/handlers/file-processor.d.ts +2 -2
- package/dist/handlers/file-processor.js +353 -109
- package/dist/index.js +64 -1
- package/dist/parsers/index.d.ts +2 -3
- package/dist/parsers/index.js +155 -46
- package/dist/security/audit-log.d.ts +22 -0
- package/dist/security/audit-log.js +129 -0
- package/dist/security/resource-limiter.d.ts +36 -0
- package/dist/security/resource-limiter.js +83 -0
- package/dist/security/sandbox.d.ts +24 -0
- package/dist/security/sandbox.js +112 -0
- package/dist/security/task-verifier.d.ts +24 -0
- package/dist/security/task-verifier.js +91 -0
- package/package.json +1 -1
|
@@ -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
|
|
5
|
-
*
|
|
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
|
|
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
|
|
12
|
-
*
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
88
|
-
|
|
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}"
|
|
172
|
+
text: `Parsed "${fileName}" — ${csvData.rowCount} rows, ${csvData.headers.length} columns.\n\nHeaders: ${csvData.headers.join(", ")}`,
|
|
91
173
|
parsed_file: parsed,
|
|
92
|
-
|
|
93
|
-
duration_ms: durationMs,
|
|
174
|
+
duration_ms: Date.now() - start,
|
|
94
175
|
};
|
|
95
176
|
}
|
|
96
|
-
|
|
97
|
-
|
|
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
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
const
|
|
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(
|
|
109
|
-
description: descCol >= 0 ?
|
|
110
|
-
amount:
|
|
111
|
-
category: categoryCol >= 0 ?
|
|
112
|
-
date: dateCol >= 0 ?
|
|
113
|
-
})).filter(
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
if (
|
|
125
|
-
return
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
|
145
|
-
const descCol =
|
|
146
|
-
const amountCol =
|
|
147
|
-
const typeCol =
|
|
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(
|
|
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
|
-
|
|
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),
|
package/dist/parsers/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* File parser registry.
|
|
3
|
-
* Detects file type
|
|
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
|
|
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 };
|