terminal-smart-cli 0.92.2 → 0.93.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/lib/agent.js +4 -0
- package/lib/core.js +31 -1
- package/lib/tools.js +30 -2
- package/package.json +1 -1
package/lib/agent.js
CHANGED
|
@@ -73,6 +73,8 @@ REGRAS:
|
|
|
73
73
|
- BINÁRIO NATIVO: ao subir/instalar um executável numa máquina, valide com file (arquitetura: 32 vs 64-bit) E ldd (as bibliotecas resolvem?) ANTES de declarar "pronto/instalado" — "está no lugar" NÃO é "roda". Se ldd mostrar "not found", instale a lib faltante e revalide.
|
|
74
74
|
- WINDOWS / SHELL BASH: o shell aqui é o bash do Git, NÃO o cmd. (a) "node -e"/"python -c" com várias linhas ou com import() FALHA — escreva um arquivo .mjs/.py e rode "node arquivo.mjs"/"python arquivo.py". (b) pra ESPERAR use "sleep N" (NUNCA "timeout /t", que é do cmd e quebra no bash). (c) comandos que só existem no cmd (dir, type, if exist) rode via "cmd /c \"...\"". (d) pra matar um servidor/processo de teste, ache o PID pela PORTA ("netstat -ano | findstr :PORTA" → "taskkill /PID <pid> /F") — NUNCA por imagem ("taskkill /IM node.exe" mata processos não relacionados, inclusive os do usuário).
|
|
75
75
|
- PROVE O CAMINHO REAL antes de declarar "pronto/corrigido": "testes de API passando" NÃO é "o app funciona". Se há UI ou rotas condicionais (por papel/role, por filtro), rode o FLUXO EXATO do usuário afetado — a tela/rota que quebrava — e veja o resultado; numa entrega WEB, verifique com olhos (navegador). Em fix de código com ramos (SQL com subqueries/parâmetros, condicional por role): conte placeholder-a-placeholder e rode de novo o caminho que falhava. Só diga "corrigido" COM a prova verde do caminho real — e NÃO peça pro usuário rodar a prova que você mesmo consegue rodar. Se subir um server de teste, ENCERRE-o (por PID) ao terminar — não deixe processo órfão na porta.
|
|
76
|
+
- DEPLOY EM CONTAINER: edite sempre a FONTE (o diretório do projeto no host, ex /opt/projects/<app>, ou o código local), NUNCA dentro do container (/app/... é efêmero) — o "docker build" monta a imagem a partir da FONTE, então um fix feito dentro do container SOME no rebuild e você fica achando que corrigiu. Do mesmo jeito: NUNCA inclua arquivos de segredo (.env) no pacote/tar de deploy — o .env de exemplo do repo sobrescreve o .env REAL do servidor e a app volta pro modo stub. Ao recriar um container, repasse rede, portas, volumes E o --env-file que ele já tinha.
|
|
77
|
+
- SEU PRÓPRIO TESTE PODE ESTAR ERRADO: antes de confiar num veredito "FALHOU", confira a ASSERÇÃO (HTTP 200/201 é SUCESSO, não falha; 401/403 numa rota protegida sem token é o comportamento CORRETO). E não re-leia/re-escreva o MESMO arquivo várias vezes: se você já leu, use o que leu.
|
|
76
78
|
- ESTILO DA RESPOSTA (importante): escreva em linguagem NATURAL e direta, como explicando pra uma pessoa — evite jargão e detalhe técnico interno que não interessa a quem só quer o resultado. NÃO use emojis decorativos (nada de 🎮📡🔬🌿✅❌ etc.); pra marcar certo/errado use SÓ os símbolos ✓ (deu certo) ou ✗ (falhou). Frases curtas; comando/código/caminho sempre em bloco de código. Não repita o que já apareceu nos passos acima.
|
|
77
79
|
- Termine SEMPRE com um resumo curto: o que foi feito, resultado e caminhos de arquivos criados/alterados.
|
|
78
80
|
- Responda no idioma do usuário (padrão: português do Brasil).`
|
|
@@ -101,6 +103,8 @@ RULES:
|
|
|
101
103
|
- NATIVE BINARY: when uploading/installing an executable on a machine, validate with file (architecture: 32 vs 64-bit) AND ldd (do the libraries resolve?) BEFORE declaring "done/installed" — "it's in place" is NOT "it runs". If ldd shows "not found", install the missing lib and re-validate.
|
|
102
104
|
- WINDOWS / BASH SHELL: the shell here is Git bash, NOT cmd. (a) "node -e"/"python -c" with multiple lines or with import() FAILS — write a .mjs/.py file and run "node file.mjs"/"python file.py". (b) to WAIT use "sleep N" (NEVER "timeout /t", which is cmd and breaks in bash). (c) cmd-only commands (dir, type, if exist) run via "cmd /c \"...\"". (d) to kill a test server/process, find the PID by PORT ("netstat -ano | findstr :PORT" → "taskkill /PID <pid> /F") — NEVER by image ("taskkill /IM node.exe" kills unrelated processes, including the user's).
|
|
103
105
|
- PROVE THE REAL PATH before declaring "done/fixed": "API tests passing" is NOT "the app works". If there's UI or conditional routes (by role, by filter), run the EXACT flow of the affected user — the screen/route that was breaking — and see the result; on a WEB deliverable, verify with eyes (browser). In a code fix with branches (SQL with subqueries/params, role conditionals): count placeholder-by-placeholder and re-run the path that was failing. Only say "fixed" WITH green proof of the real path — and do NOT ask the user to run a proof you can run yourself. If you start a test server, SHUT IT DOWN (by PID) when done — don't leave an orphan process on the port.
|
|
106
|
+
- CONTAINER DEPLOY: always edit the SOURCE (the project dir on the host, e.g. /opt/projects/<app>, or the local code), NEVER inside the container (/app/... is ephemeral) — "docker build" builds the image FROM THE SOURCE, so a fix made inside the container VANISHES on rebuild while you think it's fixed. Likewise: NEVER include secret files (.env) in the deploy tar/package — the repo's sample .env overwrites the REAL server .env and the app falls back to stub mode. When recreating a container, re-pass its network, ports, volumes AND the --env-file it had.
|
|
107
|
+
- YOUR OWN TEST MAY BE WRONG: before trusting a "FAILED" verdict, check the ASSERTION (HTTP 200/201 is SUCCESS, not failure; 401/403 on a protected route without a token is the CORRECT behavior). And don't re-read/re-write the SAME file repeatedly: if you already read it, use what you read.
|
|
104
108
|
- RESPONSE STYLE (important): write in NATURAL, plain language, like explaining to a person — avoid jargon and internal technical detail that doesn't matter to someone who just wants the result. NO decorative emojis (no 🎮📡🔬✅❌ etc.); to mark pass/fail use ONLY the symbols ✓ (worked) or ✗ (failed). Short sentences; commands/code/paths always in a code block. Don't repeat what already showed up in the steps above.
|
|
105
109
|
- ALWAYS end with a short summary: what was done, the result, and paths of created/changed files.
|
|
106
110
|
- Answer in the user's language.`);
|
package/lib/core.js
CHANGED
|
@@ -195,8 +195,38 @@ function classifyToolResult(raw) {
|
|
|
195
195
|
return { ok: true, status: 'ok', errorClass: null, retryable: false, evidence: String(ev || '').replace(/\s+/g, ' ').slice(0, 200) };
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
// ── SEGREDOS: nunca imprimir chave/token na saída (B18) ───────────────────────
|
|
199
|
+
// O agente vaza segredo enquanto DIAGNOSTICA (cat .env, docker inspect, echo $KEY,
|
|
200
|
+
// grep numa store de chaves) mesmo instruído a não fazer isso — regra sozinha não
|
|
201
|
+
// segura. Aqui a saída de ferramenta passa por uma máscara determinística ANTES de
|
|
202
|
+
// virar contexto/log. Mantém o prefixo (o agente ainda reconhece "é uma chave sk-…")
|
|
203
|
+
// mas o valor não circula. Não altera o que é ENVIADO ao comando, só o que é EXIBIDO.
|
|
204
|
+
const _SECRET_RES = [
|
|
205
|
+
// chaves com prefixo conhecido: sk-…, sk-cdc-…, ghp_…, xoxb-…, AKIA…, AIza…
|
|
206
|
+
[/\b((?:sk|pk|rk)-(?:[a-z0-9]+-)?)[A-Za-z0-9_-]{16,}/gi, (m, p) => p + '••••REDACTED'],
|
|
207
|
+
[/\b(gh[pousr]_)[A-Za-z0-9]{16,}/g, (m, p) => p + '••••REDACTED'],
|
|
208
|
+
[/\b(xox[baprs]-)[A-Za-z0-9-]{10,}/gi, (m, p) => p + '••••REDACTED'],
|
|
209
|
+
[/\b(AKIA)[0-9A-Z]{12,}/g, (m, p) => p + '••••REDACTED'],
|
|
210
|
+
[/\b(AIza)[A-Za-z0-9_-]{20,}/g, (m, p) => p + '••••REDACTED'],
|
|
211
|
+
// JWT (header.payload.signature)
|
|
212
|
+
[/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g, () => 'eyJ••••REDACTED_JWT'],
|
|
213
|
+
// atribuição de env/JSON com nome sensível: API_KEY=…, "password": "…", Bearer …
|
|
214
|
+
[/((?:api[_-]?key|apikey|secret|token|password|passwd|senha|authorization|auth[_-]?token|private[_-]?key)["']?\s*[:=]\s*["']?)([^\s"',;&|)]{8,})/gi,
|
|
215
|
+
(m, p) => p + '••••REDACTED'],
|
|
216
|
+
[/\b(Bearer\s+)[A-Za-z0-9._~+/-]{16,}=*/g, (m, p) => p + '••••REDACTED'],
|
|
217
|
+
];
|
|
218
|
+
function redactSecrets(input) {
|
|
219
|
+
if (input == null) return input;
|
|
220
|
+
if (typeof input !== 'string') {
|
|
221
|
+
try { return JSON.parse(redactSecrets(JSON.stringify(input))); } catch (_) { return input; }
|
|
222
|
+
}
|
|
223
|
+
let s = input;
|
|
224
|
+
for (const [re, fn] of _SECRET_RES) s = s.replace(re, fn);
|
|
225
|
+
return s;
|
|
226
|
+
}
|
|
227
|
+
|
|
198
228
|
module.exports = {
|
|
199
|
-
DESTRUCTIVE, isDestructive, touchesTsConfig,
|
|
229
|
+
DESTRUCTIVE, isDestructive, touchesTsConfig, redactSecrets,
|
|
200
230
|
GLOBAL_STATE, globalStateWarning, describeDestructive,
|
|
201
231
|
MODEL_WINDOWS, CTX_WINDOW_DEFAULT, DEFAULT_EXECUTOR, COMPACT_AT, KEEP_TAIL, winFor, estMsgsTok,
|
|
202
232
|
AgentEvents, EVENT_TYPES,
|
package/lib/tools.js
CHANGED
|
@@ -224,6 +224,25 @@ function _guardTsHome(p) {
|
|
|
224
224
|
// por algo que JAMAIS deve rodar é desperdício puro: recusamos NA HORA, apontando a
|
|
225
225
|
// alternativa cirúrgica. Retorna a mensagem (motivo pro modelo) quando é auto-destrutivo,
|
|
226
226
|
// ou null quando é seguro (aí segue o fluxo normal, inclusive o gate de aprovação comum).
|
|
227
|
+
// Gotchas de shell no Windows que falham de um jeito CONFUSO (o agente perdia vários
|
|
228
|
+
// passos tentando de novo). Devolve a mensagem com o CONSERTO pronto, ou null.
|
|
229
|
+
function _shellGotcha(cmd) {
|
|
230
|
+
const s = String(cmd || '');
|
|
231
|
+
const multi = /[\r\n]/.test(s);
|
|
232
|
+
// python -c / node -e multilinha ou com import() dinâmico: quebra no eval / morre calado
|
|
233
|
+
if (/\bpython3?\s+-c\b/.test(s) && multi)
|
|
234
|
+
return 'python -c MULTILINHA falha em silêncio no Windows. Escreva um arquivo .py com escrever_arquivo e rode "python arquivo.py".';
|
|
235
|
+
if (/\bnode\s+-e\b/.test(s) && (multi || /\bimport\s*\(/.test(s) || /\bawait\b/.test(s)))
|
|
236
|
+
return 'node -e MULTILINHA ou com import()/await de topo falha no eval. Escreva um arquivo .mjs com escrever_arquivo e rode "node arquivo.mjs".';
|
|
237
|
+
// timeout /t é do cmd; sob git-bash pega o timeout do coreutils e dá "invalid time interval"
|
|
238
|
+
if (/\btimeout\s+\/t\b/i.test(s))
|
|
239
|
+
return 'Este shell é bash (não cmd): "timeout /t N" falha. Para esperar use "sleep N". Comandos só-do-cmd (dir, type, if exist) rode via: cmd /c "…".';
|
|
240
|
+
// matar processo por IMAGEM mata tudo com esse nome (inclusive a própria missão)
|
|
241
|
+
if (/\btaskkill\b[^\n]*\/im\b/i.test(s))
|
|
242
|
+
return 'taskkill /IM mata TODOS os processos com esse nome (inclusive os do usuário e possivelmente o próprio ts). Mate pelo PID da PORTA: netstat -ano | findstr :PORTA → taskkill /PID <pid> /F.';
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
|
|
227
246
|
function selfDestructiveReason(cmd, opts = {}) {
|
|
228
247
|
const c = String(cmd || '');
|
|
229
248
|
if (!c.trim()) return null;
|
|
@@ -345,6 +364,11 @@ async function execute(name, input, opts = {}) {
|
|
|
345
364
|
// RECUSA INSTANTÂNEA (defesa em profundidade — o gate do agent.js já pega antes):
|
|
346
365
|
// comando auto-destrutivo NUNCA roda e NUNCA espera aprovação.
|
|
347
366
|
{ const sd = selfDestructiveReason(comando, { cwd: baseDir }); if (sd) return { erro: sd }; }
|
|
367
|
+
// GOTCHAS DE SHELL (B2/B6/B10): padrões que FALHAM de forma confusa no Windows.
|
|
368
|
+
// Barrar ANTES de rodar (com o conserto na mão) economiza o ciclo erro→tentativa→erro
|
|
369
|
+
// que o agente repetia: python -c multilinha morria em silêncio, node -e com import()
|
|
370
|
+
// quebrava no eval, e `timeout /t` (cmd) virava o timeout do coreutils sob git-bash.
|
|
371
|
+
{ const g = _shellGotcha(comando); if (g) return { erro: g }; }
|
|
348
372
|
// Comandos de INSTALL/BUILD (apt/npm/pip/make/gcc/docker build…) levam muito mais que 60s —
|
|
349
373
|
// sem isso o apt-get de vários pacotes MORRIA no timeout padrão (nada instalava, em silêncio).
|
|
350
374
|
// Eles ganham 10min de default (até 30min se o agente pedir timeout_s); resto segue 60s.
|
|
@@ -355,7 +379,9 @@ async function execute(name, input, opts = {}) {
|
|
|
355
379
|
return await new Promise((res) => {
|
|
356
380
|
require('child_process').exec(cmd, { timeout, shell: true, windowsHide: true, maxBuffer: 4 * 1024 * 1024, cwd: fs.existsSync(baseDir) ? baseDir : undefined }, (e, out, err) => {
|
|
357
381
|
const c = compactor.compact(comando, out || '', { codigo: e?.code ?? 0 });
|
|
358
|
-
|
|
382
|
+
// B18: mascara chave/token ANTES de virar contexto do modelo e linha de log.
|
|
383
|
+
const _rd = require('./core').redactSecrets;
|
|
384
|
+
const r = { stdout: _rd(c.out), stderr: _rd(compactor.stripAnsi(err || '').slice(0, 1500)), codigo: e?.code ?? 0 };
|
|
359
385
|
if (c.note) r.aviso = c.note;
|
|
360
386
|
if (!r.stdout.trim() && !r.stderr.trim() && r.codigo === 0 && /python3? -c/.test(comando) && comando.includes('\n'))
|
|
361
387
|
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".';
|
|
@@ -392,7 +418,9 @@ async function execute(name, input, opts = {}) {
|
|
|
392
418
|
const comando = String(input.comando || '');
|
|
393
419
|
const out = await ssh.exec(comando, { timeoutMs: 180000 });
|
|
394
420
|
const c = compactor.compact(comando, out.stdout || '', { codigo: out.code });
|
|
395
|
-
|
|
421
|
+
// B18: foi AQUI que a chave da IA vazou (cat .env / grep na store de chaves na VPS).
|
|
422
|
+
const _rd = require('./core').redactSecrets;
|
|
423
|
+
const r = { remoto: (ssh.info() && ssh.info().host) || '', stdout: _rd(c.out), stderr: _rd(compactor.stripAnsi(out.stderr || '').slice(0, 1500)), codigo: out.code };
|
|
396
424
|
if (c.note) r.aviso = c.note;
|
|
397
425
|
return r;
|
|
398
426
|
}
|