primocode 7.3.0 → 7.4.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/README.md CHANGED
@@ -1,16 +1,35 @@
1
- # PrimoCode v7.1.0
1
+ # PrimoCode v7.4.0
2
2
 
3
3
  Agente de engenharia brasileiro para o terminal, no estilo Claude Code.
4
- Cria arquivos de verdade, roda comandos, abre o navegador — e lembra do que fez
5
- entre uma sessão e outra.
4
+ Cria arquivos de verdade, roda comandos, **controla o navegador e o desktop** —
5
+ e lembra do que fez entre uma sessão e outra.
6
6
 
7
7
  ```
8
- ╷
9
- ╭─┴─╮ PrimoCode
10
- │◕ ◕│ v7.1.0
11
- ╰───╯ projeto: jogo-de-carro
8
+ ╭◠╮
9
+ ╭─┴─┴─╮
10
+ ◖┫ ● ● ┣◗ PrimoCode v7.4.0
11
+ ┃ ‿ ┃ projeto: jogo-de-carro
12
+ ╰──┬──╯
13
+ ╰─┴─╯
12
14
  ```
13
15
 
16
+ Personalize o mascote (`primo`, `fox`, `cat`) e o nome com que ele te chama —
17
+ veja `/mascot` e `/name`.
18
+
19
+ ## Por que trocar o Claude Code pelo PrimoCode
20
+
21
+ | | PrimoCode | Assistentes gringos de terminal |
22
+ |---|---|---|
23
+ | **Idioma** | Nasceu em português do Brasil — fala como gente, não como manual traduzido | Inglês primeiro; PT-BR é tradução |
24
+ | **Chave de API** | **Não precisa.** Instala e usa — o modelo já vem hospedado | Exige conta e chave de API própria |
25
+ | **Controla a máquina** | Navegador (Chrome real) **e** desktop: mouse, teclado, tela | Em geral só arquivos e shell |
26
+ | **Memória** | Cada projeto lembra o que foi feito, entre sessões | Esquece ao fechar |
27
+ | **Instalação** | Node puro, **zero dependências** — segundos | Pacotes e dependências |
28
+ | **Cara própria** | Mascote, nome e temas que você escolhe | Fixo |
29
+ | **Custo** | Roda em modelos rápidos e baratos (Groq/Llama) | Cobrança por token da API |
30
+
31
+ Em uma frase: **é o poder de um agente de código no terminal, em português, controlando o seu computador de verdade — sem você precisar configurar chave de API nenhuma.**
32
+
14
33
  ## Instalação
15
34
 
