terminal-smart-cli 0.94.2 → 0.97.1

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,226 @@
1
+ 'use strict';
2
+
3
+ // Android/Android TV via ADB. Todas as chamadas usam execFile (sem shell) e
4
+ // validação estrita, portanto IP, pacote, serial e caminhos não viram comandos.
5
+ const fs = require('fs');
6
+ const os = require('os');
7
+ const path = require('path');
8
+ const cp = require('child_process');
9
+ const crypto = require('crypto');
10
+
11
+ const HOST_RE = /^(?:[a-z0-9.-]+|\[[0-9a-f:]+\])$/i;
12
+ const SERIAL_RE = /^[a-z0-9._:[\]-]+$/i;
13
+ const PACKAGE_RE = /^[a-z][a-z0-9_]*(?:\.[a-z0-9_]+)+$/i;
14
+
15
+ function _candidateAdbPaths() {
16
+ const sdk = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT ||
17
+ (process.platform === 'win32'
18
+ ? path.join(os.homedir(), 'AppData', 'Local', 'Android', 'Sdk')
19
+ : path.join(os.homedir(), 'Android', 'Sdk'));
20
+ const exe = process.platform === 'win32' ? 'adb.exe' : 'adb';
21
+ return [
22
+ path.join(sdk, 'platform-tools', exe),
23
+ process.platform === 'win32' ? path.join(process.env.LOCALAPPDATA || '', 'Android', 'Sdk', 'platform-tools', exe) : '',
24
+ 'adb',
25
+ ].filter(Boolean);
26
+ }
27
+
28
+ function findAdb() {
29
+ for (const candidate of _candidateAdbPaths()) {
30
+ if (candidate === 'adb' || fs.existsSync(candidate)) return candidate;
31
+ }
32
+ return 'adb';
33
+ }
34
+
35
+ function _run(args, options = {}) {
36
+ const timeout = Math.min(180000, Math.max(3000, Number(options.timeoutMs) || 30000));
37
+ return new Promise((resolve) => {
38
+ cp.execFile(findAdb(), args, {
39
+ encoding: options.buffer ? null : 'utf8',
40
+ timeout,
41
+ windowsHide: true,
42
+ maxBuffer: options.maxBuffer || 8 * 1024 * 1024,
43
+ }, (error, stdout, stderr) => {
44
+ const code = typeof error?.code === 'number' ? error.code : (error ? 1 : 0);
45
+ resolve({
46
+ ok: !error,
47
+ codigo: code,
48
+ stdout: options.buffer ? stdout : String(stdout || '').trim(),
49
+ stderr: options.buffer ? stderr : String(stderr || error?.message || '').trim(),
50
+ });
51
+ });
52
+ });
53
+ }
54
+
55
+ function _runWithInput(args, input, options = {}) {
56
+ const timeout = Math.min(180000, Math.max(3000, Number(options.timeoutMs) || 30000));
57
+ return new Promise((resolve) => {
58
+ const child = cp.spawn(findAdb(), args, { windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'] });
59
+ const out = [], err = [];
60
+ let settled = false;
61
+ const timer = setTimeout(() => { try { child.kill(); } catch (_) {} }, timeout);
62
+ child.stdout.on('data', b => out.push(b));
63
+ child.stderr.on('data', b => err.push(b));
64
+ child.on('error', (error) => {
65
+ if (settled) return;
66
+ settled = true; clearTimeout(timer);
67
+ resolve({ ok: false, codigo: 1, stdout: Buffer.concat(out).toString('utf8').trim(), stderr: error.message });
68
+ });
69
+ child.on('close', (code) => {
70
+ if (settled) return;
71
+ settled = true; clearTimeout(timer);
72
+ resolve({
73
+ ok: code === 0,
74
+ codigo: typeof code === 'number' ? code : 1,
75
+ stdout: Buffer.concat(out).toString('utf8').trim(),
76
+ stderr: Buffer.concat(err).toString('utf8').trim(),
77
+ });
78
+ });
79
+ // O código não aparece na linha de comando nem em listagens de processos.
80
+ child.stdin.end(String(input || '') + '\n');
81
+ });
82
+ }
83
+
84
+ function _endpoint(host, port) {
85
+ const h = String(host || '').trim();
86
+ const p = Number(port);
87
+ if (!HOST_RE.test(h)) throw new Error('Host/IP inválido.');
88
+ if (!Number.isInteger(p) || p < 1 || p > 65535) throw new Error('Porta inválida.');
89
+ return `${h}:${p}`;
90
+ }
91
+
92
+ function _serialArgs(serial) {
93
+ const value = String(serial || '').trim();
94
+ if (!value) return [];
95
+ if (!SERIAL_RE.test(value)) throw new Error('Serial do dispositivo inválido.');
96
+ return ['-s', value];
97
+ }
98
+
99
+ function _package(value) {
100
+ const pkg = String(value || '').trim();
101
+ if (!PACKAGE_RE.test(pkg)) throw new Error('Nome de pacote Android inválido.');
102
+ return pkg;
103
+ }
104
+
105
+ async function adbInfo() {
106
+ const version = await _run(['version'], { timeoutMs: 10000 });
107
+ if (!version.ok) {
108
+ return {
109
+ ok: false,
110
+ erro: 'ADB não encontrado ou indisponível.',
111
+ adb: findAdb(),
112
+ detalhe: version.stderr,
113
+ correcao: 'Instale Android SDK Platform-Tools ou configure ANDROID_HOME/ANDROID_SDK_ROOT.',
114
+ };
115
+ }
116
+ return { ok: true, adb: findAdb(), versao: version.stdout.split(/\r?\n/)[0] || version.stdout };
117
+ }
118
+
119
+ async function listarDispositivos() {
120
+ const info = await adbInfo();
121
+ if (!info.ok) return info;
122
+ const result = await _run(['devices', '-l'], { timeoutMs: 15000 });
123
+ if (!result.ok) return { ok: false, erro: 'Falha ao listar dispositivos.', detalhe: result.stderr };
124
+ const dispositivos = result.stdout.split(/\r?\n/).slice(1).map(s => s.trim()).filter(Boolean).map((line) => {
125
+ const [serial, estado, ...rest] = line.split(/\s+/);
126
+ const meta = {};
127
+ for (const item of rest) {
128
+ const i = item.indexOf(':');
129
+ if (i > 0) meta[item.slice(0, i)] = item.slice(i + 1);
130
+ }
131
+ return { serial, estado, modelo: meta.model || null, produto: meta.product || null, transporte: meta.transport_id || null };
132
+ });
133
+ return { ok: true, adb: info.adb, total: dispositivos.length, dispositivos };
134
+ }
135
+
136
+ async function parear({ host, porta, codigo }) {
137
+ const endpoint = _endpoint(host, porta);
138
+ const code = String(codigo || '').trim();
139
+ if (!/^\d{6}$/.test(code)) return { ok: false, erro: 'Código de pareamento inválido; informe os 6 dígitos exibidos no aparelho.' };
140
+ const result = await _runWithInput(['pair', endpoint], code, { timeoutMs: 30000 });
141
+ return result.ok
142
+ ? { ok: true, endpoint, mensagem: result.stdout || 'Dispositivo pareado.' }
143
+ : { ok: false, erro: 'Pareamento ADB falhou.', endpoint, detalhe: result.stderr || result.stdout };
144
+ }
145
+
146
+ async function conectar({ host, porta }) {
147
+ const endpoint = _endpoint(host, porta);
148
+ const result = await _run(['connect', endpoint], { timeoutMs: 30000 });
149
+ const accepted = result.ok && /connected|already connected|conectado/i.test(result.stdout);
150
+ return accepted
151
+ ? { ok: true, serial: endpoint, mensagem: result.stdout }
152
+ : { ok: false, erro: 'Conexão ADB falhou.', endpoint, detalhe: result.stderr || result.stdout };
153
+ }
154
+
155
+ async function instalar({ apk, serial, substituir = true, conceder_permissoes = false }) {
156
+ const file = path.resolve(String(apk || ''));
157
+ if (!/\.apk$/i.test(file)) return { ok: false, erro: 'O arquivo precisa terminar em .apk.' };
158
+ if (!fs.existsSync(file) || !fs.statSync(file).isFile()) return { ok: false, erro: 'APK não encontrado: ' + file };
159
+ const args = [..._serialArgs(serial), 'install'];
160
+ if (substituir !== false) args.push('-r');
161
+ if (conceder_permissoes) args.push('-g');
162
+ args.push(file);
163
+ const result = await _run(args, { timeoutMs: 180000, maxBuffer: 16 * 1024 * 1024 });
164
+ const accepted = result.ok && /success/i.test(result.stdout);
165
+ return accepted
166
+ ? { ok: true, apk: file, serial: serial || 'padrão', tamanho_bytes: fs.statSync(file).size, mensagem: result.stdout }
167
+ : { ok: false, erro: 'Instalação do APK falhou.', apk: file, detalhe: result.stderr || result.stdout, codigo: result.codigo };
168
+ }
169
+
170
+ async function iniciar({ pacote, serial }) {
171
+ const pkg = _package(pacote);
172
+ const result = await _run([..._serialArgs(serial), 'shell', 'monkey', '-p', pkg, '-c', 'android.intent.category.LAUNCHER', '1'], { timeoutMs: 30000 });
173
+ return result.ok
174
+ ? { ok: true, pacote: pkg, serial: serial || 'padrão', mensagem: result.stdout.slice(-1200) }
175
+ : { ok: false, erro: 'Não foi possível iniciar o aplicativo.', detalhe: result.stderr || result.stdout };
176
+ }
177
+
178
+ async function logs({ serial, pacote, linhas = 300, nivel = '' }) {
179
+ const max = Math.min(2000, Math.max(20, Number(linhas) || 300));
180
+ const base = _serialArgs(serial);
181
+ let pid = '';
182
+ if (pacote) {
183
+ const pkg = _package(pacote);
184
+ const found = await _run([...base, 'shell', 'pidof', pkg], { timeoutMs: 10000 });
185
+ pid = found.ok ? String(found.stdout).trim().split(/\s+/)[0] : '';
186
+ if (!pid) return { ok: false, erro: `O processo ${pkg} não está em execução. Inicie o app e tente novamente.` };
187
+ }
188
+ const args = [...base, 'logcat', '-d', '-t', String(max), '-v', 'threadtime'];
189
+ if (pid && /^\d+$/.test(pid)) args.push('--pid', pid);
190
+ if (nivel && /^[VDIWEF]$/i.test(String(nivel))) args.push(`*:${String(nivel).toUpperCase()}`);
191
+ const result = await _run(args, { timeoutMs: 30000, maxBuffer: 16 * 1024 * 1024 });
192
+ if (!result.ok) return { ok: false, erro: 'Falha ao ler logcat.', detalhe: result.stderr };
193
+ const text = result.stdout;
194
+ const crashes = text.split(/\r?\n/).filter(l => /FATAL EXCEPTION|AndroidRuntime|ANR in |Process: .* has died/i.test(l)).slice(-30);
195
+ return { ok: true, serial: serial || 'padrão', pacote: pacote || null, linhas_retornadas: text ? text.split(/\r?\n/).length : 0, crashes, log: text.slice(-120000) };
196
+ }
197
+
198
+ async function capturarTela({ caminho, serial }) {
199
+ const target = path.resolve(String(caminho || ''));
200
+ if (!/\.png$/i.test(target)) return { ok: false, erro: 'O caminho da captura precisa terminar em .png.' };
201
+ const result = await _run([..._serialArgs(serial), 'exec-out', 'screencap', '-p'], { timeoutMs: 30000, buffer: true, maxBuffer: 32 * 1024 * 1024 });
202
+ if (!result.ok || !Buffer.isBuffer(result.stdout) || result.stdout.length < 100) {
203
+ return { ok: false, erro: 'Falha ao capturar a tela.', detalhe: Buffer.isBuffer(result.stderr) ? result.stderr.toString('utf8') : result.stderr };
204
+ }
205
+ fs.mkdirSync(path.dirname(target), { recursive: true });
206
+ fs.writeFileSync(target, result.stdout);
207
+ return {
208
+ ok: true,
209
+ caminho: target,
210
+ tamanho_bytes: result.stdout.length,
211
+ sha256: crypto.createHash('sha256').update(result.stdout).digest('hex'),
212
+ serial: serial || 'padrão',
213
+ };
214
+ }
215
+
216
+ module.exports = {
217
+ findAdb,
218
+ adbInfo,
219
+ listarDispositivos,
220
+ parear,
221
+ conectar,
222
+ instalar,
223
+ iniciar,
224
+ logs,
225
+ capturarTela,
226
+ };
package/lib/diagnose.js CHANGED
@@ -1,3 +1,4 @@
1
+
1
2
  // lib/diagnose.js — MODO DIAGNÓSTICO do TS: um loop de INVESTIGAÇÃO (não de build).
2
3
  // Dado um SINTOMA (erro/comando que falha), o agente forma HIPÓTESES rankeadas, testa
3
4
  // cada uma com uma SONDA somente-leitura, lê o resultado, dá um VEREDITO (confirma/refuta)
@@ -5,6 +6,7 @@
5
6
  // É o que faltava pro TS encarar legado obscuro (ex: instalar um servidor de MMO) em vez de
6
7
  // só construir do zero: hipótese → teste → descarte → nova hipótese.
7
8
  const { api, ApiError } = require('./api');
9
+ const keyring = require('./keyring');
8
10
  const agent = require('./agent');
9
11
  const tools = require('./tools');
10
12
 
@@ -46,8 +48,10 @@ function extractJson(s) {
46
48
 
47
49
  async function diagnose(symptom, opts = {}) {
48
50
  const { token, lang = 'pt', model = null, cwd = process.cwd(), target = '', maxRounds = 12, onEvent = () => {} } = opts;
49
- const k = await api('/api/ai/key?feature=cli_agent', { token, timeoutMs: 20000 });
51
+ const k = await keyring.resolve(token, { feature: 'cli_agent' });
50
52
  if (!k || !k.key) throw new ApiError('ai_key', {});
53
+ // BYOK: sem modelo explícito, o id do gateway não existe no provedor do usuário (404).
54
+ const mdl = keyring.modeloPara(k, model);
51
55
  const wrap = (probe) => target ? `${target} ${JSON.stringify(probe)}` : probe; // remoto = prefixo ssh + sonda quotada
52
56
 
53
57
  const evidence = []; // { round, hypothesis, probe, output }
@@ -60,7 +64,7 @@ async function diagnose(symptom, opts = {}) {
60
64
  const userMsg = (lang === 'en' ? 'SYMPTOM:\n' : 'SINTOMA:\n') + symptom + '\n\n' + (lang === 'en' ? 'EVIDENCE SO FAR:\n' : 'EVIDÊNCIAS ATÉ AGORA:\n') + evLog + '\n\n' + (lang === 'en' ? `Round ${round}/${maxRounds}. Next step as JSON.` : `Rodada ${round}/${maxRounds}. Próximo passo em JSON.`);
61
65
  let r;
62
66
  try {
63
- r = await agent.llm({ baseUrl: k.baseUrl, key: k.key, model, noTools: true, signalMs: 90000, messages: [{ role: 'system', content: sysPrompt(lang, target) }, { role: 'user', content: userMsg }] });
67
+ r = await agent.llm({ baseUrl: k.baseUrl, key: k.key, model: mdl, noTools: true, signalMs: 90000, messages: [{ role: 'system', content: sysPrompt(lang, target) }, { role: 'user', content: userMsg }] });
64
68
  } catch (e) { if (e.code === 'no_credits') { result.status = 'no_credits'; break; } throw e; }
65
69
  inTok += (r.usage.prompt_tokens || 0); outTok += (r.usage.completion_tokens || 0);
66
70
  const j = extractJson(r.msg.content) || { status: 'stuck', thought: 'resposta não-JSON', probe: '' };
@@ -77,7 +81,7 @@ async function diagnose(symptom, opts = {}) {
77
81
  const vAsk = lang === 'en'
78
82
  ? 'Before finalizing, ADVERSARIALLY refute this. Give ONE read-only probe whose output would DISPROVE the root cause if it is wrong. Classic trap: a symbol/lib/file that looks "missing/broken" but is provided elsewhere (e.g. an undefined symbol in a .so is resolved by the MAIN executable at runtime → check the main binary with nm -C). JSON {"refute_probe":"...","disproves_if":"..."}'
79
83
  : 'Antes de finalizar, REFUTE adversarialmente. Dê UMA sonda só-leitura cuja saída DESMENTIRIA a causa raiz se ela estiver errada. Armadilha clássica: um símbolo/lib/arquivo que parece "faltando/quebrado" mas é provido em OUTRO lugar (ex.: símbolo indefinido numa .so é resolvido pelo EXECUTÁVEL principal em runtime → cheque o binário principal com nm -C). JSON {"refute_probe":"...","disproves_if":"..."}';
80
- let vr = null; try { vr = await agent.llm({ baseUrl: k.baseUrl, key: k.key, model, noTools: true, signalMs: 90000, messages: [{ role: 'system', content: sysPrompt(lang, target) }, { role: 'user', content: (lang === 'en' ? 'SYMPTOM:\n' : 'SINTOMA:\n') + symptom + '\n\n' + (lang === 'en' ? 'EVIDENCE:\n' : 'EVIDÊNCIAS:\n') + evLog + '\n\n' + (lang === 'en' ? 'PROPOSED ROOT CAUSE: ' : 'CAUSA RAIZ PROPOSTA: ') + claim + '\n\n' + vAsk }] }); } catch (_) {}
84
+ let vr = null; try { vr = await agent.llm({ baseUrl: k.baseUrl, key: k.key, model: mdl, noTools: true, signalMs: 90000, messages: [{ role: 'system', content: sysPrompt(lang, target) }, { role: 'user', content: (lang === 'en' ? 'SYMPTOM:\n' : 'SINTOMA:\n') + symptom + '\n\n' + (lang === 'en' ? 'EVIDENCE:\n' : 'EVIDÊNCIAS:\n') + evLog + '\n\n' + (lang === 'en' ? 'PROPOSED ROOT CAUSE: ' : 'CAUSA RAIZ PROPOSTA: ') + claim + '\n\n' + vAsk }] }); } catch (_) {}
81
85
  if (vr) { inTok += (vr.usage.prompt_tokens || 0); outTok += (vr.usage.completion_tokens || 0); }
82
86
  const vj = vr ? extractJson(vr.msg.content) : null;
83
87
  const rprobe = vj && String(vj.refute_probe || '').trim();
@@ -86,7 +90,7 @@ async function diagnose(symptom, opts = {}) {
86
90
  onEvent({ type: 'probe', round, hypothesis: '↯ verificação: ' + (j.hypothesis || ''), probe: rprobe, expect: vj.disproves_if || '' });
87
91
  let rout; try { const ex = await tools.execute('executar_comando', { comando: wrap(rprobe) }, { baseDir: cwd, token }); rout = ex.erro ? ('ERRO: ' + ex.erro) : ([ex.stdout, ex.stderr].filter(Boolean).join('\n') || ('(sem saída, exit ' + (ex.codigo ?? '?') + ')')); } catch (e) { rout = 'FALHA: ' + (e.message || e); }
88
92
  evidence.push({ round, hypothesis: '↯ verificação: ' + (j.hypothesis || ''), probe: rprobe, output: rout });
89
- let fr = null; try { fr = await agent.llm({ baseUrl: k.baseUrl, key: k.key, model, noTools: true, signalMs: 60000, messages: [{ role: 'system', content: sysPrompt(lang, target) }, { role: 'user', content: (lang === 'en' ? 'Root cause claim: ' : 'Causa raiz alegada: ') + claim + '\n' + (lang === 'en' ? 'Refutation probe output:\n' : 'Saída da sonda de refutação:\n') + String(rout).slice(0, 1600) + '\n\n' + (lang === 'en' ? 'CONFIRMED or REFUTED? JSON {"verdict":"confirmed|refuted","reason":"..."}' : 'CONFIRMADA ou REFUTADA? JSON {"verdict":"confirmed|refuted","reason":"..."}') }] }); } catch (_) {}
93
+ let fr = null; try { fr = await agent.llm({ baseUrl: k.baseUrl, key: k.key, model: mdl, noTools: true, signalMs: 60000, messages: [{ role: 'system', content: sysPrompt(lang, target) }, { role: 'user', content: (lang === 'en' ? 'Root cause claim: ' : 'Causa raiz alegada: ') + claim + '\n' + (lang === 'en' ? 'Refutation probe output:\n' : 'Saída da sonda de refutação:\n') + String(rout).slice(0, 1600) + '\n\n' + (lang === 'en' ? 'CONFIRMED or REFUTED? JSON {"verdict":"confirmed|refuted","reason":"..."}' : 'CONFIRMADA ou REFUTADA? JSON {"verdict":"confirmed|refuted","reason":"..."}') }] }); } catch (_) {}
90
94
  if (fr) { inTok += (fr.usage.prompt_tokens || 0); outTok += (fr.usage.completion_tokens || 0); }
91
95
  const fj = fr ? extractJson(fr.msg.content) : null;
92
96
  if (fj && /refut/i.test(String(fj.verdict))) {
package/lib/doctor.js CHANGED
@@ -59,6 +59,23 @@ async function run(opts = {}) {
59
59
  const dir = path.join(os.homedir(), '.ts');
60
60
  results.push({ nome: 'Config (~/.ts)', level: fs.existsSync(dir) ? 'ok' : 'warn', detail: fs.existsSync(dir) ? dir : 'ainda não criado (normal se nunca rodou nada)' });
61
61
 
62
+ // 2b) de onde vem a IA: chave própria (BYOK) ou créditos do TS.
63
+ // Provedor ativo cujo modelo NÃO chama ferramenta é um aviso de verdade: o agente
64
+ // vira um chat que narra o que faria, sem executar nada.
65
+ try {
66
+ const keyring = require('./keyring');
67
+ const a = keyring.ativo();
68
+ if (a) {
69
+ const prov = (require('./providers').info(a.id) || {}).nome || a.id;
70
+ results.push(a.toolsOk
71
+ ? { nome: 'IA (sua chave)', level: 'ok', detail: prov + (a.modelo ? ' · ' + a.modelo : '') }
72
+ : { nome: 'IA (sua chave)', level: 'warn', detail: prov + ' — o modelo não chama ferramentas',
73
+ dica: 'troque o modelo (ts conectar ' + a.id + ') ou volte: ts conectar nuvem' });
74
+ } else {
75
+ results.push({ nome: 'IA', level: 'ok', detail: 'TS Cloud (seus créditos)' });
76
+ }
77
+ } catch (_) { /* keyring é opcional pro doctor: nunca derruba o diagnóstico */ }
78
+
62
79
  // 3) gateway/backend (rede, best-effort)
63
80
  if (typeof opts.pingBackend === 'function') {
64
81
  try { const ok = await opts.pingBackend(); results.push({ nome: 'Backend', level: ok ? 'ok' : 'warn', detail: ok ? (opts.base || 'acessível') : 'sem resposta', dica: ok ? undefined : 'cheque a conexão / status em terminalsmart.com.br' }); }
package/lib/eval.js CHANGED
@@ -19,6 +19,7 @@ const os = require('os');
19
19
  const fs = require('fs');
20
20
  const path = require('path');
21
21
  const { api } = require('./api');
22
+ const keyring = require('./keyring');
22
23
  const agent = require('./agent');
23
24
  const intelligence = require('./intelligence-core');
24
25
 
@@ -154,7 +155,7 @@ async function runSuite(suite, opts = {}) {
154
155
  const { token, lang = 'pt', model = null, judgeModel = DEFAULT_JUDGE } = opts;
155
156
  const onCase = opts.onCase || (() => {});
156
157
  // chave sk-hub (mesma do agente) — reusada pra chamar o juiz direto no gateway.
157
- const k = await api('/api/ai/key?feature=cli_agent', { token, timeoutMs: 20000 });
158
+ const k = await keyring.resolve(token, { feature: 'cli_agent' });
158
159
  if (!k || !k.key) { const e = new Error('sem chave de IA'); e.code = 'ai_key'; throw e; }
159
160
 
160
161
  const results = [];
@@ -215,7 +216,8 @@ async function runSuite(suite, opts = {}) {
215
216
  if (!substituted) {
216
217
  onCase({ i, total, id: c.id, phase: 'judge' });
217
218
  try {
218
- verdict = await judge({ key: k.key, baseUrl: k.baseUrl, model: judgeModel, caseObj: c, evidence });
219
+ // BYOK: o juiz padrão é um id do gateway e não existe no provedor do usuário (404).
220
+ verdict = await judge({ key: k.key, baseUrl: k.baseUrl, model: keyring.modeloPara(k, judgeModel), caseObj: c, evidence });
219
221
  judgeTin = verdict._tin || 0; judgeTout = verdict._tout || 0;
220
222
  } catch (e) {
221
223
  if (e && (e.code === 'no_credits' || e.status === 402)) throw e;
package/lib/i18n.js CHANGED
@@ -15,10 +15,12 @@ const STR = {
15
15
  ] },
16
16
  { title: 'Galeria de Skills (comunidade)', items: [
17
17
  ['ts skills', 'lista as skills da comunidade (mais instaladas)'],
18
- ['ts skills buscar "x"', 'busca skills por termo'],
19
18
  ['ts skills ver <slug>', 'lê a skill INTEIRA antes de instalar'],
20
19
  ['ts skills add <slug>', 'instala em ~/.ts/skills'],
21
20
  ['ts skills publicar <pasta>', 'publica sua skill na galeria'],
21
+ ['ts skills buscar "o que preciso"', 'ACHA a skill certa entre instaladas + catálogo (o agente também busca sozinho)'],
22
+ ['ts skills indexar', 'monta o índice de busca (galeria + fontes externas)'],
23
+ ['ts skills fontes add NVIDIA/skills', 'soma um catálogo externo no formato Agent Skills'],
22
24
  ['ts skills pendentes', 'skills que o agente propôs em modo --yes (revisar)'],
23
25
  ['ts skills aprovar <slug>', 'aprova uma pendência (vira skill ativa)'],
24
26
  ] },
@@ -38,6 +40,7 @@ const STR = {
38
40
  ['ts agente "..." --yes', 'autônomo (destrutivo pede aprovação no Telegram)'],
39
41
  ['ts diagnosticar "erro"', 'INVESTIGA a causa raiz: hipótese→sonda→veredito (só-leitura)'],
40
42
  ['ts diagnosticar "..." --remoto "ssh user@host"', 'investiga uma máquina remota'],
43
+ ['ts diagnosticar "..." --registrar', 'salva a investigação como nota em .ts-conhecimento (inclusive o que foi REFUTADO)'],
41
44
  ['ts sentinela add "nome" --cmd "..."', 'VIGIA determinístico (regra do if, ZERO token); --escalar diagnostica se falhar; --autofix conserta (com aprovação no Telegram)'],
42
45
  ['ts sentinela instalar', 'agenda os checks no SO (cron/Task Scheduler) e avisa no Telegram'],
43
46
  ['ts agente --continuar "..."', 'retoma o trabalho anterior desta pasta'],
@@ -54,6 +57,12 @@ const STR = {
54
57
  ['ts runbook rodar <nome>', 'procedimento DevOps reexecutável: roda os passos (destrutivo pede OK) e PROVA o resultado'],
55
58
  ['ts acp', 'servidor Agent Client Protocol (conecta o ts a editores tipo Zed)'],
56
59
  ['ts mcp', 'servidores MCP: as ferramentas deles entram no agente'],
60
+ ['ts politica', 'permissões por projeto (.ts-politica.json): allow/ask/deny por categoria'],
61
+ ['ts politica testar "rm -rf build"', 'mostra o veredito e DE QUAL regra veio'],
62
+ ['ts checkpoints', 'o que o agente mudou em cada sessão'],
63
+ ['ts desfazer --sessao [id]', 'DESFAZ TUDO o que o agente fez na sessão (pula o que você editou depois)'],
64
+ ['ts conectar', 'usa SUA chave de IA (NVIDIA/Google/Cerebras/Groq/Ollama) em vez dos créditos'],
65
+ ['ts conectar nuvem', 'volta a usar os créditos do TS'],
57
66
  ] },
58
67
  { title: 'Agentes na nuvem (orquestração)', items: [
59
68
  ['ts run "objetivo"', 'planeja → você aprova → executa'],
@@ -66,6 +75,7 @@ const STR = {
66
75
  ['ts uso', 'créditos e consumo do mês'],
67
76
  ['ts quem', 'conta conectada'],
68
77
  ['ts doctor', 'diagnóstico do ambiente (Node, login, gateway, versão, deps opcionais)'],
78
+ ['ts tema', 'paleta do terminal (7 temas; a cor indica o ESTADO)'],
69
79
  ['ts idioma pt|en', 'idioma (padrão pt-BR)'],
70
80
  ] },
71
81
  ],
@@ -88,6 +98,10 @@ const STR = {
88
98
  ['/run "objetivo"', 'orquestra agentes na nuvem (plano + aprovação)'],
89
99
  ['cd <pasta> · pwd', 'muda/mostra a pasta de trabalho do agente'],
90
100
  ['/runs', 'últimas orquestrações'],
101
+ ['/modelo [nome]', 'mostra ou troca o modelo em execução'],
102
+ ['/custo', 'quanto está gastando (ou de quem é a conta)'],
103
+ ['/tema [nome]', 'troca a paleta do terminal'],
104
+ ['/limpar', 'limpa a tela (o contexto continua)'],
91
105
  ['/uso', 'créditos do mês'],
92
106
  ['/nova', 'zera o contexto'],
93
107
  ['sair', 'encerra'],
@@ -146,6 +160,7 @@ const STR = {
146
160
  agent_blocked: 'bloqueado (destrutivo)',
147
161
  agent_approve: (cmd) => `O agente quer rodar um comando DESTRUTIVO:\n\n ${cmd}\n\n Permitir? (s/N) `,
148
162
  agent_footer: (st, cr, s) => `${st} passo(s) · ${cr} créditos · ${s}`,
163
+ agent_footer_byok: (st, prov, s) => `${st} passo(s) · sua chave (${prov}) · ${s}`,
149
164
  agent_remote_wait: (ttl) => `comando destrutivo — pedido de aprovação enviado no seu Telegram (responda em até ${Math.round(ttl / 60)} min; sem resposta = negado)`,
150
165
  meta_need: 'Descreva o objetivo. Ex.: ts meta "monte um site de portfólio completo nesta pasta" --budget 300',
151
166
  meta_title: 'MISSÃO',
@@ -181,7 +196,6 @@ const STR = {
181
196
  ] },
182
197
  { title: 'Skills gallery (community)', items: [
183
198
  ['ts skills', 'list community skills (most installed)'],
184
- ['ts skills buscar "x"', 'search skills by term'],
185
199
  ['ts skills ver <slug>', 'read the WHOLE skill before installing'],
186
200
  ['ts skills add <slug>', 'install to ~/.ts/skills'],
187
201
  ['ts skills publicar <folder>', 'publish your skill to the gallery'],
@@ -207,6 +221,12 @@ const STR = {
207
221
  ['ts eval suite.json', 'grade the agent on a case suite (AI judge + score)'],
208
222
  ['ts acp', 'Agent Client Protocol server (plug ts into editors like Zed)'],
209
223
  ['ts mcp', 'MCP servers: their tools plug into the agent'],
224
+ ['ts politica', 'per-project permissions (.ts-politica.json): allow/ask/deny by category'],
225
+ ['ts politica testar "rm -rf build"', 'shows the verdict and WHICH rule produced it'],
226
+ ['ts checkpoints', 'what the agent changed in each session'],
227
+ ['ts desfazer --sessao [id]', 'UNDO everything the agent did in that session (skips what you edited after)'],
228
+ ['ts conectar', 'use YOUR own AI key (NVIDIA/Google/Cerebras/Groq/Ollama) instead of credits'],
229
+ ['ts conectar nuvem', 'switch back to TS credits'],
210
230
  ] },
211
231
  { title: 'Cloud agents (orchestration)', items: [
212
232
  ['ts run "goal"', 'plan → you approve → execute'],
@@ -218,6 +238,7 @@ const STR = {
218
238
  ['ts login · ts logout', 'connect / disconnect this terminal'],
219
239
  ['ts uso', 'monthly credits and usage'],
220
240
  ['ts quem', 'connected account'],
241
+ ['ts tema', 'terminal palette (7 themes; color means STATE)'],
221
242
  ['ts idioma pt|en', 'language (default pt-BR)'],
222
243
  ] },
223
244
  ],
@@ -240,6 +261,10 @@ const STR = {
240
261
  ['/run "goal"', 'orchestrate cloud agents (plan + approval)'],
241
262
  ['cd <folder> · pwd', 'change/show the agent working folder'],
242
263
  ['/runs', 'recent orchestrations'],
264
+ ['/modelo [name]', 'show or switch the running model'],
265
+ ['/custo', 'what it is costing (and whose bill it is)'],
266
+ ['/tema [name]', 'switch the terminal palette'],
267
+ ['/limpar', 'clear the screen (context stays)'],
243
268
  ['/uso', 'monthly credits'],
244
269
  ['/nova', 'reset context'],
245
270
  ['exit', 'quit'],
@@ -298,6 +323,7 @@ const STR = {
298
323
  agent_blocked: 'blocked (destructive)',
299
324
  agent_approve: (cmd) => `The agent wants to run a DESTRUCTIVE command:\n\n ${cmd}\n\n Allow? (y/N) `,
300
325
  agent_footer: (st, cr, s) => `${st} step(s) · ${cr} credits · ${s}`,
326
+ agent_footer_byok: (st, prov, s) => `${st} step(s) · your key (${prov}) · ${s}`,
301
327
  agent_remote_wait: (ttl) => `destructive command — approval request sent to your Telegram (answer within ${Math.round(ttl / 60)} min; no answer = denied)`,
302
328
  meta_need: 'Describe the goal. E.g.: ts meta "build a complete portfolio site in this folder" --budget 300',
303
329
  meta_title: 'MISSION',