primocode 8.33.1 → 8.34.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/bin/primocode.js +51 -32
- package/lib/boas-vindas.js +193 -109
- package/package.json +2 -2
package/bin/primocode.js
CHANGED
|
@@ -63,6 +63,12 @@ const state = {
|
|
|
63
63
|
// O histórico que o app desktop e o celular enxergam. Cifrado antes de
|
|
64
64
|
// sair daqui; o porquê de cada peça está em lib/nuvem.js.
|
|
65
65
|
gravador: nuvem.gravador(),
|
|
66
|
+
/* Começa LIBERADO e a abertura decide depois. Começar bloqueado deixaria
|
|
67
|
+
um instante em que a pessoa digita e leva "sem acesso" antes de o
|
|
68
|
+
plano ter sido conferido — e num pipe, onde a abertura nem roda, o
|
|
69
|
+
bloqueio ficaria para sempre. */
|
|
70
|
+
liberado: true,
|
|
71
|
+
motivoBloqueio: null,
|
|
66
72
|
lastResponse: '',
|
|
67
73
|
currentProject: null,
|
|
68
74
|
workDir: null, // pasta onde o agente trabalha nesta sessão
|
|
@@ -80,7 +86,6 @@ ${c.bold('Comandos')}
|
|
|
80
86
|
${c.brand('/project')} lista projetos ou cria em ~/primocode: /project <nome>
|
|
81
87
|
${c.brand('/mode')} /mode primocode|chat|auto
|
|
82
88
|
${c.brand('/effort')} esforço do agente — do rápido ao mais esperto
|
|
83
|
-
${c.brand('/claude')} liga o Claude Code local como motor (sem API paga) · /claude off volta ao padrão
|
|
84
89
|
${c.brand('/attach')} <arquivo> anexa arquivo ao próximo pedido
|
|
85
90
|
${c.brand('/files')} lista anexos ${c.brand('/detach')} remove
|
|
86
91
|
${c.brand('/tudo')} lista tudo que ele sabe fazer
|
|
@@ -90,7 +95,7 @@ ${c.bold('Comandos')}
|
|
|
90
95
|
${c.brand('/open')} <arquivo|url> abre no navegador
|
|
91
96
|
${c.brand('/rc')} abre a janela do PrimoCode (chat, sem terminal)
|
|
92
97
|
${c.brand('/claude')} usa o Claude Code da SUA conta como motor (Opus)
|
|
93
|
-
${c.brand('/
|
|
98
|
+
${c.brand('/gpt')} usa o GPT (Codex) da SUA conta como motor
|
|
94
99
|
${c.brand('/chave')} <provedor> sua própria chave de API (Claude, Codex)
|
|
95
100
|
${c.brand('/conectar')} entra na sua conta Conecta Primo · ${c.brand('/conta')} mostra o plano
|
|
96
101
|
${c.brand('/conversas')} [n] suas conversas salvas — as mesmas do app e do celular
|
|
@@ -126,7 +131,6 @@ const SLASH_COMANDOS = [
|
|
|
126
131
|
['/dir', 'trabalhar numa pasta sua'],
|
|
127
132
|
['/mode', 'primocode | chat | auto'],
|
|
128
133
|
['/effort', 'esforço: low → ultracode (←/→ para ajustar)'],
|
|
129
|
-
['/claude', 'liga o Claude Code local como motor · /claude off volta ao padrão'],
|
|
130
134
|
['/attach', 'anexa arquivo ao pedido'],
|
|
131
135
|
['/detach', 'remove os anexos'],
|
|
132
136
|
['/files', 'lista os anexos'],
|
|
@@ -137,7 +141,7 @@ const SLASH_COMANDOS = [
|
|
|
137
141
|
['/open', 'abre no navegador'],
|
|
138
142
|
['/rc', 'abre a janela do PrimoCode'],
|
|
139
143
|
['/claude', 'usa o Claude Code da sua conta como motor'],
|
|
140
|
-
['/
|
|
144
|
+
['/gpt', 'usa o GPT (Codex) da sua conta como motor'],
|
|
141
145
|
['/chave', 'sua própria chave de API (Claude, Codex)'],
|
|
142
146
|
['/conectar', 'entra na sua conta Conecta Primo'],
|
|
143
147
|
['/conta', 'sua conta, plano e computadores conectados'],
|
|
@@ -420,6 +424,22 @@ function prepararPasta(userText) {
|
|
|
420
424
|
}
|
|
421
425
|
|
|
422
426
|
async function sendPrompt(userText) {
|
|
427
|
+
/* O portão do plano. Sem ele o bloqueio da abertura seria só um texto na
|
|
428
|
+
tela: a pessoa leria "você não tem acesso" e mandaria a primeira coisa
|
|
429
|
+
do mesmo jeito, e funcionaria.
|
|
430
|
+
|
|
431
|
+
De cortesia, não de segurança — este arquivo vai publicado no npm com o
|
|
432
|
+
código à vista, e a checagem é uma linha que qualquer um apaga. Quem
|
|
433
|
+
segura de verdade é o primocode-server. O que ele faz aqui é dizer o
|
|
434
|
+
que houve e como resolver, em vez de deixar a pessoa achando que o
|
|
435
|
+
produto está quebrado. */
|
|
436
|
+
if (state.liberado === false) {
|
|
437
|
+
console.log(c.err(' O PrimoCode pede o plano Premium ou Super.'));
|
|
438
|
+
console.log(c.muted(' Assine em ') + c.brand('conecta-primo-ai.vercel.app')
|
|
439
|
+
+ c.muted(' · já assinou? ') + c.brand('/conectar'));
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
|
|
423
443
|
let prompt = userText;
|
|
424
444
|
if (state.attachedFiles.length) {
|
|
425
445
|
const parts = state.attachedFiles.map((f) => `Arquivo: ${f.path}\n\`\`\`\n${f.content}\n\`\`\``);
|
|
@@ -821,7 +841,10 @@ async function handleSlash(text) {
|
|
|
821
841
|
|
|
822
842
|
/* O Codex da conta da pessoa, com o modelo barato.
|
|
823
843
|
O protocolo é o MESMO do /claude — ver lib/codex-engine.js. */
|
|
824
|
-
|
|
844
|
+
/* `/gpt` e `/codex` são a mesma coisa e existem os dois de propósito:
|
|
845
|
+
o programa se chama Codex, mas quem usa fala "GPT" — e o comando que
|
|
846
|
+
a pessoa tenta é o nome que ela tem na cabeça, não o do fabricante. */
|
|
847
|
+
case 'gpt': case 'codex': {
|
|
825
848
|
if (rest.toLowerCase() === 'off') {
|
|
826
849
|
if (state.engine !== 'codex') { console.log(c.muted(' Já estava no motor padrão.')); break; }
|
|
827
850
|
state.engine = 'server';
|
|
@@ -1510,34 +1533,30 @@ async function main() {
|
|
|
1510
1533
|
|
|
1511
1534
|
rl.on('close', () => { closed = true; if (!busy) sayBye(); });
|
|
1512
1535
|
|
|
1513
|
-
/* A
|
|
1536
|
+
/* A ABERTURA — e ela roda AQUI, depois de `line` e `close` existirem.
|
|
1514
1537
|
*
|
|
1515
|
-
* Estava
|
|
1516
|
-
*
|
|
1517
|
-
*
|
|
1518
|
-
*
|
|
1519
|
-
*
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
// Um assistente que falha não pode levar o CLI junto: ele é uma
|
|
1538
|
-
// apresentação, não uma condição para usar o programa.
|
|
1539
|
-
.catch(() => {});
|
|
1540
|
-
}
|
|
1538
|
+
* Estava antes deles. O `await` segurava o resto do main(), e tudo o que
|
|
1539
|
+
* chegasse pelo teclado nesse meio-tempo caía no vazio: não havia ouvinte
|
|
1540
|
+
* para pegar. Quem colasse dois comandos perdia o segundo, e quem
|
|
1541
|
+
* fechasse a entrada saía sem nem o "Até logo". Digitando devagar ninguém
|
|
1542
|
+
* via — os ouvintes ficam prontos milissegundos depois. */
|
|
1543
|
+
boasVindas.rodar({
|
|
1544
|
+
perguntar: ask,
|
|
1545
|
+
mostrar: (t) => console.log(t),
|
|
1546
|
+
cores: c,
|
|
1547
|
+
interativo: !!(process.stdin.isTTY && process.stdout.isTTY),
|
|
1548
|
+
conectado: () => conta.atual(),
|
|
1549
|
+
conectar: () => handleSlash('/conectar').then(() => conta.atual()),
|
|
1550
|
+
sessao: () => conta.sessao(),
|
|
1551
|
+
aplicar: (cmd) => handleSlash(cmd),
|
|
1552
|
+
}).then((r) => {
|
|
1553
|
+
// O bloqueio de quem não é Premium/Super. É de CORTESIA: este arquivo
|
|
1554
|
+
// vai publicado no npm com o código à vista. Quem segura de verdade é
|
|
1555
|
+
// o primocode-server, que tem a chave do provedor.
|
|
1556
|
+
state.liberado = !r || r.liberado !== false;
|
|
1557
|
+
state.motivoBloqueio = r && r.motivo;
|
|
1558
|
+
if (!closed && !busy) rl.prompt();
|
|
1559
|
+
}).catch(() => { state.liberado = true; });
|
|
1541
1560
|
}
|
|
1542
1561
|
|
|
1543
1562
|
main().catch((e) => { console.error(c.err(e.message || e)); process.exit(1); });
|
package/lib/boas-vindas.js
CHANGED
|
@@ -1,26 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* boas-vindas.js — a
|
|
2
|
+
* boas-vindas.js — a abertura do PrimoCode.
|
|
3
3
|
*
|
|
4
|
-
* "
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* "O usuário vai rodar o comando de instalação do npm, vai instalar, vai
|
|
5
|
+
* rodar `primo`, e aí, como acabou de instalar, vai aparecer pra fazer
|
|
6
|
+
* login. Ele vai abrir automaticamente a janela lá no Conecta Primo AI com
|
|
7
|
+
* o token, tudo pra fazer login na conta. Aí fez o login, o terminal checa
|
|
8
|
+
* se sua conta é Premium ou Super. Se for, ele libera para a próxima etapa.
|
|
9
|
+
* Se não, ele fala que você não tem acesso ao PrimoCode, porque tem que ter
|
|
10
|
+
* o plano Premium ou Super.
|
|
8
11
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
12
|
+
* A próxima etapa, se liberar o acesso, é selecionar: continuar com a API
|
|
13
|
+
* padrão, ou usar conta Claude, GPT etc. Continuar com a API padrão, ele
|
|
14
|
+
* libera e aparece aquela tela pronta pra enviar o primeiro pedido. Se
|
|
15
|
+
* clicar em continuar com Claude/GPT, você seleciona, deu Enter, ele vai
|
|
16
|
+
* pra próxima tela, e aí você seleciona Claude ou GPT — e quando seleciona,
|
|
17
|
+
* ele dá aquele /gpt ou /claude, e vai funcionar como já funciona hoje."
|
|
13
18
|
*
|
|
14
|
-
* ──
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
+
* ── A ORDEM É O PRODUTO ──────────────────────────────────────────────────
|
|
20
|
+
* Conectar → conferir o plano → escolher o motor. Nesta ordem, e não em
|
|
21
|
+
* outra. A primeira versão disto oferecia o login como UMA OPÇÃO de menu, ao
|
|
22
|
+
* lado de "decidir depois", e deixava quem não assina usar assim mesmo. Era
|
|
23
|
+
* um produto diferente do que foi pedido.
|
|
19
24
|
*
|
|
20
|
-
* ──
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
25
|
+
* ── O BLOQUEIO É DELIBERADO ──────────────────────────────────────────────
|
|
26
|
+
* Sem Premium ou Super, o PrimoCode não trabalha. Isso é uma decisão de
|
|
27
|
+
* produto do dono, tomada com o custo na mesa: quem já instalou no gratuito
|
|
28
|
+
* perde o acesso na atualização. Está escrito aqui para ninguém "consertar"
|
|
29
|
+
* isso mais adiante achando que é bug.
|
|
30
|
+
*
|
|
31
|
+
* O bloqueio é de CORTESIA, não de segurança: este arquivo vai publicado no
|
|
32
|
+
* npm com o código à vista e qualquer um apaga a checagem. Quem segura de
|
|
33
|
+
* verdade é o primocode-server, que é quem tem a chave do provedor.
|
|
34
|
+
*
|
|
35
|
+
* ── POR QUE ISTO É UM MÓDULO ─────────────────────────────────────────────
|
|
36
|
+
* Porque tem decisão dentro, e decisão precisa de teste — e teste não digita
|
|
37
|
+
* em terminal. Aqui a entrada e a saída são funções que quem chama fornece.
|
|
24
38
|
*/
|
|
25
39
|
|
|
26
40
|
'use strict';
|
|
@@ -31,131 +45,201 @@ const path = require('path');
|
|
|
31
45
|
|
|
32
46
|
const MARCA = () => path.join(os.homedir(), '.primocode', 'ja-abriu.json');
|
|
33
47
|
|
|
34
|
-
/**
|
|
35
|
-
|
|
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;
|
|
48
|
+
/** Já escolheu o motor alguma vez? A marca guarda a escolha, não o login. */
|
|
49
|
+
function jaEscolheu() {
|
|
50
|
+
try { return fs.existsSync(MARCA()); } catch { return false; }
|
|
46
51
|
}
|
|
47
52
|
|
|
48
|
-
function
|
|
53
|
+
function marcar(dados = {}) {
|
|
49
54
|
try {
|
|
50
55
|
const caminho = MARCA();
|
|
51
56
|
fs.mkdirSync(path.dirname(caminho), { recursive: true });
|
|
52
|
-
fs.writeFileSync(caminho, JSON.stringify({
|
|
53
|
-
em: new Date().toISOString(), ...dados,
|
|
54
|
-
}, null, 2), 'utf8');
|
|
57
|
+
fs.writeFileSync(caminho, JSON.stringify({ em: new Date().toISOString(), ...dados }, null, 2), 'utf8');
|
|
55
58
|
return true;
|
|
56
59
|
} catch { return false; }
|
|
57
60
|
}
|
|
58
61
|
|
|
59
|
-
/** Para o teste e para quem quiser rever: apaga a marca. */
|
|
60
62
|
function esquecer() {
|
|
61
63
|
try { fs.rmSync(MARCA(), { force: true }); return true; } catch { return false; }
|
|
62
64
|
}
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
detalhe: 'A que já vem pronta
|
|
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
|
-
},
|
|
66
|
+
// ── as duas telas de escolha ─────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
const MOTORES = [
|
|
69
|
+
{ id: 'padrao', titulo: 'Continuar com a API padrão',
|
|
70
|
+
detalhe: 'A que já vem pronta, incluída no seu plano.' },
|
|
71
|
+
{ id: 'propria', titulo: 'Usar a minha conta (Claude, GPT…)',
|
|
72
|
+
detalhe: 'O Claude Code ou o Codex que você já usa, na sua assinatura.' },
|
|
80
73
|
];
|
|
81
74
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
}
|
|
75
|
+
const PROVEDORES = [
|
|
76
|
+
{ id: 'claude', titulo: 'Claude', comando: '/claude',
|
|
77
|
+
detalhe: 'Usa o Claude Code da sua conta, no Opus.' },
|
|
78
|
+
{ id: 'gpt', titulo: 'GPT', comando: '/gpt',
|
|
79
|
+
detalhe: 'Usa o Codex da sua conta, num modelo barato.' },
|
|
80
|
+
];
|
|
96
81
|
|
|
97
82
|
/**
|
|
98
|
-
* O
|
|
83
|
+
* O que a pessoa digitou → qual item da lista.
|
|
99
84
|
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
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
|
|
85
|
+
* Aceita o número, o id, o começo do nome, e Enter (que é sempre o primeiro:
|
|
86
|
+
* quem aperta Enter sem ler está dizendo "faz o que for melhor").
|
|
107
87
|
*/
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
88
|
+
function escolher(texto, lista) {
|
|
89
|
+
const t = String(texto || '').trim().toLowerCase();
|
|
90
|
+
if (!t) return lista[0].id;
|
|
91
|
+
const n = Number(t);
|
|
92
|
+
if (Number.isInteger(n) && n >= 1 && n <= lista.length) return lista[n - 1].id;
|
|
93
|
+
for (const e of lista) {
|
|
94
|
+
if (t === e.id || t === e.titulo.toLowerCase()) return e.id;
|
|
95
|
+
if (e.id.length >= 3 && t.startsWith(e.id.slice(0, 3))) return e.id;
|
|
96
|
+
}
|
|
97
|
+
// O jeito como as pessoas falam, e não como a lista se chama.
|
|
98
|
+
if (lista === MOTORES) {
|
|
99
|
+
if (/padr|inclu|mesmo|pronta|voc[eê]s|de voc/.test(t)) return 'padrao';
|
|
100
|
+
if (/minha|meu|pr[oó]pri|conta|chave|assinatura/.test(t)) return 'propria';
|
|
101
|
+
} else {
|
|
102
|
+
if (/claude|anthropic|opus|sonnet/.test(t)) return 'claude';
|
|
103
|
+
if (/gpt|openai|codex|chatgpt/.test(t)) return 'gpt';
|
|
116
104
|
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
117
107
|
|
|
108
|
+
async function perguntarDaLista({ perguntar, mostrar, cores: c, titulo, ajuda, lista }) {
|
|
118
109
|
mostrar('');
|
|
119
|
-
mostrar(c.bold(c.brand('
|
|
120
|
-
mostrar(c.muted('
|
|
110
|
+
mostrar(c.bold(c.brand(' ' + titulo)));
|
|
111
|
+
if (ajuda) mostrar(c.muted(' ' + ajuda));
|
|
121
112
|
mostrar('');
|
|
122
|
-
|
|
123
|
-
mostrar(' ' + c.brand(String(i + 1)) + c.muted('. ') + c.white(e.titulo)
|
|
124
|
-
+ (i === 0 ? c.dim(' (recomendado)') : ''));
|
|
113
|
+
lista.forEach((e, i) => {
|
|
114
|
+
mostrar(' ' + c.brand(String(i + 1)) + c.muted('. ') + c.white(e.titulo));
|
|
125
115
|
mostrar(' ' + c.dim(e.detalhe));
|
|
126
116
|
});
|
|
127
117
|
mostrar('');
|
|
128
118
|
|
|
129
|
-
let
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (
|
|
119
|
+
for (let tentativa = 0; tentativa < 3; tentativa++) {
|
|
120
|
+
const dito = await perguntar(c.brand(' ❯ ')
|
|
121
|
+
+ c.muted(`1 a ${lista.length} (Enter = 1) `));
|
|
122
|
+
const qual = escolher(dito, lista);
|
|
123
|
+
if (qual) return qual;
|
|
124
|
+
mostrar(c.muted(` Não entendi. Digite 1 a ${lista.length}.`));
|
|
134
125
|
}
|
|
135
|
-
// Três tentativas sem entender:
|
|
136
|
-
//
|
|
137
|
-
|
|
126
|
+
// Três tentativas sem entender: fica com a primeira em vez de prender
|
|
127
|
+
// alguém numa pergunta para usar o programa.
|
|
128
|
+
return lista[0].id;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ── a abertura ───────────────────────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @param {object} op
|
|
135
|
+
* @param {(q:string)=>Promise<string>} op.perguntar
|
|
136
|
+
* @param {(s:string)=>void} op.mostrar
|
|
137
|
+
* @param {object} op.cores
|
|
138
|
+
* @param {boolean} op.interativo false = pipe/CI: não pergunta nada
|
|
139
|
+
* @param {()=>any} op.conectado a credencial local, ou null
|
|
140
|
+
* @param {()=>Promise<any>} op.conectar roda o login (abre o navegador)
|
|
141
|
+
* @param {()=>Promise<any>} op.sessao confirma o plano NO SERVIDOR
|
|
142
|
+
* @param {(cmd:string)=>Promise<any>} op.aplicar executa /claude ou /gpt
|
|
143
|
+
*/
|
|
144
|
+
async function rodar({ perguntar, mostrar, cores: c, interativo = true,
|
|
145
|
+
conectado, conectar, sessao, aplicar } = {}) {
|
|
138
146
|
|
|
139
|
-
|
|
140
|
-
|
|
147
|
+
/* Sem terminal de verdade (pipe, script, CI) não se pergunta nada — uma
|
|
148
|
+
pergunta onde ninguém pode responder trava o processo para sempre, e
|
|
149
|
+
trava calado. Também não se BLOQUEIA: quem chamou não pode fazer login,
|
|
150
|
+
e barrar aqui transformaria um script quebrado num script mudo. Quem
|
|
151
|
+
segura de verdade é o servidor. */
|
|
152
|
+
if (!interativo) return { liberado: true, motivo: 'sem-terminal', perguntou: false };
|
|
153
|
+
|
|
154
|
+
// ── 1. conectar ──────────────────────────────────────────────────────
|
|
155
|
+
let credencial = conectado ? conectado() : null;
|
|
156
|
+
if (!credencial) {
|
|
157
|
+
mostrar('');
|
|
158
|
+
mostrar(c.bold(c.brand(' Bem-vindo ao PrimoCode.')));
|
|
159
|
+
mostrar(c.muted(' Primeiro, entre na sua conta do Conecta Primo AI.'));
|
|
160
|
+
mostrar(c.dim(' Vou abrir o navegador; é só confirmar por lá.'));
|
|
161
|
+
mostrar('');
|
|
162
|
+
const r = await conectar();
|
|
163
|
+
if (!r || r.ok === false) {
|
|
164
|
+
mostrar('');
|
|
165
|
+
mostrar(c.err(' Não deu para conectar' + (r && r.error ? `: ${r.error}` : '.')));
|
|
166
|
+
mostrar(c.muted(' Tente de novo com ') + c.brand('/conectar') + c.muted('.'));
|
|
167
|
+
return { liberado: false, motivo: 'sem-login' };
|
|
168
|
+
}
|
|
169
|
+
credencial = conectado ? conectado() : { plano: r.plano };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ── 2. o plano ───────────────────────────────────────────────────────
|
|
173
|
+
const s = await sessao();
|
|
174
|
+
if (s && s.offline) {
|
|
175
|
+
/* Sem rede não se decide. Bloquear alguém porque o wi-fi tropeçou
|
|
176
|
+
seria transformar um problema de rede num "você não tem acesso" —
|
|
177
|
+
e a pessoa iria conferir a assinatura, que está em dia. Vale o
|
|
178
|
+
último plano conhecido; quem confere de verdade é o servidor, na
|
|
179
|
+
primeira chamada que sair daqui. */
|
|
180
|
+
mostrar(c.muted(' Sem rede para confirmar o plano — seguindo com o último conhecido.'));
|
|
181
|
+
} else if (!s || !s.ok) {
|
|
182
|
+
mostrar('');
|
|
183
|
+
mostrar(c.err(' Não consegui confirmar sua conta' + (s && s.error ? `: ${s.error}` : '.')));
|
|
184
|
+
mostrar(c.muted(' Entre de novo com ') + c.brand('/conectar') + c.muted('.'));
|
|
185
|
+
return { liberado: false, motivo: 'sessao-invalida' };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const plano = (s && s.ok ? s.plano : credencial && credencial.plano) || 'free';
|
|
189
|
+
const pro = s && s.ok ? !!s.pro : !!(credencial && credencial.pro);
|
|
190
|
+
if (!pro) {
|
|
191
|
+
mostrar('');
|
|
192
|
+
mostrar(c.err(' Você ainda não tem acesso ao PrimoCode.'));
|
|
193
|
+
mostrar(c.muted(` Sua conta está no plano ${plano}, e o PrimoCode pede `)
|
|
194
|
+
+ c.brand('Premium') + c.muted(' ou ') + c.brand('Super') + c.muted('.'));
|
|
195
|
+
mostrar('');
|
|
196
|
+
mostrar(c.dim(' Assine em ') + c.brand('conecta-primo-ai.vercel.app')
|
|
197
|
+
+ c.dim(' e rode ') + c.brand('primo') + c.dim(' de novo.'));
|
|
198
|
+
mostrar(c.dim(' Já assinou? ') + c.brand('/conectar')
|
|
199
|
+
+ c.dim(' atualiza o plano nesta máquina.'));
|
|
141
200
|
mostrar('');
|
|
142
|
-
|
|
143
|
-
}
|
|
201
|
+
return { liberado: false, motivo: 'sem-plano', plano };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
mostrar('');
|
|
205
|
+
mostrar(c.ok(' Conta liberada.') + c.muted(` Plano ${plano}.`));
|
|
206
|
+
|
|
207
|
+
// ── 3. o motor ───────────────────────────────────────────────────────
|
|
208
|
+
// Só na primeira vez: quem já escolheu não quer responder isso toda vez.
|
|
209
|
+
if (jaEscolheu()) return { liberado: true, motivo: 'ja-escolheu', plano, perguntou: false };
|
|
210
|
+
|
|
211
|
+
const motor = await perguntarDaLista({
|
|
212
|
+
perguntar, mostrar, cores: c,
|
|
213
|
+
titulo: 'Com qual IA você quer trabalhar?',
|
|
214
|
+
lista: MOTORES,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
if (motor === 'padrao') {
|
|
218
|
+
marcar({ motor: 'padrao', plano });
|
|
144
219
|
mostrar('');
|
|
145
|
-
|
|
146
|
-
} else if (escolha === 'depois') {
|
|
220
|
+
mostrar(c.muted(' Pronto. Pode pedir a primeira coisa.'));
|
|
147
221
|
mostrar('');
|
|
148
|
-
|
|
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'));
|
|
222
|
+
return { liberado: true, motor: 'padrao', plano, perguntou: true };
|
|
151
223
|
}
|
|
152
224
|
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
225
|
+
// ── 4. qual provedor ─────────────────────────────────────────────────
|
|
226
|
+
const qual = await perguntarDaLista({
|
|
227
|
+
perguntar, mostrar, cores: c,
|
|
228
|
+
titulo: 'Qual conta?',
|
|
229
|
+
ajuda: 'Ele usa o programa que você já tem instalado e logado.',
|
|
230
|
+
lista: PROVEDORES,
|
|
231
|
+
});
|
|
232
|
+
const escolhido = PROVEDORES.find((p) => p.id === qual) || PROVEDORES[0];
|
|
233
|
+
|
|
234
|
+
marcar({ motor: escolhido.id, plano });
|
|
235
|
+
mostrar('');
|
|
236
|
+
if (aplicar) await aplicar(escolhido.comando);
|
|
157
237
|
mostrar('');
|
|
158
|
-
return {
|
|
238
|
+
return { liberado: true, motor: escolhido.id, comando: escolhido.comando,
|
|
239
|
+
plano, perguntou: true };
|
|
159
240
|
}
|
|
160
241
|
|
|
161
|
-
module.exports = {
|
|
242
|
+
module.exports = {
|
|
243
|
+
rodar, jaEscolheu, marcar, esquecer, escolher,
|
|
244
|
+
MOTORES, PROVEDORES, MARCA,
|
|
245
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "8.
|
|
4
|
-
"description": "PrimoCode — agente de engenharia com IA e cursor próprio.
|
|
3
|
+
"version": "8.34.0",
|
|
4
|
+
"description": "PrimoCode — agente de engenharia com IA e cursor próprio. Requer conta Conecta Primo AI (Premium ou Super). 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": {
|
|
7
7
|
"primocode": "bin/primocode.js"
|