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.
package/lib/tools.js CHANGED
@@ -101,6 +101,11 @@ const DEFS = [
101
101
  consulta: { type: 'string', description: 'os termos de busca (seja específico)' },
102
102
  max: { type: 'number', description: 'quantos resultados (padrão 8, máx 20)' },
103
103
  }, required: ['consulta'] } } },
104
+ { type: 'function', function: { name: 'buscar_skill',
105
+ description: 'Procura uma SKILL (procedimento pronto, escrito por humano) que resolva a tarefa — entre as instaladas nesta máquina e as disponíveis na galeria/catálogos. Use quando a tarefa parece um procedimento que alguém já escreveu (revisar PR, montar planilha, auditar MCP, publicar release). Skill INSTALADA: leia o arquivo dela e siga. Skill NÃO instalada: avise o usuário que existe e que `ts skills add <slug>` instala — NUNCA instale por conta própria.',
106
+ parameters: { type: 'object', properties: {
107
+ consulta: { type: 'string', description: 'o que você precisa fazer, em poucas palavras' },
108
+ }, required: ['consulta'] } } },
104
109
  { type: 'function', function: { name: 'info_sistema',
105
110
  description: 'Informações desta máquina: SO, memória, CPU, uptime, usuário, diretório atual.',
106
111
  parameters: { type: 'object', properties: {}, required: [] } } },
@@ -150,6 +155,52 @@ const DEFS = [
150
155
  parameters: { type: 'object', properties: {
151
156
  tarefa: { type: 'string', description: 'a pergunta/investigação a delegar (clara e específica)' },
152
157
  }, required: ['tarefa'] } } },
158
+ { type: 'function', function: { name: 'ler_documento',
159
+ description: 'Lê Word .docx com paginação ou busca, retornando texto, quantidade de parágrafos, tabelas e imagens.',
160
+ parameters: { type: 'object', properties: {
161
+ caminho: { type: 'string' }, buscar: { type: 'string' },
162
+ inicio_char: { type: 'number' }, tamanho: { type: 'number' },
163
+ }, required: ['caminho'] } } },
164
+ { type: 'function', function: { name: 'ler_apresentacao',
165
+ description: 'Lê PowerPoint .pptx por slides, retornando o texto estruturado e a quantidade de imagens.',
166
+ parameters: { type: 'object', properties: {
167
+ caminho: { type: 'string' }, inicio_slide: { type: 'number' }, slides: { type: 'number' },
168
+ }, required: ['caminho'] } } },
169
+ { type: 'function', function: { name: 'android_dispositivos',
170
+ description: 'Lista dispositivos Android/Android TV via ADB, com serial, estado e modelo. Somente leitura.',
171
+ parameters: { type: 'object', properties: {}, required: [] } } },
172
+ { type: 'function', function: { name: 'android_parear',
173
+ description: 'Pareia Android/Android TV por depuração sem fio. Use a porta DE PAREAMENTO e o código de 6 dígitos exibido no aparelho.',
174
+ parameters: { type: 'object', properties: {
175
+ host: { type: 'string' }, porta: { type: 'number' }, codigo: { type: 'string' },
176
+ }, required: ['host', 'porta', 'codigo'] } } },
177
+ { type: 'function', function: { name: 'android_conectar',
178
+ description: 'Conecta ao Android/Android TV já pareado. Use a porta DE CONEXÃO ADB, que pode ser diferente da porta de pareamento.',
179
+ parameters: { type: 'object', properties: {
180
+ host: { type: 'string' }, porta: { type: 'number' },
181
+ }, required: ['host', 'porta'] } } },
182
+ { type: 'function', function: { name: 'android_instalar',
183
+ description: 'Instala ou atualiza um APK real via ADB e só retorna sucesso se o ADB responder Success.',
184
+ parameters: { type: 'object', properties: {
185
+ apk: { type: 'string' }, serial: { type: 'string' },
186
+ substituir: { type: 'boolean' }, conceder_permissoes: { type: 'boolean' },
187
+ }, required: ['apk'] } } },
188
+ { type: 'function', function: { name: 'android_iniciar',
189
+ description: 'Inicia um aplicativo Android instalado pelo package name.',
190
+ parameters: { type: 'object', properties: {
191
+ pacote: { type: 'string' }, serial: { type: 'string' },
192
+ }, required: ['pacote'] } } },
193
+ { type: 'function', function: { name: 'android_logs',
194
+ description: 'Lê logcat, opcionalmente filtrado pelo package name, e destaca crashes/FATAL EXCEPTION/ANR. Somente leitura.',
195
+ parameters: { type: 'object', properties: {
196
+ serial: { type: 'string' }, pacote: { type: 'string' },
197
+ linhas: { type: 'number' }, nivel: { type: 'string', enum: ['V', 'D', 'I', 'W', 'E', 'F'] },
198
+ }, required: [] } } },
199
+ { type: 'function', function: { name: 'android_capturar_tela',
200
+ description: 'Captura a tela real do Android/Android TV em PNG e retorna caminho, tamanho e SHA-256.',
201
+ parameters: { type: 'object', properties: {
202
+ caminho: { type: 'string' }, serial: { type: 'string' },
203
+ }, required: ['caminho'] } } },
153
204
  ];
