terminal-smart-cli 0.97.7 → 0.97.8
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 +93 -8
- package/lib/agent.js +125 -12
- package/lib/doctor.js +12 -4
- package/lib/i18n.js +6 -0
- package/lib/intelligence-core.js +95 -6
- package/lib/keyring.js +11 -0
- package/lib/office-editors.js +171 -0
- package/lib/providers.js +24 -7
- package/lib/tools.js +158 -0
- package/lib/ui.js +13 -3
- package/lib/xlsx-compat-editor.js +228 -0
- package/package.json +4 -3
package/bin/ts.js
CHANGED
|
@@ -119,7 +119,37 @@ function help() {
|
|
|
119
119
|
console.log(out.join('\n'));
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
function privacyCmd() {
|
|
123
|
+
const privacyUrl = 'https://terminalsmart.com.br/privacidade';
|
|
124
|
+
console.log((cfg.lang === 'en' ? 'Privacy Policy: ' : 'Política de Privacidade: ') + privacyUrl);
|
|
125
|
+
}
|
|
126
|
+
|
|
122
127
|
// ── Login por código (device-code) ───────────────────────────────────────────
|
|
128
|
+
async function accountCmd(args) {
|
|
129
|
+
const action = String((args && args[0]) || '').toLowerCase();
|
|
130
|
+
const accountUrl = base() + '/minha-conta';
|
|
131
|
+
if (!action || ['abrir', 'open', 'excluir', 'delete'].includes(action)) {
|
|
132
|
+
console.log((cfg.lang === 'en' ? 'Account, data export and deletion: ' : 'Conta, exportação de dados e exclusão: ') + accountUrl);
|
|
133
|
+
if (process.env.TS_NO_BROWSER !== '1') try {
|
|
134
|
+
const { exec } = require('child_process');
|
|
135
|
+
const cmd = process.platform === 'win32' ? `start "" "${accountUrl}"` : process.platform === 'darwin' ? `open "${accountUrl}"` : `xdg-open "${accountUrl}"`;
|
|
136
|
+
exec(cmd, () => {});
|
|
137
|
+
} catch (_) {}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (['exportar', 'export', 'baixar', 'download'].includes(action)) {
|
|
141
|
+
const token = needToken();
|
|
142
|
+
const data = await api('/api/account/export', { token, retry: false });
|
|
143
|
+
const fs = require('fs'), path = require('path');
|
|
144
|
+
const safeUser = String(cfg.username || 'usuario').replace(/[^a-z0-9_-]/gi, '-');
|
|
145
|
+
const file = path.resolve(`terminal-smart-dados-${safeUser}-${new Date().toISOString().slice(0, 10)}.json`);
|
|
146
|
+
fs.writeFileSync(file, JSON.stringify(data, null, 2), { encoding: 'utf8', mode: 0o600 });
|
|
147
|
+
console.log(ui.okLine((cfg.lang === 'en' ? 'Data saved to: ' : 'Dados salvos em: ') + file));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
console.log(ui.infoLine(cfg.lang === 'en' ? 'Use: ts account export | ts account open' : 'Use: ts conta exportar | ts conta abrir'));
|
|
151
|
+
}
|
|
152
|
+
|
|
123
153
|
async function login() {
|
|
124
154
|
if (cfg.token) {
|
|
125
155
|
try {
|
|
@@ -1134,16 +1164,19 @@ async function agentCmd(words, { askFn, cwd: cwdIn = null, onCwd = null, maxIter
|
|
|
1134
1164
|
const token = needToken();
|
|
1135
1165
|
// flags que levam VALOR: o filtro remove só a flag (começa com "-"), deixando o VALOR nos
|
|
1136
1166
|
// words → tira o valor daqui pra ele não virar parte do texto da tarefa.
|
|
1137
|
-
for (const flag of ['--modelo', '--model', '--output-format']) {
|
|
1167
|
+
for (const flag of ['--modelo', '--model', '--output-format', '--orcamento-creditos', '--max-creditos', '--tempo-max-seg', '--max-tokens']) {
|
|
1138
1168
|
const _i = process.argv.findIndex(a => a === flag); const _v = _i >= 0 ? process.argv[_i + 1] : null;
|
|
1139
1169
|
if (_v) { const j = words.indexOf(_v); if (j >= 0) words = words.slice(0, j).concat(words.slice(j + 1)); }
|
|
1140
1170
|
}
|
|
1141
1171
|
let task = words.join(' ').trim();
|
|
1142
1172
|
// Flags INLINE no texto (pro REPL, onde não há argv): --passos N / --steps N / --yolo.
|
|
1143
1173
|
// Assim "faça tudo ... --passos 60 --yolo" digitado no chat vale igual à linha de comando.
|
|
1144
|
-
let _inlinePassos, _inlineYolo = false;
|
|
1174
|
+
let _inlinePassos, _inlineYolo = false, _inlineMaxCredits, _inlineMaxSeconds, _inlineMaxTokens;
|
|
1145
1175
|
task = task
|
|
1146
1176
|
.replace(/(?:^|\s)--(?:passos|steps|max-passos)[\s=]+(\d{1,3})\b/gi, (_m, n) => { _inlinePassos = Number(n); return ' '; })
|
|
1177
|
+
.replace(/(?:^|\s)--(?:orcamento-creditos|max-creditos)[\s=]+(\d{1,5})\b/gi, (_m, n) => { _inlineMaxCredits = Number(n); return ' '; })
|
|
1178
|
+
.replace(/(?:^|\s)--tempo-max-seg[\s=]+(\d{1,5})\b/gi, (_m, n) => { _inlineMaxSeconds = Number(n); return ' '; })
|
|
1179
|
+
.replace(/(?:^|\s)--max-tokens[\s=]+(\d{3,7})\b/gi, (_m, n) => { _inlineMaxTokens = Number(n); return ' '; })
|
|
1147
1180
|
.replace(/(?:^|\s)--(?:yolo|full-auto|auto-tudo)\b/gi, () => { _inlineYolo = true; return ' '; })
|
|
1148
1181
|
.replace(/\s{2,}/g, ' ').trim();
|
|
1149
1182
|
const piped = await readStdin();
|
|
@@ -1221,8 +1254,15 @@ async function agentCmd(words, { askFn, cwd: cwdIn = null, onCwd = null, maxIter
|
|
|
1221
1254
|
// Pra missões grandes ("faça tudo de uma vez") sem o pára-e-continua.
|
|
1222
1255
|
const _pi = process.argv.findIndex(a => a === '--passos' || a === '--steps' || a === '--max-passos');
|
|
1223
1256
|
const _maxPassos = _pi >= 0 ? Number(process.argv[_pi + 1]) : (_inlinePassos ?? maxIterIn ?? undefined);
|
|
1257
|
+
const _ci = process.argv.findIndex(a => a === '--orcamento-creditos' || a === '--max-creditos');
|
|
1258
|
+
const _ti = process.argv.findIndex(a => a === '--tempo-max-seg');
|
|
1259
|
+
const _tki = process.argv.findIndex(a => a === '--max-tokens');
|
|
1260
|
+
const _maxCredits = _ci >= 0 ? Number(process.argv[_ci + 1]) : _inlineMaxCredits;
|
|
1261
|
+
const _maxDurationMs = 1000 * (_ti >= 0 ? Number(process.argv[_ti + 1]) : (_inlineMaxSeconds || 0));
|
|
1262
|
+
const _maxTokens = _tki >= 0 ? Number(process.argv[_tki + 1]) : _inlineMaxTokens;
|
|
1224
1263
|
out = await agent.run(task, {
|
|
1225
1264
|
token, lang: cfg.lang || 'pt', yes: YES, autoAll: (YOLO || _inlineYolo || autoAllIn), model, priorMessages, cwd: startCwd, readOnly, plan, browser: useBrowser, maxIter: _maxPassos,
|
|
1265
|
+
maxCredits: _maxCredits, maxDurationMs: _maxDurationMs || undefined, maxTokens: _maxTokens,
|
|
1226
1266
|
onThinking: (p) => {
|
|
1227
1267
|
if (p && typeof p === 'object') {
|
|
1228
1268
|
_live.steps = p.steps ?? _live.steps; _live.inTok = p.inTok ?? _live.inTok; _live.outTok = p.outTok ?? _live.outTok;
|
|
@@ -1288,10 +1328,10 @@ async function agentCmd(words, { askFn, cwd: cwdIn = null, onCwd = null, maxIter
|
|
|
1288
1328
|
// HEADLESS stream-json: fecha com assistant (texto) + result (métricas) e sai.
|
|
1289
1329
|
if (streamJson) {
|
|
1290
1330
|
_emit(core.AgentEvents.assistant(out.text || ''));
|
|
1291
|
-
_emit(core.AgentEvents.result({ steps: out.steps, credits: out.credits, tokens: out.tokens, context: out.context || null, needHuman: out.needHuman || null, toolErrors: out.toolErrors || [], cwd: effCwd, duration_ms: Date.now() - t0, worktree: _wt ? { path: _wt.path, branch: _wt.branch, base: _wt.base, changed: (_wtInfo && _wtInfo.changed) || 0 } : null }));
|
|
1331
|
+
_emit(core.AgentEvents.result({ steps: out.steps, credits: out.credits, tokens: out.tokens, context: out.context || null, needHuman: out.needHuman || null, toolErrors: out.toolErrors || [], guard: out.guard || null, cwd: effCwd, duration_ms: Date.now() - t0, worktree: _wt ? { path: _wt.path, branch: _wt.branch, base: _wt.base, changed: (_wtInfo && _wtInfo.changed) || 0 } : null }));
|
|
1292
1332
|
return;
|
|
1293
1333
|
}
|
|
1294
|
-
if (JSON_OUT) { console.log(JSON.stringify({ ok: true, result: out.text, steps: out.steps, credits: out.credits, tokens: out.tokens, context: out.context, needHuman: out.needHuman })); return; }
|
|
1334
|
+
if (JSON_OUT) { console.log(JSON.stringify({ ok: true, result: out.text, steps: out.steps, credits: out.credits, tokens: out.tokens, context: out.context, needHuman: out.needHuman, guard: out.guard || null })); return; }
|
|
1295
1335
|
// BLOQUEIO HUMANO: o agente pediu uma ação que só o usuário faz — mostra o pedido
|
|
1296
1336
|
// (antes o retorno vinha com texto vazio e o usuário via só um cabeçalho em branco).
|
|
1297
1337
|
if (out.needHuman) {
|
|
@@ -2594,6 +2634,9 @@ async function doctorCmd() {
|
|
|
2594
2634
|
cfg, version: pkg.version, base: base(),
|
|
2595
2635
|
// backend no ar? 401 conta como "respondeu" (só não autenticado).
|
|
2596
2636
|
pingBackend: async () => { try { await api('/api/auth/check', { token: cfg.token, timeoutMs: 8000 }); return true; } catch (e) { return (e && e.status === 401); } },
|
|
2637
|
+
// Plano autoritativo. O de ~/.ts é do último login e envelhece a cada assinatura,
|
|
2638
|
+
// troca ou cancelamento — o diagnóstico mostrava um plano que já não existe mais.
|
|
2639
|
+
planoAtual: async () => { try { const r = await api('/api/credits', { token: cfg.token, timeoutMs: 8000 }); return (r && r.plan) || null; } catch (_) { return null; } },
|
|
2597
2640
|
// última versão publicada no npm (best-effort).
|
|
2598
2641
|
latestVersion: async () => { try { const r = await fetch('https://registry.npmjs.org/terminal-smart-cli/latest', { signal: AbortSignal.timeout(8000) }); const j = await r.json(); return j && j.version; } catch (_) { return null; } },
|
|
2599
2642
|
});
|
|
@@ -2662,9 +2705,10 @@ async function siteCmd(args) {
|
|
|
2662
2705
|
if (JSON_OUT) return console.log(JSON.stringify(r));
|
|
2663
2706
|
if (!r.sites || !r.sites.length) return console.log(ui.infoLine('Nenhum site criado. Use: ts site criar <nome> "<descrição>"'));
|
|
2664
2707
|
console.log('\n' + ui.box(r.sites.map(s =>
|
|
2665
|
-
C.bold(s.slug) + C.dim(' · ') + (s.status === 'active' ? C.ok('online') : C.dim(s.status)) +
|
|
2666
|
-
C.dim(s.preview ? '
|
|
2667
|
-
|
|
2708
|
+
C.bold(s.name || s.slug) + C.dim(' · ') + (s.status === 'active' ? C.ok('online') : C.dim(s.status)) +
|
|
2709
|
+
C.dim(` · ${s.kind || (s.preview ? 'preview' : 'permanente')}`) + '\n ' + C.cyan(s.url) +
|
|
2710
|
+
(s.expiresAt ? C.dim('\n expira: ' + s.expiresAt) : '')
|
|
2711
|
+
), { title: `ts site · ${r.plan} · ${(r.limits && r.limits.used) ?? r.sites.length}/${(r.limits && r.limits.maximum) || r.siteLimit || 'demo'}` }) + '\n');
|
|
2668
2712
|
return;
|
|
2669
2713
|
}
|
|
2670
2714
|
if (['criar', 'create', 'gerar', 'generate', 'editar', 'edit', 'alterar', 'update'].includes(sub)) {
|
|
@@ -2871,7 +2915,8 @@ async function uso() {
|
|
|
2871
2915
|
'',
|
|
2872
2916
|
unlimited
|
|
2873
2917
|
? C.ok(T.uso_unlimited) + C.dim(` · ${r.used || 0} ${T.uso_credits}`)
|
|
2874
|
-
|
|
2918
|
+
// A barra mostra o que RESTA, igual ao número ao lado dela.
|
|
2919
|
+
: ui.bar(Math.max(0, r.remaining ?? 0) / Math.max(1, r.granted || 1), 22, { restante: true }) + ` ${C.bold(r.remaining ?? '?')} ${C.dim(`/ ${r.granted} ${T.uso_remaining}`)}`,
|
|
2875
2920
|
];
|
|
2876
2921
|
const ledger = (r.ledger || []).slice(0, 5);
|
|
2877
2922
|
if (ledger.length) {
|
|
@@ -2893,6 +2938,43 @@ async function quem() {
|
|
|
2893
2938
|
], { title: T.quem_title }) + '\n');
|
|
2894
2939
|
}
|
|
2895
2940
|
|
|
2941
|
+
async function integracoesCmd(args) {
|
|
2942
|
+
const token = needToken(); const en = cfg.lang === 'en';
|
|
2943
|
+
const sub = String(args[0] || 'status').toLowerCase();
|
|
2944
|
+
const rawProvider = String(args[1] || '').toLowerCase();
|
|
2945
|
+
const provider = ['google', 'gmail', 'drive'].includes(rawProvider) ? 'google'
|
|
2946
|
+
: ['microsoft', 'outlook', 'hotmail', 'office'].includes(rawProvider) ? 'microsoft' : '';
|
|
2947
|
+
const names = { google: 'Google Workspace', microsoft: 'Microsoft/Outlook' };
|
|
2948
|
+
const getStatus = async p => {
|
|
2949
|
+
try { return await api(`/api/integrations/${p}/status`, { token }); }
|
|
2950
|
+
catch (e) { return { connected: false, error: e.message }; }
|
|
2951
|
+
};
|
|
2952
|
+
if (['status', 'listar', 'list', 'ls'].includes(sub)) {
|
|
2953
|
+
const [google, microsoft] = await Promise.all([getStatus('google'), getStatus('microsoft')]);
|
|
2954
|
+
const data = { google, microsoft };
|
|
2955
|
+
if (JSON_OUT) { console.log(JSON.stringify(data)); return; }
|
|
2956
|
+
const line = (label, item) => C.bold(label.padEnd(22)) + (item.connected ? C.ok(en ? 'connected' : 'conectado') + C.dim(item.email ? ` · ${item.email}` : '') : C.dim(en ? 'not connected' : 'não conectado'));
|
|
2957
|
+
console.log('\n' + ui.box([line('Google Workspace', google), line('Microsoft/Outlook', microsoft), '', C.dim(en ? 'Connect: ts integrations connect google|microsoft' : 'Conectar: ts integracoes conectar google|microsoft'), C.dim(en ? 'Manage on web: terminalsmart.com.br/integracoes' : 'Gerenciar na Web: terminalsmart.com.br/integracoes')], { title: en ? 'Integrations' : 'Integrações' }) + '\n');
|
|
2958
|
+
return;
|
|
2959
|
+
}
|
|
2960
|
+
if (!provider) { console.error(ui.infoLine(en ? 'Choose google or microsoft.' : 'Escolha google ou microsoft.')); process.exit(2); }
|
|
2961
|
+
if (['conectar', 'connect', 'reconectar', 'reconnect'].includes(sub)) {
|
|
2962
|
+
const data = await api(`/api/integrations/${provider}/connect`, { method: 'POST', token, body: { source: 'cli' } });
|
|
2963
|
+
if (JSON_OUT) { console.log(JSON.stringify({ provider, authUrl: data.authUrl })); return; }
|
|
2964
|
+
console.log('\n' + ui.box([C.bold(en ? `Authorize ${names[provider]}` : `Autorize ${names[provider]}`), '', data.authUrl, '', C.dim(en ? 'After authorization, run: ts integrations' : 'Depois de autorizar, rode: ts integracoes')], { title: 'OAuth' }) + '\n');
|
|
2965
|
+
try { require('child_process').exec(process.platform === 'win32' ? `start "" "${data.authUrl}"` : process.platform === 'darwin' ? `open "${data.authUrl}"` : `xdg-open "${data.authUrl}"`); } catch (_) {}
|
|
2966
|
+
return;
|
|
2967
|
+
}
|
|
2968
|
+
if (['desconectar', 'disconnect', 'remover', 'remove'].includes(sub)) {
|
|
2969
|
+
let confirmed = FLAGS.has('--confirmar') || FLAGS.has('--confirm');
|
|
2970
|
+
if (!confirmed && process.stdin.isTTY) confirmed = ['s','sim','y','yes'].includes(String(await ui.ask(C.warn(`Desconectar ${names[provider]}? (s/N) `))).trim().toLowerCase());
|
|
2971
|
+
if (!confirmed) { console.log(ui.infoLine(en ? 'Cancelled. Use --confirm to automate this specific action.' : 'Cancelado. Use --confirmar para automatizar esta ação específica.')); return; }
|
|
2972
|
+
await api(`/api/integrations/${provider}/disconnect`, { method: 'POST', token, body: { confirmed: true } });
|
|
2973
|
+
console.log(ui.okLine(`${names[provider]} ${en ? 'disconnected' : 'desconectado'}`)); return;
|
|
2974
|
+
}
|
|
2975
|
+
console.log(ui.infoLine(en ? 'Usage: ts integrations [status|connect|disconnect] [google|microsoft]' : 'Uso: ts integracoes [status|conectar|desconectar] [google|microsoft]'));
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2896
2978
|
function idioma(l) {
|
|
2897
2979
|
const lang = String(l || '').toLowerCase();
|
|
2898
2980
|
if (!['pt', 'en'].includes(lang)) { console.error(ui.infoLine(T.lang_invalid)); process.exit(2); }
|
|
@@ -2993,6 +3075,9 @@ function recallCmd(args) {
|
|
|
2993
3075
|
case 'runbook': case 'runbooks': case 'procedimento': return runbookCmd(POS.slice(1));
|
|
2994
3076
|
case 'trilha': case 'trail': case 'auditoria': return trilhaCmd();
|
|
2995
3077
|
case 'doctor': case 'diagnostico-ambiente': case 'checkup': return doctorCmd();
|
|
3078
|
+
case 'privacidade': case 'privacy': return privacyCmd();
|
|
3079
|
+
case 'conta': case 'account': return accountCmd(POS.slice(1));
|
|
3080
|
+
case 'integracoes': case 'integrações': case 'integrations': return integracoesCmd(POS.slice(1));
|
|
2996
3081
|
case 'meta': case 'missao': case 'mission': return metaCmd();
|
|
2997
3082
|
case 'runs': return runsCmd();
|
|
2998
3083
|
case 'status': return statusCmd(POS[1]);
|
package/lib/agent.js
CHANGED
|
@@ -8,6 +8,7 @@ const fs = require('fs');
|
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const { api, ApiError, withRetry } = require('./api');
|
|
10
10
|
const keyring = require('./keyring');
|
|
11
|
+
const providers = require('./providers');
|
|
11
12
|
const policy = require('./policy');
|
|
12
13
|
const checkpoint = require('./checkpoint');
|
|
13
14
|
const skillIndex = require('./skill-index');
|
|
@@ -36,6 +37,44 @@ function installedSkills() {
|
|
|
36
37
|
|
|
37
38
|
const MAX_ITER = 15;
|
|
38
39
|
const TOOL_RESULT_CAP = 6000; // chars por resultado no contexto (compactor já reduz antes)
|
|
40
|
+
const DEFAULT_MISSION_CREDIT_CAP = 40;
|
|
41
|
+
const DEFAULT_MISSION_TOKEN_CAP = 180000;
|
|
42
|
+
const DEFAULT_MISSION_TIME_MS = 120000;
|
|
43
|
+
const DEFAULT_EQUIVALENT_FAILURE_CAP = 2;
|
|
44
|
+
|
|
45
|
+
function failureFingerprint(tool, classified, result) {
|
|
46
|
+
const evidence = String((classified && classified.evidence) || (result && (result.erro || result.stderr)) || '')
|
|
47
|
+
.toLowerCase()
|
|
48
|
+
.replace(/[a-f0-9]{16,}/g, '<id>')
|
|
49
|
+
.replace(/\d+/g, '#')
|
|
50
|
+
.replace(/\s+/g, ' ')
|
|
51
|
+
.trim()
|
|
52
|
+
.slice(0, 180);
|
|
53
|
+
return `${String(tool || 'tool')}|${String((classified && classified.errorClass) || 'unknown')}|${evidence}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function validarModeloByok(k) {
|
|
57
|
+
if (!k || k.source !== 'byok') return null;
|
|
58
|
+
const estado = providers.estadoModelo(k.provedor, k.modelo);
|
|
59
|
+
if (estado.disponivel) return null;
|
|
60
|
+
const dica = estado.substituto ? ' Troque para "' + estado.substituto + '"' : ' Escolha outro modelo';
|
|
61
|
+
return new ApiError('O modelo BYOK "' + k.modelo + '" foi aposentado: ' + estado.motivo + '.' + dica +
|
|
62
|
+
' com `ts conectar ' + k.provedor + '`. O modo Automático pode usar fallback com `ts conectar nuvem`.',
|
|
63
|
+
{ status: 410, code: 'model_retired' });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function missionGuardDecision({ credits = 0, maxCredits = DEFAULT_MISSION_CREDIT_CAP,
|
|
67
|
+
tokens = 0, maxTokens = DEFAULT_MISSION_TOKEN_CAP, elapsedMs = 0,
|
|
68
|
+
maxMs = DEFAULT_MISSION_TIME_MS, equivalentFailures = 0,
|
|
69
|
+
maxEquivalentFailures = DEFAULT_EQUIVALENT_FAILURE_CAP } = {}) {
|
|
70
|
+
if (maxCredits > 0 && credits >= maxCredits) return { kind: 'credits', limit: maxCredits, spent: credits };
|
|
71
|
+
if (maxTokens > 0 && tokens >= maxTokens) return { kind: 'tokens', limit: maxTokens, spent: tokens };
|
|
72
|
+
if (maxMs > 0 && elapsedMs >= maxMs) return { kind: 'time', limit: maxMs, spent: elapsedMs };
|
|
73
|
+
if (maxEquivalentFailures > 0 && equivalentFailures >= maxEquivalentFailures) {
|
|
74
|
+
return { kind: 'equivalent_failures', limit: maxEquivalentFailures, spent: equivalentFailures };
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
39
78
|
|
|
40
79
|
// ── NÚCLEO CANÔNICO ──────────────────────────────────────────────────────────
|
|
41
80
|
// Executor padrão, janelas de contexto e auto-compactação vêm de lib/core.js (fonte
|
|
@@ -122,8 +161,9 @@ RULES:
|
|
|
122
161
|
// fechamento garantido pra o modelo não tentar chamar ferramenta de novo).
|
|
123
162
|
// Ferramentas SÓ-LEITURA: usadas no modo Ask (--ler), no Plan (--plano) e no sub-agente
|
|
124
163
|
// de exploração (nunca escrevem/rodam comando destrutivo → seguras por construção).
|
|
125
|
-
const READONLY = new Set(['ler_arquivo', 'ler_documento', 'ler_apresentacao', 'listar_diretorio', 'buscar_arquivos', 'buscar_codigo', 'mapa_projeto', 'info_sistema', 'buscar_web', 'buscar_skill', 'android_dispositivos', 'android_logs']);
|
|
164
|
+
const READONLY = new Set(['ler_arquivo', 'ler_documento', 'ler_apresentacao', 'listar_diretorio', 'buscar_arquivos', 'buscar_codigo', 'mapa_projeto', 'info_sistema', 'buscar_web', 'buscar_skill', 'android_dispositivos', 'android_logs', 'status_microsoft365', 'listar_emails_outlook', 'ler_email_outlook', 'listar_pastas_outlook', 'obter_anexo_outlook', 'status_google_workspace', 'listar_emails_gmail', 'ler_email_gmail', 'listar_pastas_gmail', 'obter_anexo_gmail', 'listar_arquivos_drive', 'ler_google_docs', 'ler_google_sheets']);
|
|
126
165
|
const DEVICE_MUTATING = new Set(['android_parear', 'android_conectar', 'android_instalar', 'android_iniciar', 'android_capturar_tela']);
|
|
166
|
+
const CLOUD_MUTATING = new Set(['conectar_microsoft365', 'alterar_email_outlook', 'criar_resposta_outlook', 'criar_encaminhamento_outlook', 'criar_rascunho_outlook', 'enviar_rascunho_outlook', 'alterar_email_gmail', 'criar_resposta_gmail', 'criar_encaminhamento_gmail', 'criar_rascunho_gmail', 'enviar_rascunho_gmail', 'editar_google_docs', 'editar_google_sheets']);
|
|
127
167
|
async function llm({ baseUrl, key, messages, model, signalMs = 180000, noTools = false, toolsOverride = null, onRetry = null }) {
|
|
128
168
|
// RESILIÊNCIA: o gateway CDC pode reiniciar/oscilar no meio de uma missão longa.
|
|
129
169
|
// withRetry cobre conn/timeout/5xx (backoff+jitter); NUNCA re-tenta no_credits/auth.
|
|
@@ -268,6 +308,11 @@ async function _remoteApprove(comando, token, onRemote) {
|
|
|
268
308
|
async function run(task, opts = {}) {
|
|
269
309
|
const { token, lang = 'pt', yes = false, autoAll = false, model = null, confineDir = null, onStep = () => {}, onStepDone = () => {}, askApprove = async () => false, onThinking = () => {}, onRemote = () => {} } = opts;
|
|
270
310
|
const maxIter = Math.max(1, Math.min(Number(opts.maxIter) || MAX_ITER, 120)); // --passos N (teto 120)
|
|
311
|
+
const maxCredits = Math.max(1, Math.min(Number(opts.maxCredits) || DEFAULT_MISSION_CREDIT_CAP, 5000));
|
|
312
|
+
const maxTokens = Math.max(1000, Math.min(Number(opts.maxTokens) || DEFAULT_MISSION_TOKEN_CAP, 2000000));
|
|
313
|
+
const maxDurationMs = Math.max(10000, Math.min(Number(opts.maxDurationMs) || DEFAULT_MISSION_TIME_MS, 3600000));
|
|
314
|
+
const maxEquivalentFailures = Math.max(1, Math.min(Number(opts.maxEquivalentFailures) || DEFAULT_EQUIVALENT_FAILURE_CAP, 10));
|
|
315
|
+
const missionStartedAt = Date.now();
|
|
271
316
|
// FULL-AUTO (autoridade total): aprova destrutivos sem perguntar. Começa por --yolo/--full-auto
|
|
272
317
|
// (opts.autoAll) e pode ser LIGADO no meio da sessão quando o humano responde "a" (aprovar tudo).
|
|
273
318
|
let autoApproveDestructive = !!autoAll;
|
|
@@ -296,6 +341,11 @@ async function run(task, opts = {}) {
|
|
|
296
341
|
// o gate por plano no backend (402 plan_limit → mensagem de upgrade).
|
|
297
342
|
const k = await keyring.resolve(token, { feature: 'cli_agent' });
|
|
298
343
|
if (!k || !k.key) throw new ApiError('ai_key', {});
|
|
344
|
+
// BYOK é uma escolha explícita do usuário: nunca substituímos silenciosamente
|
|
345
|
+
// o modelo. Se ele foi aposentado, paramos ANTES da primeira chamada e indicamos
|
|
346
|
+
// como trocar. No modo TS Cloud/Automático, o gateway continua livre para fallback.
|
|
347
|
+
const erroModeloByok = validarModeloByok(k);
|
|
348
|
+
if (erroModeloByok) throw erroModeloByok;
|
|
299
349
|
let selectedModel = model;
|
|
300
350
|
// BYOK: o catálogo do roteador é do GATEWAY (ids tipo "deepseek-v4-flash"); o provedor
|
|
301
351
|
// do usuário tem os seus ("deepseek-ai/deepseek-v4-flash") e devolve 404 pro id errado.
|
|
@@ -491,7 +541,15 @@ async function run(task, opts = {}) {
|
|
|
491
541
|
const _mcpSeen = new Set(); // servidores MCP já autorizados NESTA sessão (1ª chamada pede OK)
|
|
492
542
|
const actions = []; // ações REAIS bem-sucedidas (evidência objetiva pro marcador do meta)
|
|
493
543
|
const _toolErrs = []; // erros de ferramenta TIPADOS na run (core.classifyToolResult) — sinal duro anti-done-falso
|
|
544
|
+
const _failureCounts = new Map();
|
|
494
545
|
let finalText = '', usedModel = 'smart', steps = 0, charged = 0, _visionCredits = 0;
|
|
546
|
+
let guardStopped = null;
|
|
547
|
+
const _guard = (equivalentFailures = 0) => missionGuardDecision({
|
|
548
|
+
credits: charged + _visionCredits, maxCredits,
|
|
549
|
+
tokens: acc.inTok + acc.outTok, maxTokens,
|
|
550
|
+
elapsedMs: Date.now() - missionStartedAt, maxMs: maxDurationMs,
|
|
551
|
+
equivalentFailures, maxEquivalentFailures,
|
|
552
|
+
});
|
|
495
553
|
const ctxWindow = winFor(selectedModel);
|
|
496
554
|
// MEMÓRIA EPISÓDICA: ao fim da run, grava UM episódio (o que fez aqui) → a próxima run
|
|
497
555
|
// deste projeto LEMBRA e dá continuidade (resolve o "esquecimento entre execuções").
|
|
@@ -569,6 +627,8 @@ async function run(task, opts = {}) {
|
|
|
569
627
|
let stopped = false;
|
|
570
628
|
for (let iter = 0; iter < maxIter; iter++) {
|
|
571
629
|
if (opts.shouldStop && opts.shouldStop()) { stopped = true; break; }
|
|
630
|
+
guardStopped = _guard();
|
|
631
|
+
if (guardStopped) break;
|
|
572
632
|
// passa o snapshot de progresso pra a status line ao vivo (tempo é contado no cliente)
|
|
573
633
|
onThinking({ iter, inTok: acc.inTok, outTok: acc.outTok, cachedTok: acc.cachedTok, steps, model: usedModel });
|
|
574
634
|
await _compactIfNeeded(false); // proativo: compacta ao cruzar ~65% da janela
|
|
@@ -600,12 +660,28 @@ async function run(task, opts = {}) {
|
|
|
600
660
|
}
|
|
601
661
|
messages.push({ role: 'assistant', content: r.msg.content || '', tool_calls: tcs });
|
|
602
662
|
|
|
663
|
+
// A chamada que acabou de responder já consumiu créditos/tokens. Se ela atingiu o
|
|
664
|
+
// teto, não executamos as ferramentas propostas nem fazemos outra chamada cara.
|
|
665
|
+
guardStopped = _guard();
|
|
666
|
+
if (guardStopped) {
|
|
667
|
+
for (const tc of tcs) messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify({
|
|
668
|
+
erro: 'MISSÃO INTERROMPIDA PELO LIMITE DE CUSTO/TEMPO. Nenhuma ferramenta deste lote foi executada.',
|
|
669
|
+
guard: guardStopped,
|
|
670
|
+
}) });
|
|
671
|
+
break;
|
|
672
|
+
}
|
|
673
|
+
|
|
603
674
|
for (const tc of tcs) {
|
|
604
675
|
const name = (tc.function && tc.function.name) || '';
|
|
605
676
|
let input = {}; try { input = JSON.parse((tc.function && tc.function.arguments) || '{}'); } catch (_) {}
|
|
606
677
|
let result;
|
|
607
678
|
let _ran = false; // true só quando uma ferramenta REALMENTE executou (não gate/bloqueio) → status ✓/✗
|
|
608
679
|
|
|
680
|
+
guardStopped = _guard();
|
|
681
|
+
if (guardStopped) {
|
|
682
|
+
result = { erro: 'MISSÃO INTERROMPIDA PELO LIMITE DE CUSTO/TEMPO. Esta ferramenta não foi executada.', guard: guardStopped };
|
|
683
|
+
}
|
|
684
|
+
|
|
609
685
|
// ── WATCHDOG anti-loop: mede repetição ANTES de qualquer gate/execução ──
|
|
610
686
|
const _sig = loopSig(name, input);
|
|
611
687
|
_recentSigs.push(_sig); if (_recentSigs.length > 8) _recentSigs.shift();
|
|
@@ -722,6 +798,24 @@ async function run(task, opts = {}) {
|
|
|
722
798
|
// Dispositivos físicos: parear/conectar/instalar/iniciar/capturar alteram o
|
|
723
799
|
// aparelho ou coletam sua tela. Passam por consentimento explícito sem
|
|
724
800
|
// transformar IP/código/caminho em comando shell.
|
|
801
|
+
// Nuvem pessoal: cada alteração exige consentimento específico. Nem --yes nem
|
|
802
|
+
// "aprovar tudo" silenciam este gate.
|
|
803
|
+
if (result === undefined && CLOUD_MUTATING.has(name)) {
|
|
804
|
+
const payload = JSON.stringify(input || {});
|
|
805
|
+
const provider = name.includes('outlook') || name.includes('microsoft365') ? 'MICROSOFT 365' : 'GOOGLE WORKSPACE';
|
|
806
|
+
const label = `${provider} → ${name}: ${payload.length > 1500 ? payload.slice(0, 1500) + ` …(+${payload.length - 1500} chars)` : payload}`;
|
|
807
|
+
let approved = false, remoteTried = false;
|
|
808
|
+
if (!yes) {
|
|
809
|
+
const dec = await askApprove({ kind: 'cloud_mutation', cmd: label, warning: name.includes('enviar_') ? 'O e-mail será enviado ao destinatário.' : `Dados da conta ${provider} serão alterados.` });
|
|
810
|
+
approved = decideApproval({ autoAll: false, decision: dec }).approved;
|
|
811
|
+
} else ({ approved, remoteTried } = await _remoteApprove(label, token, onRemote));
|
|
812
|
+
if (!approved) {
|
|
813
|
+
result = { erro: yes
|
|
814
|
+
? (remoteTried ? `RECUSADO: o dono negou ou não respondeu à alteração ${provider}.` : `RECUSADO: alteração ${provider} exige aprovação remota no modo --yes.`)
|
|
815
|
+
: `O usuário recusou a alteração na conta ${provider}. Não repita nesta sessão.` };
|
|
816
|
+
onStep({ name, detail: argsShort(name, input), blocked: true });
|
|
817
|
+
}
|
|
818
|
+
}
|
|
725
819
|
if (result === undefined && DEVICE_MUTATING.has(name)) {
|
|
726
820
|
const safe = name === 'android_parear'
|
|
727
821
|
? `${input.host || '?'}:${input.porta || '?'} (código oculto)`
|
|
@@ -887,6 +981,16 @@ async function run(task, opts = {}) {
|
|
|
887
981
|
const _tr = core.classifyToolResult(result);
|
|
888
982
|
if (!_tr.ok && _tr.status !== 'blocked') {
|
|
889
983
|
_toolErrs.push({ tool: name, class: _tr.errorClass, retryable: _tr.retryable, evidence: _tr.evidence });
|
|
984
|
+
const _failureKey = failureFingerprint(name, _tr, result);
|
|
985
|
+
const _equivalentFailures = (_failureCounts.get(_failureKey) || 0) + 1;
|
|
986
|
+
_failureCounts.set(_failureKey, _equivalentFailures);
|
|
987
|
+
const _failureGuard = _guard(_equivalentFailures);
|
|
988
|
+
if (_failureGuard && _failureGuard.kind === 'equivalent_failures') {
|
|
989
|
+
guardStopped = Object.assign({}, _failureGuard, { tool: name, evidence: String(_tr.evidence || '').slice(0, 240) });
|
|
990
|
+
result = Object.assign({}, result, {
|
|
991
|
+
_guard: `A mesma falha operacional ocorreu ${_equivalentFailures} vezes. O agente foi interrompido para não gastar tentando variações sem prova de progresso.`,
|
|
992
|
+
});
|
|
993
|
+
}
|
|
890
994
|
// LEDGER DE ERROS (Evolve 3): registro determinístico, zero IA.
|
|
891
995
|
try { require('./memoria').logErro(confineDir || cwd, name, String(result.erro || result.stderr || ('exit ' + result.codigo))); } catch (_) {}
|
|
892
996
|
// RECOVERY ENGINE: erro de classe CONHECIDA → injeta a estratégia de conserto no
|
|
@@ -901,7 +1005,7 @@ async function run(task, opts = {}) {
|
|
|
901
1005
|
if (result && result._setCwd) { cwd = result._setCwd; delete result._setCwd; }
|
|
902
1006
|
// registra a ação se deu certo (arquivo escrito / comando com código 0)
|
|
903
1007
|
if (!result.erro) {
|
|
904
|
-
if (
|
|
1008
|
+
if (['escrever_arquivo', 'editar_arquivo', 'editar_documento', 'editar_planilha'].includes(name) && result.ok) {
|
|
905
1009
|
actions.push({ name, target: result.caminho });
|
|
906
1010
|
// CHECKPOINT da sessão: anota o que mudou e onde está o backup (que o _snapshot
|
|
907
1011
|
// já criou). É o que permite "desfaz tudo o que o agente fez", em vez de só um
|
|
@@ -939,14 +1043,23 @@ async function run(task, opts = {}) {
|
|
|
939
1043
|
return { text: finalText, steps, credits: charged + _visionCredits, tokens: acc, model: usedModel, actions, cwd, context: lastCtx, needHuman: { motivo: result.motivo, o_que_fazer: result.o_que_fazer } };
|
|
940
1044
|
}
|
|
941
1045
|
messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify(result).slice(0, TOOL_RESULT_CAP) });
|
|
1046
|
+
if (guardStopped) break;
|
|
942
1047
|
}
|
|
943
|
-
if (loopedOut) break; // watchdog cortou →
|
|
1048
|
+
if (loopedOut || guardStopped) break; // watchdog/trava de orçamento cortou → fechamento determinístico
|
|
944
1049
|
|
|
945
1050
|
}
|
|
946
1051
|
|
|
947
1052
|
// CANCELADO cooperativamente: não faz a chamada de fechamento (não gastar mais IA);
|
|
948
1053
|
// devolve um texto curto e o que já rolou.
|
|
949
1054
|
if (stopped && !finalText) finalText = lang !== 'en' ? '(execução cancelada)' : '(run cancelled)';
|
|
1055
|
+
if (guardStopped && !finalText) {
|
|
1056
|
+
const labels = lang !== 'en'
|
|
1057
|
+
? { credits: 'o teto de créditos', tokens: 'o teto de tokens', time: 'o tempo máximo', equivalent_failures: 'a mesma falha repetida' }
|
|
1058
|
+
: { credits: 'the credit cap', tokens: 'the token cap', time: 'the time limit', equivalent_failures: 'the same repeated failure' };
|
|
1059
|
+
finalText = lang !== 'en'
|
|
1060
|
+
? `Interrompi esta missão porque ela atingiu ${labels[guardStopped.kind] || 'o limite de segurança'}. Foram executados ${steps} passo(s) e consumidos ${charged + _visionCredits} crédito(s). O que já foi concluído permanece válido; a etapa que falhou não foi declarada como pronta. Revise o último erro antes de continuar ou aumente o orçamento explicitamente.`
|
|
1061
|
+
: `I stopped this mission because it reached ${labels[guardStopped.kind] || 'the safety limit'}. ${steps} step(s) ran and ${charged + _visionCredits} credit(s) were consumed. Completed work remains valid; the failed step was not reported as done. Review the last error before continuing or explicitly raise the budget.`;
|
|
1062
|
+
}
|
|
950
1063
|
// FECHAMENTO GARANTIDO: o loop NUNCA termina em silêncio. Se saiu sem texto
|
|
951
1064
|
// (esgotou MAX_ITER ainda chamando ferramentas, ou o modelo devolveu vazio),
|
|
952
1065
|
// faz UMA chamada final SEM ferramentas pedindo um resumo honesto ao usuário.
|
|
@@ -974,7 +1087,7 @@ async function run(task, opts = {}) {
|
|
|
974
1087
|
}
|
|
975
1088
|
|
|
976
1089
|
if (_hooks._any) { try { _hooksMod.run(_hooks, 'Stop', { cwd, text: finalText, steps }); } catch (_) {} }
|
|
977
|
-
_logEp(stopped ? 'cancel' : (loopedOut ? 'stuck' : 'done'));
|
|
1090
|
+
_logEp(stopped ? 'cancel' : ((loopedOut || guardStopped) ? 'stuck' : 'done'));
|
|
978
1091
|
await _bill();
|
|
979
1092
|
// toolErrors: erros de ferramenta que NÃO foram seguidos de uma ação bem-sucedida da MESMA
|
|
980
1093
|
// ferramenta depois (heurística leve de "não-recuperado") — sinal duro pro meta não marcar done falso.
|
|
@@ -1000,12 +1113,12 @@ async function run(task, opts = {}) {
|
|
|
1000
1113
|
const _handoff = {
|
|
1001
1114
|
workstreamId: _workstreamId,
|
|
1002
1115
|
goal: taskText,
|
|
1003
|
-
status: stopped ? 'paused' : (loopedOut ? 'blocked' : 'done'),
|
|
1004
|
-
changedFiles: actions.filter(a =>
|
|
1116
|
+
status: stopped ? 'paused' : ((loopedOut || guardStopped) ? 'blocked' : 'done'),
|
|
1117
|
+
changedFiles: actions.filter(a => ['escrever_arquivo', 'editar_arquivo', 'editar_documento', 'editar_planilha'].includes(a.name)).map(a => a.target),
|
|
1005
1118
|
evidence: actions.map(a => `${a.name}: ${a.target}`),
|
|
1006
1119
|
failedApproaches: _toolErrs.map(e => `${e.tool || 'tool'}: ${e.errorClass || e.message || 'failed'}`),
|
|
1007
|
-
nextActions: loopedOut ? ['
|
|
1008
|
-
budget: { currency: 'credits', spent: charged + _visionCredits, remaining:
|
|
1120
|
+
nextActions: (loopedOut || guardStopped) ? ['Revisar o último erro e retomar com outra abordagem ou orçamento explícito.'] : [],
|
|
1121
|
+
budget: { currency: 'credits', spent: charged + _visionCredits, limit: maxCredits, remaining: Math.max(0, maxCredits - charged - _visionCredits), stoppedBy: guardStopped && guardStopped.kind },
|
|
1009
1122
|
};
|
|
1010
1123
|
const _actionExpected = /\b(cri|fa[cç]|implemente|corrija|edite|altere|instale|execute|rode|deploy|publique|remova|apague|delete|escreva|salve)\w*/i.test(taskText);
|
|
1011
1124
|
const _syncTasks = [
|
|
@@ -1021,8 +1134,8 @@ async function run(task, opts = {}) {
|
|
|
1021
1134
|
model: usedModel,
|
|
1022
1135
|
taskKind: /\b(c[oó]digo|code|bug|teste|test|node|javascript|typescript|python|html|css|api|arquivo|projeto)\b/i.test(taskText) ? 'code' : 'general',
|
|
1023
1136
|
usedTools: steps > 0,
|
|
1024
|
-
success: !stopped && !loopedOut,
|
|
1025
|
-
falseDone: !stopped && !loopedOut && _actionExpected && actions.length === 0,
|
|
1137
|
+
success: !stopped && !loopedOut && !guardStopped,
|
|
1138
|
+
falseDone: !stopped && !loopedOut && !guardStopped && _actionExpected && actions.length === 0,
|
|
1026
1139
|
protocolError: _toolErrs.some(e => e.class === 'invalid_schema'),
|
|
1027
1140
|
usage: { input_tokens: acc.inTok, output_tokens: acc.outTok, cached_tokens: acc.cachedTok },
|
|
1028
1141
|
source: 'cli',
|
|
@@ -1030,7 +1143,7 @@ async function run(task, opts = {}) {
|
|
|
1030
1143
|
}));
|
|
1031
1144
|
await Promise.all(_syncTasks);
|
|
1032
1145
|
} catch (_) {}
|
|
1033
|
-
return { text: finalText, steps, credits: charged + _visionCredits, tokens: acc, model: usedModel, actions, messages, cwd, context: lastCtx, toolErrors: _toolErrs, lastToolError: _lastErr };
|
|
1146
|
+
return { text: finalText, steps, credits: charged + _visionCredits, tokens: acc, model: usedModel, actions, messages, cwd, context: lastCtx, toolErrors: _toolErrs, lastToolError: _lastErr, guard: guardStopped };
|
|
1034
1147
|
}
|
|
1035
1148
|
|
|
1036
|
-
module.exports = { run, llm, _test: { winFor, estMsgsTok, COMPACT_AT, KEEP_TAIL, loopSig, isCycle, loopDecision, decideApproval } };
|
|
1149
|
+
module.exports = { run, llm, _test: { winFor, estMsgsTok, COMPACT_AT, KEEP_TAIL, loopSig, isCycle, loopDecision, decideApproval, failureFingerprint, missionGuardDecision, validarModeloByok } };
|
package/lib/doctor.js
CHANGED
|
@@ -12,10 +12,16 @@ const { execFileSync } = require('child_process');
|
|
|
12
12
|
// ── helpers PUROS (testáveis) ────────────────────────────────────────────────
|
|
13
13
|
function _nodeMajor(v) { const m = String(v || '').replace(/^v/, '').split('.'); return Number(m[0]) || 0; }
|
|
14
14
|
function _nodeOk(v) { return _nodeMajor(v) >= 18; }
|
|
15
|
-
//
|
|
16
|
-
|
|
15
|
+
// Estado de autenticação. O plano vem do SERVIDOR quando disponível: o valor em ~/.ts é
|
|
16
|
+
// só o que estava valendo no login, e continua lá depois de assinar, trocar ou cancelar
|
|
17
|
+
// um plano — o diagnóstico exibia "ultra" para uma conta que o servidor já tratava como
|
|
18
|
+
// "pro". Sem rede, o valor guardado ainda é melhor que nada, mas fica marcado como tal.
|
|
19
|
+
function _authLevel(cfg, planoDoServidor) {
|
|
17
20
|
if (!cfg || !cfg.token) return { level: 'fail', detail: 'não logado', dica: 'rode: ts login' };
|
|
18
|
-
|
|
21
|
+
const plano = planoDoServidor || cfg.plan;
|
|
22
|
+
const local = !planoDoServidor && cfg.plan;
|
|
23
|
+
return { level: 'ok', detail: 'logado' + (cfg.username ? ' como ' + cfg.username : '')
|
|
24
|
+
+ (plano ? ' · plano ' + plano + (local ? ' (do último login)' : '') : '') };
|
|
19
25
|
}
|
|
20
26
|
// compara versão instalada × última do npm → ok / warn (desatualizado)
|
|
21
27
|
function _versionLevel(current, latest) {
|
|
@@ -55,7 +61,9 @@ async function run(opts = {}) {
|
|
|
55
61
|
results.push({ nome: 'Node.js', level: _nodeOk(nv) ? 'ok' : 'fail', detail: nv + (_nodeOk(nv) ? '' : ' (precisa 18+)'), dica: _nodeOk(nv) ? undefined : 'instale Node 18 ou superior' });
|
|
56
62
|
|
|
57
63
|
// 2) config ~/.ts + login
|
|
58
|
-
|
|
64
|
+
let planoDoServidor = null;
|
|
65
|
+
if (typeof opts.planoAtual === 'function') { try { planoDoServidor = await opts.planoAtual(); } catch (_) {} }
|
|
66
|
+
const auth = _authLevel(cfg, planoDoServidor); results.push(Object.assign({ nome: 'Login' }, auth));
|
|
59
67
|
const dir = path.join(os.homedir(), '.ts');
|
|
60
68
|
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
69
|
|
package/lib/i18n.js
CHANGED
|
@@ -75,7 +75,10 @@ const STR = {
|
|
|
75
75
|
['ts login · ts logout', 'conecta / desconecta este terminal'],
|
|
76
76
|
['ts uso', 'créditos e consumo do mês'],
|
|
77
77
|
['ts quem', 'conta conectada'],
|
|
78
|
+
['ts conta exportar', 'baixa seus dados sem senhas, tokens ou chaves privadas'],
|
|
79
|
+
['ts conta abrir', 'abre Minha Conta para privacidade e exclusão segura'],
|
|
78
80
|
['ts doctor', 'diagnóstico do ambiente (Node, login, gateway, versão, deps opcionais)'],
|
|
81
|
+
['ts privacidade', 'mostra o endereço da Política de Privacidade'],
|
|
79
82
|
['ts tema', 'paleta do terminal (7 temas; a cor indica o ESTADO)'],
|
|
80
83
|
['ts idioma pt|en', 'idioma (padrão pt-BR)'],
|
|
81
84
|
] },
|
|
@@ -240,6 +243,9 @@ const STR = {
|
|
|
240
243
|
['ts login · ts logout', 'connect / disconnect this terminal'],
|
|
241
244
|
['ts uso', 'monthly credits and usage'],
|
|
242
245
|
['ts quem', 'connected account'],
|
|
246
|
+
['ts account export', 'download your data without passwords, tokens or private keys'],
|
|
247
|
+
['ts account open', 'open Account for privacy controls and safe deletion'],
|
|
248
|
+
['ts privacy', 'shows the Privacy Policy address'],
|
|
243
249
|
['ts tema', 'terminal palette (7 themes; color means STATE)'],
|
|
244
250
|
['ts idioma pt|en', 'language (default pt-BR)'],
|
|
245
251
|
] },
|