primocode 7.4.0 → 7.5.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 +30 -12
- package/bin/primocode.js +11 -0
- package/lib/browser.js +3 -2
- package/lib/ui.js +163 -48
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
# PrimoCode v7.
|
|
1
|
+
# PrimoCode v7.5.0
|
|
2
2
|
|
|
3
3
|
Agente de engenharia brasileiro para o terminal, no estilo Claude Code.
|
|
4
4
|
Cria arquivos de verdade, roda comandos, **controla o navegador e o desktop** —
|
|
5
5
|
e lembra do que fez entre uma sessão e outra.
|
|
6
6
|
|
|
7
7
|
```
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
┃ ‿ ┃ projeto: jogo-de-carro
|
|
12
|
-
╰──┬──╯
|
|
13
|
-
╰─┴─╯
|
|
8
|
+
▐▛███▜▌ Primo Code v7.5.0
|
|
9
|
+
▝▜█████▛▘ Bem-vindo de volta, Joel
|
|
10
|
+
▘▘ ▝▝ ~/projetos/jogo-de-carro
|
|
14
11
|
```
|
|
15
12
|
|
|
16
13
|
Personalize o mascote (`primo`, `fox`, `cat`) e o nome com que ele te chama —
|
|
@@ -32,14 +29,35 @@ Em uma frase: **é o poder de um agente de código no terminal, em português, c
|
|
|
32
29
|
|
|
33
30
|
## Instalação
|
|
34
31
|
|
|
32
|
+
**macOS / Linux / WSL** — um comando, e ele resolve tudo (inclusive instalar o Node, se faltar):
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
curl -fsSL https://raw.githubusercontent.com/ConectaPrimoAI/primocode/main/install.sh | bash
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Windows** — baixe o repositório e rode:
|
|
39
|
+
|
|
40
|
+
```cmd
|
|
41
|
+
install.cmd
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Já tem Node 18.17+? Então basta:
|
|
45
|
+
|
|
35
46
|
```bash
|
|
36
|
-
|
|
37
|
-
cd primocode
|
|
38
|
-
npm link # registra 'primocode' globalmente
|
|
39
|
-
primocode
|
|
47
|
+
npm install -g primocode
|
|
40
48
|
```
|
|
41
49
|
|
|
42
|
-
Requer **Node.js 18.17+**. O CLI não tem dependências — só a stdlib do Node.
|
|
50
|
+
Requer **Node.js 18.17+**. O CLI não tem dependências — só a stdlib do Node, por isso instala em segundos.
|
|
51
|
+
|
|
52
|
+
### Se der erro
|
|
53
|
+
|
|
54
|
+
| Erro | O que fazer |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `command not found: npm` | Você não tem Node. Rode o `install.sh` — ele detecta, pergunta e instala pra você. |
|
|
57
|
+
| `EACCES ... mkdir '/usr/local/lib/node_modules'` | O npm está apontando para uma pasta do sistema. **Não use `sudo`** — rode o `install.sh`, que move o npm para `~/.npm-global`. |
|
|
58
|
+
| `command not found: primocode` | A instalação não terminou, ou o PATH não foi recarregado. Abra um terminal novo; se persistir, rode o `install.sh` de novo. |
|
|
59
|
+
|
|
60
|
+
O `install.sh` é seguro de rodar quantas vezes quiser — ele conserta o que estiver errado e não duplica nada.
|
|
43
61
|
|
|
44
62
|
## Como funciona
|
|
45
63
|
|
package/bin/primocode.js
CHANGED
|
@@ -392,6 +392,17 @@ async function main() {
|
|
|
392
392
|
|
|
393
393
|
const args = process.argv.slice(2);
|
|
394
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
|
+
|
|
395
406
|
// Modo one-shot: primocode "crie um jogo de carro"
|
|
396
407
|
if (args.length && !args[0].startsWith('-')) {
|
|
397
408
|
const fileFlag = args.indexOf('-f');
|
package/lib/browser.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// mínimo escrito à mão em cima de 'net'/'tls'.
|
|
4
4
|
//
|
|
5
5
|
// Como funciona:
|
|
6
|
-
// 1) launch() abre o Chrome do usuário com --remote-debugging-port=
|
|
6
|
+
// 1) launch() abre o Chrome do usuário com --remote-debugging-port=3199
|
|
7
7
|
// e um --user-data-dir dedicado (~/.primocode/chrome-profile) — assim
|
|
8
8
|
// o usuário logado nesse profile persiste (login GitHub, Vercel, etc).
|
|
9
9
|
// 2) attach() descobre a aba ativa em /json/list e abre um WebSocket CDP.
|
|
@@ -23,7 +23,8 @@ const crypto = require('crypto');
|
|
|
23
23
|
const { spawn, execSync } = require('child_process');
|
|
24
24
|
|
|
25
25
|
const PROFILE_DIR = path.join(os.homedir(), '.primocode', 'chrome-profile');
|
|
26
|
-
|
|
26
|
+
// Dentro da faixa 3100-3199 do ambiente local, para o start.sh cuidar dela.
|
|
27
|
+
const DEBUG_PORT = Number(process.env.PRIMOCODE_CDP_PORT || 3199);
|
|
27
28
|
|
|
28
29
|
// ---- Helpers ---------------------------------------------------
|
|
29
30
|
function findChrome() {
|
package/lib/ui.js
CHANGED
|
@@ -37,63 +37,178 @@ const c = {
|
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
// ── Mascotes ─────────────────────────────────────────────────────────────
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
40
|
+
// O mascote é um bloco fixo de 3 linhas × 9 colunas. Tamanho fixo porque ele
|
|
41
|
+
// não fica sozinho: as três linhas de identidade do cabeçalho (nome, saudação,
|
|
42
|
+
// pasta) sentam ao lado dele, uma por linha do desenho. Mudar a altura
|
|
43
|
+
// desalinha o cabeçalho inteiro.
|
|
44
|
+
//
|
|
45
|
+
// Só glifos de bloco (U+2580–U+259F): largura 1 garantida em qualquer terminal
|
|
46
|
+
// monospace, sem emoji e sem depender de fonte instalada.
|
|
47
|
+
//
|
|
48
|
+
// A família compartilha corpo e pés — o que muda é a linha de cima. É de
|
|
49
|
+
// propósito: são o mesmo bicho de orelhas diferentes, não três desenhos de
|
|
50
|
+
// autores diferentes. Troque com /mascot.
|
|
51
|
+
|
|
52
|
+
const ART_W = 9; // colunas de cada linha do desenho
|
|
53
|
+
const ART_H = 3; // linhas — igual ao número de linhas de identidade
|
|
54
|
+
|
|
55
|
+
const CORPO = '▝▜█████▛▘';
|
|
56
|
+
const PES = ' ▘▘ ▝▝ ';
|
|
57
|
+
|
|
44
58
|
const MASCOTS = {
|
|
45
|
-
// Padrão:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
` ${c.brand('┃')} ${c.magenta('‿')} ${c.brand('┃')} `,
|
|
52
|
-
` ${c.brand('╰──┬──╯')} `,
|
|
53
|
-
` ${c.brandDim('╰─┴─╯')} `,
|
|
54
|
-
],
|
|
55
|
-
// Raposa — carismática e simétrica.
|
|
56
|
-
fox: () => [
|
|
57
|
-
` ${c.warn('╱╲')} ${c.warn('╱╲')} `,
|
|
58
|
-
`${c.warn('╱')} ${c.warn('╲_╱')} ${c.warn('╲')}`,
|
|
59
|
-
`${c.warn('▏')} ${c.cyan('◕')} ${c.cyan('◕')} ${c.warn('▕')}`,
|
|
60
|
-
`${c.warn('╲')} ${c.magenta('▾')} ${c.warn('╱')}`,
|
|
61
|
-
` ${c.warn('╲__')}${c.magenta('ᵕ')}${c.warn('__╱')} `,
|
|
62
|
-
],
|
|
63
|
-
// Gato — minimalista e fofo.
|
|
64
|
-
cat: () => [
|
|
65
|
-
` ${c.brand('╱╲_╱╲')} `,
|
|
66
|
-
`${c.brand('│')} ${c.cyan('◕')} ${c.magenta('◡')} ${c.cyan('◕')} ${c.brand('│')}`,
|
|
67
|
-
` ${c.brand('╲')} ${c.magenta('▿')} ${c.brand('╱')} `,
|
|
68
|
-
` ${c.brand('╲───╯')} `,
|
|
69
|
-
],
|
|
59
|
+
// Padrão: o Primo — cabeça cheia, ombros marcados, dois pezinhos.
|
|
60
|
+
primo: { topo: ' ▐▛███▜▌ ', full: 'brand', edge: 'brandDim' },
|
|
61
|
+
// Raposa — orelhas largas, abertas para fora.
|
|
62
|
+
fox: { topo: ' ▛▀ ▀▜ ', full: 'warn', edge: 'dim' },
|
|
63
|
+
// Gato — orelhas curtas e juntas.
|
|
64
|
+
cat: { topo: ' ▛▘ ▝▜ ', full: 'cyan', edge: 'brandDim' },
|
|
70
65
|
};
|
|
71
66
|
|
|
67
|
+
// Bloco cheio na cor viva, contorno na apagada: dá volume ao desenho sem
|
|
68
|
+
// precisar de gradiente nem de cor de fundo.
|
|
69
|
+
function shade(line, full, edge) {
|
|
70
|
+
let out = '';
|
|
71
|
+
for (const ch of line) {
|
|
72
|
+
if (ch === ' ') out += ch;
|
|
73
|
+
else if (ch === '█' || ch === '▀') out += c[full](ch);
|
|
74
|
+
else out += c[edge](ch);
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
|
|
72
79
|
function mascot(style) {
|
|
73
|
-
const
|
|
74
|
-
return
|
|
80
|
+
const m = MASCOTS[style] || MASCOTS.primo;
|
|
81
|
+
return [m.topo, CORPO, PES].map((l) => shade(l, m.full, m.edge));
|
|
75
82
|
}
|
|
76
83
|
|
|
84
|
+
// ── Medição de largura ───────────────────────────────────────────────────
|
|
85
|
+
// Alinhar caixa em terminal exige contar colunas VISÍVEIS: os códigos ANSI
|
|
86
|
+
// não ocupam espaço, e emoji/CJK ocupam duas colunas. Usar .length desalinha.
|
|
87
|
+
|
|
88
|
+
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
89
|
+
|
|
90
|
+
const stripAnsi = (s) => String(s).replace(ANSI_RE, '');
|
|
91
|
+
|
|
92
|
+
function charWidth(cp) {
|
|
93
|
+
if (cp === 0x200d) return 0; // zero-width joiner
|
|
94
|
+
if (cp >= 0xfe00 && cp <= 0xfe0f) return 0; // seletores de variação
|
|
95
|
+
if (cp >= 0x0300 && cp <= 0x036f) return 0; // acentos combinantes
|
|
96
|
+
if (
|
|
97
|
+
(cp >= 0x1100 && cp <= 0x115f) ||
|
|
98
|
+
(cp >= 0x2e80 && cp <= 0xa4cf) ||
|
|
99
|
+
(cp >= 0xac00 && cp <= 0xd7a3) ||
|
|
100
|
+
(cp >= 0xf900 && cp <= 0xfaff) ||
|
|
101
|
+
(cp >= 0xfe30 && cp <= 0xfe6f) ||
|
|
102
|
+
(cp >= 0xff00 && cp <= 0xff60) ||
|
|
103
|
+
(cp >= 0xffe0 && cp <= 0xffe6) ||
|
|
104
|
+
(cp >= 0x1f300 && cp <= 0x1f64f) || // emoji
|
|
105
|
+
(cp >= 0x1f900 && cp <= 0x1f9ff) ||
|
|
106
|
+
(cp >= 0x1f680 && cp <= 0x1f6ff)
|
|
107
|
+
) return 2;
|
|
108
|
+
return 1;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function visibleWidth(s) {
|
|
112
|
+
let w = 0;
|
|
113
|
+
for (const ch of stripAnsi(s)) w += charWidth(ch.codePointAt(0));
|
|
114
|
+
return w;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Corta preservando os códigos ANSI (senão a cor "vaza" para o resto da linha).
|
|
118
|
+
function truncate(s, max) {
|
|
119
|
+
if (visibleWidth(s) <= max) return s;
|
|
120
|
+
const str = String(s);
|
|
121
|
+
let out = '', w = 0, i = 0;
|
|
122
|
+
while (i < str.length) {
|
|
123
|
+
ANSI_RE.lastIndex = i;
|
|
124
|
+
const m = ANSI_RE.exec(str);
|
|
125
|
+
if (m && m.index === i) { out += m[0]; i += m[0].length; continue; }
|
|
126
|
+
const ch = String.fromCodePoint(str.codePointAt(i));
|
|
127
|
+
const cw = charWidth(ch.codePointAt(0));
|
|
128
|
+
if (w + cw > max - 1) break;
|
|
129
|
+
out += ch; w += cw; i += ch.length;
|
|
130
|
+
}
|
|
131
|
+
return out + PALETTE.reset + PALETTE.dim + '…' + PALETTE.reset;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const padRight = (s, w) => s + ' '.repeat(Math.max(0, w - visibleWidth(s)));
|
|
135
|
+
|
|
136
|
+
// ── Conteúdo do cabeçalho ────────────────────────────────────────────────
|
|
137
|
+
// Editar aqui é o suficiente para mudar o que aparece na abertura.
|
|
138
|
+
|
|
139
|
+
const NOME = 'Primo Code';
|
|
140
|
+
|
|
141
|
+
const TIPS = [
|
|
142
|
+
'Peça um app novo, ou aponte para um repositório e mande clonar',
|
|
143
|
+
'Ele controla navegador e desktop de verdade — peça e veja',
|
|
144
|
+
'/help lista tudo · /mascot troca o mascote · /status confere a API',
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
const NEWS = [
|
|
148
|
+
'Cabeçalho novo: mascote em blocos e identidade em três linhas',
|
|
149
|
+
'Mascotes redesenhados na mesma família — primo, fox e cat',
|
|
150
|
+
'Instalador detecta e instala o Node sozinho, sem sudo e sem EACCES',
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
// ── Cabeçalho ────────────────────────────────────────────────────────────
|
|
154
|
+
// Duas faixas: identidade (mascote + 3 linhas) e orientação (dicas,
|
|
155
|
+
// novidades, modos). Sem caixa — moldura em terminal estreito quebra, e o
|
|
156
|
+
// alinhamento do desenho já dá a borda esquerda de graça.
|
|
157
|
+
|
|
77
158
|
function welcomeBox(version, contexto, opts = {}) {
|
|
78
159
|
const { mascot: style, userName } = opts;
|
|
79
160
|
const art = mascot(style);
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
`${c.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
161
|
+
const cols = Math.max(32, Math.min(process.stdout.columns || 100, 120));
|
|
162
|
+
|
|
163
|
+
const saudacao = userName
|
|
164
|
+
? `${c.muted('Bem-vindo de volta, ')}${c.bold(c.white(userName))}`
|
|
165
|
+
: c.muted('Bem-vindo de volta');
|
|
166
|
+
|
|
167
|
+
// As três linhas de identidade — uma por linha do mascote, na ordem em que
|
|
168
|
+
// se lê: quem é, quem chegou, onde estamos.
|
|
169
|
+
const ident = [
|
|
170
|
+
`${c.bold(c.brand(NOME))} ${c.dim('v' + version)}`,
|
|
171
|
+
saudacao,
|
|
172
|
+
contexto ? c.brandDim(contexto) : c.dim('nenhuma pasta ativa'),
|
|
88
173
|
];
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
174
|
+
|
|
175
|
+
const out = [''];
|
|
176
|
+
|
|
177
|
+
// Terminal estreito: o texto ao lado não caberia, então empilha embaixo.
|
|
178
|
+
const largo = cols >= ART_W + 2 + 28;
|
|
179
|
+
if (largo) {
|
|
180
|
+
const textW = cols - ART_W - 2;
|
|
181
|
+
for (let i = 0; i < ART_H; i++) {
|
|
182
|
+
out.push(art[i] + ' ' + truncate(ident[i] || '', textW));
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
out.push(...art);
|
|
186
|
+
out.push('');
|
|
187
|
+
out.push(...ident.map((l) => truncate(l, cols)));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Cada item ocupa 5 colunas de prefixo (" 1. "), então o texto tem cols-5.
|
|
191
|
+
// Estourar por uma coluna quebra a linha e o cabeçalho parece defeito.
|
|
192
|
+
const secao = (titulo, itens, marca) => {
|
|
193
|
+
out.push('');
|
|
194
|
+
out.push(' ' + c.bold(c.white(titulo)));
|
|
195
|
+
itens.forEach((t, i) => {
|
|
196
|
+
out.push(` ${c.dim(marca(i))} ${truncate(c.muted(t), cols - 5)}`);
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
secao('Dicas para começar', TIPS, (i) => `${i + 1}.`);
|
|
201
|
+
secao('Novidades', NEWS, () => ' ·');
|
|
202
|
+
|
|
203
|
+
// Rodapé de modos. Em terminal apertado a dica do /help sai antes de
|
|
204
|
+
// empurrar a linha para baixo — cabeçalho que quebra parece defeito.
|
|
205
|
+
const modos =
|
|
206
|
+
` ${c.magenta('●')} ${c.muted('Primo Code')}` +
|
|
207
|
+
` ${c.brand('●')} ${c.muted('Chat')}`;
|
|
208
|
+
out.push('');
|
|
209
|
+
out.push(cols >= 44 ? `${modos} ${c.dim('/help para tudo')}` : modos);
|
|
210
|
+
out.push('');
|
|
211
|
+
return out.join('\n');
|
|
97
212
|
}
|
|
98
213
|
|
|
99
214
|
class Spinner {
|
|
@@ -234,4 +349,4 @@ function askYesNo(question) {
|
|
|
234
349
|
});
|
|
235
350
|
}
|
|
236
351
|
|
|
237
|
-
module.exports = { c, mascot, welcomeBox, Spinner, ProgressBar, actionHeader, toolLine, toolResult, fmtBytes, renderMarkdown, askYesNo, friendlyError };
|
|
352
|
+
module.exports = { c, mascot, welcomeBox, Spinner, ProgressBar, actionHeader, toolLine, toolResult, fmtBytes, renderMarkdown, askYesNo, friendlyError, visibleWidth, stripAnsi, truncate, padRight };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.5.0",
|
|
4
4
|
"description": "PrimoCode — agente de engenharia com IA e cursor próprio. Modo primocode cria arquivos, controla o desktop e navegador. Modo chat é conversa pura.",
|
|
5
5
|
"main": "bin/primocode.js",
|
|
6
6
|
"bin": {
|