154
205
 
155
206
  // GRAFO DE IMPORTS (ideia do Graphfy/Knowledge Graph): parser DETERMINÍSTICO — lê só as
@@ -511,9 +562,12 @@ async function execute(name, input, opts = {}) {
511
562
  }
512
563
  fs.mkdirSync(path.dirname(p), { recursive: true });
513
564
  const existed = fs.existsSync(p);
514
- if (existed) _snapshot(p); // backup antes de sobrescrever (reversível via restaurar_arquivo)
565
+ // O caminho do backup era DESCARTADO aqui. Agora volta no resultado (_backup) pra
566
+ // o loop registrar no checkpoint da sessão e permitir o "desfaz tudo".
567
+ const _bk = existed ? _snapshot(p) : null; // backup antes de sobrescrever
515
568
  fs.writeFileSync(p, String(input.conteudo ?? ''), 'utf8');
516
569
  const r = { ok: true, caminho: p, bytes: Buffer.byteLength(String(input.conteudo ?? '')), sobrescreveu: existed };
570
+ r._backup = _bk || ''; r._acao = existed ? 'alterado' : 'criado';
517
571
  if (_rebased) r.aviso = 'Caminho fora do diretório de trabalho foi RE-BASEADO para dentro dele. Use ESTE caminho a partir de agora: ' + p;
518
572
  return r;
519
573
  }
@@ -553,9 +607,9 @@ async function execute(name, input, opts = {}) {
553
607
  }
554
608
  if (count > 1 && !input.todas) return { erro: `Trecho AMBÍGUO: ${count} ocorrências. Inclua mais linhas de contexto em "buscar" pra ficar único, ou passe todas:true.` };
555
609
  const novo = input.todas ? orig.split(buscar).join(substituir) : orig.replace(buscar, substituir);
556
- _snapshot(p); // backup antes de editar (reversível via restaurar_arquivo)
610
+ const _bk2 = _snapshot(p); // backup antes de editar (reversível via restaurar_arquivo)
557
611
  fs.writeFileSync(p, novo, 'utf8');
558
- return { ok: true, caminho: p, ocorrencias: input.todas ? count : 1, bytes_antes: Buffer.byteLength(orig), bytes_depois: Buffer.byteLength(novo) };
612
+ return { ok: true, caminho: p, ocorrencias: input.todas ? count : 1, bytes_antes: Buffer.byteLength(orig), bytes_depois: Buffer.byteLength(novo), _backup: _bk2 || '', _acao: 'alterado' };
559
613
  }
