primocode 7.3.0 → 7.4.0
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 +65 -8
- package/bin/primocode.js +54 -5
- package/lib/act.js +48 -6
- package/lib/browser.js +507 -492
- package/lib/config.js +5 -2
- package/lib/desktop.js +189 -142
- package/lib/tools.js +113 -0
- package/lib/ui.js +236 -165
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,16 +1,35 @@
|
|
|
1
|
-
# PrimoCode v7.
|
|
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,
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
| `
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
|
@@ -373,7 +419,10 @@ async function main() {
|
|
|
373
419
|
process.exit(0);
|
|
374
420
|
}
|
|
375
421
|
|
|
376
|
-
console.log(welcomeBox(VERSION, state.workDir ? shortPath(state.workDir) : null
|
|
422
|
+
console.log(welcomeBox(VERSION, state.workDir ? shortPath(state.workDir) : null, {
|
|
423
|
+
mascot: state.config.mascot,
|
|
424
|
+
userName: state.config.userName,
|
|
425
|
+
}));
|
|
377
426
|
rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: makePrompt() });
|
|
378
427
|
rl.prompt();
|
|
379
428
|
|
|
@@ -400,7 +449,7 @@ async function main() {
|
|
|
400
449
|
if (isSlash(text)) await handleSlash(text);
|
|
401
450
|
else await sendPrompt(text);
|
|
402
451
|
} catch (e) {
|
|
403
|
-
console.log(c.err(
|
|
452
|
+
console.log('\n' + c.err(ui.friendlyError(e)));
|
|
404
453
|
}
|
|
405
454
|
}
|
|
406
455
|
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
|
-
|
|
139
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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.
|