terminal-smart-cli 0.91.1 → 0.92.2

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 CHANGED
@@ -913,8 +913,13 @@ async function agentCmd(words, { askFn, cwd: cwdIn = null, onCwd = null, maxIter
913
913
  // saída → o log ficava VAZIO enquanto o agente rodava. Nesses casos usa spinner NOOP: os passos
914
914
  // saem por console.log (visíveis no arquivo/pipe) e o progresso aparece de verdade.
915
915
  const _noTTY = !process.stdout.isTTY;
916
+ // B4: headless (não-TTY, redirect >arquivo/pipe) — o console.log é bufferizado em BLOCO e o
917
+ // log fica VAZIO durante a run (impossível acompanhar ao vivo; foi o que escondeu o hang de
918
+ // 40min no PW). _hlog escreve SÍNCRONO no fd 1 (fs.writeSync), descarregando cada linha na
919
+ // hora. No TTY segue o console.log normal (o spinner cuida do redesenho).
920
+ const _hlog = (s) => { if (_noTTY && !streamJson) { try { require('fs').writeSync(1, s + '\n'); return; } catch (_) {} } console.log(s); };
916
921
  const sp = (streamJson || _noTTY) ? { text() {}, start() {}, stop() {} } : ui.spinner(T.agent_thinking).start();
917
- if (_noTTY && !streamJson) console.log(' ' + C.dim(T.agent_thinking));
922
+ if (_noTTY && !streamJson) _hlog(' ' + C.dim(T.agent_thinking));
918
923
  const t0 = Date.now();
919
924
  // STATUS LINE ao vivo (estilo Claude/Verboo): tempo · modelo · tokens · passo — atualiza a cada 1s.
920
925
  const _live = { steps: 0, inTok: 0, outTok: 0, model: model || 'auto' };
@@ -950,13 +955,13 @@ async function agentCmd(words, { askFn, cwd: cwdIn = null, onCwd = null, maxIter
950
955
  const tag = auto ? C.warn('▲ auto') : retry ? C.warn('⟳') : loop ? C.warn('↻ loop') : blocked ? C.err('■ ' + T.agent_blocked) : C.cyan('›');
951
956
  // nome AMIGÁVEL (Rodando no servidor…) em vez do técnico (executar_remoto); detalhe sem cortar palavra
952
957
  const _label = ui.friendlyTool(name, cfg.lang === 'en');
953
- console.log(' ' + tag + ' ' + C.bold(_label) + (detail ? C.dim(' · ' + ui.wordTrunc(detail, 60)) : ''));
958
+ _hlog(' ' + tag + ' ' + C.bold(_label) + (detail ? C.dim(' · ' + ui.wordTrunc(detail, 60)) : ''));
954
959
  sp.start();
955
960
  },
956
961
  onStepDone: (e) => {
957
962
  if (streamJson) return; // o evento 'result' já cobre; aqui é só a UI bonita
958
963
  sp.stop();
959
- console.log(stepDoneLine(e, ' '));
964
+ _hlog(stepDoneLine(e, ' '));
960
965
  sp.start();
961
966
  },
962
967
  askApprove: async (payload) => {
@@ -970,7 +975,7 @@ async function agentCmd(words, { askFn, cwd: cwdIn = null, onCwd = null, maxIter
970
975
  onRemote: ({ ttl }) => {
971
976
  if (streamJson) { _emit(core.AgentEvents.tool({ subtype: 'approval_remote', ttl: ttl || 120 })); return; }
972
977
  sp.stop();
973
- console.log(' ' + C.warn('▲') + ' ' + C.dim(T.agent_remote_wait(ttl || 120)));
978
+ _hlog(' ' + C.warn('▲') + ' ' + C.dim(T.agent_remote_wait(ttl || 120)));
974
979
  sp.start();
975
980
  },
976
981
  });
@@ -1247,7 +1252,10 @@ async function vpsCmd(words) {
1247
1252
  const ssh = require('../lib/ssh');
1248
1253
  const sub = (words[0] || '').toLowerCase();
1249
1254
  const rest = words.slice(1);
1250
- const flag = (n) => { const i = rest.indexOf(n); return i >= 0 ? rest[i + 1] : null; };
1255
+ // a flag do process.argv CRU: o parser de topo tira os nomes de flag (--host…) dos
1256
+ // positionais (POS), então `words` só traz os VALORES soltos e o pareamento se perdia
1257
+ // (ts vps set --host X --user Y ignorava tudo e salvava o perfil velho). O argv preserva par.
1258
+ const flag = (n) => { const i = process.argv.indexOf(n); return i >= 0 ? process.argv[i + 1] : null; };
1251
1259
 
1252
1260
  // ts vps set --host IP --user ubuntu --chave caminho.pem (ou --login arquivo.txt / --senha ...)
1253
1261
  if (sub === 'set' || sub === 'config' || sub === 'add') {
package/lib/ssh.js CHANGED
@@ -88,10 +88,22 @@ function connect(opts = {}) {
88
88
  function exec(cmd, { timeoutMs = 120000 } = {}) {
89
89
  return new Promise((resolve, reject) => {
90
90
  if (!_conn) return reject(new Error('Não conectado. Rode "ts vps" ou conectar_vps primeiro.'));
91
- _conn.exec(cmd, (err, stream) => {
92
- if (err) return reject(err);
93
- let stdout = '', stderr = '', done = false;
94
- const to = setTimeout(() => { if (!done) { done = true; try { stream.close(); } catch (_) {} resolve({ code: 124, stdout, stderr: stderr + '\n[timeout]' }); } }, timeoutMs);
91
+ let stdout = '', stderr = '', done = false, stream = null;
92
+ // Timeout GLOBAL da chamada — armado ANTES do exec, então dispara MESMO se _conn.exec
93
+ // nunca chamar o callback. Numa conexão meia-morta (TCP vivo, sshd travado) o exec ficava
94
+ // pendurado pra SEMPRE (bug do hang de 40min). Ao estourar, derruba a conexão pra a PRÓXIMA
95
+ // chamada reconectar do zero (via _lastGood) em vez de pendurar de novo.
96
+ const to = setTimeout(() => {
97
+ if (done) return; done = true;
98
+ try { if (stream) stream.close(); } catch (_) {}
99
+ try { if (_conn) _conn.end(); } catch (_) {}
100
+ _conn = null; _info = null;
101
+ resolve({ code: 124, stdout, stderr: (stderr || '') + '\n[timeout: sem resposta em ' + Math.round(timeoutMs / 1000) + 's — a conexão pode ter travado; reconecte e tente de novo, e rode tarefas longas em background com setsid]' });
102
+ }, timeoutMs);
103
+ _conn.exec(cmd, (err, s) => {
104
+ if (done) { try { s && s.close(); } catch (_) {} return; }
105
+ if (err) { done = true; clearTimeout(to); return reject(err); }
106
+ stream = s;
95
107
  stream.on('close', (code) => { if (done) return; done = true; clearTimeout(to); resolve({ code: code == null ? 0 : code, stdout, stderr }); });
96
108
  stream.on('data', (d) => { stdout += d.toString(); if (stdout.length > 200000) stdout = stdout.slice(-120000); });
97
109
  stream.stderr.on('data', (d) => { stderr += d.toString(); if (stderr.length > 60000) stderr = stderr.slice(-40000); });
package/lib/tools.js CHANGED
@@ -381,7 +381,14 @@ async function execute(name, input, opts = {}) {
381
381
  }
382
382
  case 'executar_remoto': {
383
383
  const ssh = require('./ssh');
384
- if (!ssh.isConnected()) return { erro: 'Não conectado a nenhum servidor. Chame conectar_vps primeiro.' };
384
+ // Conexão caiu (ou um exec anterior estourou o timeout e a derrubou)? Reconecta 1x
385
+ // SOZINHO com a última que funcionou (_lastGood: mesmo host/user/chave) — sem pedir
386
+ // nada ao usuário. Assim um SSH que travou não pendura a missão: o timeout do exec
387
+ // devolve o controle, e a próxima chamada se reconecta e segue.
388
+ if (!ssh.isConnected()) {
389
+ if (ssh.lastGood()) { try { await ssh.connect(); } catch (_) {} }
390
+ if (!ssh.isConnected()) return { erro: 'Não conectado a nenhum servidor. Chame conectar_vps primeiro.' };
391
+ }
385
392
  const comando = String(input.comando || '');
386
393
  const out = await ssh.exec(comando, { timeoutMs: 180000 });
387
394
  const c = compactor.compact(comando, out.stdout || '', { codigo: out.code });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terminal-smart-cli",
3
- "version": "0.91.1",
3
+ "version": "0.92.2",
4
4
  "description": "Terminal Smart no seu terminal — pergunte, analise logs por pipe e orquestre agentes de IA. Comando: ts",
5
5
  "bin": {
6
6
  "ts": "bin/ts.js"