primocode 8.32.0 → 8.33.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/app/app.css +175 -106
- package/app/app.js +171 -4
- package/app/index.html +125 -48
- package/app/primo-marca.js +113 -0
- package/bin/primocode.js +57 -15
- package/lib/api.js +67 -3
- package/lib/app.js +39 -1
- package/lib/boas-vindas.js +161 -0
- package/lib/modo.js +43 -3
- package/package.json +2 -2
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* boas-vindas.js — a primeira vez que alguém roda `primo`.
|
|
3
|
+
*
|
|
4
|
+
* "Eu desinstalei e instalei novamente, está na versão nova, e quando mandei
|
|
5
|
+
* ele já abriu. Não veio a parte pra mim fazer login, selecionar qual IA eu
|
|
6
|
+
* quero — se quero usar já o Conecta Primo AI como IA, se quero usar outra
|
|
7
|
+
* IA. Não tem nada disso."
|
|
8
|
+
*
|
|
9
|
+
* Tinha razão: o CLI abria direto no prompt. Quem instalava não descobria que
|
|
10
|
+
* existe conta, não descobria que dá para usar a própria chave, e ia usar o
|
|
11
|
+
* gratuito achando que era só aquilo — até bater na cota e concluir que o
|
|
12
|
+
* produto não presta.
|
|
13
|
+
*
|
|
14
|
+
* ── POR QUE ISTO É UM MÓDULO, E NÃO CÓDIGO SOLTO NO bin/ ─────────────────
|
|
15
|
+
* Porque tem decisão dentro: quando aparecer, o que perguntar, o que gravar,
|
|
16
|
+
* e o que fazer quando a pessoa só aperta Enter. Isso precisa de teste, e
|
|
17
|
+
* teste não digita em terminal. Aqui a entrada e a saída são funções que o
|
|
18
|
+
* teste passa; o bin/ liga nas de verdade.
|
|
19
|
+
*
|
|
20
|
+
* ── APARECE UMA VEZ SÓ, E DÁ PARA PULAR ──────────────────────────────────
|
|
21
|
+
* Um assistente que volta toda vez vira obstáculo. Este grava a marca de que
|
|
22
|
+
* já rodou e não volta — nem se a pessoa escolher "depois". Quem quiser
|
|
23
|
+
* rever chama `/conectar` ou `/chave`, que é onde as duas coisas moram.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
'use strict';
|
|
27
|
+
|
|
28
|
+
const fs = require('fs');
|
|
29
|
+
const os = require('os');
|
|
30
|
+
const path = require('path');
|
|
31
|
+
|
|
32
|
+
const MARCA = () => path.join(os.homedir(), '.primocode', 'ja-abriu.json');
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Já passou por aqui?
|
|
36
|
+
*
|
|
37
|
+
* Duas provas, e basta uma: a marca própria, ou um config.json que já existe
|
|
38
|
+
* de uma versão anterior. A segunda evita que quem já usava o PrimoCode há
|
|
39
|
+
* meses veja um "bem-vindo, é sua primeira vez" ao atualizar — o que faria a
|
|
40
|
+
* atualização parecer uma reinstalação, e é o tipo de coisa que assusta.
|
|
41
|
+
*/
|
|
42
|
+
function jaAbriu({ arquivoConfig } = {}) {
|
|
43
|
+
try { if (fs.existsSync(MARCA())) return true; } catch {}
|
|
44
|
+
try { if (arquivoConfig && fs.existsSync(arquivoConfig)) return true; } catch {}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function marcarQueAbriu(dados = {}) {
|
|
49
|
+
try {
|
|
50
|
+
const caminho = MARCA();
|
|
51
|
+
fs.mkdirSync(path.dirname(caminho), { recursive: true });
|
|
52
|
+
fs.writeFileSync(caminho, JSON.stringify({
|
|
53
|
+
em: new Date().toISOString(), ...dados,
|
|
54
|
+
}, null, 2), 'utf8');
|
|
55
|
+
return true;
|
|
56
|
+
} catch { return false; }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Para o teste e para quem quiser rever: apaga a marca. */
|
|
60
|
+
function esquecer() {
|
|
61
|
+
try { fs.rmSync(MARCA(), { force: true }); return true; } catch { return false; }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const ESCOLHAS = [
|
|
65
|
+
{
|
|
66
|
+
id: 'conecta',
|
|
67
|
+
titulo: 'Usar a IA do Conecta Primo AI',
|
|
68
|
+
detalhe: 'A que já vem pronta. Entra na sua conta; Premium ou Super libera o limite.',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: 'propria',
|
|
72
|
+
titulo: 'Usar a minha própria IA',
|
|
73
|
+
detalhe: 'Sua chave do Claude ou do Codex, ou o Claude Code / Codex já instalados.',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'depois',
|
|
77
|
+
titulo: 'Decidir depois',
|
|
78
|
+
detalhe: 'Começa no gratuito. /conectar e /chave estão lá quando você quiser.',
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
/** O que a pessoa digitou → qual escolha. Aceita número, o id, ou Enter. */
|
|
83
|
+
function lerEscolha(texto) {
|
|
84
|
+
const t = String(texto || '').trim().toLowerCase();
|
|
85
|
+
// Enter puro é a primeira opção: é a recomendada, e quem aperta Enter sem
|
|
86
|
+
// ler está dizendo "faz o que for melhor", não "não quero nada".
|
|
87
|
+
if (!t) return ESCOLHAS[0].id;
|
|
88
|
+
const n = Number(t);
|
|
89
|
+
if (Number.isInteger(n) && n >= 1 && n <= ESCOLHAS.length) return ESCOLHAS[n - 1].id;
|
|
90
|
+
for (const e of ESCOLHAS) if (t === e.id || t.startsWith(e.id.slice(0, 4))) return e.id;
|
|
91
|
+
if (/^(n|nao|não|depois|pular|skip)$/.test(t)) return 'depois';
|
|
92
|
+
if (/claude|codex|chave|propri/.test(t)) return 'propria';
|
|
93
|
+
if (/conecta|primo|conta|entrar/.test(t)) return 'conecta';
|
|
94
|
+
return null; // não entendi: quem chama pergunta de novo
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* O assistente.
|
|
99
|
+
*
|
|
100
|
+
* @param {object} op
|
|
101
|
+
* @param {(q:string)=>Promise<string>} op.perguntar lê uma linha
|
|
102
|
+
* @param {(s:string)=>void} op.mostrar escreve na tela
|
|
103
|
+
* @param {object} op.cores o `c` do ui.js
|
|
104
|
+
* @param {()=>Promise<any>} op.conectar roda o login (o /conectar)
|
|
105
|
+
* @param {()=>Promise<any>} op.configurarChave abre o /chave
|
|
106
|
+
* @param {boolean} op.interativo false = não pergunta nada
|
|
107
|
+
*/
|
|
108
|
+
async function rodar({ perguntar, mostrar, cores: c, conectar, configurarChave,
|
|
109
|
+
interativo = true } = {}) {
|
|
110
|
+
/* Sem terminal de verdade (pipe, script, CI) NÃO se pergunta nada. Um
|
|
111
|
+
assistente que espera resposta num lugar onde ninguém pode responder
|
|
112
|
+
trava o processo para sempre — e trava calado, que é o pior jeito. */
|
|
113
|
+
if (!interativo) {
|
|
114
|
+
marcarQueAbriu({ escolha: 'sem-terminal' });
|
|
115
|
+
return { ok: true, escolha: 'depois', perguntou: false };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
mostrar('');
|
|
119
|
+
mostrar(c.bold(c.brand(' Primeira vez por aqui.')));
|
|
120
|
+
mostrar(c.muted(' Escolha qual IA vai pensar por trás do PrimoCode:'));
|
|
121
|
+
mostrar('');
|
|
122
|
+
ESCOLHAS.forEach((e, i) => {
|
|
123
|
+
mostrar(' ' + c.brand(String(i + 1)) + c.muted('. ') + c.white(e.titulo)
|
|
124
|
+
+ (i === 0 ? c.dim(' (recomendado)') : ''));
|
|
125
|
+
mostrar(' ' + c.dim(e.detalhe));
|
|
126
|
+
});
|
|
127
|
+
mostrar('');
|
|
128
|
+
|
|
129
|
+
let escolha = null;
|
|
130
|
+
for (let tentativa = 0; tentativa < 3 && !escolha; tentativa++) {
|
|
131
|
+
const dito = await perguntar(c.brand(' ❯ ') + c.muted('1, 2 ou 3 (Enter = 1) '));
|
|
132
|
+
escolha = lerEscolha(dito);
|
|
133
|
+
if (!escolha) mostrar(c.muted(' Não entendi. Digite 1, 2 ou 3.'));
|
|
134
|
+
}
|
|
135
|
+
// Três tentativas sem entender: segue no gratuito em vez de insistir.
|
|
136
|
+
// Ninguém deve ficar preso numa pergunta para usar o programa.
|
|
137
|
+
if (!escolha) escolha = 'depois';
|
|
138
|
+
|
|
139
|
+
let resultado = { escolha };
|
|
140
|
+
if (escolha === 'conecta' && conectar) {
|
|
141
|
+
mostrar('');
|
|
142
|
+
resultado.conexao = await conectar();
|
|
143
|
+
} else if (escolha === 'propria' && configurarChave) {
|
|
144
|
+
mostrar('');
|
|
145
|
+
resultado.chave = await configurarChave();
|
|
146
|
+
} else if (escolha === 'depois') {
|
|
147
|
+
mostrar('');
|
|
148
|
+
mostrar(c.muted(' Tudo bem. Você está no gratuito — ele funciona, mas tem cota.'));
|
|
149
|
+
mostrar(c.dim(' ') + c.brand('/conectar') + c.dim(' entra na sua conta · ')
|
|
150
|
+
+ c.brand('/chave') + c.dim(' usa a sua própria IA'));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// A marca é gravada DEPOIS de tudo, mas grava mesmo se o login falhar:
|
|
154
|
+
// insistir no assistente a cada abertura porque a rede caiu uma vez
|
|
155
|
+
// seria punir a pessoa por um problema que não é dela.
|
|
156
|
+
marcarQueAbriu({ escolha });
|
|
157
|
+
mostrar('');
|
|
158
|
+
return { ok: true, perguntou: true, ...resultado };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = { jaAbriu, marcarQueAbriu, esquecer, lerEscolha, rodar, ESCOLHAS, MARCA };
|
package/lib/modo.js
CHANGED
|
@@ -109,10 +109,50 @@ function escolherModo(texto, ctx = {}) {
|
|
|
109
109
|
if (RE_SAUDACAO.test(t)) return { modo: 'chat', motivo: 'cumprimento' };
|
|
110
110
|
if (RE_SATISFEITO.test(t)) return { modo: 'chat', motivo: 'está satisfeito' };
|
|
111
111
|
|
|
112
|
-
// Pergunta sobre ele mesmo
|
|
113
|
-
// "como faço para criar um site?" é dúvida, não pedido de execução.
|
|
112
|
+
// Pergunta sobre ele mesmo ganha do verbo: "o que você faz?" é dúvida.
|
|
114
113
|
if (RE_SOBRE_ELE.test(t)) return { modo: 'chat', motivo: 'pergunta sobre ele' };
|
|
115
|
-
|
|
114
|
+
|
|
115
|
+
/* ── A ENCOMENDA GANHA DA PERGUNTA ───────────────────────────────────
|
|
116
|
+
"como faço para criar um site?" é dúvida. Mas "pesquise COMO FUNCIONA
|
|
117
|
+
o WAV e escreva o resultado em wav.md" é encomenda: tem verbo de ação E
|
|
118
|
+
diz o arquivo que deve nascer. A frase conceitual ali é o ASSUNTO do
|
|
119
|
+
trabalho, não o pedido.
|
|
120
|
+
|
|
121
|
+
Antes, a checagem conceitual vinha antes destas duas e ganhava sempre.
|
|
122
|
+
O efeito era mudo: o pedido virava conversa, a resposta vinha certa, e
|
|
123
|
+
o arquivo NUNCA nascia — com um "não posso criar arquivos" no fim, que
|
|
124
|
+
é a única pista, e ela chega depois de a pessoa já ter esperado.
|
|
125
|
+
Aconteceu de verdade num teste: `wav.md` não existiu, e "crie uma
|
|
126
|
+
apresentação sobre X: o que é, o que faz…" foi para o chat porque o
|
|
127
|
+
ROTEIRO DOS SLIDES tinha "o que é" dentro.
|
|
128
|
+
|
|
129
|
+
O que separa uma coisa da outra é ONDE a frase conceitual está. No
|
|
130
|
+
COMEÇO ela governa a frase inteira e é a pergunta: "como faço para
|
|
131
|
+
criar um site?", "por que o node é assíncrono". Mais para dentro, ela
|
|
132
|
+
é o assunto de um pedido que já começou com outra coisa: "pesquise
|
|
133
|
+
como funciona…", "crie uma apresentação: o que é, o que faz…".
|
|
134
|
+
|
|
135
|
+
E nem o arquivo nomeado escapa da regra: "pra que serve o package.json"
|
|
136
|
+
cita um arquivo e continua sendo pergunta. O que decide é a POSIÇÃO,
|
|
137
|
+
não a presença. */
|
|
138
|
+
const conceitual = t.match(RE_CONCEITUAL);
|
|
139
|
+
if (conceitual) {
|
|
140
|
+
/* O que decide é o que vem ANTES dela. Se a frase já tinha começado
|
|
141
|
+
com um verbo de ação, a pergunta é o assunto do trabalho:
|
|
142
|
+
|
|
143
|
+
"pesquise | como funciona o WAV e escreva em wav.md" → encomenda
|
|
144
|
+
"crie uma apresentação: | o que é, o que faz…" → encomenda
|
|
145
|
+
"| pra que serve o package.json" → pergunta
|
|
146
|
+
"| como funciona o WAV?" → pergunta
|
|
147
|
+
"me | explica o que é recursão" → pergunta
|
|
148
|
+
|
|
149
|
+
Contar caracteres não serve: "pesquise" tem nove letras e "crie uma
|
|
150
|
+
apresentação de 4 slides sobre o PrimoCode:" tem quarenta e três, e
|
|
151
|
+
os dois são a mesma coisa. Quem manda é o verbo, não a régua. */
|
|
152
|
+
const antes = t.slice(0, conceitual.index);
|
|
153
|
+
const jaEraOrdem = RE_VERBO.test(antes) || RE_PECA.test(antes);
|
|
154
|
+
if (!jaEraOrdem) return { modo: 'chat', motivo: 'pergunta conceitual' };
|
|
155
|
+
}
|
|
116
156
|
|
|
117
157
|
// "refaz", "de novo", "continua" — retomada, com ou sem contexto.
|
|
118
158
|
if (RE_CONTINUACAO.test(t)) return { modo: 'primocode', motivo: 'retoma a tarefa' };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.33.1",
|
|
4
4
|
"description": "PrimoCode — agente de engenharia com IA e cursor próprio. Modelos grátis. Cria arquivos, roda comandos, controla navegador e desktop: abre apps, clica em botões e ícones pelo nome, digita e usa atalhos.",
|
|
5
5
|
"main": "bin/primocode.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/primocode.js",
|
|
11
|
-
"test": "node test/tools.test.js && node test/ui.test.js && node test/modo.test.js && node test/pasta.test.js && node test/catalogo.test.js && node test/desktop.test.js && node test/effort.test.js && node test/claude-engine.test.js && node test/regressao.test.js && node test/memoria-conversa.test.js && node test/repeticao.test.js && node test/referencia.test.js && node test/parar.test.js && node test/continuar.test.js && node test/studio-spec.test.js && node test/studio-versoes.test.js && node test/voz.test.js && node test/editor.test.js && node test/estudio-projeto.test.js && node test/vocabulario.test.js && node test/video-export.test.js && node test/monotonia.test.js && node test/clipe.test.js && node test/midia-envio.test.js && node test/pausas.test.js && node test/app.test.js && node test/elementos.test.js && node test/remotion.test.js && node test/aplicativo.test.js && node test/conta.test.mjs && node test/nuvem.test.js && node test/codex.test.js && node test/chaves.test.js"
|
|
11
|
+
"test": "node test/tools.test.js && node test/ui.test.js && node test/modo.test.js && node test/pasta.test.js && node test/catalogo.test.js && node test/desktop.test.js && node test/effort.test.js && node test/claude-engine.test.js && node test/regressao.test.js && node test/memoria-conversa.test.js && node test/repeticao.test.js && node test/referencia.test.js && node test/parar.test.js && node test/continuar.test.js && node test/studio-spec.test.js && node test/studio-versoes.test.js && node test/voz.test.js && node test/editor.test.js && node test/estudio-projeto.test.js && node test/vocabulario.test.js && node test/video-export.test.js && node test/monotonia.test.js && node test/clipe.test.js && node test/midia-envio.test.js && node test/pausas.test.js && node test/app.test.js && node test/elementos.test.js && node test/remotion.test.js && node test/aplicativo.test.js && node test/conta.test.mjs && node test/nuvem.test.js && node test/codex.test.js && node test/chaves.test.js && node test/prazo.test.js && node test/primeira-vez.test.js && node test/janela.test.js"
|
|
12
12
|
},
|
|
13
13
|
"engines": {
|
|
14
14
|
"node": ">=18.17.0"
|