16
35
  ```bash
@@ -67,18 +86,53 @@ Alterne com `/mode primocode` ou `/mode chat`.
67
86
  |---|---|
68
87
  | `write_file` | Cria arquivo novo ou reescreve inteiro |
69
88
  | `edit_file` | **Troca um trecho exato.** É o que evita reescrever 800 linhas pra mudar uma |
89
+ | `multi_edit` | Várias trocas no mesmo arquivo, em ordem, tudo-ou-nada |
70
90
  | `read_file` | Lê o arquivo, com `offset`/`limit` para os grandes |
71
91
  | `glob` | Acha arquivos por padrão: `**/*.js`, `src/**/*.{ts,tsx}` |
72
92
  | `grep` | Procura texto/regex **dentro** dos arquivos, com arquivo e linha |
73
93
  | `list_dir` | Árvore de arquivos |
74
94
  | `run_shell` | Roda comando na pasta do projeto |
75
- | `open_in_browser` | Abre HTML ou URL |
95
+ | `run_in_new_terminal` | Sobe servidor de dev / watch em outro terminal |
96
+ | `write_todos` | Plano de tarefas da sessão, visível no terminal |
97
+ | `open_in_browser` | Abre HTML ou URL no navegador padrão |
76
98
  | `spawn_agent` | Delega uma sub-tarefa a outro agente |
77
99
 
78
100
  O `edit_file` é rigoroso de propósito: se o trecho não existe, ou aparece mais
79
101
  de uma vez sem `replace_all`, ele **recusa** e devolve as linhas reais do
80
102
  arquivo — em vez de editar o lugar errado.
81
103
 
104
+ ### Controle de navegador e desktop
105
+
106
+ O agente não só abre o navegador — ele **age** nele, e no computador também.
107
+
108
+ | Tool | Para quê |
109
+ |---|---|
110
+ | `browser_open` | Abre a página num Chrome controlado (CDP) e espera carregar |
111
+ | `browser_click` | Clica por texto visível, seletor CSS ou coordenada |
112
+ | `browser_type` | Digita num campo (por seletor) ou onde o foco estiver |
113
+ | `browser_key` | Enter, Tab, Escape, setas… |
114
+ | `browser_read` | Lê o texto visível da página (conferir o resultado) |
115
+ | `browser_screenshot` | Print da página, salvo no projeto |
116
+ | `browser_scroll` / `browser_wait` / `browser_eval` | Rolar, esperar seletor, rodar JS |
117
+ | `desktop_screenshot` | Print da tela inteira do usuário |
118
+ | `desktop_click` / `desktop_move` / `desktop_drag` | Mouse na tela real |
119
+ | `desktop_type` / `desktop_key` / `desktop_scroll` | Teclado e rolagem do sistema |
120
+ | `desktop_size` | Resolução da tela, para calcular coordenadas |
121
+
122
+ O navegador roda via Chrome DevTools Protocol (sem dependências npm).
123
+
124
+ **Requisitos por plataforma** (as ferramentas dão um erro claro se faltar algo):
125
+
126
+ | | Navegador | Desktop (mouse/teclado) | Screenshot da tela |
127
+ |---|---|---|---|
128
+ | **Windows** | Chrome ou Edge | nativo (PowerShell) | nativo (PowerShell) |
129
+ | **macOS** | Chrome/Chromium | `brew install cliclick` | nativo (`screencapture`) |
130
+ | **Linux** | Chrome/Chromium | `apt install xdotool` | `scrot`, `gnome-screenshot`, `spectacle`, `maim` ou ImageMagick |
131
+
132
+ Variáveis de ambiente úteis:
133
+ - `CHROME_PATH` / `PRIMOCODE_CHROME` — aponta para um Chrome fora do caminho padrão
134
+ - `PRIMOCODE_BROWSER_HEADLESS=1` — roda o navegador sem janela (CI/servidor)
135
+
82
136
  ## Subagentes
83
137
 
84
138
  Para tarefas com partes independentes, o agente delega:
@@ -143,6 +197,9 @@ primocode -f app.js "onde está o bug?" # com arquivo anexado
143
197
  | `/run <comando>` | Executa no shell, dentro da pasta do projeto |
144
198
  | `/open <arquivo\|url>` | Abre no navegador |
145
199
  | `/status` | Health check do servidor |
200
+ | `/name <nome>` | Como o Primo te chama na saudação |
201
+ | `/mascot primo\|fox\|cat` | Escolhe o mascote (mostra os três) |
202
+ | `/config` | Mostra suas preferências e onde ficam salvas |
146
203
  | `/clear` · `/cls` | Limpa conversa / tela |
147
204
  | `/uninstall` | Remove o PrimoCode (seus projetos ficam) |
148
205
  | `/exit` | Sair |
package/bin/primocode.js CHANGED
@@ -13,7 +13,8 @@ const fs = require('fs');
13
13
  const os = require('os');
14
14
 
15
15
  const pkg = require('../package.json');
16
- const { c, welcomeBox, Spinner } = require('../lib/ui');
16
+ const ui = require('../lib/ui');
17
+ const { c, welcomeBox, Spinner } = ui;
17
18
  const api = require('../lib/api');
18
19
  const tools = require('../lib/tools');
19
20
  const cfg = require('../lib/config');
@@ -35,6 +36,7 @@ const HELP = `
35
36
  ${c.bold('Comandos')}
36
37
  ${c.brand('/project')} lista projetos ou muda: /project <nome>
37
38
  ${c.brand('/mode')} alterna: /mode primocode|chat
39
+ ${c.brand('/model')} alterna: /model main|fast
38
40
  ${c.brand('/attach')} <arquivo> anexa arquivo ao próximo pedido
39
41
  ${c.brand('/files')} lista anexos ${c.brand('/detach')} remove
40
42
  ${c.brand('/tree')} [n] árvore do projeto
@@ -42,6 +44,9 @@ ${c.bold('Comandos')}
42
44
  ${c.brand('/run')} <comando> executa no shell do projeto
43
45
  ${c.brand('/open')} <arquivo|url> abre no navegador
44
46
  ${c.brand('/status')} checa o servidor
47
+ ${c.brand('/name')} <nome> como o Primo te chama
48
+ ${c.brand('/mascot')} <estilo> mascote: primo|fox|cat
49
+ ${c.brand('/config')} mostra suas preferências
45
50
  ${c.brand('/clear')} limpa a conversa ${c.brand('/cls')} limpa a tela
46
51
  ${c.brand('/uninstall')} remove o PrimoCode
47
52
  ${c.brand('/exit')} sair
