terminal-smart-cli 0.97.6 → 0.97.8

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.
@@ -0,0 +1,228 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const JSZip = require('jszip');
6
+
7
+ const CELL_RE = /^[A-Z]{1,3}[1-9][0-9]{0,6}$/;
8
+
9
+ function xmlDecode(value = '') {
10
+ return String(value)
11
+ .replace(/&lt;/g, '<').replace(/&gt;/g, '>')
12
+ .replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, '&');
13
+ }
14
+
15
+ function xmlEncode(value = '') {
16
+ return String(value)
17
+ .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
18
+ .replace(/"/g, '&quot;').replace(/'/g, '&apos;');
19
+ }
20
+
21
+ function attr(xml, name) {
22
+ const match = String(xml).match(new RegExp(`(?:^|\\s)${name}="([^"]*)"`, 'i'));
23
+ return match ? xmlDecode(match[1]) : '';
24
+ }
25
+
26
+ function tagPrefix(xml) {
27
+ const match = String(xml).match(/<(\w+:)?worksheet\b/i);
28
+ return match && match[1] ? match[1] : '';
29
+ }
30
+
31
+ function normalizeTarget(target) {
32
+ const clean = String(target || '').replace(/\\/g, '/').replace(/^\//, '');
33
+ return clean.startsWith('xl/') ? clean : path.posix.normalize(`xl/${clean}`);
34
+ }
35
+
36
+ async function workbookSheets(zip) {
37
+ const workbookFile = zip.file('xl/workbook.xml');
38
+ const relsFile = zip.file('xl/_rels/workbook.xml.rels');
39
+ if (!workbookFile || !relsFile) throw new Error('Estrutura XLSX incompleta: workbook ou relacionamentos ausentes.');
40
+ const [workbookXml, relsXml] = await Promise.all([workbookFile.async('string'), relsFile.async('string')]);
41
+ const rels = new Map();
42
+ for (const match of relsXml.matchAll(/<(?:\w+:)?Relationship\b([^>]*?)\/?\s*>/gi)) {
43
+ const type = attr(match[1], 'Type');
44
+ if (!/\/worksheet$/i.test(type)) continue;
45
+ rels.set(attr(match[1], 'Id'), normalizeTarget(attr(match[1], 'Target')));
46
+ }
47
+ const sheets = [];
48
+ for (const match of workbookXml.matchAll(/<(?:\w+:)?sheet\b([^>]*?)\/?\s*>/gi)) {
49
+ const name = attr(match[1], 'name');
50
+ const relId = attr(match[1], 'r:id');
51
+ const file = rels.get(relId);
52
+ if (name && file && zip.file(file)) sheets.push({ name, file });
53
+ }
54
+ if (!sheets.length) throw new Error('Nenhuma aba editável foi encontrada no XLSX.');
55
+ return sheets;
56
+ }
57
+
58
+ function cellColumn(address) {
59
+ const letters = address.match(/^[A-Z]+/)[0];
60
+ let value = 0;
61
+ for (const char of letters) value = value * 26 + char.charCodeAt(0) - 64;
62
+ return value;
63
+ }
64
+
65
+ function cellRow(address) {
66
+ return Number(address.match(/\d+$/)[0]);
67
+ }
68
+
69
+ function setType(openTag, type) {
70
+ let tag = openTag.replace(/\s+t="[^"]*"/i, '');
71
+ if (type) tag = tag.replace(/\s*\/?>$/, ` t="${type}">`);
72
+ else tag = tag.replace(/\s*\/?>$/, '>');
73
+ return tag;
74
+ }
75
+
76
+ function rewriteCell(cellXml, address, update, prefix) {
77
+ const open = cellXml && cellXml.match(/^<[^>]+>/)?.[0];
78
+ const base = open || `<${prefix}c r="${address}">`;
79
+ if (update.formula != null) {
80
+ const formula = xmlEncode(String(update.formula).replace(/^=/, ''));
81
+ return `${setType(base, '')}<${prefix}f>${formula}</${prefix}f></${prefix}c>`;
82
+ }
83
+ const raw = update.valor;
84
+ if (raw == null) return `${setType(base, '')}</${prefix}c>`;
85
+ const numeric = typeof raw === 'number' || (typeof raw === 'string' && /^-?\d+(?:[.,]\d+)?$/.test(raw.trim()));
86
+ if (numeric) {
87
+ const number = typeof raw === 'number' ? raw : Number(raw.trim().replace(',', '.'));
88
+ if (!Number.isFinite(number)) throw new Error(`Valor numérico inválido em ${address}.`);
89
+ return `${setType(base, 'n')}<${prefix}v>${number}</${prefix}v></${prefix}c>`;
90
+ }
91
+ const text = xmlEncode(String(raw));
92
+ const preserve = /^\s|\s$/.test(String(raw)) ? ' xml:space="preserve"' : '';
93
+ return `${setType(base, 'inlineStr')}<${prefix}is><${prefix}t${preserve}>${text}</${prefix}t></${prefix}is></${prefix}c>`;
94
+ }
95
+
96
+ function findCell(xml, address) {
97
+ const escaped = address.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
98
+ const start = `<(?:\\w+:)?c\\b(?=[^>]*\\br="${escaped}")`;
99
+ const re = new RegExp(`(?:${start}[^>]*\\/>|${start}[^>]*>[\\s\\S]*?<\\/(?:\\w+:)?c>)`, 'i');
100
+ const match = re.exec(xml);
101
+ return match ? { index: match.index, length: match[0].length, xml: match[0] } : null;
102
+ }
103
+
104
+ function insertCell(xml, address, cellXml, prefix) {
105
+ const rowNumber = cellRow(address);
106
+ const rowRe = new RegExp(`<(?:\\w+:)?row\\b[^>]*\\br="${rowNumber}"[^>]*>[\\s\\S]*?<\\/(?:\\w+:)?row>`, 'i');
107
+ const rowMatch = rowRe.exec(xml);
108
+ if (rowMatch) {
109
+ const rowXml = rowMatch[0];
110
+ const targetColumn = cellColumn(address);
111
+ let insertAt = rowXml.search(new RegExp(`</${prefix}row>`, 'i'));
112
+ for (const match of rowXml.matchAll(/(?:<(?:\w+:)?c\b(?=[^>]*\br="([A-Z]+\d+)")[^>]*\/>|<(?:\w+:)?c\b(?=[^>]*\br="([A-Z]+\d+)")[^>]*>[\s\S]*?<\/(?:\w+:)?c>)/gi)) {
113
+ const existingAddress = match[1] || match[2];
114
+ if (cellColumn(existingAddress) > targetColumn) { insertAt = match.index; break; }
115
+ }
116
+ const updatedRow = rowXml.slice(0, insertAt) + cellXml + rowXml.slice(insertAt);
117
+ return xml.slice(0, rowMatch.index) + updatedRow + xml.slice(rowMatch.index + rowMatch[0].length);
118
+ }
119
+ const sheetData = /<(?:\w+:)?sheetData\b[^>]*>[\s\S]*?<\/(?:\w+:)?sheetData>/i.exec(xml);
120
+ if (!sheetData) throw new Error('Estrutura XLSX inválida: sheetData ausente.');
121
+ const dataXml = sheetData[0];
122
+ let insertAt = dataXml.search(new RegExp(`</${prefix}sheetData>`, 'i'));
123
+ for (const match of dataXml.matchAll(/<(?:\w+:)?row\b[^>]*\br="(\d+)"[^>]*(?:\/>|>[\s\S]*?<\/(?:\w+:)?row>)/gi)) {
124
+ if (Number(match[1]) > rowNumber) { insertAt = match.index; break; }
125
+ }
126
+ const rowXml = `<${prefix}row r="${rowNumber}">${cellXml}</${prefix}row>`;
127
+ const updatedData = dataXml.slice(0, insertAt) + rowXml + dataXml.slice(insertAt);
128
+ return xml.slice(0, sheetData.index) + updatedData + xml.slice(sheetData.index + sheetData[0].length);
129
+ }
130
+
131
+ function updateCell(xml, address, update) {
132
+ const normalized = String(address || '').toUpperCase();
133
+ if (!CELL_RE.test(normalized)) throw new Error(`Célula inválida: ${address}`);
134
+ const prefix = tagPrefix(xml);
135
+ const found = findCell(xml, normalized);
136
+ const replacement = rewriteCell(found?.xml, normalized, update, prefix);
137
+ if (!found) return insertCell(xml, normalized, replacement, prefix);
138
+ return xml.slice(0, found.index) + replacement + xml.slice(found.index + found.length);
139
+ }
140
+
141
+ function cellText(cellXml, sharedStrings) {
142
+ const type = attr(cellXml.match(/^<[^>]+>/)?.[0] || '', 't');
143
+ if (type === 'inlineStr') {
144
+ return [...cellXml.matchAll(/<(?:\w+:)?t\b[^>]*>([\s\S]*?)<\/(?:\w+:)?t>/gi)].map(m => xmlDecode(m[1])).join('');
145
+ }
146
+ const value = cellXml.match(/<(?:\w+:)?v\b[^>]*>([\s\S]*?)<\/(?:\w+:)?v>/i)?.[1];
147
+ if (value == null) return null;
148
+ if (type === 's') return sharedStrings[Number(xmlDecode(value))] ?? null;
149
+ if (type === 'str') return xmlDecode(value);
150
+ return null;
151
+ }
152
+
153
+ function parseSharedStrings(xml) {
154
+ if (!xml) return [];
155
+ return [...xml.matchAll(/<(?:\w+:)?si\b[^>]*>([\s\S]*?)<\/(?:\w+:)?si>/gi)].map(si =>
156
+ [...si[1].matchAll(/<(?:\w+:)?t\b[^>]*>([\s\S]*?)<\/(?:\w+:)?t>/gi)].map(t => xmlDecode(t[1])).join('')
157
+ );
158
+ }
159
+
160
+ function substituteInSheet(xml, rule, sharedStrings) {
161
+ const search = String(rule.buscar || '');
162
+ if (!search) return { xml, changes: [] };
163
+ const replacement = String(rule.substituir ?? '');
164
+ const prefix = tagPrefix(xml);
165
+ const changes = [];
166
+ const cellRe = /(?:<(?:\w+:)?c\b(?=[^>]*\br="([A-Z]+\d+)")[^>]*\/>|<(?:\w+:)?c\b(?=[^>]*\br="([A-Z]+\d+)")[^>]*>[\s\S]*?<\/(?:\w+:)?c>)/gi;
167
+ let output = '';
168
+ let cursor = 0;
169
+ for (const match of xml.matchAll(cellRe)) {
170
+ if (!rule.todas && changes.length) break;
171
+ const address = match[1] || match[2];
172
+ const current = cellText(match[0], sharedStrings);
173
+ if (typeof current !== 'string' || !current.includes(search)) continue;
174
+ const next = rule.todas ? current.split(search).join(replacement) : current.replace(search, replacement);
175
+ const rewritten = rewriteCell(match[0], address, { valor: next }, prefix);
176
+ output += xml.slice(cursor, match.index) + rewritten;
177
+ cursor = match.index + match[0].length;
178
+ changes.push(address);
179
+ }
180
+ return { xml: changes.length ? output + xml.slice(cursor) : xml, changes };
181
+ }
182
+
183
+ async function editXlsxCompat(source, destination, input = {}) {
184
+ const zip = await JSZip.loadAsync(fs.readFileSync(source));
185
+ const sheets = await workbookSheets(zip);
186
+ const sharedXml = zip.file('xl/sharedStrings.xml') ? await zip.file('xl/sharedStrings.xml').async('string') : '';
187
+ const sharedStrings = parseSharedStrings(sharedXml);
188
+ const xmlByFile = new Map();
189
+ const changes = [];
190
+ const getSheet = name => {
191
+ if (!name) return sheets[0];
192
+ return sheets.find(sheet => sheet.name.toLowerCase() === String(name).toLowerCase());
193
+ };
194
+ const readXml = async sheet => {
195
+ if (!xmlByFile.has(sheet.file)) xmlByFile.set(sheet.file, await zip.file(sheet.file).async('string'));
196
+ return xmlByFile.get(sheet.file);
197
+ };
198
+
199
+ for (const update of (Array.isArray(input.alteracoes) ? input.alteracoes : []).slice(0, 500)) {
200
+ const sheet = getSheet(update.aba);
201
+ if (!sheet) throw new Error(`Aba não encontrada: ${update.aba}`);
202
+ const address = String(update.celula || '').toUpperCase();
203
+ const xml = await readXml(sheet);
204
+ xmlByFile.set(sheet.file, updateCell(xml, address, update));
205
+ changes.push(`${sheet.name}!${address}`);
206
+ }
207
+ for (const rule of (Array.isArray(input.substituicoes) ? input.substituicoes : []).slice(0, 50)) {
208
+ let replaced = 0;
209
+ for (const sheet of sheets) {
210
+ if (rule.aba && sheet.name.toLowerCase() !== String(rule.aba).toLowerCase()) continue;
211
+ const result = substituteInSheet(await readXml(sheet), { ...rule, todas: rule.todas && true }, sharedStrings);
212
+ if (result.changes.length) {
213
+ xmlByFile.set(sheet.file, result.xml);
214
+ for (const address of result.changes) changes.push(`${sheet.name}!${address}`);
215
+ replaced += result.changes.length;
216
+ }
217
+ if (!rule.todas && replaced) break;
218
+ }
219
+ if (!replaced) throw new Error(`Texto não encontrado na planilha: ${String(rule.buscar || '').slice(0, 80)}`);
220
+ }
221
+ if (!changes.length) throw new Error('Informe alteracoes ou substituicoes.');
222
+ for (const [file, xml] of xmlByFile) zip.file(file, xml);
223
+ const buffer = await zip.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE' });
224
+ fs.writeFileSync(destination, buffer);
225
+ return { changes, engine: 'ooxml-compat' };
226
+ }
227
+
228
+ module.exports = { editXlsxCompat, _updateCell: updateCell, _substituteInSheet: substituteInSheet };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "terminal-smart-cli",
3
- "version": "0.97.6",
3
+ "version": "0.97.8",
4
4
  "description": "Terminal Smart no seu terminal — pergunte, analise logs por pipe e orquestre agentes de IA. Comando: ts",
5
5
  "bin": {
6
6
  "ts": "bin/ts.js"
7
7
  },
8
8
  "scripts": {
9
- "test": "node test/core.test.js && node test/intelligence-core.test.js && node test/cloud-slug.test.js && node test/eval-model.test.js && node test/project-cache.test.js && node test/memory-bus.test.js && node test/capabilities.test.js && node test/mcp-e2e.test.js && node test/erros.test.js && node test/capability-pack.test.js && node test/byok.test.js && node test/conhecimento.test.js && node test/policy.test.js && node test/temas.test.js && node test/skill-index.test.js"
9
+ "test": "node test/core.test.js && node test/intelligence-core.test.js && node test/cloud-slug.test.js && node test/eval-model.test.js && node test/project-cache.test.js && node test/memory-bus.test.js && node test/capabilities.test.js && node test/mcp-e2e.test.js && node test/erros.test.js && node test/capability-pack.test.js && node test/byok.test.js && node test/conhecimento.test.js && node test/policy.test.js && node test/temas.test.js && node test/skill-index.test.js && node test/doctor.test.js && node test/google-workspace-tools.test.js && node test/office-editors.test.js"
10
10
  },
11
11
  "files": [
12
12
  "bin",
@@ -24,13 +24,14 @@
24
24
  "ssh",
25
25
  "agente"
26
26
  ],
27
- "author": "Terminal Smart <contato@terminalsmart.com.br>",
27
+ "author": "Terminal Smart <contato@g3tecnegocios.com>",
28
28
  "homepage": "https://terminalsmart.com.br/cli",
29
29
  "bugs": {
30
30
  "url": "https://terminalsmart.com.br/cli"
31
31
  },
32
32
  "license": "UNLICENSED",
33
33
  "dependencies": {
34
+ "exceljs": "^4.4.0",
34
35
  "jszip": "^3.10.1",
35
36
  "puppeteer-core": "^23.11.1",
36
37
  "qrcode-terminal": "^0.12.0",