terminal-smart-cli 0.86.0 → 0.88.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/bin/ts.js +11 -7
- package/lib/core.js +1 -0
- package/package.json +1 -1
package/bin/ts.js
CHANGED
|
@@ -37,11 +37,13 @@ function fmtK(n) { return n >= 1000 ? (n / 1000).toFixed(1) + 'k' : String(n); }
|
|
|
37
37
|
// heredoc SQL a 1ª linha é o comando externo, ex "mysql -u root pw <<'SQL'"). Aceita:
|
|
38
38
|
// s/N = sim/não neste passo · a = aprovar TUDO na sessão (full-auto) · v = ver o comando inteiro
|
|
39
39
|
// Retorna true | false | 'all'.
|
|
40
|
-
async function askDestructive(cmd, { en = false, warning = null } = {}) {
|
|
40
|
+
async function askDestructive(cmd, { en = false, warning = null, askFn = null } = {}) {
|
|
41
41
|
const raw = String(cmd || '');
|
|
42
42
|
const { shown, hidden } = ui.cmdSummary(raw);
|
|
43
43
|
const more = hidden > 0 ? C.dim(` … +${hidden} ${en ? 'lines' : 'linhas'}`) : '';
|
|
44
44
|
const desc = require('../lib/core').describeDestructive(raw, { en }); // o que a ação FAZ, em linguagem natural
|
|
45
|
+
// no REPL, reusa o readline do REPL (askFn) — criar um novo com ui.ask ESTRAGA o do REPL (teclado morre).
|
|
46
|
+
const _ask = askFn || ui.ask;
|
|
45
47
|
let first = true;
|
|
46
48
|
for (;;) {
|
|
47
49
|
if (first) {
|
|
@@ -53,24 +55,26 @@ async function askDestructive(cmd, { en = false, warning = null } = {}) {
|
|
|
53
55
|
first = false;
|
|
54
56
|
}
|
|
55
57
|
const q = C.dim(en ? ' (S) Yes · (N) No · (A) Always · (V) View command › ' : ' (S) Sim · (N) Não · (A) Sim sempre · (V) Ver comando › ');
|
|
56
|
-
const a = String(await
|
|
58
|
+
const a = String(await _ask(q)).trim().toLowerCase();
|
|
57
59
|
if (['v', 'ver', 'view'].includes(a)) { console.log('\n' + raw.split('\n').map(l => ' ' + C.dim(l)).join('\n') + '\n'); continue; }
|
|
58
60
|
if (['a', 'all', 'tudo', 'sempre', 'always'].includes(a)) return 'all';
|
|
59
61
|
return ['s', 'sim', 'y', 'yes'].includes(a);
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
64
|
// askLabelApprove: aprovação de um LABEL já formatado (skill/MCP, não é comando cru). s/N + a=tudo.
|
|
63
|
-
async function askLabelApprove(label, { en = false } = {}) {
|
|
64
|
-
const
|
|
65
|
+
async function askLabelApprove(label, { en = false, askFn = null } = {}) {
|
|
66
|
+
const _ask = askFn || ui.ask;
|
|
67
|
+
const a = String(await _ask(C.warn('▲ ') + T.agent_approve(C.bold(label)) + C.dim(en ? '(a=all) ' : '(a=tudo) '))).trim().toLowerCase();
|
|
65
68
|
if (['a', 'all', 'tudo'].includes(a)) return 'all';
|
|
66
69
|
return ['s', 'sim', 'y', 'yes'].includes(a);
|
|
67
70
|
}
|
|
68
71
|
// Roteia o payload do askApprove do agente (obj destrutivo → resumo; string label → skill/MCP).
|
|
69
|
-
|
|
72
|
+
// askFn: quando vem do REPL, é o readline DELE — usar ui.ask novo aqui estragaria o teclado do REPL.
|
|
73
|
+
async function askApproveRoute(payload, askFn = null) {
|
|
70
74
|
const en = cfg.lang === 'en';
|
|
71
75
|
const isObj = payload && typeof payload === 'object';
|
|
72
76
|
const cmd = isObj ? payload.cmd : String(payload);
|
|
73
|
-
return (isObj && payload.kind === 'destructive') ? askDestructive(cmd, { en, warning: payload.warning }) : askLabelApprove(cmd, { en });
|
|
77
|
+
return (isObj && payload.kind === 'destructive') ? askDestructive(cmd, { en, warning: payload.warning, askFn }) : askLabelApprove(cmd, { en, askFn });
|
|
74
78
|
}
|
|
75
79
|
// Linha de STATUS por etapa (✓ verde / ✗ vermelho) sob a linha ⚙, com recuo `pad`.
|
|
76
80
|
function stepDoneLine({ ok, evidence }, pad = ' ') {
|
|
@@ -926,7 +930,7 @@ async function agentCmd(words, { askFn, cwd: cwdIn = null, onCwd = null } = {})
|
|
|
926
930
|
// headless (stream-json): não dá pra perguntar → NEGA o destrutivo e sinaliza o evento.
|
|
927
931
|
if (streamJson) { const cmd = payload && typeof payload === 'object' ? payload.cmd : payload; _emit(core.AgentEvents.tool({ subtype: 'approval_denied', reason: 'headless (--stream-json): destructive command not auto-approved', command: cmd })); return false; }
|
|
928
932
|
sp.stop();
|
|
929
|
-
const r = await askApproveRoute(payload);
|
|
933
|
+
const r = await askApproveRoute(payload, askFn); // reusa o readline do REPL (não estraga o teclado)
|
|
930
934
|
sp.start();
|
|
931
935
|
return r;
|
|
932
936
|
},
|
package/lib/core.js
CHANGED
|
@@ -45,6 +45,7 @@ const _DESTR_DESC = [
|
|
|
45
45
|
[/\bmkfs\b|\bdd\s+if=|(?:^|[\s&;|])format\s+[a-z]:|\bdiskpart\b|>\s?\/dev\/sd/i, ['formatar ou sobrescrever um disco', 'format or overwrite a disk']],
|
|
46
46
|
[/\bDROP\s+DATABASE\b/i, ['apagar um banco de dados INTEIRO', 'delete an ENTIRE database']],
|
|
47
47
|
[/\bDROP\s+TABLE\b/i, ['apagar uma tabela do banco', 'delete a database table']],
|
|
48
|
+
[/\btruncate\s+-/i, ['zerar ou reduzir um arquivo', 'empty or shrink a file']], // comando shell (truncate -s) — ANTES do SQL
|
|
48
49
|
[/\bTRUNCATE\b/i, ['esvaziar uma tabela do banco', 'empty a database table']],
|
|
49
50
|
[/\bDELETE\s+FROM\b/i, ['apagar registros de uma tabela', 'delete rows from a table']],
|
|
50
51
|
[/\bALTER\s+USER\b[^;]*IDENTIFIED|(\bSET\s+PASSWORD|GRANT\s+ALL\s+PRIVILEGES)\b/i, ['mudar credenciais/permissões do banco (afeta tudo que usa esse acesso)', "change DB credentials/permissions (affects everything using that access)"]],
|