@@ -178,7 +183,7 @@ async function runChat(promptForModel, userTextForMemory) {
178
183
  model: state.config.model,
179
184
  token: state.config.token,
180
185
  }, (evt) => {
181
- if (evt.erro) { if (!started) sp.stop(); console.log(c.err(`\nerro: ${evt.erro}`)); return; }
186
+ if (evt.erro) { if (!started) sp.stop(); console.log('\n' + c.err(ui.friendlyError(evt.erro))); return; }
182
187
  if (evt.delta) {
183
188
  if (!started) { sp.stop(); process.stdout.write('\n'); started = true; }
184
189
  text += evt.delta;
@@ -188,7 +193,7 @@ async function runChat(promptForModel, userTextForMemory) {
188
193
  });
189
194
  } catch (e) {
190
195
  sp.stop();
191
- console.log(c.err(`\n${e.message || e}`));
196
+ console.log('\n' + c.err(ui.friendlyError(e)));
192
197
  return;
193
198
  }
194
199
  sp.stop();
@@ -294,6 +299,47 @@ async function handleSlash(text) {
294
299
  break;
295
300
  }
296
301
 
302
+ case 'name': {
303
+ if (!rest) { console.log(c.muted(` Nome atual: ${state.config.userName || '(nenhum)'}`)); break; }
304
+ state.config.userName = rest;
305
+ cfg.saveConfig({ userName: rest });
306
+ console.log(c.ok(`Feito! Vou te chamar de ${rest}.`));
307
+ break;
308
+ }
309
+
310
+ case 'mascot': {
311
+ const estilos = ['primo', 'fox', 'cat'];
312
+ const m = rest.toLowerCase();
313
+ if (!m) {
314
+ console.log('');
315
+ for (const e of estilos) {
316
+ console.log(` ${e === state.config.mascot ? c.brand('● ' + e) : c.muted('○ ' + e)}`);
317
+ ui.mascot(e).forEach((l) => console.log(' ' + l));
318
+ console.log('');
319
+ }
320
+ console.log(c.muted(' Escolha: /mascot <estilo>'));
321
+ break;
322
+ }
323
+ if (!estilos.includes(m)) { console.log(c.err(`Uso: /mascot ${estilos.join('|')}`)); break; }
324
+ state.config.mascot = m;
325
+ cfg.saveConfig({ mascot: m });
326
+ console.log('');
327
+ ui.mascot(m).forEach((l) => console.log(' ' + l));
328
+ console.log(c.ok(`\nMascote: ${m}`));
329
+ break;
330
+ }
331
+
332
+ case 'config': {
333
+ console.log('');
334
+ console.log(` ${c.muted('nome')} ${c.white(state.config.userName || '(nenhum)')}`);
335
+ console.log(` ${c.muted('mascote')} ${c.white(state.config.mascot || 'primo')}`);
336
+ console.log(` ${c.muted('modelo')} ${c.white(state.config.model)}`);
337
+ console.log(` ${c.muted('modo')} ${c.white(state.mode)}`);
338
+ console.log(` ${c.muted('servidor')} ${c.dim(state.config.server)}`);
339
+ console.log(c.muted(`\n ${cfg.CONFIG_FILE}`));
340
+ break;
341
+ }
342
+
297
343
  case 'clear': state.history = []; console.log(c.ok('Conversa limpa.')); break;
298
344
  case 'cls': process.stdout.write('\x1Bc'); break;
299
345
 
@@ -346,6 +392,17 @@ async function main() {
346
392
 
347
393
  const args = process.argv.slice(2);
348
394
 
395
+ // Flags padrão de CLI — funcionam em qualquer máquina, úteis em scripts/CI.
396
+ if (args[0] === '--version' || args[0] === '-v' || args[0] === '-V') {
397
+ console.log(`PrimoCode v${VERSION}`);
398
+ process.exit(0);
399
+ }
400
+ if (args[0] === '--help' || args[0] === '-h') {
401
+ console.log(welcomeBox(VERSION, null, { mascot: state.config.mascot, userName: state.config.userName }));
402
+ console.log(HELP);
403
+ process.exit(0);
404
+ }
405
+
349
406
  // Modo one-shot: primocode "crie um jogo de carro"
350
407
  if (args.length && !args[0].startsWith('-')) {
351
408
  const fileFlag = args.indexOf('-f');
@@ -373,7 +430,10 @@ async function main() {
373
430
  process.exit(0);
374
431
  }
375
432
 
376
- console.log(welcomeBox(VERSION, state.workDir ? shortPath(state.workDir) : null));
433
+ console.log(welcomeBox(VERSION, state.workDir ? shortPath(state.workDir) : null, {
434
+ mascot: state.config.mascot,
435
+ userName: state.config.userName,
436
+ }));
377
437
  rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: makePrompt() });
