terminal-smart-cli 0.79.0 → 0.81.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 +24 -10
- package/lib/agent.js +3 -1
- package/lib/core.js +31 -1
- package/lib/i18n.js +2 -2
- package/lib/ui.js +69 -11
- package/package.json +1 -1
package/bin/ts.js
CHANGED
|
@@ -41,14 +41,21 @@ async function askDestructive(cmd, { en = false, warning = 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
|
-
|
|
45
|
-
|
|
44
|
+
const desc = require('../lib/core').describeDestructive(raw, { en }); // o que a ação FAZ, em linguagem natural
|
|
45
|
+
let first = true;
|
|
46
46
|
for (;;) {
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
if (first) {
|
|
48
|
+
// pergunta em LINGUAGEM NATURAL + o comando resumido em amarelo + opções claras
|
|
49
|
+
console.log('');
|
|
50
|
+
console.log(C.warn(' ▲ ') + C.bold(en ? `This action will ${desc}. Continue?` : `Essa ação vai ${desc}. Podemos continuar?`));
|
|
51
|
+
console.log(' ' + C.warn(shown) + more);
|
|
52
|
+
if (warning) console.log(' ' + C.dim((en ? 'note: ' : 'obs: ') + warning));
|
|
53
|
+
first = false;
|
|
54
|
+
}
|
|
55
|
+
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 › ');
|
|
49
56
|
const a = String(await ui.ask(q)).trim().toLowerCase();
|
|
50
57
|
if (['v', 'ver', 'view'].includes(a)) { console.log('\n' + raw.split('\n').map(l => ' ' + C.dim(l)).join('\n') + '\n'); continue; }
|
|
51
|
-
if (['a', 'all', 'tudo'].includes(a)) return 'all';
|
|
58
|
+
if (['a', 'all', 'tudo', 'sempre', 'always'].includes(a)) return 'all';
|
|
52
59
|
return ['s', 'sim', 'y', 'yes'].includes(a);
|
|
53
60
|
}
|
|
54
61
|
}
|
|
@@ -188,9 +195,12 @@ async function sendMessage(token, content) {
|
|
|
188
195
|
}
|
|
189
196
|
}
|
|
190
197
|
function printAnswer(text, ms) {
|
|
198
|
+
const en = cfg.lang === 'en';
|
|
199
|
+
const w = Math.min(process.stdout.columns || 80, 96);
|
|
191
200
|
console.log('');
|
|
192
|
-
|
|
193
|
-
console.log(
|
|
201
|
+
// rótulo de quem fala, cor INDIGO (o usuário é ciano) — fica claro onde o TS começa
|
|
202
|
+
console.log(' ' + C.indigo(C.bold(en ? 'Terminal Smart' : 'Terminal Smart')) + C.dim(':'));
|
|
203
|
+
console.log(ui.md(text, w - 2).split('\n').map(l => ' ' + l).join('\n'));
|
|
194
204
|
if (ms) console.log(' ' + C.dim('· ' + (ms / 1000).toFixed(1).replace('.', ',') + 's'));
|
|
195
205
|
console.log('');
|
|
196
206
|
}
|
|
@@ -511,7 +521,9 @@ async function chatRepl() {
|
|
|
511
521
|
let closed = false, pending = null;
|
|
512
522
|
rl.on('close', () => { closed = true; if (pending) { const p = pending; pending = null; p(null); } });
|
|
513
523
|
const ask1 = (q) => new Promise((res) => { pending = res; rl.question(q, (a) => { pending = null; res(a); }); });
|
|
514
|
-
|
|
524
|
+
// rótulo do usuário em CIANO (o Terminal Smart responde em indigo) — cores distintas deixam
|
|
525
|
+
// claro onde começa cada fala.
|
|
526
|
+
const prompt = C.cyan(C.bold(T.chat_prompt));
|
|
515
527
|
// cwd da SESSÃO do agente: persiste entre mensagens (o problema 1). "cd"/"pwd"
|
|
516
528
|
// no REPL alteram/mostram; cada msg roteada pro agente resolve caminhos daqui.
|
|
517
529
|
const _pp = require('path'), _oo = require('os'), _ff = require('fs');
|
|
@@ -880,8 +892,10 @@ async function agentCmd(words, { askFn, cwd: cwdIn = null, onCwd = null } = {})
|
|
|
880
892
|
onStep: ({ name, detail, blocked, loop, retry, auto }) => {
|
|
881
893
|
if (streamJson) { _emit(core.AgentEvents.tool({ subtype: retry ? 'retry' : loop ? 'loop' : blocked ? 'blocked' : auto ? 'auto_approved' : 'started', tool: name, detail: detail || '' })); return; }
|
|
882
894
|
sp.stop();
|
|
883
|
-
const tag = auto ? C.warn('▲ auto') : retry ? C.warn('⟳') : loop ? C.warn('↻ loop') : blocked ? C.err('■ ' + T.agent_blocked) : C.cyan('
|
|
884
|
-
|
|
895
|
+
const tag = auto ? C.warn('▲ auto') : retry ? C.warn('⟳') : loop ? C.warn('↻ loop') : blocked ? C.err('■ ' + T.agent_blocked) : C.cyan('›');
|
|
896
|
+
// nome AMIGÁVEL (Rodando no servidor…) em vez do técnico (executar_remoto); detalhe sem cortar palavra
|
|
897
|
+
const _label = ui.friendlyTool(name, cfg.lang === 'en');
|
|
898
|
+
console.log(' ' + tag + ' ' + C.bold(_label) + (detail ? C.dim(' · ' + ui.wordTrunc(detail, 60)) : ''));
|
|
885
899
|
sp.start();
|
|
886
900
|
},
|
|
887
901
|
onStepDone: (e) => {
|
package/lib/agent.js
CHANGED
|
@@ -68,6 +68,7 @@ REGRAS:
|
|
|
68
68
|
- NUNCA peça segredo ao usuário: jamais peça senha/chave/token/credencial no chat. Se uma operação remota (ssh/scp) falhar por autenticação, REUSE a conexão/chave que JÁ funcionou nesta sessão (mesmo host/user/chave — não reconstrua o comando do zero esquecendo o -i da chave). Se ainda assim não conectar, peça pro usuário EXECUTAR a ação — nunca entregar o segredo.
|
|
69
69
|
- NÃO DESISTA sem TENTAR: é PROIBIDO responder "não tenho acesso" / "não consigo" / "preciso que você me passe X" enquanto houver uma ferramenta que você ainda não usou pra tentar. Antes de declarar que algo é impossível, AJA: conecte (conectar_vps), procure (buscar_arquivos, ou grep/find via executar_comando/executar_remoto), leia (ler_arquivo). Ex.: pediram pra ler um código-fonte que "você não tem"? Se há uma VPS/pasta onde ele pode estar, CONECTE e procure (grep -rn "<símbolo>" <dir>) ANTES de dizer que não tem. Só afirme que não conseguiu DEPOIS de ter tentado de fato e mostre o erro/saída REAL que te barrou.
|
|
70
70
|
- 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.
|
|
71
|
+
- 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.
|
|
71
72
|
- Termine SEMPRE com um resumo curto: o que foi feito, resultado e caminhos de arquivos criados/alterados.
|
|
72
73
|
- Responda no idioma do usuário (padrão: português do Brasil).`
|
|
73
74
|
: 'You are the Terminal Smart agent running ON THIS machine via CLI, with REAL tools. Act for real — never say "I will do it" without calling the tool in the same turn, and never invent results that did not come from a tool.'
|
|
@@ -90,6 +91,7 @@ RULES:
|
|
|
90
91
|
- NEVER ask the user for a secret: never ask for a password/key/token/credential in chat. If a remote op (ssh/scp) fails on auth, REUSE the connection/key that ALREADY worked this session (same host/user/key — don't rebuild the command dropping the -i key flag). If it still won't connect, ask the user to PERFORM the action — never to hand over the secret.
|
|
91
92
|
- DON'T GIVE UP without TRYING: it is FORBIDDEN to answer "I don't have access" / "I can't" / "I need you to give me X" while there's a tool you haven't used yet to try. Before declaring something impossible, ACT: connect (conectar_vps), search (buscar_arquivos, or grep/find via executar_comando/executar_remoto), read (ler_arquivo). E.g. asked to read source code you "don't have"? If there's a VPS/folder where it might live, CONNECT and search (grep -rn "<symbol>" <dir>) BEFORE saying you don't have it. Only claim you couldn't do it AFTER actually trying, and show the REAL error/output that blocked you.
|
|
92
93
|
- 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.
|
|
94
|
+
- 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.
|
|
93
95
|
- ALWAYS end with a short summary: what was done, the result, and paths of created/changed files.
|
|
94
96
|
- Answer in the user's language.`);
|
|
95
97
|
const volatile = `\n\nMáquina: ${process.platform} ${os.release()} · host ${os.hostname()} · user ${os.userInfo().username}`
|
|
@@ -134,7 +136,7 @@ async function llm({ baseUrl, key, messages, model, signalMs = 180000, noTools =
|
|
|
134
136
|
// argsShort: resumo de 1 linha do input da ferramenta pro passo exibido no terminal
|
|
135
137
|
function argsShort(name, input) {
|
|
136
138
|
const v = input.comando || input.caminho || input.padrao || input.tarefa || input.slug || '';
|
|
137
|
-
return String(v).replace(/\s+/g, ' ').slice(0,
|
|
139
|
+
return String(v).replace(/\s+/g, ' ').slice(0, 200); // corte fino (sem partir palavra) é feito na UI com wordTrunc
|
|
138
140
|
}
|
|
139
141
|
|
|
140
142
|
// ── LOOP-DETECTION (anti-trava) ──────────────────────────────────────────────
|
package/lib/core.js
CHANGED
|
@@ -39,6 +39,36 @@ function globalStateWarning(cmd) {
|
|
|
39
39
|
return null;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
// Descrição em LINGUAGEM NATURAL do que um comando destrutivo faz — pra a aprovação dizer
|
|
43
|
+
// "Essa ação vai APAGAR ARQUIVOS" em vez de só despejar o comando cru. Bilíngue.
|
|
44
|
+
const _DESTR_DESC = [
|
|
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
|
+
[/\bDROP\s+DATABASE\b/i, ['apagar um banco de dados INTEIRO', 'delete an ENTIRE database']],
|
|
47
|
+
[/\bDROP\s+TABLE\b/i, ['apagar uma tabela do banco', 'delete a database table']],
|
|
48
|
+
[/\bTRUNCATE\b/i, ['esvaziar uma tabela do banco', 'empty a database table']],
|
|
49
|
+
[/\bDELETE\s+FROM\b/i, ['apagar registros de uma tabela', 'delete rows from a table']],
|
|
50
|
+
[/\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)"]],
|
|
51
|
+
[/\bDROP\s+USER\b/i, ['remover um usuário do banco', 'remove a database user']],
|
|
52
|
+
[/\/etc\/ssh\/sshd_config/i, ['editar a config do SSH (pode te trancar pra fora)', 'edit the SSH config (may lock you out)']],
|
|
53
|
+
[/\b(ufw|iptables)\b/i, ['mexer no firewall (pode cortar seu acesso)', 'change the firewall (may cut your access)']],
|
|
54
|
+
[/\b(shutdown|reboot|halt|poweroff)\b/i, ['desligar ou reiniciar a máquina', 'shut down or reboot the machine']],
|
|
55
|
+
[/\b(pkill|killall)\b|taskkill\b[^\n]*\/im|Stop-Process\b[^|;&]*-Name/i, ['encerrar processos pelo NOME (todos com esse nome)', 'kill processes by NAME (all of them)']],
|
|
56
|
+
[/\b(kill|taskkill)\b/i, ['encerrar um processo', 'kill a process']],
|
|
57
|
+
[/\bsystemctl\s+(stop|disable|mask)\b/i, ['parar ou desabilitar um serviço do sistema', 'stop or disable a system service']],
|
|
58
|
+
[/\bdocker\s+(rm|rmi|system\s+prune|volume\s+rm)\b/i, ['remover containers, imagens ou volumes do Docker', 'remove Docker containers, images or volumes']],
|
|
59
|
+
[/\bgit\s+push\s+.*--force(?!-with-lease)/i, ['sobrescrever o histórico remoto do git', 'overwrite remote git history']],
|
|
60
|
+
[/\bgit\s+reset\s+--hard/i, ['descartar mudanças locais do git', 'discard local git changes']],
|
|
61
|
+
[/\bchmod\s+(-R\s+)?777\b/i, ['dar permissão TOTAL a arquivos (inseguro)', 'grant FULL permissions to files (insecure)']],
|
|
62
|
+
[/\b(curl|wget)\b[^|]*\|\s*(sudo\s+)?(ba)?sh/i, ['baixar e EXECUTAR um script da internet', 'download and RUN a script from the internet']],
|
|
63
|
+
[/\brm\s+(-[a-z]*[rf]|.*\*)|\brmdir\b|\bdel\s+\/[sq]|\brd\s+\/s|Remove-Item[^\n]*-Recurse/i, ['apagar arquivos ou pastas', 'delete files or folders']],
|
|
64
|
+
[/\buserdel\b|\bpasswd\b|\/etc\/sudoers|\bvisudo\b|\busermod\b/i, ['mexer em usuários/permissões do sistema', 'change system users/permissions']],
|
|
65
|
+
];
|
|
66
|
+
function describeDestructive(cmd, opts = {}) {
|
|
67
|
+
const en = !!opts.en, s = String(cmd || '');
|
|
68
|
+
for (const [re, [pt, eng]] of _DESTR_DESC) if (re.test(s)) return en ? eng : pt;
|
|
69
|
+
return en ? 'make a change that is hard to undo' : 'fazer uma alteração difícil de desfazer';
|
|
70
|
+
}
|
|
71
|
+
|
|
42
72
|
// Padrões de comando destrutivo/irreversível — SEMPRE pedem aprovação humana.
|
|
43
73
|
// (fonte única: antes vivia em tools.js; App e Web tinham cópias divergentes.)
|
|
44
74
|
const DESTRUCTIVE = [
|
|
@@ -166,7 +196,7 @@ function classifyToolResult(raw) {
|
|
|
166
196
|
|
|
167
197
|
module.exports = {
|
|
168
198
|
DESTRUCTIVE, isDestructive, touchesTsConfig,
|
|
169
|
-
GLOBAL_STATE, globalStateWarning,
|
|
199
|
+
GLOBAL_STATE, globalStateWarning, describeDestructive,
|
|
170
200
|
MODEL_WINDOWS, CTX_WINDOW_DEFAULT, DEFAULT_EXECUTOR, COMPACT_AT, KEEP_TAIL, winFor, estMsgsTok,
|
|
171
201
|
AgentEvents, EVENT_TYPES,
|
|
172
202
|
ERROR_CLASSES, RETRYABLE_CLASSES, classifyError, classifyToolResult,
|
package/lib/i18n.js
CHANGED
|
@@ -139,7 +139,7 @@ const STR = {
|
|
|
139
139
|
no_credits: 'Seus créditos acabaram. Gerencie em terminalsmart.com.br/planos',
|
|
140
140
|
ask_empty: 'Diga o que você precisa. Ex.: ts "como liberar a porta 443 no ufw?"',
|
|
141
141
|
chat_hint: 'digite sua mensagem · "sair" encerra · "/nova" zera o contexto',
|
|
142
|
-
chat_prompt: '
|
|
142
|
+
chat_prompt: 'Você › ',
|
|
143
143
|
chat_bye: 'Até logo! A conversa continua salva na web (/ia).',
|
|
144
144
|
agent_need: 'Descreva a tarefa. Ex.: ts agente "libere espaço em disco analisando as pastas maiores"',
|
|
145
145
|
agent_thinking: 'agente pensando',
|
|
@@ -291,7 +291,7 @@ const STR = {
|
|
|
291
291
|
no_credits: 'You are out of credits. Manage at terminalsmart.com.br/planos',
|
|
292
292
|
ask_empty: 'Tell me what you need. E.g.: ts "how do I open port 443 on ufw?"',
|
|
293
293
|
chat_hint: 'type your message · "exit" quits · "/new" resets context',
|
|
294
|
-
chat_prompt: '
|
|
294
|
+
chat_prompt: 'You › ',
|
|
295
295
|
chat_bye: 'See you! The conversation stays saved on the web (/ia).',
|
|
296
296
|
agent_need: 'Describe the task. E.g.: ts agente "free disk space by analyzing the largest folders"',
|
|
297
297
|
agent_thinking: 'agent thinking',
|
package/lib/ui.js
CHANGED
|
@@ -62,28 +62,86 @@ function spinner(initial) {
|
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
// Quebra UMA linha de texto por PALAVRA na largura w — nunca parte palavra no meio
|
|
66
|
+
// (pedido do user: se não coube, a palavra inteira desce). Preserva o recuo/bullet na 1ª sub-linha.
|
|
67
|
+
function _wrapLine(line, w) {
|
|
68
|
+
if (line.length <= w) return [line];
|
|
69
|
+
const lead = (line.match(/^\s*(?:[-*]\s+)?/) || [''])[0]; // recuo + bullet só na 1ª
|
|
70
|
+
const cont = ' '.repeat(lead.length);
|
|
71
|
+
const words = line.slice(lead.length).split(/\s+/).filter(Boolean);
|
|
72
|
+
const rows = []; let cur = lead;
|
|
73
|
+
for (const wd of words) {
|
|
74
|
+
const atStart = (cur === lead || cur === cont);
|
|
75
|
+
if (!atStart && (cur.length + 1 + wd.length) > w) { rows.push(cur); cur = cont + wd; }
|
|
76
|
+
else cur = atStart ? cur + wd : cur + ' ' + wd;
|
|
77
|
+
}
|
|
78
|
+
rows.push(cur);
|
|
79
|
+
return rows;
|
|
80
|
+
}
|
|
81
|
+
|
|
65
82
|
// Markdown-lite: negrito, código inline, blocos de código com borda, títulos, listas.
|
|
66
|
-
|
|
83
|
+
// wrapW = largura pra word-wrap do texto (não quebra dentro de bloco de código).
|
|
84
|
+
function md(text, wrapW) {
|
|
67
85
|
if (!TTY) return String(text);
|
|
86
|
+
const w = Math.max(24, (wrapW || _width()) - 2);
|
|
68
87
|
const out = [];
|
|
69
88
|
const lines = String(text).split('\n');
|
|
70
89
|
let inCode = false;
|
|
71
90
|
for (const line of lines) {
|
|
72
91
|
if (/^\s*```/.test(line)) { inCode = !inCode; out.push(C.dim(' ' + (inCode ? '╭──' : '╰──'))); continue; }
|
|
73
92
|
if (inCode) { out.push(C.dim(' │ ') + C.cyan(line)); continue; }
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
93
|
+
// word-wrap ANTES de colorir (o ANSI de negrito/código não muda a largura visível)
|
|
94
|
+
for (let l of _wrapLine(line, w)) {
|
|
95
|
+
l = l.replace(/\[CMD\]\s*([\s\S]*?)\s*\[\/CMD\]/g, (_, c) => C.cyan('$ ' + c));
|
|
96
|
+
l = l.replace(/\[\/?CMD\]/g, '');
|
|
97
|
+
l = l.replace(/^#{1,3}\s+(.+)$/, (_, h) => C.bold(C.cyan(h)));
|
|
98
|
+
l = l.replace(/\*\*([^*]+)\*\*/g, (_, b) => C.bold(b));
|
|
99
|
+
l = l.replace(/`([^`]+)`/g, (_, c) => C.cyan(c));
|
|
100
|
+
l = l.replace(/^(\s*)[-*]\s+/, (_, sp) => sp + C.cyan('•') + ' ');
|
|
101
|
+
out.push(l);
|
|
102
|
+
}
|
|
83
103
|
}
|
|
84
104
|
return out.join('\n');
|
|
85
105
|
}
|
|
86
106
|
|
|
107
|
+
// Trunca sem CORTAR palavra: se passar de n, volta ao último espaço + reticências.
|
|
108
|
+
function wordTrunc(s, n = 64) {
|
|
109
|
+
const str = String(s == null ? '' : s).replace(/\s+/g, ' ').trim();
|
|
110
|
+
if (str.length <= n) return str;
|
|
111
|
+
const cut = str.slice(0, n);
|
|
112
|
+
const sp = cut.lastIndexOf(' ');
|
|
113
|
+
// volta ao último espaço (não parte palavra); só corta cru se a 1ª palavra já passa de n.
|
|
114
|
+
return (sp > 0 ? cut.slice(0, sp) : cut).replace(/\s+$/, '') + '…';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Nome da ferramenta em LINGUAGEM NATURAL (o que ela FAZ) — some com "executar_remoto" e cia.
|
|
118
|
+
const _TOOL_PT = {
|
|
119
|
+
executar_comando: 'Rodando comando', executar_remoto: 'Rodando no servidor',
|
|
120
|
+
ler_arquivo: 'Lendo arquivo', escrever_arquivo: 'Criando arquivo', editar_arquivo: 'Editando arquivo',
|
|
121
|
+
listar_diretorio: 'Listando a pasta', buscar_arquivos: 'Procurando arquivos', buscar_codigo: 'Buscando no código',
|
|
122
|
+
mapa_projeto: 'Mapeando o projeto', mudar_diretorio: 'Entrando na pasta', info_sistema: 'Checando o sistema',
|
|
123
|
+
conectar_vps: 'Conectando no servidor', enviar_arquivo: 'Enviando arquivo', buscar_web: 'Pesquisando na web',
|
|
124
|
+
navegador: 'Navegando na web', explorar: 'Explorando o projeto', lembrar: 'Salvando na memória',
|
|
125
|
+
skill_gerenciar: 'Ajustando uma habilidade', compactar_contexto: 'Resumindo o histórico',
|
|
126
|
+
pedir_ajuda_humano: 'Pedindo sua ajuda', ler_planilha: 'Lendo a planilha',
|
|
127
|
+
};
|
|
128
|
+
const _TOOL_EN = {
|
|
129
|
+
executar_comando: 'Running command', executar_remoto: 'Running on the server',
|
|
130
|
+
ler_arquivo: 'Reading file', escrever_arquivo: 'Writing file', editar_arquivo: 'Editing file',
|
|
131
|
+
listar_diretorio: 'Listing folder', buscar_arquivos: 'Finding files', buscar_codigo: 'Searching the code',
|
|
132
|
+
mapa_projeto: 'Mapping the project', mudar_diretorio: 'Entering folder', info_sistema: 'Checking the system',
|
|
133
|
+
conectar_vps: 'Connecting to the server', enviar_arquivo: 'Uploading file', buscar_web: 'Searching the web',
|
|
134
|
+
navegador: 'Browsing the web', explorar: 'Exploring the project', lembrar: 'Saving to memory',
|
|
135
|
+
skill_gerenciar: 'Tuning a skill', compactar_contexto: 'Summarizing history',
|
|
136
|
+
pedir_ajuda_humano: 'Asking for your help', ler_planilha: 'Reading the spreadsheet',
|
|
137
|
+
};
|
|
138
|
+
function friendlyTool(name, en) {
|
|
139
|
+
const map = en ? _TOOL_EN : _TOOL_PT;
|
|
140
|
+
if (map[name]) return map[name];
|
|
141
|
+
if (/^mcp_/.test(name)) return (en ? 'External tool' : 'Ferramenta externa');
|
|
142
|
+
return String(name || '').replace(/_/g, ' '); // fallback legível
|
|
143
|
+
}
|
|
144
|
+
|
|
87
145
|
// Barra de progresso ▰▰▰▱▱ (uso de créditos)
|
|
88
146
|
function bar(frac, width = 22) {
|
|
89
147
|
const n = Math.round(Math.max(0, Math.min(1, frac)) * width);
|
|
@@ -115,4 +173,4 @@ const okLine = (s) => ' ' + C.ok('✔') + ' ' + s;
|
|
|
115
173
|
const errLine = (s) => ' ' + C.err('✖') + ' ' + s;
|
|
116
174
|
const infoLine = (s) => ' ' + C.cyan('⌁') + ' ' + s;
|
|
117
175
|
|
|
118
|
-
module.exports = { C, TTY, gradient, banner, box, spinner, md, bar, ask, cmdSummary, okLine, errLine, infoLine };
|
|
176
|
+
module.exports = { C, TTY, gradient, banner, box, spinner, md, bar, ask, cmdSummary, okLine, errLine, infoLine, wordTrunc, friendlyTool, _wrapLine };
|