terminal-smart-cli 0.97.29 → 0.97.31
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/bin/ts.js +73 -6
- package/lib/acp.js +40 -18
- package/lib/agent.js +522 -73
- package/lib/core.js +11 -4
- package/lib/eval.js +2 -2
- package/lib/evolution-proposal.js +47 -0
- package/lib/file-lock.js +42 -0
- package/lib/gateways.js +69 -0
- package/lib/intelligence-core.js +115 -33
- package/lib/meta.js +6 -2
- package/lib/owner-audit.js +112 -0
- package/lib/providers.js +4 -1
- package/lib/recovery.js +12 -0
- package/lib/tools.js +145 -25
- package/lib/ui.js +10 -0
- package/package.json +2 -2
package/lib/tools.js
CHANGED
|
@@ -6,6 +6,7 @@ const os = require('os');
|
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const crypto = require('crypto');
|
|
8
8
|
const compactor = require('./compactor');
|
|
9
|
+
const fileLock = require('./file-lock');
|
|
9
10
|
|
|
10
11
|
// ── SNAPSHOT antes de sobrescrever ──────────────────────────────────────────
|
|
11
12
|
// Toda escrita/edição faz uma cópia do original em ~/.ts/backups/<hash>/<nome>.<ts>.bak
|
|
@@ -38,6 +39,23 @@ function _snapshot(absPath) {
|
|
|
38
39
|
// pra compatibilidade (tools.isDestructive segue funcionando pra quem já importava).
|
|
39
40
|
const { isDestructive } = require('./core');
|
|
40
41
|
|
|
42
|
+
// Conteúdo de e-mail/site/arquivo pode tentar convencer o modelo a mandar
|
|
43
|
+
// segredos para fora. O prompt ajuda, mas a defesa precisa existir também no
|
|
44
|
+
// executor: se um comando combina transporte de rede com fonte sensível, ele
|
|
45
|
+
// nunca é executado, inclusive quando foi formado por uma injeção indireta.
|
|
46
|
+
function secretExfiltrationReason(cmd) {
|
|
47
|
+
const text = String(cmd || '');
|
|
48
|
+
const transports = /\b(?:curl|wget|invoke-webrequest|invoke-restmethod|iwr|irm)\b/i.test(text)
|
|
49
|
+
|| /\b(?:fetch|axios|requests\.(?:get|post|put)|http\.request|https\.request)\b/i.test(text);
|
|
50
|
+
if (!transports) return null;
|
|
51
|
+
const sensitiveFile = /(?:^|[\s"'=:@])(?:~[\\/])?(?:\.env(?:\.[a-z0-9_-]+)?|\.ssh[\\/](?:id_[a-z0-9_-]+|authorized_keys|config)|(?:id_rsa|id_ed25519|credentials(?:\.json)?|service[-_]?account[^\s"']*\.json))\b/i.test(text);
|
|
52
|
+
const sensitiveRuntime = /\b(?:process\.env|os\.environ|\$env:|get-childitem\s+env:)\b/i.test(text);
|
|
53
|
+
if (sensitiveFile || sensitiveRuntime) {
|
|
54
|
+
return 'BLOQUEADO: possível exfiltração de segredo. Um comando de rede não pode ler/enviar .env, chaves SSH, credenciais ou variáveis de ambiente. Nunca transmita segredos; use apenas dados públicos explicitamente necessários.';
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
41
59
|
// Definições no formato OpenAI function-calling — schemas SIMPLES de propósito
|
|
42
60
|
// (Gemini via CDC rejeita propertyNames/additionalProperties; só type/properties/required).
|
|
43
61
|
const DEFS = [
|
|
@@ -382,11 +400,55 @@ function _guardTsHome(p) {
|
|
|
382
400
|
// por algo que JAMAIS deve rodar é desperdício puro: recusamos NA HORA, apontando a
|
|
383
401
|
// alternativa cirúrgica. Retorna a mensagem (motivo pro modelo) quando é auto-destrutivo,
|
|
384
402
|
// ou null quando é seguro (aí segue o fluxo normal, inclusive o gate de aprovação comum).
|
|
403
|
+
// Apenas conversões de leitura, de UMA operação e sem encadeamento. Não tentamos
|
|
404
|
+
// "adivinhar" comandos mutantes: nesses casos, a recusa explicada é mais segura.
|
|
405
|
+
function _psLiteral(value) { return String(value || '').replace(/'/g, "''"); }
|
|
406
|
+
function _normalizeWindowsReadOnlyCommand(cmd, platform = process.platform) {
|
|
407
|
+
if (platform !== 'win32') return null;
|
|
408
|
+
const s = String(cmd || '').trim();
|
|
409
|
+
if (!s || /[\r\n;&|><`]/.test(s) || /^\s*(?:powershell|pwsh)(?:\.exe)?\b/i.test(s)) return null;
|
|
410
|
+
const drivePath = (raw) => {
|
|
411
|
+
const found = String(raw || '').trim().match(/^\/([a-zA-Z])\/(.+)$/);
|
|
412
|
+
return found ? `${found[1].toUpperCase()}:\\${found[2].replace(/\//g, '\\')}` : String(raw || '').trim();
|
|
413
|
+
};
|
|
414
|
+
let m = s.match(/^grep\s+(-n\s+)?(?:--\s+)?(?:"([^"]+)"|'([^']+)'|(\S+))\s+([^\s]+)$/i);
|
|
415
|
+
if (m) {
|
|
416
|
+
const pattern = m[2] || m[3] || m[4];
|
|
417
|
+
const file = drivePath(m[5]);
|
|
418
|
+
return {
|
|
419
|
+
command: `powershell -NoProfile -Command "Select-String -LiteralPath '${_psLiteral(file)}' -Pattern '${_psLiteral(pattern)}' | ForEach-Object { '{0}:{1}' -f $_.LineNumber,$_.Line }"`,
|
|
420
|
+
note: 'grep convertido para Select-String (somente leitura).',
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
m = s.match(/^(tail|head)\s+-n\s+(\d{1,5})\s+([^\s]+)$/i);
|
|
424
|
+
if (m) {
|
|
425
|
+
const count = Math.min(10000, Math.max(1, Number(m[2])));
|
|
426
|
+
const file = drivePath(m[3]);
|
|
427
|
+
const part = m[1].toLowerCase() === 'tail' ? `-Tail ${count}` : `-TotalCount ${count}`;
|
|
428
|
+
return {
|
|
429
|
+
command: `powershell -NoProfile -Command "Get-Content -LiteralPath '${_psLiteral(file)}' ${part}"`,
|
|
430
|
+
note: `${m[1].toLowerCase()} convertido para Get-Content (somente leitura).`,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
if (/^ls(?:\s+-[a-z]+)?$/i.test(s)) return { command: 'dir /a', note: 'ls convertido para dir /a (somente leitura).' };
|
|
434
|
+
if (/^pwd$/i.test(s)) return { command: 'cd', note: 'pwd convertido para cd (somente leitura).' };
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
|
|
385
438
|
// Gotchas de shell no Windows que falham de um jeito CONFUSO (o agente perdia vários
|
|
386
439
|
// passos tentando de novo). Devolve a mensagem com o CONSERTO pronto, ou null.
|
|
387
440
|
function _shellGotcha(cmd) {
|
|
388
441
|
const s = String(cmd || '');
|
|
389
442
|
const multi = /[\r\n]/.test(s);
|
|
443
|
+
// executar_comando usa cmd.exe. Modelos alternam facilmente entre os dois
|
|
444
|
+
// dialetos e `Select-String`/`Get-Content` soltos viram "não reconhecido",
|
|
445
|
+
// desperdiçando uma rodada antes de o recovery engine conseguir explicar.
|
|
446
|
+
// Recusar antes da execução preserva a etapa e entrega a forma correta.
|
|
447
|
+
if (process.platform === 'win32'
|
|
448
|
+
&& !/^\s*(?:powershell|pwsh)(?:\.exe)?\b/i.test(s)
|
|
449
|
+
&& /(?:^|[&|]\s*|\s)(?:Select-String|Get-(?:Content|ChildItem|Item|FileHash|NetTCPConnection)|Test-Path|Set-(?:Content|Location)|Add-Content|Remove-Item|Copy-Item|Move-Item|New-Item)(?:\s|$)/i.test(s)) {
|
|
450
|
+
return 'Este shell é cmd.exe, mas o comando é do PowerShell. Chame explicitamente: powershell -NoProfile -Command "<comando>". Não repita o cmdlet solto.';
|
|
451
|
+
}
|
|
390
452
|
// python -c / node -e multilinha ou com import() dinâmico: quebra no eval / morre calado
|
|
391
453
|
if (/\bpython3?\s+-c\b/.test(s) && multi)
|
|
392
454
|
return 'python -c MULTILINHA falha em silêncio no Windows. Escreva um arquivo .py com escrever_arquivo e rode "python arquivo.py".';
|
|
@@ -410,6 +472,39 @@ function _shellGotcha(cmd) {
|
|
|
410
472
|
return null;
|
|
411
473
|
}
|
|
412
474
|
|
|
475
|
+
// `path.resolve` protege contra "..", mas não contra `raiz/link -> /fora`.
|
|
476
|
+
// Para não ler/escrever através de junction/symlink, comparamos também o caminho
|
|
477
|
+
// REAL do alvo (ou do ancestral existente quando o arquivo ainda será criado).
|
|
478
|
+
function _pathStartsWith(child, parent) {
|
|
479
|
+
const norm = (p) => process.platform === 'win32' ? String(p).toLowerCase() : String(p);
|
|
480
|
+
const c = norm(child), r = norm(parent);
|
|
481
|
+
return c === r || c.startsWith(r + path.sep);
|
|
482
|
+
}
|
|
483
|
+
function _nearestExistingReal(p) {
|
|
484
|
+
let cur = path.resolve(p);
|
|
485
|
+
for (;;) {
|
|
486
|
+
try { return fs.realpathSync.native ? fs.realpathSync.native(cur) : fs.realpathSync(cur); } catch (_) {}
|
|
487
|
+
const parent = path.dirname(cur);
|
|
488
|
+
if (parent === cur) return null;
|
|
489
|
+
cur = parent;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
function _scopeContains(confineDir, candidate) {
|
|
493
|
+
const root = path.resolve(confineDir);
|
|
494
|
+
const abs = path.resolve(candidate);
|
|
495
|
+
if (!_pathStartsWith(abs, root)) return false;
|
|
496
|
+
const rootReal = _nearestExistingReal(root);
|
|
497
|
+
const targetReal = _nearestExistingReal(abs);
|
|
498
|
+
// Se não conseguimos resolver (raiz removida no meio da operação), falha
|
|
499
|
+
// fechada: é mais seguro recusar que abrir caminho fora da missão.
|
|
500
|
+
return !!rootReal && !!targetReal && _pathStartsWith(targetReal, rootReal);
|
|
501
|
+
}
|
|
502
|
+
function _acquireFileMutation(target) {
|
|
503
|
+
const lock = fileLock.acquire(target);
|
|
504
|
+
if (lock) return { lock };
|
|
505
|
+
return { erro: 'ARQUIVO_OCUPADO: outro processo do Terminal Smart está alterando este arquivo. Aguarde e tente uma vez; nunca force a remoção do lock.' };
|
|
506
|
+
}
|
|
507
|
+
|
|
413
508
|
function _commandScopeViolation(cmd, confineDir) {
|
|
414
509
|
if (!confineDir) return null;
|
|
415
510
|
const text = String(cmd || '');
|
|
@@ -422,9 +517,14 @@ function _commandScopeViolation(cmd, confineDir) {
|
|
|
422
517
|
const candidates = [];
|
|
423
518
|
for (const m of text.matchAll(/[A-Za-z]:[\\/][^ \t\r\n"'|;&<>]*/g)) candidates.push(m[0]);
|
|
424
519
|
for (const m of text.matchAll(/(?:^|[\s"'=])((?:\/(?!\/))[^ \t\r\n"'|;&<>]*)/g)) candidates.push(m[1]);
|
|
520
|
+
// Alvos relativos (ex.: `echo x > link/arquivo`) também podem atravessar
|
|
521
|
+
// um symlink. Capturamos apenas tokens com separador, ignorando flags/URLs.
|
|
522
|
+
for (const m of text.matchAll(/(?:^|[\s"'=<>])((?:\.?[\\/])?[A-Za-z0-9_.@-]+(?:[\\/][A-Za-z0-9_.@-]+)+)/g)) {
|
|
523
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(m[1])) candidates.push(m[1]);
|
|
524
|
+
}
|
|
425
525
|
for (const raw of candidates) {
|
|
426
|
-
const abs = path.resolve(raw);
|
|
427
|
-
if (
|
|
526
|
+
const abs = path.resolve(root, raw);
|
|
527
|
+
if (!_scopeContains(root, abs)) {
|
|
428
528
|
return `CAPABILITY_DENIED: comando mutante referencia "${raw}", fora da raiz autorizada "${root}".`;
|
|
429
529
|
}
|
|
430
530
|
}
|
|
@@ -553,9 +653,7 @@ async function execute(name, input, opts = {}) {
|
|
|
553
653
|
}
|
|
554
654
|
const _outsideScope = (p) => {
|
|
555
655
|
if (!opts.confineDir) return false;
|
|
556
|
-
|
|
557
|
-
const abs = path.resolve(p);
|
|
558
|
-
return abs !== root && !abs.startsWith(root + path.sep);
|
|
656
|
+
return !_scopeContains(opts.confineDir, p);
|
|
559
657
|
};
|
|
560
658
|
const _scopeError = (p) => ({ erro: `CAPABILITY_DENIED: "${path.resolve(p)}" está fora da raiz autorizada "${path.resolve(opts.confineDir)}".` });
|
|
561
659
|
try {
|
|
@@ -563,6 +661,7 @@ async function execute(name, input, opts = {}) {
|
|
|
563
661
|
case 'executar_comando': {
|
|
564
662
|
const comando = String(input.comando || '');
|
|
565
663
|
{ const scope = _commandScopeViolation(comando, opts.confineDir); if (scope) return { erro: scope }; }
|
|
664
|
+
{ const exfil = secretExfiltrationReason(comando); if (exfil) return { erro: exfil }; }
|
|
566
665
|
// RECUSA INSTANTÂNEA (defesa em profundidade — o gate do agent.js já pega antes):
|
|
567
666
|
// comando auto-destrutivo NUNCA roda e NUNCA espera aprovação.
|
|
568
667
|
{ const sd = selfDestructiveReason(comando, { cwd: baseDir }); if (sd) return { erro: sd }; }
|
|
@@ -570,14 +669,16 @@ async function execute(name, input, opts = {}) {
|
|
|
570
669
|
// Barrar ANTES de rodar (com o conserto na mão) economiza o ciclo erro→tentativa→erro
|
|
571
670
|
// que o agente repetia: python -c multilinha morria em silêncio, node -e com import()
|
|
572
671
|
// quebrava no eval, e `timeout /t` (cmd) virava o timeout do coreutils sob git-bash.
|
|
573
|
-
|
|
672
|
+
const normalized = _normalizeWindowsReadOnlyCommand(comando);
|
|
673
|
+
const comandoExecutado = normalized ? normalized.command : comando;
|
|
674
|
+
{ const g = _shellGotcha(comandoExecutado); if (g) return { erro: g }; }
|
|
574
675
|
// Comandos de INSTALL/BUILD (apt/npm/pip/make/gcc/docker build…) levam muito mais que 60s —
|
|
575
676
|
// sem isso o apt-get de vários pacotes MORRIA no timeout padrão (nada instalava, em silêncio).
|
|
576
677
|
// Eles ganham 10min de default (até 30min se o agente pedir timeout_s); resto segue 60s.
|
|
577
|
-
const _slow = /(^|[\s;|&(])(apt|apt-get|aptitude|dpkg|yum|dnf|zypper|pacman|snap|brew|pip|pip3|npm|yarn|pnpm|gem|cargo|go\s+(get|build|install)|make|cmake|meson|ninja|gcc|g\+\+|clang|mvn|gradle|\.\/gradlew|dotnet|composer|bundle|flutter\s+(build|pub)|docker\s+(build|pull|compose)|configure|\.\/configure|\.\/build)([\s;|&)]|$)/i.test(
|
|
678
|
+
const _slow = /(^|[\s;|&(])(apt|apt-get|aptitude|dpkg|yum|dnf|zypper|pacman|snap|brew|pip|pip3|npm|yarn|pnpm|gem|cargo|go\s+(get|build|install)|make|cmake|meson|ninja|gcc|g\+\+|clang|mvn|gradle|\.\/gradlew|dotnet|composer|bundle|flutter\s+(build|pub)|docker\s+(build|pull|compose)|configure|\.\/configure|\.\/build)([\s;|&)]|$)/i.test(comandoExecutado);
|
|
578
679
|
const _dflt = _slow ? 600 : 60;
|
|
579
680
|
const timeout = Math.min(1800, Math.max(5, Number(input.timeout_s) || _dflt)) * 1000;
|
|
580
|
-
const cmd = process.platform === 'win32' ? `chcp 65001>nul & ${
|
|
681
|
+
const cmd = process.platform === 'win32' ? `chcp 65001>nul & ${comandoExecutado}` : comandoExecutado;
|
|
581
682
|
return await new Promise((res) => {
|
|
582
683
|
require('child_process').exec(cmd, { timeout, shell: true, windowsHide: true, maxBuffer: 4 * 1024 * 1024, cwd: fs.existsSync(baseDir) ? baseDir : undefined }, (e, out, err) => {
|
|
583
684
|
const c = compactor.compact(comando, out || '', { codigo: e?.code ?? 0 });
|
|
@@ -585,6 +686,7 @@ async function execute(name, input, opts = {}) {
|
|
|
585
686
|
const _rd = require('./core').redactSecrets;
|
|
586
687
|
const r = { stdout: _rd(c.out), stderr: _rd(compactor.stripAnsi(err || '').slice(0, 1500)), codigo: e?.code ?? 0 };
|
|
587
688
|
if (c.note) r.aviso = c.note;
|
|
689
|
+
if (normalized) r.normalizado = normalized.note;
|
|
588
690
|
if (!r.stdout.trim() && !r.stderr.trim() && r.codigo === 0 && /python3? -c/.test(comando) && comando.includes('\n'))
|
|
589
691
|
r.aviso = 'stdout VAZIO: no Windows, python -c multi-linha falha em silêncio. ESCREVA um .py com escrever_arquivo e rode "python arquivo.py".';
|
|
590
692
|
res(r);
|
|
@@ -683,16 +785,20 @@ async function execute(name, input, opts = {}) {
|
|
|
683
785
|
_rebased = p;
|
|
684
786
|
}
|
|
685
787
|
}
|
|
686
|
-
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
788
|
+
if (_outsideScope(p)) return _scopeError(p);
|
|
789
|
+
const held = _acquireFileMutation(p); if (held.erro) return { erro: held.erro };
|
|
790
|
+
try {
|
|
791
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
792
|
+
const existed = fs.existsSync(p);
|
|
793
|
+
// O caminho do backup era DESCARTADO aqui. Agora volta no resultado (_backup) pra
|
|
794
|
+
// o loop registrar no checkpoint da sessão e permitir o "desfaz tudo".
|
|
795
|
+
const _bk = existed ? _snapshot(p) : null; // backup antes de sobrescrever
|
|
796
|
+
fs.writeFileSync(p, String(input.conteudo ?? ''), 'utf8');
|
|
797
|
+
const r = { ok: true, caminho: p, bytes: Buffer.byteLength(String(input.conteudo ?? '')), sobrescreveu: existed };
|
|
798
|
+
r._backup = _bk || ''; r._acao = existed ? 'alterado' : 'criado';
|
|
799
|
+
if (_rebased) r.aviso = 'Caminho fora do diretório de trabalho foi RE-BASEADO para dentro dele. Use ESTE caminho a partir de agora: ' + p;
|
|
800
|
+
return r;
|
|
801
|
+
} finally { held.lock.release(); }
|
|
696
802
|
}
|
|
697
803
|
case 'editar_arquivo': {
|
|
698
804
|
// Edição por ÂNCORA (ideia do hashline do OMYP): troca SÓ o trecho buscado, sem
|
|
@@ -708,6 +814,9 @@ async function execute(name, input, opts = {}) {
|
|
|
708
814
|
p = path.join(base, i >= 0 ? parts.slice(i).join(path.sep) : parts[parts.length - 1]);
|
|
709
815
|
}
|
|
710
816
|
}
|
|
817
|
+
if (_outsideScope(p)) return _scopeError(p);
|
|
818
|
+
const held = _acquireFileMutation(p); if (held.erro) return { erro: held.erro };
|
|
819
|
+
try {
|
|
711
820
|
if (!fs.existsSync(p)) {
|
|
712
821
|
const v = _vizinhos(p);
|
|
713
822
|
return { erro: 'Arquivo não existe: ' + p + (v.length ? '. Nomes parecidos na mesma pasta: ' + v.join(', ') + ' — confira se não errou o nome.' : '') + ' Pra criar arquivo NOVO use escrever_arquivo.' };
|
|
@@ -733,10 +842,13 @@ async function execute(name, input, opts = {}) {
|
|
|
733
842
|
const _bk2 = _snapshot(p); // backup antes de editar (reversível via restaurar_arquivo)
|
|
734
843
|
fs.writeFileSync(p, novo, 'utf8');
|
|
735
844
|
return { ok: true, caminho: p, ocorrencias: input.todas ? count : 1, bytes_antes: Buffer.byteLength(orig), bytes_depois: Buffer.byteLength(novo), _backup: _bk2 || '', _acao: 'alterado' };
|
|
845
|
+
} finally { held.lock.release(); }
|
|
736
846
|
}
|
|
737
847
|
case 'restaurar_arquivo': {
|
|
738
848
|
const p = _abs(input.caminho, baseDir);
|
|
739
849
|
if (_outsideScope(p)) return _scopeError(p);
|
|
850
|
+
const held = _acquireFileMutation(p); if (held.erro) return { erro: held.erro };
|
|
851
|
+
try {
|
|
740
852
|
const dir = path.join(BK_DIR, _bkKey(p));
|
|
741
853
|
let baks = [];
|
|
742
854
|
try { baks = fs.readdirSync(dir).filter(f => f.endsWith('.bak')).sort(); } catch (_) {}
|
|
@@ -745,6 +857,7 @@ async function execute(name, input, opts = {}) {
|
|
|
745
857
|
_snapshot(p); // guarda o estado ATUAL antes de reverter (permite "refazer")
|
|
746
858
|
fs.copyFileSync(ultimo, p);
|
|
747
859
|
return { ok: true, caminho: p, restaurado: true, de_backup: path.basename(ultimo) };
|
|
860
|
+
} finally { held.lock.release(); }
|
|
748
861
|
}
|
|
749
862
|
case 'listar_diretorio': {
|
|
750
863
|
const p = _abs(input.caminho || '.', baseDir);
|
|
@@ -909,18 +1022,24 @@ async function execute(name, input, opts = {}) {
|
|
|
909
1022
|
if (_outsideScope(target)) return _scopeError(target);
|
|
910
1023
|
const copy = input.caminho_copia ? _abs(input.caminho_copia, baseDir) : undefined;
|
|
911
1024
|
if (copy && _outsideScope(copy)) return _scopeError(copy);
|
|
912
|
-
const
|
|
913
|
-
|
|
914
|
-
|
|
1025
|
+
const held = _acquireFileMutation(target); if (held.erro) return { erro: held.erro };
|
|
1026
|
+
try {
|
|
1027
|
+
const result = await require('./office-editors').editarDocumento({ ...input, caminho: target, caminho_copia: copy });
|
|
1028
|
+
if (result.ok) { result._backup = result.backup || ''; result._acao = result.editou_original ? 'alterado' : 'criado'; }
|
|
1029
|
+
return result;
|
|
1030
|
+
} finally { held.lock.release(); }
|
|
915
1031
|
}
|
|
916
1032
|
case 'editar_planilha': {
|
|
917
1033
|
const target = _abs(input.caminho, baseDir);
|
|
918
1034
|
if (_outsideScope(target)) return _scopeError(target);
|
|
919
1035
|
const copy = input.caminho_copia ? _abs(input.caminho_copia, baseDir) : undefined;
|
|
920
1036
|
if (copy && _outsideScope(copy)) return _scopeError(copy);
|
|
921
|
-
const
|
|
922
|
-
|
|
923
|
-
|
|
1037
|
+
const held = _acquireFileMutation(target); if (held.erro) return { erro: held.erro };
|
|
1038
|
+
try {
|
|
1039
|
+
const result = await require('./office-editors').editarPlanilha({ ...input, caminho: target, caminho_copia: copy });
|
|
1040
|
+
if (result.ok) { result._backup = result.backup || ''; result._acao = result.editou_original ? 'alterado' : 'criado'; }
|
|
1041
|
+
return result;
|
|
1042
|
+
} finally { held.lock.release(); }
|
|
924
1043
|
}
|
|
925
1044
|
case 'ler_planilha': {
|
|
926
1045
|
const target = _abs(input.caminho, baseDir);
|
|
@@ -1045,4 +1164,5 @@ async function execute(name, input, opts = {}) {
|
|
|
1045
1164
|
} catch (e) { return { erro: String((e && e.message) || e).slice(0, 400) }; }
|
|
1046
1165
|
}
|
|
1047
1166
|
|
|
1048
|
-
module.exports = { DEFS, execute, isDestructive, selfDestructiveReason, buildProjectMap,
|
|
1167
|
+
module.exports = { DEFS, execute, isDestructive, selfDestructiveReason, secretExfiltrationReason, buildProjectMap,
|
|
1168
|
+
_test: { shellGotcha: _shellGotcha, normalizeWindowsReadOnlyCommand: _normalizeWindowsReadOnlyCommand } };
|
package/lib/ui.js
CHANGED
|
@@ -129,6 +129,11 @@ const _TOOL_PT = {
|
|
|
129
129
|
navegador: 'Navegando na web', explorar: 'Explorando o projeto', lembrar: 'Salvando na memória',
|
|
130
130
|
skill_gerenciar: 'Ajustando uma habilidade', compactar_contexto: 'Resumindo o histórico',
|
|
131
131
|
pedir_ajuda_humano: 'Pedindo sua ajuda', ler_planilha: 'Lendo a planilha',
|
|
132
|
+
ler_planilha_office_web: 'Lendo a planilha', editar_planilha_office_web: 'Editando a planilha',
|
|
133
|
+
ler_documento_office_web: 'Lendo o documento', editar_documento_office_web: 'Editando o documento',
|
|
134
|
+
ler_apresentacao_office_web: 'Lendo a apresentação', editar_apresentacao_office_web: 'Editando a apresentação',
|
|
135
|
+
google_mail_list: 'Verificando e-mails', google_mail_search: 'Procurando e-mails', google_mail_draft: 'Criando rascunho de e-mail',
|
|
136
|
+
microsoft_mail_list: 'Verificando e-mails', microsoft_mail_search: 'Procurando e-mails', microsoft_mail_draft: 'Criando rascunho de e-mail',
|
|
132
137
|
};
|
|
133
138
|
const _TOOL_EN = {
|
|
134
139
|
executar_comando: 'Running command', executar_remoto: 'Running on the server',
|
|
@@ -139,6 +144,11 @@ const _TOOL_EN = {
|
|
|
139
144
|
navegador: 'Browsing the web', explorar: 'Exploring the project', lembrar: 'Saving to memory',
|
|
140
145
|
skill_gerenciar: 'Tuning a skill', compactar_contexto: 'Summarizing history',
|
|
141
146
|
pedir_ajuda_humano: 'Asking for your help', ler_planilha: 'Reading the spreadsheet',
|
|
147
|
+
ler_planilha_office_web: 'Reading the spreadsheet', editar_planilha_office_web: 'Editing the spreadsheet',
|
|
148
|
+
ler_documento_office_web: 'Reading the document', editar_documento_office_web: 'Editing the document',
|
|
149
|
+
ler_apresentacao_office_web: 'Reading the presentation', editar_apresentacao_office_web: 'Editing the presentation',
|
|
150
|
+
google_mail_list: 'Checking email', google_mail_search: 'Searching email', google_mail_draft: 'Creating an email draft',
|
|
151
|
+
microsoft_mail_list: 'Checking email', microsoft_mail_search: 'Searching email', microsoft_mail_draft: 'Creating an email draft',
|
|
142
152
|
};
|
|
143
153
|
function friendlyTool(name, en) {
|
|
144
154
|
const map = en ? _TOOL_EN : _TOOL_PT;
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terminal-smart-cli",
|
|
3
|
-
"version": "0.97.
|
|
3
|
+
"version": "0.97.31",
|
|
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/audit-packs.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/evolution-telemetry.test.js && node test/capability-pack.test.js && node test/video-generation.test.js && node test/image-job.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"
|
|
9
|
+
"test": "node test/core.test.js && node test/windows-shell-normalization.test.js && node test/gateways.test.js && node test/agent-recovery-guard.test.js && node test/agent-plan-model-contract.test.js && node test/agent-mission-limits.test.js && node test/agent-external-approval.test.js && node test/agent-prompt-injection.test.js && node test/file-concurrency.test.js && node test/mission-observability.test.js && node test/audit-packs.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/evolution-telemetry.test.js && node test/owner-audit.test.js && node test/capability-pack.test.js && node test/video-generation.test.js && node test/image-job.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",
|