378
438
  rl.prompt();
379
439
 
@@ -400,7 +460,7 @@ async function main() {
400
460
  if (isSlash(text)) await handleSlash(text);
401
461
  else await sendPrompt(text);
402
462
  } catch (e) {
403
- console.log(c.err(`\n${e.message || e}`));
463
+ console.log('\n' + c.err(ui.friendlyError(e)));
404
464
  }
405
465
  }
406
466
  busy = false;
package/lib/act.js CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  'use strict';
12
12
 
13
- const { c, actionHeader, Spinner } = require('./ui');
13
+ const { c, actionHeader, Spinner, friendlyError } = require('./ui');
14
14
  const tools = require('./tools');
15
15
  const api = require('./api');
16
16
  const cfg = require('./config');
@@ -71,6 +71,46 @@ function logResult(kind, result, profundidade) {
71
71
  case 'open_in_browser':
72
72
  console.log(p + c.muted(result.opener || 'aberto'));
73
73
  return;
74
+ case 'multi_edit': {
75
+ const delta = result.linhasDepois - result.linhasAntes;
76
+ const sinal = delta > 0 ? c.ok(`+${delta}`) : delta < 0 ? c.err(`${delta}`) : c.muted('±0');
77
+ console.log(p + c.muted(`${result.substituicoes} trocas · `) + sinal + c.muted(' linhas'));
78
+ return;
79
+ }
80
+ case 'write_todos': {
81
+ const marca = { completed: c.ok('✔'), in_progress: c.brand('▶'), pending: c.dim('○') };
82
+ for (const t of result.todos || []) {
83
+ const texto = t.status === 'completed' ? c.dim(t.content) : c.white(t.content);
84
+ console.log(p + `${marca[t.status] || c.dim('○')} ${texto}`);
85
+ }
86
+ console.log(p + c.muted(`${result.feitos}/${result.total} concluídas`));
87
+ return;
88
+ }
89
+ case 'browser_open':
90
+ console.log(p + c.muted(result.url || 'aberto'));
91
+ return;
92
+ case 'browser_read':
93
+ console.log(p + c.muted(`${(result.text || '').length} caracteres lidos da página`));
94
+ return;
95
+ case 'browser_screenshot':
96
+ case 'desktop_screenshot':
97
+ console.log(p + c.muted(`${result.path} · ${tools.fmtBytes(result.bytes || 0)}`));
98
+ return;
99
+ case 'browser_click':
100
+ case 'browser_type':
101
+ case 'browser_key':
102
+ case 'browser_scroll':
103
+ case 'browser_wait':
104
+ case 'browser_eval':
105
+ case 'desktop_click':
106
+ case 'desktop_move':
107
+ case 'desktop_drag':
108
+ case 'desktop_type':
109
+ case 'desktop_key':
110
+ case 'desktop_scroll':
111
+ case 'desktop_size':
112
+ console.log(p + c.ok('ok') + (result.text || result.tag || result.selector || result.width ? c.muted(' · ' + (result.selector || result.tag || (result.width ? `${result.width}×${result.height}` : ''))) : ''));
113
+ return;
74
114
  }
75
115
  }
76
116
 
@@ -135,16 +175,18 @@ async function runAct(userPrompt, opts) {
135
175
  });
136
176
  } catch (e) {
137
177
  sp.stop();
138
- if (subagent) return { assistantMessage: { role: 'assistant', content: `Subagente falhou: ${e.message || e}` } };
139
- console.log(c.err(`\nErro: ${e.message || e}`));
178
+ const amigavel = friendlyError(e);
179
+ if (subagent) return { assistantMessage: { role: 'assistant', content: `Subagente falhou: ${amigavel}` } };
180
+ console.log('\n' + c.err(amigavel));
140
181
  throw e;
141
182
  }
142
183
  sp.stop();
143
184
 
144
185
  if (streamError) {
145
- if (subagent) return { assistantMessage: { role: 'assistant', content: `Subagente falhou: ${streamError}` } };
146
- console.log(c.err(`\nErro: ${streamError}`));
147
- return { assistantMessage: { role: 'assistant', content: `Erro: ${streamError}` } };
186
+ const amigavel = friendlyError(streamError);
187
+ if (subagent) return { assistantMessage: { role: 'assistant', content: `Subagente falhou: ${amigavel}` } };
188
+ console.log('\n' + c.err(amigavel));
189
+ return { assistantMessage: { role: 'assistant', content: amigavel } };
148
190
  }
149
191
 
150
192
  // Sem tool_calls: o modelo respondeu e acabou.