primocode 8.33.0 → 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 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('/codex')} usa o Codex da SUA conta como motor (modelo barato)
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
- ['/codex', 'usa o Codex da sua conta como motor'],
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
- case 'codex': {
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';
@@ -1403,22 +1426,6 @@ async function main() {
1403
1426
  }));
1404
1427
  rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: makePrompt(), completer: completarSlash });
1405
1428
 
1406
- /* A primeira vez. Antes o CLI abria direto no prompt e quem instalava não
1407
- descobria que existe conta, nem que dá para usar a própria chave — ia
1408
- usar o gratuito achando que era só aquilo, até bater na cota e concluir
1409
- que o produto não presta. Aparece UMA vez; depois disso, /conectar e
1410
- /chave é que são o caminho. */
1411
- if (!boasVindas.jaAbriu({ arquivoConfig: cfg.CONFIG_FILE })) {
1412
- await boasVindas.rodar({
1413
- perguntar: ask,
1414
- mostrar: (t) => console.log(t),
1415
- cores: c,
1416
- interativo: !!(process.stdin.isTTY && process.stdout.isTTY),
1417
- conectar: () => handleSlash('/conectar'),
1418
- configurarChave: () => handleSlash('/chave'),
1419
- });
1420
- }
1421
-
1422
1429
  rl.prompt();
1423
1430
 
1424
1431
  // Fila: o readline continua emitindo 'line' enquanto um pedido está sendo
@@ -1525,6 +1532,31 @@ async function main() {
1525
1532
  });
1526
1533
 
1527
1534
  rl.on('close', () => { closed = true; if (!busy) sayBye(); });
1535
+
1536
+ /* A ABERTURA — e ela roda AQUI, depois de `line` e `close` existirem.
1537
+ *
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; });
1528
1560
  }
1529
1561
 
1530
1562
  main().catch((e) => { console.error(c.err(e.message || e)); process.exit(1); });
@@ -1,26 +1,40 @@
1
1
  /**
2
- * boas-vindas.js — a primeira vez que alguém roda `primo`.
2
+ * boas-vindas.js — a abertura do PrimoCode.
3
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."
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
- * 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.
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
- * ── 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
+ * ── 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
- * ── 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.
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
- * 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;
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 marcarQueAbriu(dados = {}) {
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
- 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
- },
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
- /** 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
- }
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 assistente.
83
+ * O que a pessoa digitou → qual item da lista.
99
84
  *
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
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
- 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 };
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(' Primeira vez por aqui.')));
120
- mostrar(c.muted(' Escolha qual IA vai pensar por trás do PrimoCode:'));
110
+ mostrar(c.bold(c.brand(' ' + titulo)));
111
+ if (ajuda) mostrar(c.muted(' ' + ajuda));
121
112
  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)') : ''));
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 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.'));
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: segue no gratuito em vez de insistir.
136
- // Ninguém deve ficar preso numa pergunta para usar o programa.
137
- if (!escolha) escolha = 'depois';
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
- let resultado = { escolha };
140
- if (escolha === 'conecta' && conectar) {
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
- resultado.conexao = await conectar();
143
- } else if (escolha === 'propria' && configurarChave) {
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
- resultado.chave = await configurarChave();
146
- } else if (escolha === 'depois') {
220
+ mostrar(c.muted(' Pronto. Pode pedir a primeira coisa.'));
147
221
  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'));
222
+ return { liberado: true, motor: 'padrao', plano, perguntou: true };
151
223
  }
152
224
 
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 });
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 { ok: true, perguntou: true, ...resultado };
238
+ return { liberado: true, motor: escolhido.id, comando: escolhido.comando,
239
+ plano, perguntou: true };
159
240
  }
160
241
 
161
- module.exports = { jaAbriu, marcarQueAbriu, esquecer, lerEscolha, rodar, ESCOLHAS, MARCA };
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.33.0",
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.",
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"