560
614
  case 'restaurar_arquivo': {
561
615
  const p = _abs(input.caminho, baseDir);
@@ -619,6 +673,22 @@ async function execute(name, input, opts = {}) {
619
673
  if (!hits.length) return { resultados: [], aviso: 'nada relevante no índice — tente outros termos, ou rode "ts indexar" se o projeto mudou muito.' };
620
674
  return { resultados: hits.map(h => ({ arquivo: h.file, linhas: h.l0 + '-' + h.l1, score: h.score, ...(h.sig && h.sig.length ? { define: h.sig } : {}), previa: h.snippet })), ...(construiu ? { nota: 'índice construído agora (' + idx.N + ' trechos)' } : {}) };
621
675
  }
676
+ case 'buscar_skill': {
677
+ const q = String(input.consulta || '').trim();
678
+ if (!q) return { erro: 'consulta vazia.' };
679
+ try {
680
+ const si = require('./skill-index');
681
+ const achadas = si.buscar(si.todas(), q, { k: 5 });
682
+ if (!achadas.length) return { consulta: q, achadas: [], aviso: 'Nenhuma skill conhecida cobre isso — siga com suas ferramentas normais.' };
683
+ return { consulta: q, achadas: achadas.map(a => ({
684
+ slug: a.slug, descricao: a.descricao, origem: a.origem,
685
+ instalada: !!a.instalada,
686
+ // instalada → o caminho pra LER; não instalada → só o comando (a instalação é do humano)
687
+ arquivo: a.instalada ? a.caminho : undefined,
688
+ instalar_com: a.instalada ? undefined : ('ts skills add ' + a.slug),
689
+ })) };
690
+ } catch (e) { return { erro: 'não deu pra consultar o índice de skills: ' + (e.message || e) }; }
691
+ }
622
692
  case 'buscar_web': {
623
693
  const q = String(input.consulta || '').trim();
624
694
  if (!q) return { erro: 'consulta vazia.' };
@@ -703,6 +773,33 @@ async function execute(name, input, opts = {}) {
703
773
  return { diretorio: base, arquivos: m.arquivos, dependencias: m.dependencias,
704
774
  hubs: m.hubs, mapa: m.edges.map(([a, b]) => a + ' -> ' + b) };
705
775
  }
776
+ case 'android_dispositivos':
777
+ return await require('./device-tools').listarDispositivos();
778
+ case 'ler_documento': {
779
+ const target = _abs(input.caminho, baseDir);
780
+ if (_outsideScope(target)) return _scopeError(target);
781
+ return await require('./office-readers').lerDocumento({ ...input, caminho: target });
782
+ }
783
+ case 'ler_apresentacao': {
784
+ const target = _abs(input.caminho, baseDir);
785
+ if (_outsideScope(target)) return _scopeError(target);
786
+ return await require('./office-readers').lerApresentacao({ ...input, caminho: target });
787
+ }
788
+ case 'android_parear':
789
+ return await require('./device-tools').parear(input);
790
+ case 'android_conectar':
791
+ return await require('./device-tools').conectar(input);
792
+ case 'android_instalar':
793
+ return await require('./device-tools').instalar(input);
794
+ case 'android_iniciar':
795
+ return await require('./device-tools').iniciar(input);
796
+ case 'android_logs':
797
+ return await require('./device-tools').logs(input);
798
+ case 'android_capturar_tela': {
799
+ const target = _abs(input.caminho, baseDir);
800
+ if (_outsideScope(target)) return _scopeError(target);
801
+ return await require('./device-tools').capturarTela({ ...input, caminho: target });
802
+ }
706
803
  default: {
707
804
  // sugere o nome REAL mais parecido (o modelo às vezes inventa uma variante):
708
805
  // 1) substring óbvia, 2) menor distância de edição, 3) mesmo radical antes do "_".
package/lib/ui.js CHANGED
@@ -3,30 +3,26 @@
3
3
  const TTY = !!process.stdout.isTTY && !process.env.NO_COLOR;
4
4
 
5
5
  const esc = (s) => TTY ? s : '';
6
- const rgb = (r, g, b) => (s) => TTY ? `\x1b[38;2;${r};${g};${b}m${s}\x1b[39m` : String(s);
7
-
8
- const C = {
9
- cyan: rgb(34, 211, 238),
10
- indigo: rgb(129, 140, 248),
11
- ok: rgb(52, 211, 153),
12
- warn: rgb(251, 191, 36),
13
- err: rgb(248, 113, 113),
14
- bold: (s) => TTY ? `\x1b[1m${s}\x1b[22m` : String(s),
15
- dim: (s) => TTY ? `\x1b[2m${s}\x1b[22m` : String(s),
16
- };
17
6
 
18
- // Gradiente cyan→indigo caractere a caractere (a assinatura visual do ts)
19
- function gradient(text) {
20
- if (!TTY) return text;
21
- const a = [34, 211, 238], b = [129, 140, 248];
22
- const chars = [...text];
23
- const n = Math.max(1, chars.length - 1);
24
- return chars.map((ch, i) => {
25
- const k = i / n;
26
- const [r, g, bl] = a.map((v, j) => Math.round(v + (b[j] - v) * k));
27
- return `\x1b[38;2;${r};${g};${bl}m${ch}`;
28
- }).join('') + '\x1b[39m';
7
+ // TEMA: a paleta vem de lib/temas.js e é escolhida em ~/.ts/config.json (`ts tema`).
8
+ // `C` mantém os mesmos nomes de sempre (cyan/indigo/ok/warn/err) — só os VALORES mudam,
9
+ // então nenhum dos ~centenas de usos no CLI precisou ser tocado.
10
+ // Leitura direta do config (sem require de ./config) pra não criar ciclo de módulos.
11
+ const _temas = require('./temas');
12
+ function _temaAtivo() {
13
+ if (process.env.TS_TEMA && _temas.existe(process.env.TS_TEMA)) return process.env.TS_TEMA.toLowerCase();
14
+ try {
15
+ const os = require('os'), path = require('path'), fs = require('fs');
16
+ const cfg = JSON.parse(fs.readFileSync(path.join(os.homedir(), '.ts', 'config.json'), 'utf8'));
17
+ if (cfg && cfg.tema && _temas.existe(cfg.tema)) return String(cfg.tema).toLowerCase();
18
+ } catch (_) {}
19
+ return _temas.PADRAO;
29
20
  }
21
+ const TEMA = _temaAtivo();
22
+ const C = _temas.montar(TEMA, TTY);
23
+
24
+ // Gradiente primária→secundária caractere a caractere (a assinatura visual do ts)
25
+ function gradient(text) { return _temas.gradienteDe(TEMA, text, TTY); }
30
26
 
31
27
  function banner(version, tagline) {
32
28
  const title = 'T E R M I N A L S M A R T';
@@ -182,4 +178,4 @@ const okLine = (s) => ' ' + C.ok('✔') + ' ' + s;
182
178
  const errLine = (s) => ' ' + C.err('✖') + ' ' + s;
183
179
  const infoLine = (s) => ' ' + C.cyan('⌁') + ' ' + s;
184
180
 
185
- module.exports = { C, TTY, gradient, banner, box, spinner, md, bar, ask, cmdSummary, okLine, errLine, infoLine, wordTrunc, friendlyTool, _wrapLine };
181
+ module.exports = { C, TTY, TEMA, gradient, banner, box, spinner, md, bar, ask, cmdSummary, okLine, errLine, infoLine, wordTrunc, friendlyTool, _wrapLine };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "terminal-smart-cli",
3
- "version": "0.94.2",
3
+ "version": "0.97.1",
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"
7
7
  },
8
8
  "scripts": {
9
- "test": "node test/core.test.js && node test/intelligence-core.test.js && node test/eval-model.test.js && node test/project-cache.test.js && node test/memory-bus.test.js && node test/capabilities.test.js && node test/mcp-e2e.test.js && node test/erros.test.js"
9
+ "test": "node test/core.test.js && node test/intelligence-core.test.js && node test/eval-model.test.js && node test/project-cache.test.js && node test/memory-bus.test.js && node test/capabilities.test.js && node test/mcp-e2e.test.js && node test/erros.test.js && node test/capability-pack.test.js && node test/byok.test.js && node test/conhecimento.test.js && node test/policy.test.js && node test/temas.test.js && node test/skill-index.test.js"
10
10
  },
11
11
  "files": [
12
12
  "bin",
@@ -31,6 +31,7 @@
31
31
  },
32
32
  "license": "UNLICENSED",
33
33
  "dependencies": {
34
+ "jszip": "^3.10.1",
34
35
  "puppeteer-core": "^23.11.1",
35
36
  "qrcode-terminal": "^0.12.0",
36
37
  "ssh2": "^1.17.0"