@yeyuan98/opencode-bioresearcher-plugin 1.7.1-alpha.0 → 1.7.2
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/tools/misc/markdown-to-html.js +223 -0
- package/dist/tools/parser/pubmed/utils.js +2 -0
- package/dist/tools/table/backends/base.d.ts +8 -0
- package/dist/tools/table/backends/base.js +23 -0
- package/dist/tools/table/backends/csv/index.d.ts +64 -0
- package/dist/tools/table/backends/csv/index.js +660 -0
- package/dist/tools/table/backends/factory.d.ts +2 -0
- package/dist/tools/table/backends/factory.js +14 -0
- package/dist/tools/table/backends/interface.d.ts +68 -0
- package/dist/tools/table/backends/interface.js +1 -0
- package/dist/tools/table/backends/xlsx/index.d.ts +62 -0
- package/dist/tools/table/backends/xlsx/index.js +547 -0
- package/dist/tools/table/index.d.ts +6 -4
- package/dist/tools/table/tools.d.ts +6 -4
- package/dist/tools/table/tools.js +291 -458
- package/dist/tools/table/utils.d.ts +4 -4
- package/dist/tools/table/utils.js +17 -38
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import { tool } from '@opencode-ai/plugin/tool';
|
|
2
2
|
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
3
4
|
import * as XLSX from '../../vendor/xlsx.mjs';
|
|
5
|
+
XLSX.set_fs(fs);
|
|
4
6
|
import * as z from 'zod';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
+
import { formatError, resolvePath, writeAtomic, invalidateWorkbookCache, DEFAULT_MAX_ROW_CAP, } from './utils';
|
|
8
|
+
import { createBackend } from './backends/factory';
|
|
7
9
|
const FORCE_DESC = "Override file size / row count limits. Use when you need to work with large files and accept the performance implications.";
|
|
10
|
+
function resolveSheetIndex(backend, sheetName) {
|
|
11
|
+
if (!sheetName)
|
|
12
|
+
return 0;
|
|
13
|
+
const sheets = backend.listSheets();
|
|
14
|
+
const idx = sheets.findIndex(s => s.name === sheetName);
|
|
15
|
+
if (idx === -1)
|
|
16
|
+
throw new Error(`Sheet "${sheetName}" not found`);
|
|
17
|
+
return idx;
|
|
18
|
+
}
|
|
19
|
+
function getSheetName(backend, sheetIndex) {
|
|
20
|
+
return backend.listSheets()[sheetIndex].name;
|
|
21
|
+
}
|
|
8
22
|
export const tableGetSheetPreview = tool({
|
|
9
23
|
description: "Get first 6 rows of a worksheet to preview data structure",
|
|
10
24
|
args: {
|
|
@@ -14,23 +28,24 @@ export const tableGetSheetPreview = tool({
|
|
|
14
28
|
},
|
|
15
29
|
execute: async (args, context) => {
|
|
16
30
|
await context.ask({ permission: "tableGetSheetPreview", patterns: [args.file_path], always: ["*"], metadata: {} });
|
|
31
|
+
const resolvedPath = resolvePath(args.file_path, context.directory);
|
|
17
32
|
try {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
33
|
+
const backend = createBackend(resolvedPath, args.force);
|
|
34
|
+
try {
|
|
35
|
+
const sheetIndex = resolveSheetIndex(backend, args.sheet_name);
|
|
36
|
+
const { rows: preview, totalRows } = backend.getPreview(sheetIndex, 6);
|
|
37
|
+
return JSON.stringify({
|
|
38
|
+
sheet_name: args.sheet_name || getSheetName(backend, sheetIndex),
|
|
39
|
+
total_rows: totalRows,
|
|
40
|
+
preview,
|
|
41
|
+
}, null, 2);
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
backend.dispose();
|
|
45
|
+
}
|
|
31
46
|
}
|
|
32
47
|
catch (error) {
|
|
33
|
-
return formatError({ file_path: args.file_path, sheet_name: args.sheet_name, operation: "get_sheet_preview", error });
|
|
48
|
+
return formatError({ file_path: args.file_path, resolved_path: resolvedPath, sheet_name: args.sheet_name, operation: "get_sheet_preview", error });
|
|
34
49
|
}
|
|
35
50
|
}
|
|
36
51
|
});
|
|
@@ -42,14 +57,19 @@ export const tableListSheets = tool({
|
|
|
42
57
|
},
|
|
43
58
|
execute: async (args, context) => {
|
|
44
59
|
await context.ask({ permission: "tableListSheets", patterns: [args.file_path], always: ["*"], metadata: {} });
|
|
60
|
+
const resolvedPath = resolvePath(args.file_path, context.directory);
|
|
45
61
|
try {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
62
|
+
const backend = createBackend(resolvedPath, args.force);
|
|
63
|
+
try {
|
|
64
|
+
const sheets = backend.listSheets();
|
|
65
|
+
return JSON.stringify({ sheets: sheets.map(s => s.name) }, null, 2);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
backend.dispose();
|
|
69
|
+
}
|
|
50
70
|
}
|
|
51
71
|
catch (error) {
|
|
52
|
-
return formatError({ file_path: args.file_path, operation: "list_sheets", error });
|
|
72
|
+
return formatError({ file_path: args.file_path, resolved_path: resolvedPath, operation: "list_sheets", error });
|
|
53
73
|
}
|
|
54
74
|
}
|
|
55
75
|
});
|
|
@@ -62,26 +82,24 @@ export const tableGetHeaders = tool({
|
|
|
62
82
|
},
|
|
63
83
|
execute: async (args, context) => {
|
|
64
84
|
await context.ask({ permission: "tableGetHeaders", patterns: [args.file_path], always: ["*"], metadata: {} });
|
|
85
|
+
const resolvedPath = resolvePath(args.file_path, context.directory);
|
|
65
86
|
try {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
87
|
+
const backend = createBackend(resolvedPath, args.force);
|
|
88
|
+
try {
|
|
89
|
+
const sheetIndex = resolveSheetIndex(backend, args.sheet_name);
|
|
90
|
+
const { headers, columnCount } = backend.getHeaders(sheetIndex);
|
|
91
|
+
return JSON.stringify({
|
|
92
|
+
sheet_name: args.sheet_name || getSheetName(backend, sheetIndex),
|
|
93
|
+
headers,
|
|
94
|
+
column_count: columnCount,
|
|
95
|
+
}, null, 2);
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
backend.dispose();
|
|
76
99
|
}
|
|
77
|
-
return JSON.stringify({
|
|
78
|
-
sheet_name: args.sheet_name || workbook.SheetNames[0],
|
|
79
|
-
headers: headers,
|
|
80
|
-
column_count: headers.length,
|
|
81
|
-
}, null, 2);
|
|
82
100
|
}
|
|
83
101
|
catch (error) {
|
|
84
|
-
return formatError({ file_path: args.file_path, sheet_name: args.sheet_name, operation: "get_headers", error });
|
|
102
|
+
return formatError({ file_path: args.file_path, resolved_path: resolvedPath, sheet_name: args.sheet_name, operation: "get_headers", error });
|
|
85
103
|
}
|
|
86
104
|
}
|
|
87
105
|
});
|
|
@@ -95,25 +113,25 @@ export const tableGetCell = tool({
|
|
|
95
113
|
},
|
|
96
114
|
execute: async (args, context) => {
|
|
97
115
|
await context.ask({ permission: "tableGetCell", patterns: [args.file_path], always: ["*"], metadata: {} });
|
|
116
|
+
const resolvedPath = resolvePath(args.file_path, context.directory);
|
|
98
117
|
try {
|
|
99
|
-
|
|
100
|
-
|
|
118
|
+
const backend = createBackend(resolvedPath, args.force);
|
|
119
|
+
try {
|
|
120
|
+
const sheetIndex = resolveSheetIndex(backend, args.sheet_name);
|
|
121
|
+
const { value, type } = backend.getCell(sheetIndex, args.cell_address);
|
|
122
|
+
return JSON.stringify({
|
|
123
|
+
sheet_name: args.sheet_name || getSheetName(backend, sheetIndex),
|
|
124
|
+
cell_address: args.cell_address,
|
|
125
|
+
value,
|
|
126
|
+
type,
|
|
127
|
+
}, null, 2);
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
backend.dispose();
|
|
101
131
|
}
|
|
102
|
-
const resolvedPath = resolvePath(args.file_path, context.directory);
|
|
103
|
-
checkFileSize(resolvedPath, args.force);
|
|
104
|
-
const workbook = XLSX.readFile(resolvedPath);
|
|
105
|
-
const worksheet = getSheet(workbook, args.sheet_name);
|
|
106
|
-
const cell = worksheet[args.cell_address];
|
|
107
|
-
const value = processCellValue(cell);
|
|
108
|
-
return JSON.stringify({
|
|
109
|
-
sheet_name: args.sheet_name || workbook.SheetNames[0],
|
|
110
|
-
cell_address: args.cell_address,
|
|
111
|
-
value: value,
|
|
112
|
-
type: cell?.t || 'unknown',
|
|
113
|
-
}, null, 2);
|
|
114
132
|
}
|
|
115
133
|
catch (error) {
|
|
116
|
-
return formatError({ file_path: args.file_path, sheet_name: args.sheet_name, operation: "get_cell", error });
|
|
134
|
+
return formatError({ file_path: args.file_path, resolved_path: resolvedPath, sheet_name: args.sheet_name, operation: "get_cell", error });
|
|
117
135
|
}
|
|
118
136
|
}
|
|
119
137
|
});
|
|
@@ -130,81 +148,35 @@ export const tableFilterRows = tool({
|
|
|
130
148
|
},
|
|
131
149
|
execute: async (args, context) => {
|
|
132
150
|
await context.ask({ permission: "tableFilterRows", patterns: [args.file_path], always: ["*"], metadata: {} });
|
|
151
|
+
const resolvedPath = resolvePath(args.file_path, context.directory);
|
|
133
152
|
try {
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
let matches = false;
|
|
153
|
-
switch (args.operator) {
|
|
154
|
-
case '=':
|
|
155
|
-
matches = cellValue === comparisonValue;
|
|
156
|
-
break;
|
|
157
|
-
case '!=':
|
|
158
|
-
matches = cellValue !== comparisonValue;
|
|
159
|
-
break;
|
|
160
|
-
case '>':
|
|
161
|
-
matches = cellValue > comparisonValue;
|
|
162
|
-
break;
|
|
163
|
-
case '<':
|
|
164
|
-
matches = cellValue < comparisonValue;
|
|
165
|
-
break;
|
|
166
|
-
case '>=':
|
|
167
|
-
matches = cellValue >= comparisonValue;
|
|
168
|
-
break;
|
|
169
|
-
case '<=':
|
|
170
|
-
matches = cellValue <= comparisonValue;
|
|
171
|
-
break;
|
|
172
|
-
case 'contains':
|
|
173
|
-
matches = String(cellValue).toLowerCase().includes(String(comparisonValue).toLowerCase());
|
|
174
|
-
break;
|
|
175
|
-
case 'starts_with':
|
|
176
|
-
matches = String(cellValue).toLowerCase().startsWith(String(comparisonValue).toLowerCase());
|
|
177
|
-
break;
|
|
178
|
-
case 'ends_with':
|
|
179
|
-
matches = String(cellValue).toLowerCase().endsWith(String(comparisonValue).toLowerCase());
|
|
180
|
-
break;
|
|
181
|
-
}
|
|
182
|
-
if (matches) {
|
|
183
|
-
totalMatchesFound++;
|
|
184
|
-
if (filteredRows.length < effectiveMax) {
|
|
185
|
-
filteredRows.push(row);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
if (!args.force && i >= DEFAULT_MAX_ROW_CAP - 1) {
|
|
189
|
-
break;
|
|
153
|
+
const backend = createBackend(resolvedPath, args.force);
|
|
154
|
+
try {
|
|
155
|
+
const sheetIndex = resolveSheetIndex(backend, args.sheet_name);
|
|
156
|
+
const totalDataRows = backend.getTotalDataRows(sheetIndex);
|
|
157
|
+
const maxRowsScan = args.force ? totalDataRows : Math.min(totalDataRows, DEFAULT_MAX_ROW_CAP);
|
|
158
|
+
const effectiveMax = args.force ? args.max_results : Math.min(args.max_results, DEFAULT_MAX_ROW_CAP);
|
|
159
|
+
const { matchedRows, totalMatchesFound, totalDataRows: backendTotalRows } = backend.filterRows(sheetIndex, args.column, args.operator, args.value, { maxResults: effectiveMax, maxRowsScan });
|
|
160
|
+
const truncated = !args.force && backendTotalRows > DEFAULT_MAX_ROW_CAP;
|
|
161
|
+
const result = {
|
|
162
|
+
sheet_name: args.sheet_name || getSheetName(backend, sheetIndex),
|
|
163
|
+
filter: { column: args.column, operator: args.operator, value: args.value },
|
|
164
|
+
total_matches_found: totalMatchesFound,
|
|
165
|
+
returned_count: matchedRows.length,
|
|
166
|
+
matched_rows: matchedRows,
|
|
167
|
+
};
|
|
168
|
+
if (truncated) {
|
|
169
|
+
result.truncated = true;
|
|
170
|
+
result.message = `Processed ${DEFAULT_MAX_ROW_CAP} of ${backendTotalRows} rows. Set force: true to process all rows.`;
|
|
190
171
|
}
|
|
172
|
+
return JSON.stringify(result, null, 2);
|
|
191
173
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
sheet_name: args.sheet_name || workbook.SheetNames[0],
|
|
195
|
-
filter: { column: args.column, operator: args.operator, value: args.value },
|
|
196
|
-
total_matches_found: totalMatchesFound,
|
|
197
|
-
returned_count: filteredRows.length,
|
|
198
|
-
matched_rows: filteredRows,
|
|
199
|
-
};
|
|
200
|
-
if (truncated) {
|
|
201
|
-
result.truncated = true;
|
|
202
|
-
result.message = `Processed ${DEFAULT_MAX_ROW_CAP} of ${data.length} rows. Set force: true to process all rows.`;
|
|
174
|
+
finally {
|
|
175
|
+
backend.dispose();
|
|
203
176
|
}
|
|
204
|
-
return JSON.stringify(result, null, 2);
|
|
205
177
|
}
|
|
206
178
|
catch (error) {
|
|
207
|
-
return formatError({ file_path: args.file_path, sheet_name: args.sheet_name, operation: "filter_rows", error });
|
|
179
|
+
return formatError({ file_path: args.file_path, resolved_path: resolvedPath, sheet_name: args.sheet_name, operation: "filter_rows", error });
|
|
208
180
|
}
|
|
209
181
|
}
|
|
210
182
|
});
|
|
@@ -214,56 +186,45 @@ export const tableSearch = tool({
|
|
|
214
186
|
file_path: z.string().describe("Path to table file (supports .xlsx, .ods, .csv formats)"),
|
|
215
187
|
sheet_name: z.string().optional().describe("Worksheet name (optional, uses first sheet by default)"),
|
|
216
188
|
search_term: z.string().describe("Term to search for"),
|
|
189
|
+
column: z.string().optional().describe("Limit search to a specific column name (much faster for wide tables)"),
|
|
217
190
|
max_results: z.number().default(50).describe("Maximum number of results to return"),
|
|
218
191
|
force: z.boolean().default(false).describe(FORCE_DESC),
|
|
219
192
|
},
|
|
220
193
|
execute: async (args, context) => {
|
|
221
194
|
await context.ask({ permission: "tableSearch", patterns: [args.file_path], always: ["*"], metadata: {} });
|
|
195
|
+
const resolvedPath = resolvePath(args.file_path, context.directory);
|
|
222
196
|
try {
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
197
|
+
const backend = createBackend(resolvedPath, args.force);
|
|
198
|
+
try {
|
|
199
|
+
const sheetIndex = resolveSheetIndex(backend, args.sheet_name);
|
|
200
|
+
const totalDataRows = backend.getTotalDataRows(sheetIndex);
|
|
201
|
+
const maxRowsScan = args.force ? totalDataRows : Math.min(totalDataRows, DEFAULT_MAX_ROW_CAP);
|
|
202
|
+
const effectiveMax = args.force ? args.max_results : Math.min(args.max_results, DEFAULT_MAX_ROW_CAP);
|
|
203
|
+
const { results, totalMatchesFound, rowsScanned } = backend.search(sheetIndex, args.search_term, { column: args.column, maxResults: effectiveMax, maxRowsScan });
|
|
204
|
+
const truncated = !args.force && totalDataRows > DEFAULT_MAX_ROW_CAP;
|
|
205
|
+
const result = {
|
|
206
|
+
sheet_name: args.sheet_name || getSheetName(backend, sheetIndex),
|
|
207
|
+
search_term: args.search_term,
|
|
208
|
+
total_matches_found: totalMatchesFound,
|
|
209
|
+
returned_count: results.length,
|
|
210
|
+
rows_scanned: rowsScanned,
|
|
211
|
+
results,
|
|
212
|
+
};
|
|
213
|
+
if (args.column) {
|
|
214
|
+
result.column = args.column;
|
|
215
|
+
}
|
|
216
|
+
if (truncated) {
|
|
217
|
+
result.truncated = true;
|
|
218
|
+
result.message = `Scanned ${DEFAULT_MAX_ROW_CAP} rows. Set force: true to scan all rows.`;
|
|
248
219
|
}
|
|
220
|
+
return JSON.stringify(result, null, 2);
|
|
249
221
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
sheet_name: args.sheet_name || workbook.SheetNames[0],
|
|
253
|
-
search_term: args.search_term,
|
|
254
|
-
total_matches_found: totalMatchesFound,
|
|
255
|
-
returned_count: results.length,
|
|
256
|
-
rows_scanned: rowsScanned,
|
|
257
|
-
results,
|
|
258
|
-
};
|
|
259
|
-
if (truncated) {
|
|
260
|
-
result.truncated = true;
|
|
261
|
-
result.message = `Scanned ${DEFAULT_MAX_ROW_CAP} rows. Set force: true to scan all rows.`;
|
|
222
|
+
finally {
|
|
223
|
+
backend.dispose();
|
|
262
224
|
}
|
|
263
|
-
return JSON.stringify(result, null, 2);
|
|
264
225
|
}
|
|
265
226
|
catch (error) {
|
|
266
|
-
return formatError({ file_path: args.file_path, sheet_name: args.sheet_name, operation: "search", error });
|
|
227
|
+
return formatError({ file_path: args.file_path, resolved_path: resolvedPath, sheet_name: args.sheet_name, operation: "search", error });
|
|
267
228
|
}
|
|
268
229
|
}
|
|
269
230
|
});
|
|
@@ -277,53 +238,33 @@ export const tableGetRange = tool({
|
|
|
277
238
|
},
|
|
278
239
|
execute: async (args, context) => {
|
|
279
240
|
await context.ask({ permission: "tableGetRange", patterns: [args.file_path], always: ["*"], metadata: {} });
|
|
241
|
+
const resolvedPath = resolvePath(args.file_path, context.directory);
|
|
280
242
|
try {
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
const totalCells = (clamped.e.r - clamped.s.r + 1) * (clamped.e.c - clamped.s.c + 1);
|
|
299
|
-
if (!args.force && totalCells > DEFAULT_MAX_RANGE_CELLS) {
|
|
300
|
-
throw new Error(`Range "${args.range}" exceeds ${DEFAULT_MAX_RANGE_CELLS} cell limit (${totalCells} cells). ` +
|
|
301
|
-
`Set force: true to override.`);
|
|
302
|
-
}
|
|
303
|
-
const data = [];
|
|
304
|
-
for (let R = clamped.s.r; R <= clamped.e.r; ++R) {
|
|
305
|
-
const row = [];
|
|
306
|
-
for (let C = clamped.s.c; C <= clamped.e.c; ++C) {
|
|
307
|
-
const address = XLSX.utils.encode_cell({ r: R, c: C });
|
|
308
|
-
const cell = worksheet[address];
|
|
309
|
-
row.push(processCellValue(cell));
|
|
243
|
+
const backend = createBackend(resolvedPath, args.force);
|
|
244
|
+
try {
|
|
245
|
+
const sheetIndex = resolveSheetIndex(backend, args.sheet_name);
|
|
246
|
+
const rangeResult = backend.getRange(sheetIndex, args.range);
|
|
247
|
+
const result = {
|
|
248
|
+
sheet_name: args.sheet_name || getSheetName(backend, sheetIndex),
|
|
249
|
+
range: args.range,
|
|
250
|
+
rows: rangeResult.rows,
|
|
251
|
+
columns: rangeResult.columns,
|
|
252
|
+
data: rangeResult.data,
|
|
253
|
+
};
|
|
254
|
+
if (rangeResult.out_of_bounds) {
|
|
255
|
+
result.out_of_bounds = true;
|
|
256
|
+
}
|
|
257
|
+
if (rangeResult.clamped_to) {
|
|
258
|
+
result.clamped_to = rangeResult.clamped_to;
|
|
310
259
|
}
|
|
311
|
-
|
|
260
|
+
return JSON.stringify(result, null, 2);
|
|
312
261
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
range: args.range,
|
|
316
|
-
rows: data.length,
|
|
317
|
-
columns: data[0]?.length || 0,
|
|
318
|
-
data,
|
|
319
|
-
};
|
|
320
|
-
if (clamped.s.r !== range.s.r || clamped.s.c !== range.s.c || clamped.e.r !== range.e.r || clamped.e.c !== range.e.c) {
|
|
321
|
-
result.clamped_to = XLSX.utils.encode_range(clamped);
|
|
262
|
+
finally {
|
|
263
|
+
backend.dispose();
|
|
322
264
|
}
|
|
323
|
-
return JSON.stringify(result, null, 2);
|
|
324
265
|
}
|
|
325
266
|
catch (error) {
|
|
326
|
-
return formatError({ file_path: args.file_path, sheet_name: args.sheet_name, operation: "get_range", error });
|
|
267
|
+
return formatError({ file_path: args.file_path, resolved_path: resolvedPath, sheet_name: args.sheet_name, operation: "get_range", error });
|
|
327
268
|
}
|
|
328
269
|
}
|
|
329
270
|
});
|
|
@@ -337,42 +278,33 @@ export const tableSummarize = tool({
|
|
|
337
278
|
},
|
|
338
279
|
execute: async (args, context) => {
|
|
339
280
|
await context.ask({ permission: "tableSummarize", patterns: [args.file_path], always: ["*"], metadata: {} });
|
|
281
|
+
const resolvedPath = resolvePath(args.file_path, context.directory);
|
|
340
282
|
try {
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
const stdDev = Math.sqrt(variance);
|
|
358
|
-
summaries[col] = { sum, avg, min, max, std_dev: stdDev };
|
|
283
|
+
const backend = createBackend(resolvedPath, args.force);
|
|
284
|
+
try {
|
|
285
|
+
const sheetIndex = resolveSheetIndex(backend, args.sheet_name);
|
|
286
|
+
const totalDataRows = backend.getTotalDataRows(sheetIndex);
|
|
287
|
+
const maxRowsScan = args.force ? totalDataRows : Math.min(totalDataRows, DEFAULT_MAX_ROW_CAP);
|
|
288
|
+
const { summaries, rowCount } = backend.summarize(sheetIndex, args.columns, { maxRowsScan });
|
|
289
|
+
const truncated = !args.force && totalDataRows > DEFAULT_MAX_ROW_CAP;
|
|
290
|
+
const result = {
|
|
291
|
+
sheet_name: args.sheet_name || getSheetName(backend, sheetIndex),
|
|
292
|
+
row_count: rowCount,
|
|
293
|
+
summaries,
|
|
294
|
+
};
|
|
295
|
+
if (truncated) {
|
|
296
|
+
result.total_rows = totalDataRows;
|
|
297
|
+
result.truncated = true;
|
|
298
|
+
result.message = `Summarized ${DEFAULT_MAX_ROW_CAP} of ${totalDataRows} rows. Set force: true to process all rows.`;
|
|
359
299
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
row_count: effectiveData.length,
|
|
365
|
-
summaries,
|
|
366
|
-
};
|
|
367
|
-
if (truncated) {
|
|
368
|
-
result.total_rows = data.length;
|
|
369
|
-
result.truncated = true;
|
|
370
|
-
result.message = `Summarized ${DEFAULT_MAX_ROW_CAP} of ${data.length} rows. Set force: true to process all rows.`;
|
|
300
|
+
return JSON.stringify(result, null, 2);
|
|
301
|
+
}
|
|
302
|
+
finally {
|
|
303
|
+
backend.dispose();
|
|
371
304
|
}
|
|
372
|
-
return JSON.stringify(result, null, 2);
|
|
373
305
|
}
|
|
374
306
|
catch (error) {
|
|
375
|
-
return formatError({ file_path: args.file_path, sheet_name: args.sheet_name, operation: "summarize", error });
|
|
307
|
+
return formatError({ file_path: args.file_path, resolved_path: resolvedPath, sheet_name: args.sheet_name, operation: "summarize", error });
|
|
376
308
|
}
|
|
377
309
|
}
|
|
378
310
|
});
|
|
@@ -388,73 +320,36 @@ export const tableGroupBy = tool({
|
|
|
388
320
|
},
|
|
389
321
|
execute: async (args, context) => {
|
|
390
322
|
await context.ask({ permission: "tableGroupBy", patterns: [args.file_path], always: ["*"], metadata: {} });
|
|
323
|
+
const resolvedPath = resolvePath(args.file_path, context.directory);
|
|
391
324
|
try {
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
const result = {};
|
|
412
|
-
Object.keys(grouped).forEach(groupKey => {
|
|
413
|
-
const values = grouped[groupKey];
|
|
414
|
-
if (values.length === 0) {
|
|
415
|
-
result[groupKey] = 0;
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
switch (args.agg_type) {
|
|
419
|
-
case 'sum':
|
|
420
|
-
result[groupKey] = values.reduce((a, b) => a + b, 0);
|
|
421
|
-
break;
|
|
422
|
-
case 'count':
|
|
423
|
-
result[groupKey] = values.length;
|
|
424
|
-
break;
|
|
425
|
-
case 'avg':
|
|
426
|
-
result[groupKey] = values.reduce((a, b) => a + b, 0) / values.length;
|
|
427
|
-
break;
|
|
428
|
-
case 'min': {
|
|
429
|
-
const m = iterativeMin(values);
|
|
430
|
-
result[groupKey] = m !== undefined ? m : 0;
|
|
431
|
-
break;
|
|
432
|
-
}
|
|
433
|
-
case 'max': {
|
|
434
|
-
const m = iterativeMax(values);
|
|
435
|
-
result[groupKey] = m !== undefined ? m : 0;
|
|
436
|
-
break;
|
|
437
|
-
}
|
|
325
|
+
const backend = createBackend(resolvedPath, args.force);
|
|
326
|
+
try {
|
|
327
|
+
const sheetIndex = resolveSheetIndex(backend, args.sheet_name);
|
|
328
|
+
const totalDataRows = backend.getTotalDataRows(sheetIndex);
|
|
329
|
+
const maxRowsScan = args.force ? totalDataRows : Math.min(totalDataRows, DEFAULT_MAX_ROW_CAP);
|
|
330
|
+
const { groups } = backend.groupBy(sheetIndex, args.group_column, args.agg_column, args.agg_type, { maxRowsScan });
|
|
331
|
+
const truncated = !args.force && totalDataRows > DEFAULT_MAX_ROW_CAP;
|
|
332
|
+
const response = {
|
|
333
|
+
sheet_name: args.sheet_name || getSheetName(backend, sheetIndex),
|
|
334
|
+
group_column: args.group_column,
|
|
335
|
+
agg_column: args.agg_column,
|
|
336
|
+
agg_type: args.agg_type,
|
|
337
|
+
groups,
|
|
338
|
+
};
|
|
339
|
+
if (truncated) {
|
|
340
|
+
response.total_rows = totalDataRows;
|
|
341
|
+
response.processed_rows = maxRowsScan;
|
|
342
|
+
response.truncated = true;
|
|
343
|
+
response.message = `Processed ${DEFAULT_MAX_ROW_CAP} of ${totalDataRows} rows. Set force: true to process all rows.`;
|
|
438
344
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
group_column: args.group_column,
|
|
444
|
-
agg_column: args.agg_column,
|
|
445
|
-
agg_type: args.agg_type,
|
|
446
|
-
groups: result,
|
|
447
|
-
};
|
|
448
|
-
if (truncated) {
|
|
449
|
-
response.total_rows = data.length;
|
|
450
|
-
response.processed_rows = effectiveData.length;
|
|
451
|
-
response.truncated = true;
|
|
452
|
-
response.message = `Processed ${DEFAULT_MAX_ROW_CAP} of ${data.length} rows. Set force: true to process all rows.`;
|
|
345
|
+
return JSON.stringify(response, null, 2);
|
|
346
|
+
}
|
|
347
|
+
finally {
|
|
348
|
+
backend.dispose();
|
|
453
349
|
}
|
|
454
|
-
return JSON.stringify(response, null, 2);
|
|
455
350
|
}
|
|
456
351
|
catch (error) {
|
|
457
|
-
return formatError({ file_path: args.file_path, sheet_name: args.sheet_name, operation: "group_by", error });
|
|
352
|
+
return formatError({ file_path: args.file_path, resolved_path: resolvedPath, sheet_name: args.sheet_name, operation: "group_by", error });
|
|
458
353
|
}
|
|
459
354
|
}
|
|
460
355
|
});
|
|
@@ -471,88 +366,38 @@ export const tablePivotSummary = tool({
|
|
|
471
366
|
},
|
|
472
367
|
execute: async (args, context) => {
|
|
473
368
|
await context.ask({ permission: "tablePivotSummary", patterns: [args.file_path], always: ["*"], metadata: {} });
|
|
369
|
+
const resolvedPath = resolvePath(args.file_path, context.directory);
|
|
474
370
|
try {
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
pivot
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
if (args.agg === 'min') {
|
|
497
|
-
pivot[rowKey][colKey] = Infinity;
|
|
498
|
-
}
|
|
499
|
-
else if (args.agg === 'max') {
|
|
500
|
-
pivot[rowKey][colKey] = -Infinity;
|
|
501
|
-
}
|
|
502
|
-
else {
|
|
503
|
-
pivot[rowKey][colKey] = 0;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
if (!counts[rowKey][colKey])
|
|
507
|
-
counts[rowKey][colKey] = 0;
|
|
508
|
-
counts[rowKey][colKey]++;
|
|
509
|
-
switch (args.agg) {
|
|
510
|
-
case 'sum':
|
|
511
|
-
case 'avg':
|
|
512
|
-
pivot[rowKey][colKey] += value;
|
|
513
|
-
break;
|
|
514
|
-
case 'min':
|
|
515
|
-
pivot[rowKey][colKey] = Math.min(pivot[rowKey][colKey], value);
|
|
516
|
-
break;
|
|
517
|
-
case 'max':
|
|
518
|
-
pivot[rowKey][colKey] = Math.max(pivot[rowKey][colKey], value);
|
|
519
|
-
break;
|
|
520
|
-
case 'count':
|
|
521
|
-
pivot[rowKey][colKey]++;
|
|
522
|
-
break;
|
|
523
|
-
}
|
|
371
|
+
const backend = createBackend(resolvedPath, args.force);
|
|
372
|
+
try {
|
|
373
|
+
const sheetIndex = resolveSheetIndex(backend, args.sheet_name);
|
|
374
|
+
const totalDataRows = backend.getTotalDataRows(sheetIndex);
|
|
375
|
+
const maxRowsScan = args.force ? totalDataRows : Math.min(totalDataRows, DEFAULT_MAX_ROW_CAP);
|
|
376
|
+
const { columns, pivot } = backend.pivotSummary(sheetIndex, args.row_field, args.col_field, args.value_field, args.agg, { maxRowsScan });
|
|
377
|
+
const truncated = !args.force && totalDataRows > DEFAULT_MAX_ROW_CAP;
|
|
378
|
+
const response = {
|
|
379
|
+
sheet_name: args.sheet_name || getSheetName(backend, sheetIndex),
|
|
380
|
+
row_field: args.row_field,
|
|
381
|
+
col_field: args.col_field,
|
|
382
|
+
value_field: args.value_field,
|
|
383
|
+
agg_type: args.agg,
|
|
384
|
+
columns,
|
|
385
|
+
pivot,
|
|
386
|
+
};
|
|
387
|
+
if (truncated) {
|
|
388
|
+
response.total_rows = totalDataRows;
|
|
389
|
+
response.processed_rows = maxRowsScan;
|
|
390
|
+
response.truncated = true;
|
|
391
|
+
response.message = `Processed ${DEFAULT_MAX_ROW_CAP} of ${totalDataRows} rows. Set force: true to process all rows.`;
|
|
524
392
|
}
|
|
525
|
-
|
|
526
|
-
if (args.agg === 'avg') {
|
|
527
|
-
Object.keys(pivot).forEach(rowKey => {
|
|
528
|
-
Object.keys(pivot[rowKey]).forEach(colKey => {
|
|
529
|
-
const count = counts[rowKey][colKey];
|
|
530
|
-
if (count > 0) {
|
|
531
|
-
pivot[rowKey][colKey] /= count;
|
|
532
|
-
}
|
|
533
|
-
});
|
|
534
|
-
});
|
|
393
|
+
return JSON.stringify(response, null, 2);
|
|
535
394
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
sheet_name: args.sheet_name || workbook.SheetNames[0],
|
|
539
|
-
row_field: args.row_field,
|
|
540
|
-
col_field: args.col_field,
|
|
541
|
-
value_field: args.value_field,
|
|
542
|
-
agg_type: args.agg,
|
|
543
|
-
columns: Array.from(allCols).sort(),
|
|
544
|
-
pivot,
|
|
545
|
-
};
|
|
546
|
-
if (truncated) {
|
|
547
|
-
response.total_rows = data.length;
|
|
548
|
-
response.processed_rows = effectiveData.length;
|
|
549
|
-
response.truncated = true;
|
|
550
|
-
response.message = `Processed ${DEFAULT_MAX_ROW_CAP} of ${data.length} rows. Set force: true to process all rows.`;
|
|
395
|
+
finally {
|
|
396
|
+
backend.dispose();
|
|
551
397
|
}
|
|
552
|
-
return JSON.stringify(response, null, 2);
|
|
553
398
|
}
|
|
554
399
|
catch (error) {
|
|
555
|
-
return formatError({ file_path: args.file_path, sheet_name: args.sheet_name, operation: "pivot_summary", error });
|
|
400
|
+
return formatError({ file_path: args.file_path, resolved_path: resolvedPath, sheet_name: args.sheet_name, operation: "pivot_summary", error });
|
|
556
401
|
}
|
|
557
402
|
}
|
|
558
403
|
});
|
|
@@ -566,33 +411,26 @@ export const tableAppendRows = tool({
|
|
|
566
411
|
},
|
|
567
412
|
execute: async (args, context) => {
|
|
568
413
|
await context.ask({ permission: "tableAppendRows", patterns: [args.file_path], always: ["*"], metadata: {} });
|
|
414
|
+
const resolvedPath = resolvePath(args.file_path, context.directory);
|
|
569
415
|
try {
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
416
|
+
const backend = createBackend(resolvedPath, args.force);
|
|
417
|
+
try {
|
|
418
|
+
const sheetIndex = resolveSheetIndex(backend, args.sheet_name);
|
|
419
|
+
backend.appendRows(sheetIndex, args.rows);
|
|
420
|
+
return JSON.stringify({
|
|
421
|
+
success: true,
|
|
422
|
+
file_path: args.file_path,
|
|
423
|
+
sheet_name: args.sheet_name || getSheetName(backend, sheetIndex),
|
|
424
|
+
rows_appended: args.rows.length,
|
|
425
|
+
message: `Successfully appended ${args.rows.length} rows`,
|
|
426
|
+
}, null, 2);
|
|
576
427
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
const header = hasExistingData ? undefined : Object.keys(args.rows[0] || {});
|
|
580
|
-
XLSX.utils.sheet_add_json(worksheet, args.rows, {
|
|
581
|
-
origin: -1,
|
|
582
|
-
header,
|
|
583
|
-
});
|
|
428
|
+
finally {
|
|
429
|
+
backend.dispose();
|
|
584
430
|
}
|
|
585
|
-
writeAtomic(workbook, resolvedPath);
|
|
586
|
-
return JSON.stringify({
|
|
587
|
-
success: true,
|
|
588
|
-
file_path: args.file_path,
|
|
589
|
-
sheet_name: args.sheet_name || workbook.SheetNames[0],
|
|
590
|
-
rows_appended: args.rows.length,
|
|
591
|
-
message: `Successfully appended ${args.rows.length} rows`,
|
|
592
|
-
}, null, 2);
|
|
593
431
|
}
|
|
594
432
|
catch (error) {
|
|
595
|
-
return formatError({ file_path: args.file_path, sheet_name: args.sheet_name, operation: "append_rows", error });
|
|
433
|
+
return formatError({ file_path: args.file_path, resolved_path: resolvedPath, sheet_name: args.sheet_name, operation: "append_rows", error });
|
|
596
434
|
}
|
|
597
435
|
}
|
|
598
436
|
});
|
|
@@ -607,67 +445,37 @@ export const tableUpdateCell = tool({
|
|
|
607
445
|
},
|
|
608
446
|
execute: async (args, context) => {
|
|
609
447
|
await context.ask({ permission: "tableUpdateCell", patterns: [args.file_path], always: ["*"], metadata: {} });
|
|
448
|
+
const resolvedPath = resolvePath(args.file_path, context.directory);
|
|
610
449
|
try {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
if (typeof args.value === 'number')
|
|
624
|
-
cellType = 'n';
|
|
625
|
-
else if (typeof args.value === 'boolean')
|
|
626
|
-
cellType = 'b';
|
|
627
|
-
else
|
|
628
|
-
cellType = 's';
|
|
629
|
-
worksheet[args.cell_address] = { v: args.value, t: cellType };
|
|
630
|
-
}
|
|
631
|
-
const newCellAddr = XLSX.utils.decode_cell(args.cell_address);
|
|
632
|
-
const currentRef = worksheet['!ref'];
|
|
633
|
-
if (currentRef) {
|
|
634
|
-
const currentRange = XLSX.utils.decode_range(currentRef);
|
|
635
|
-
const rowDifference = newCellAddr.r - currentRange.e.r;
|
|
636
|
-
const colDifference = newCellAddr.c - currentRange.e.c;
|
|
637
|
-
if (newCellAddr.r > currentRange.e.r || newCellAddr.c > currentRange.e.c) {
|
|
638
|
-
const maxReasonableRow = Math.max(currentRange.e.r + 1000, newCellAddr.r);
|
|
639
|
-
const maxReasonableCol = Math.max(currentRange.e.c + 26, newCellAddr.c);
|
|
640
|
-
const updatedRange = {
|
|
641
|
-
s: {
|
|
642
|
-
r: Math.min(currentRange.s.r, newCellAddr.r),
|
|
643
|
-
c: Math.min(currentRange.s.c, newCellAddr.c),
|
|
644
|
-
},
|
|
645
|
-
e: {
|
|
646
|
-
r: Math.min(Math.max(currentRange.e.r, newCellAddr.r), maxReasonableRow),
|
|
647
|
-
c: Math.min(Math.max(currentRange.e.c, newCellAddr.c), maxReasonableCol),
|
|
648
|
-
},
|
|
649
|
-
};
|
|
650
|
-
worksheet['!ref'] = XLSX.utils.encode_range(updatedRange);
|
|
651
|
-
}
|
|
450
|
+
const backend = createBackend(resolvedPath, args.force);
|
|
451
|
+
try {
|
|
452
|
+
const sheetIndex = resolveSheetIndex(backend, args.sheet_name);
|
|
453
|
+
backend.updateCell(sheetIndex, args.cell_address, args.value);
|
|
454
|
+
return JSON.stringify({
|
|
455
|
+
success: true,
|
|
456
|
+
file_path: args.file_path,
|
|
457
|
+
sheet_name: args.sheet_name || getSheetName(backend, sheetIndex),
|
|
458
|
+
cell_address: args.cell_address,
|
|
459
|
+
new_value: args.value,
|
|
460
|
+
message: `Successfully updated cell ${args.cell_address}`,
|
|
461
|
+
}, null, 2);
|
|
652
462
|
}
|
|
653
|
-
|
|
654
|
-
|
|
463
|
+
finally {
|
|
464
|
+
backend.dispose();
|
|
655
465
|
}
|
|
656
|
-
writeAtomic(workbook, resolvedPath);
|
|
657
|
-
return JSON.stringify({
|
|
658
|
-
success: true,
|
|
659
|
-
file_path: args.file_path,
|
|
660
|
-
sheet_name: args.sheet_name || workbook.SheetNames[0],
|
|
661
|
-
cell_address: args.cell_address,
|
|
662
|
-
new_value: args.value,
|
|
663
|
-
message: `Successfully updated cell ${args.cell_address}`,
|
|
664
|
-
}, null, 2);
|
|
665
466
|
}
|
|
666
467
|
catch (error) {
|
|
667
|
-
return formatError({ file_path: args.file_path, sheet_name: args.sheet_name, operation: "update_cell", error });
|
|
468
|
+
return formatError({ file_path: args.file_path, resolved_path: resolvedPath, sheet_name: args.sheet_name, operation: "update_cell", error });
|
|
668
469
|
}
|
|
669
470
|
}
|
|
670
471
|
});
|
|
472
|
+
function escapeCSVField(field, delimiter) {
|
|
473
|
+
const str = String(field ?? '');
|
|
474
|
+
if (str.includes(delimiter) || str.includes('"') || str.includes('\n') || str.includes('\r')) {
|
|
475
|
+
return '"' + str.replace(/"/g, '""') + '"';
|
|
476
|
+
}
|
|
477
|
+
return str;
|
|
478
|
+
}
|
|
671
479
|
export const tableCreateFile = tool({
|
|
672
480
|
description: "Create a new table file from data",
|
|
673
481
|
args: {
|
|
@@ -678,13 +486,14 @@ export const tableCreateFile = tool({
|
|
|
678
486
|
},
|
|
679
487
|
execute: async (args, context) => {
|
|
680
488
|
await context.ask({ permission: "tableCreateFile", patterns: [args.file_path], always: ["*"], metadata: {} });
|
|
489
|
+
const resolvedPath = resolvePath(args.file_path, context.directory);
|
|
681
490
|
try {
|
|
682
|
-
const resolvedPath = resolvePath(args.file_path, context.directory);
|
|
683
491
|
if (fs.existsSync(resolvedPath) && !args.overwrite) {
|
|
684
492
|
throw new Error(`File already exists. Set overwrite: true to replace it.`);
|
|
685
493
|
}
|
|
686
|
-
|
|
687
|
-
|
|
494
|
+
if (args.overwrite) {
|
|
495
|
+
invalidateWorkbookCache(resolvedPath);
|
|
496
|
+
}
|
|
688
497
|
let parsedData = args.data;
|
|
689
498
|
if (typeof parsedData === 'string') {
|
|
690
499
|
try {
|
|
@@ -697,27 +506,51 @@ export const tableCreateFile = tool({
|
|
|
697
506
|
if (!Array.isArray(parsedData)) {
|
|
698
507
|
throw new Error(`Data must be an array, received: ${typeof parsedData}`);
|
|
699
508
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
509
|
+
const ext = path.extname(resolvedPath).toLowerCase();
|
|
510
|
+
if (ext === '.csv' || ext === '.tsv' || ext === '.txt') {
|
|
511
|
+
const delimiter = ext === '.tsv' ? '\t' : ',';
|
|
512
|
+
const lines = [];
|
|
513
|
+
if (parsedData.length > 0) {
|
|
514
|
+
if (Array.isArray(parsedData[0])) {
|
|
515
|
+
for (const row of parsedData) {
|
|
516
|
+
lines.push(row.map((v) => escapeCSVField(v, delimiter)).join(delimiter));
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
const keys = Object.keys(parsedData[0]);
|
|
521
|
+
lines.push(keys.map(k => escapeCSVField(k, delimiter)).join(delimiter));
|
|
522
|
+
for (const row of parsedData) {
|
|
523
|
+
lines.push(keys.map(k => escapeCSVField(row[k], delimiter)).join(delimiter));
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
fs.writeFileSync(resolvedPath, lines.join('\n'));
|
|
705
528
|
}
|
|
706
529
|
else {
|
|
707
|
-
|
|
530
|
+
const workbook = XLSX.utils.book_new();
|
|
531
|
+
let worksheet;
|
|
532
|
+
if (parsedData.length === 0) {
|
|
533
|
+
worksheet = XLSX.utils.aoa_to_sheet([]);
|
|
534
|
+
}
|
|
535
|
+
else if (Array.isArray(parsedData[0])) {
|
|
536
|
+
worksheet = XLSX.utils.aoa_to_sheet(parsedData);
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
worksheet = XLSX.utils.json_to_sheet(parsedData);
|
|
540
|
+
}
|
|
541
|
+
XLSX.utils.book_append_sheet(workbook, worksheet, args.sheet_name);
|
|
542
|
+
writeAtomic(workbook, resolvedPath);
|
|
708
543
|
}
|
|
709
|
-
XLSX.utils.book_append_sheet(workbook, worksheet, args.sheet_name);
|
|
710
|
-
writeAtomic(workbook, resolvedPath);
|
|
711
544
|
return JSON.stringify({
|
|
712
545
|
success: true,
|
|
713
546
|
file_path: args.file_path,
|
|
714
547
|
sheet_name: args.sheet_name,
|
|
715
548
|
rows_created: parsedData.length,
|
|
716
|
-
message: `Successfully created
|
|
549
|
+
message: `Successfully created file with ${parsedData.length} rows`,
|
|
717
550
|
}, null, 2);
|
|
718
551
|
}
|
|
719
552
|
catch (error) {
|
|
720
|
-
return formatError({ file_path: args.file_path, sheet_name: args.sheet_name, operation: "create_file", error });
|
|
553
|
+
return formatError({ file_path: args.file_path, resolved_path: resolvedPath, sheet_name: args.sheet_name, operation: "create_file", error });
|
|
721
554
|
}
|
|
722
555
|
}
|
|
723
556
|
});
|