primocode 8.31.0 → 8.33.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/app/index.html CHANGED
@@ -1,5 +1,5 @@
1
1
  <!doctype html>
2
- <html lang="pt-BR">
2
+ <html lang="pt-BR" data-tema="escuro">
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
@@ -7,62 +7,139 @@
7
7
  <link rel="stylesheet" href="/app.css">
8
8
  <link rel="icon" href="/icone.svg">
9
9
  </head>
10
+ <!--
11
+ A janela do PrimoCode (/rc).
12
+
13
+ É a MESMA interface da página /code do Conecta Primo — de propósito. Quem
14
+ usa o terminal, abre a janela e depois entra pelo navegador tem de
15
+ reconhecer onde está no primeiro olhar: mesma barra lateral, mesmo botão de
16
+ novo chat, mesmo prompt `❯`, mesmo mascote na tela vazia.
17
+
18
+ O que muda é de onde vem o conteúdo: aqui o histórico vem do servidor local
19
+ (/api/conversas), que usa o lib/nuvem.js — a mesma cifra e a mesma coleção
20
+ que a web lê direto do Firestore.
21
+ -->
10
22
  <body>
11
23
 
12
- <header id="topo">
13
- <div class="marca">
14
- <svg class="logo" viewBox="0 0 24 24" aria-hidden="true">
15
- <path d="M4 6.5 9 12l-5 5.5" fill="none" stroke="currentColor" stroke-width="2.1"
16
- stroke-linecap="round" stroke-linejoin="round"/>
17
- <path d="M12.5 18h7.5" fill="none" stroke="currentColor" stroke-width="2.1"
18
- stroke-linecap="round"/>
19
- </svg>
20
- <span class="nome">PrimoCode</span>
21
- <span class="versao" id="versao"></span>
22
- </div>
23
- <div class="estado">
24
- <span class="chip" id="pasta" title="pasta de trabalho"></span>
25
- <span class="chip" id="modo"></span>
26
- <span class="chip" id="esforco"></span>
27
- </div>
28
- </header>
24
+ <div class="tela">
25
+ <aside class="lado" id="lado" aria-label="Conversas e ajustes">
26
+ <div class="lado-topo">
27
+ <div class="marca-linha">
28
+ <span id="marcaTopo"></span>
29
+ <span class="nome">PrimoCode</span>
30
+ <span class="versao" id="versao"></span>
31
+ </div>
32
+ <button class="novo" id="btnNovo">
33
+ <svg width="15" height="15" viewBox="0 0 24 24" aria-hidden="true">
34
+ <path d="M12 5v14M5 12h14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/>
35
+ </svg>
36
+ Novo chat
37
+ </button>
38
+ </div>
29
39
 
30
- <main id="rolo">
31
- <div id="conversa">
32
- <div class="boas-vindas">
33
- <h1>Oi. O que a gente vai construir?</h1>
34
- <p>Escreva do jeito que você falaria. Ele lê a pasta, escreve o código,
35
- roda o que precisar e conta o que fez.</p>
36
- <div class="atalhos">
37
- <button data-manda="/tudo">o que ele sabe fazer</button>
38
- <button data-manda="/project">meus projetos</button>
39
- <button data-manda="/effort">esforço</button>
40
- <button data-manda="/help">comandos</button>
40
+ <div class="rotulo">Conversas</div>
41
+ <div class="recentes" id="recentes">
42
+ <div class="vazio-lista" id="listaVazia">
43
+ As conversas aparecem aqui quando você entra na sua conta com <code>/conectar</code>.
41
44
  </div>
42
45
  </div>
43
- </div>
44
- </main>
45
46
 
46
- <footer id="baixo">
47
- <div id="pensando" hidden><span class="pulso"></span><span id="pensando-txt">trabalhando</span></div>
48
- <form id="forma">
49
- <textarea id="campo" rows="1" placeholder="Peça alguma coisa… (Enter manda, Shift+Enter quebra linha)"
50
- autocomplete="off" spellcheck="false"></textarea>
51
- <button type="submit" id="mandar" title="Enviar (Enter)" aria-label="Enviar">
52
- <svg viewBox="0 0 24 24" aria-hidden="true">
53
- <path d="M4 12h13m0 0-5-5m5 5-5 5" fill="none" stroke="currentColor"
54
- stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"/>
55
- </svg>
56
- </button>
57
- <button type="button" id="parar" title="Parar (Esc)" hidden aria-label="Parar">
58
- <svg viewBox="0 0 24 24" aria-hidden="true"><rect x="7" y="7" width="10" height="10" rx="2" fill="currentColor"/></svg>
59
- </button>
60
- </form>
61
- <div class="dica" id="dica">arraste um vídeo ou arquivo aqui para o PrimoCode usar</div>
62
- </footer>
47
+ <div class="lado-pe">
48
+ <button class="pe-btn" id="btnConfig">
49
+ <svg width="15" height="15" viewBox="0 0 24 24" aria-hidden="true">
50
+ <circle cx="12" cy="12" r="3.1" fill="none" stroke="currentColor" stroke-width="1.9"/>
51
+ <path d="M12 2.8v2.4M12 18.8v2.4M21.2 12h-2.4M5.2 12H2.8M18.5 5.5l-1.7 1.7M7.2 16.8l-1.7 1.7M18.5 18.5l-1.7-1.7M7.2 7.2 5.5 5.5"
52
+ fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/>
53
+ </svg>
54
+ Configurações
55
+ </button>
56
+ <div class="chips">
57
+ <span class="chip" id="pasta" title="pasta de trabalho"></span>
58
+ <span class="chip" id="modo"></span>
59
+ <span class="chip" id="esforco"></span>
60
+ </div>
61
+ </div>
62
+ </aside>
63
+
64
+ <main class="conversa">
65
+ <div class="rolo" id="rolo">
66
+ <div class="vazio" id="vazio">
67
+ <span id="marcaGrande"></span>
68
+ <h1>O que a gente vai construir?</h1>
69
+ <p>Escreva do jeito que você falaria. Ele lê a pasta, escreve o código,
70
+ roda o que precisar e conta o que fez.</p>
71
+ <div class="sugestoes">
72
+ <button class="sugestao" data-manda="/tudo">o que ele sabe fazer</button>
73
+ <button class="sugestao" data-manda="/project">meus projetos</button>
74
+ <button class="sugestao" data-manda="/effort">esforço</button>
75
+ <button class="sugestao" data-manda="/help">comandos</button>
76
+ </div>
77
+ </div>
78
+ <div id="conversa" class="tira" hidden></div>
79
+ </div>
80
+
81
+ <div class="rodape">
82
+ <div id="pensando" hidden><span class="pulso"></span><span id="pensando-txt">trabalhando</span></div>
83
+ <form id="forma" class="caixa">
84
+ <div class="campo-volta">
85
+ <span class="chevron">❯</span>
86
+ <textarea id="campo" rows="1" placeholder="Peça alguma coisa…"
87
+ autocomplete="off" spellcheck="false"></textarea>
88
+ </div>
89
+ <button type="submit" id="mandar" class="enviar" title="Enviar (Enter)" aria-label="Enviar">
90
+ <svg width="17" height="17" viewBox="0 0 24 24" aria-hidden="true">
91
+ <path d="M5 12h13M12.5 6l6 6-6 6" fill="none" stroke="currentColor"
92
+ stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>
93
+ </svg>
94
+ </button>
95
+ <button type="button" id="parar" class="enviar parar" title="Parar (Esc)" hidden aria-label="Parar">
96
+ <svg width="15" height="15" viewBox="0 0 24 24" aria-hidden="true"><rect x="7" y="7" width="10" height="10" rx="2" fill="currentColor"/></svg>
97
+ </button>
98
+ </form>
99
+ <div class="dica" id="dica">Enter manda · Shift+Enter quebra linha · arraste um arquivo aqui</div>
100
+ </div>
101
+ </main>
102
+ </div>
103
+
104
+ <dialog id="dlgConfig">
105
+ <div class="dlg-topo">
106
+ <h2>Configurações</h2>
107
+ <button class="copiar" id="fecharConfig">Fechar</button>
108
+ </div>
109
+ <div class="dlg-corpo">
110
+ <div class="grupo">
111
+ <h3>Tema</h3>
112
+ <div class="opcoes" id="temas">
113
+ <button class="opcao" data-tema="escuro">Escuro</button>
114
+ <button class="opcao" data-tema="claro">Claro</button>
115
+ <button class="opcao" data-tema="sistema">Do sistema</button>
116
+ </div>
117
+ </div>
118
+ <div class="grupo">
119
+ <h3>Cor</h3>
120
+ <div class="cores" id="cores">
121
+ <button class="cor" data-cor="ciano" style="background:#22d3ee" aria-label="Ciano"></button>
122
+ <button class="cor" data-cor="verde" style="background:#34d399" aria-label="Verde"></button>
123
+ <button class="cor" data-cor="roxo" style="background:#a78bfa" aria-label="Roxo"></button>
124
+ <button class="cor" data-cor="ambar" style="background:#fbbf24" aria-label="Âmbar"></button>
125
+ <button class="cor" data-cor="rosa" style="background:#fb7185" aria-label="Rosa"></button>
126
+ </div>
127
+ </div>
128
+ <div class="grupo">
129
+ <h3>Esta janela</h3>
130
+ <div class="linha-info"><span>Pasta</span><b id="pastaConfig">—</b></div>
131
+ <div class="linha-info"><span>Modo</span><b id="modoConfig">—</b></div>
132
+ <div class="linha-info"><span>Esforço</span><b id="esforcoConfig">—</b></div>
133
+ <p style="margin:10px 0 0;font-size:13px;color:var(--fraco)">
134
+ Estes três se mudam no terminal: <code>/dir</code>, <code>/mode</code> e <code>/effort</code>.
135
+ </p>
136
+ </div>
137
+ </div>
138
+ </dialog>
63
139
 
64
140
  <div id="alvo-solto"><div><strong>Solte aqui</strong><span>o caminho entra no pedido</span></div></div>
65
141
 
142
+ <script src="/primo-marca.js"></script>
66
143
  <script src="/app.js"></script>
67
144
  </body>
68
145
  </html>
@@ -0,0 +1,113 @@
1
+ /**
2
+ * primo-marca.js — o mascote do PrimoCode, desenhado em pixel.
3
+ *
4
+ * CÓPIA de conecta-primo-ai/frontend/primo-marca.js. São repositórios
5
+ * diferentes e não há como importar de um para o outro; o que dá para fazer é
6
+ * manter os dois IDÊNTICOS e o teste conferir isso — um mascote que muda num
7
+ * lado e não no outro é a marca do produto se partindo em duas sem ninguém
8
+ * ver.
9
+ *
10
+ * ── POR QUE MATRIZ, E NÃO O PNG ──────────────────────────────────────────
11
+ * O logo veio como imagem. Guardar o PNG funcionaria, mas ele já é pixel art:
12
+ * redesenhá-lo como MATRIZ dá o mesmo desenho nítido em qualquer tamanho —
13
+ * favicon de 16px, cabeçalho de 28px, tela vazia de 96px — sem borrar, sem
14
+ * três arquivos para manter, e pesando algumas centenas de bytes em vez de
15
+ * dezenas de kB.
16
+ *
17
+ * E deixa a cor viver no TEMA: o mesmo bicho no claro e no escuro, sem PNG
18
+ * com fundo grudado.
19
+ *
20
+ * Se um dia o desenho mudar, muda a matriz — é uma linha por fileira, e dá
21
+ * para ler o bicho olhando o código.
22
+ *
23
+ * C = corpo (ciano claro) · D = detalhe (azul escuro): orelhas,
24
+ * braços e pés · . = vazio
25
+ */
26
+ (function (raiz) {
27
+ "use strict";
28
+
29
+ var MATRIZ = [
30
+ "...DD....DD...",
31
+ "...DD....DD...",
32
+ "..CCCCCCCCCC..",
33
+ "..CCCCCCCCCC..",
34
+ "DDCCCCCCCCCCDD",
35
+ "DDCCCCCCCCCCDD",
36
+ "..CCCCCCCCCC..",
37
+ "..CCCCCCCCCC..",
38
+ "..CCCCCCCCCC..",
39
+ "...DD....DD...",
40
+ "...DD....DD...",
41
+ ];
42
+
43
+ var CORPO = "#5ec8ef";
44
+ var DETALHE = "#2b7fa8";
45
+
46
+ /**
47
+ * O mascote como SVG.
48
+ *
49
+ * Retângulos colados, sem `shape-rendering: crispEdges`: em pixel art
50
+ * ampliado, o antialiasing do navegador deixa fios claros entre um quadrado
51
+ * e outro. Colar meio pixel a mais em cada lado (o `+0.02`) some com a
52
+ * costura sem mudar o desenho.
53
+ *
54
+ * @param {object} op
55
+ * @param {number} [op.tamanho] lado em px (o SVG é quadrado)
56
+ * @param {string} [op.corpo] cor do corpo
57
+ * @param {string} [op.detalhe] cor das orelhas, braços e pés
58
+ * @param {string} [op.fundo] cor do quadrado de trás (vazio = sem fundo)
59
+ * @param {string} [op.rotulo] texto alternativo; vazio = decorativo
60
+ */
61
+ function svg(op) {
62
+ op = op || {};
63
+ var lado = op.tamanho || 32;
64
+ var corpo = op.corpo || CORPO;
65
+ var detalhe = op.detalhe || DETALHE;
66
+ var linhas = MATRIZ.length;
67
+ var colunas = MATRIZ[0].length;
68
+ // O desenho é mais largo que alto; a caixa é quadrada e ele fica centrado.
69
+ var caixa = Math.max(linhas, colunas);
70
+ var dx = (caixa - colunas) / 2;
71
+ var dy = (caixa - linhas) / 2;
72
+
73
+ /* UM caminho por cor, e não um retângulo por célula.
74
+ *
75
+ * Com retângulos separados, o navegador antialiasa a borda de cada um
76
+ * contra a do vizinho e o desenho ganha uma grade de fios claros no meio
77
+ * do corpo — visível já em 96px, feio em 200px. Num caminho só, as
78
+ * células vizinhas viram uma forma única e não há borda interna nenhuma
79
+ * para suavizar. */
80
+ var caminho = { C: [], D: [] };
81
+ for (var y = 0; y < linhas; y++) {
82
+ var fileira = MATRIZ[y];
83
+ for (var x = 0; x < colunas; x++) {
84
+ var ch = fileira[x];
85
+ if (ch !== "C" && ch !== "D") continue;
86
+ caminho[ch].push("M" + (x + dx) + " " + (y + dy) + "h1v1h-1z");
87
+ }
88
+ }
89
+
90
+ var partes = [];
91
+ if (op.fundo) {
92
+ partes.push('<rect width="' + caixa + '" height="' + caixa
93
+ + '" rx="' + (caixa * 0.18).toFixed(2) + '" fill="' + op.fundo + '"/>');
94
+ }
95
+ if (caminho.C.length) partes.push('<path d="' + caminho.C.join("") + '" fill="' + corpo + '"/>');
96
+ if (caminho.D.length) partes.push('<path d="' + caminho.D.join("") + '" fill="' + detalhe + '"/>');
97
+
98
+ var acessivel = op.rotulo
99
+ ? ' role="img" aria-label="' + String(op.rotulo).replace(/"/g, "&quot;") + '"'
100
+ : ' aria-hidden="true"';
101
+
102
+ return '<svg viewBox="0 0 ' + caixa + " " + caixa + '" width="' + lado
103
+ + '" height="' + lado + '"' + acessivel + ">" + partes.join("") + "</svg>";
104
+ }
105
+
106
+ /** O mesmo bicho como favicon, sem pedir arquivo ao servidor. */
107
+ function favicon() {
108
+ var s = svg({ tamanho: 32, fundo: "#0a0f16" });
109
+ return "data:image/svg+xml," + encodeURIComponent(s);
110
+ }
111
+
112
+ raiz.PrimoMarca = { svg: svg, favicon: favicon, MATRIZ: MATRIZ, CORPO: CORPO, DETALHE: DETALHE };
113
+ })(typeof window !== "undefined" ? window : globalThis);
package/bin/primocode.js CHANGED
@@ -21,6 +21,9 @@ const tools = require('../lib/tools');
21
21
  const cfg = require('../lib/config');
22
22
  const { runAct, interromper } = require('../lib/act');
23
23
  const claudeEngine = require('../lib/claude-engine');
24
+ const codexEngine = require('../lib/codex-engine');
25
+ const chaves = require('../lib/chaves');
26
+ const boasVindas = require('../lib/boas-vindas');
24
27
  const pasta = require('../lib/pasta');
25
28
  const catalogo = require('../lib/catalogo');
26
29
  const effort = require('../lib/effort');
@@ -64,10 +67,10 @@ const state = {
64
67
  currentProject: null,
65
68
  workDir: null, // pasta onde o agente trabalha nesta sessão
66
69
  workDirOrigem: null, // 'padrao' | '/dir' | 'detectada' | 'projeto' — para o /dir explicar
67
- engine: 'server', // 'server' (OpenRouter/Groq) | 'claude-code' (/claude)
70
+ engine: 'server', // 'server' (OpenRouter/Groq) | 'claude-code' (/claude) | 'codex' (/codex)
68
71
  };
69
72
 
70
- // A janela do /app, quando aberta. Uma por sessão: o segundo /app traz a que
73
+ // A janela do /rc, quando aberta. Uma por sessão: o segundo /rc traz a que
71
74
  // existe para a frente em vez de subir outro servidor ao lado.
72
75
  let janelaAberta = null;
73
76
 
@@ -85,8 +88,11 @@ ${c.bold('Comandos')}
85
88
  ${c.brand('/read')} <arquivo> mostra um arquivo
86
89
  ${c.brand('/run')} <comando> executa no shell do projeto
87
90
  ${c.brand('/open')} <arquivo|url> abre no navegador
88
- ${c.brand('/app')} abre a janela do PrimoCode (chat, sem terminal)
89
- ${c.brand('/entrar')} entra na sua conta Conecta Primo · ${c.brand('/conta')} mostra o plano
91
+ ${c.brand('/rc')} abre a janela do PrimoCode (chat, sem terminal)
92
+ ${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)
94
+ ${c.brand('/chave')} <provedor> sua própria chave de API (Claude, Codex)
95
+ ${c.brand('/conectar')} entra na sua conta Conecta Primo · ${c.brand('/conta')} mostra o plano
90
96
  ${c.brand('/conversas')} [n] suas conversas salvas — as mesmas do app e do celular
91
97
  ${c.brand('/sair')} desconecta este computador
92
98
  ${c.brand('/status')} checa o servidor
@@ -129,8 +135,11 @@ const SLASH_COMANDOS = [
129
135
  ['/read', 'mostra um arquivo'],
130
136
  ['/run', 'roda um comando'],
131
137
  ['/open', 'abre no navegador'],
132
- ['/app', 'abre a janela do PrimoCode'],
133
- ['/entrar', 'entra na sua conta Conecta Primo'],
138
+ ['/rc', 'abre a janela do PrimoCode'],
139
+ ['/claude', 'usa o Claude Code da sua conta como motor'],
140
+ ['/codex', 'usa o Codex da sua conta como motor'],
141
+ ['/chave', 'sua própria chave de API (Claude, Codex)'],
142
+ ['/conectar', 'entra na sua conta Conecta Primo'],
134
143
  ['/conta', 'sua conta, plano e computadores conectados'],
135
144
  ['/conversas', 'suas conversas salvas, as mesmas do app e do celular'],
136
145
  ['/sair', 'desconecta este computador'],
@@ -483,7 +492,7 @@ async function sendPrompt(userText) {
483
492
  // Só chamado se o agente for mesmo MEXER EM ARQUIVO — abrir o Chrome
484
493
  // não é motivo para criar pasta de projeto.
485
494
  ensureWorkDir: plan.pending ? () => createPendingProject(plan) : null,
486
- motor: state.engine === 'claude-code' ? claudeEngine : api,
495
+ motor: motorDaVez(),
487
496
  });
488
497
 
489
498
  r = await rodarPasso();
@@ -656,6 +665,17 @@ async function runChat(promptForModel, userTextForMemory) {
656
665
 
657
666
  // ── Slash commands ─────────────────────────────────────────────────────
658
667
 
668
+ /* Qual motor executa este turno.
669
+ *
670
+ * Um `if` a mais aqui e um `if` a menos em qualquer outro lugar seria o
671
+ * caminho para o /codex funcionar no modo agente e não no chat, ou ao
672
+ * contrário. Um lugar só decide, e os dois modos chamam este. */
673
+ function motorDaVez() {
674
+ if (state.engine === 'claude-code') return claudeEngine;
675
+ if (state.engine === 'codex') return codexEngine;
676
+ return api;
677
+ }
678
+
659
679
  function isSlash(text) { return text.trim().startsWith('/'); }
660
680
 
661
681
  async function handleSlash(text) {
@@ -755,6 +775,72 @@ async function handleSlash(text) {
755
775
  break;
756
776
  }
757
777
 
778
+ /* A chave de API da própria pessoa. Guardada em ~/.primocode/
779
+ chaves.json com permissão 600 — nunca no config.json, que é o
780
+ arquivo que se cola num chamado de suporte. */
781
+ case 'chave': case 'chaves': {
782
+ const [qual, ...resto] = rest.split(/\s+/).filter(Boolean);
783
+ const valor = resto.join(' ');
784
+
785
+ if (!qual) {
786
+ console.log(c.white('\n Suas chaves de API'));
787
+ for (const p of chaves.listar()) {
788
+ console.log(' ' + c.white(p.nome.padEnd(8))
789
+ + (p.tem ? c.ok(p.disfarce) : c.dim('não configurada'))
790
+ + c.dim(` ${p.variavel}`));
791
+ }
792
+ console.log(c.dim('\n /chave claude sk-ant-… guarda a chave'));
793
+ console.log(c.dim(' /chave codex remover apaga a que estiver lá'));
794
+ console.log(c.muted('\n Sem chave própria, cada motor usa a conta em que você já entrou nele.'));
795
+ break;
796
+ }
797
+ if (!chaves.PROVEDORES[qual]) {
798
+ console.log(c.err(` Não conheço "${qual}". Use: ${Object.keys(chaves.PROVEDORES).join(', ')}`));
799
+ break;
800
+ }
801
+ if (valor === 'remover' || valor === 'apagar') {
802
+ const r = chaves.esquecer(qual);
803
+ console.log(r.ok ? c.ok(` Chave do ${chaves.PROVEDORES[qual].nome} apagada.`)
804
+ : c.muted(` ${r.error}`));
805
+ break;
806
+ }
807
+ if (!valor) {
808
+ const p = chaves.PROVEDORES[qual];
809
+ console.log(c.muted(` Uso: `) + c.brand(`/chave ${qual} ${p.prefixo}…`));
810
+ console.log(c.dim(` Pegue a sua em ${p.onde}`));
811
+ break;
812
+ }
813
+ const r = chaves.guardar(qual, valor);
814
+ if (!r.ok) { console.log(c.err(` ${r.error}`)); break; }
815
+ // O que se mostra de volta é o disfarce, nunca a chave: a linha
816
+ // fica no scrollback do terminal e no print que a pessoa manda.
817
+ console.log(c.ok(` Chave do ${r.nome} guardada.`) + c.muted(` ${r.disfarce}`));
818
+ console.log(c.dim(` Ela vai como ${chaves.PROVEDORES[qual].variavel} só quando o motor rodar.`));
819
+ break;
820
+ }
821
+
822
+ /* O Codex da conta da pessoa, com o modelo barato.
823
+ O protocolo é o MESMO do /claude — ver lib/codex-engine.js. */
824
+ case 'codex': {
825
+ if (rest.toLowerCase() === 'off') {
826
+ if (state.engine !== 'codex') { console.log(c.muted(' Já estava no motor padrão.')); break; }
827
+ state.engine = 'server';
828
+ console.log(c.ok(' Motor: OpenRouter/Groq (padrão)'));
829
+ break;
830
+ }
831
+ console.log(c.dim(' Checando o Codex…'));
832
+ const r = codexEngine.garantirEngine();
833
+ if (!r.ok) {
834
+ console.log(c.err(` ${r.error}`));
835
+ if (r.comoResolver) console.log(c.muted(` ${r.comoResolver}`));
836
+ break;
837
+ }
838
+ state.engine = 'codex';
839
+ console.log(c.ok(` Motor: Codex (${r.modelo})`) + c.muted(' — na sua conta, não na minha'));
840
+ console.log(c.muted(' /codex off volta pro motor padrão.'));
841
+ break;
842
+ }
843
+
758
844
  case 'claude': {
759
845
  if (rest.toLowerCase() === 'off') {
760
846
  if (state.engine !== 'claude-code') { console.log(c.muted(' Já estava no motor padrão.')); break; }
@@ -771,7 +857,8 @@ async function handleSlash(text) {
771
857
  }
772
858
  state.engine = 'claude-code';
773
859
  const onde = r.caminho === 'A' ? 'sessão contínua via ORCA' : 'chamada avulsa (claude -p, sem ORCA)';
774
- console.log(c.ok(` Motor: Claude Code (${onde})`));
860
+ console.log(c.ok(` Motor: Claude Code (${onde})`)
861
+ + c.muted(` — ${claudeEngine.MODELO_CLAUDE || 'o padrão da sua conta'}, na sua conta`));
775
862
  console.log(c.muted(' /claude off volta pro motor padrão.'));
776
863
  break;
777
864
  }
@@ -953,14 +1040,22 @@ async function handleSlash(text) {
953
1040
  break;
954
1041
  }
955
1042
 
956
- /* A janela. "Se mandar /app pra ele, ele abre o aplicativo — para o
1043
+ /* A janela. "Se mandar /rc pra ele, ele abre o aplicativo — para o
957
1044
  Mac e o Windows, já instalado junto com o npm, com as cores do
958
1045
  PrimoCode. Um chat estilo terminal para quem não quer o terminal."
959
1046
 
960
- Uma janela por sessão: mandar /app de novo traz a que já existe
1047
+ Uma janela por sessão: mandar /rc de novo traz a que já existe
961
1048
  para a frente em vez de abrir outra do lado. */
962
- case 'app': {
963
- // `/app --instalar` põe o PrimoCode no Dock, no menu Iniciar ou no
1049
+ /* O nome antigo some da ajuda e do menu, mas continua respondendo —
1050
+ avisando. Some de vez e quem tinha o dedo viciado bate num "comando
1051
+ não existe" e acha que a atualização quebrou alguma coisa; o aviso
1052
+ custa uma linha e ensina o nome novo na hora em que ele é
1053
+ procurado, que é a única hora em que alguém lê. */
1054
+ case 'rc': case 'app': {
1055
+ if (cmd.toLowerCase() === 'app') {
1056
+ console.log(c.muted(' Agora é ') + c.brand('/rc') + c.muted('. Abrindo…'));
1057
+ }
1058
+ // `/rc --instalar` põe o PrimoCode no Dock, no menu Iniciar ou no
964
1059
  // menu de aplicativos, com o ícone do mascote. Uma vez só.
965
1060
  if (/^--?(instalar|install)$/.test(rest)) {
966
1061
  const r = require('../lib/instalar-app.js').instalar(VERSION);
@@ -1007,7 +1102,7 @@ async function handleSlash(text) {
1007
1102
  }
1008
1103
  console.log(c.muted(' O terminal continua funcionando: os dois mostram a mesma coisa.'));
1009
1104
  // Oferecido UMA vez, e só a quem ainda não tem: repetir todo
1010
- // /app viraria ruído para quem já instalou de propósito.
1105
+ // /rc viraria ruído para quem já instalou de propósito.
1011
1106
  if (!require('../lib/instalar-app.js').instalado()) {
1012
1107
  console.log(c.dim(' Quer o ícone no ') + c.dim(os.platform() === 'darwin' ? 'Dock' : 'menu Iniciar')
1013
1108
  + c.dim('? ') + c.brand('/app --instalar'));
@@ -1021,12 +1116,15 @@ async function handleSlash(text) {
1021
1116
 
1022
1117
  /* A conta do Conecta Primo. O login inteiro mora em lib/conta.js;
1023
1118
  aqui é a tela dele. */
1024
- case 'entrar': case 'login': {
1119
+ case 'conectar': case 'entrar': case 'login': {
1120
+ if (/^(entrar|login)$/.test(cmd.toLowerCase())) {
1121
+ console.log(c.muted(' Agora é ') + c.brand('/conectar') + c.muted('.'));
1122
+ }
1025
1123
  const jaEsta = conta.atual();
1026
1124
  if (jaEsta && rest !== 'trocar') {
1027
1125
  console.log(c.muted(' Já conectado como ') + c.brand(jaEsta.uid.slice(0, 12) + '…')
1028
1126
  + c.muted(` · plano ${jaEsta.plano}`));
1029
- console.log(c.dim(' Para entrar com outra conta: ') + c.brand('/entrar trocar'));
1127
+ console.log(c.dim(' Para entrar com outra conta: ') + c.brand('/conectar trocar'));
1030
1128
  break;
1031
1129
  }
1032
1130
  if (rest === 'trocar') conta.sair();
@@ -1062,7 +1160,7 @@ async function handleSlash(text) {
1062
1160
  mostram. Sem número, lista; com número, abre e continua dali. */
1063
1161
  case 'conversas': case 'historico': {
1064
1162
  if (!conta.atual()) {
1065
- console.log(c.muted(' Não conectado. ') + c.brand('/entrar')
1163
+ console.log(c.muted(' Não conectado. ') + c.brand('/conectar')
1066
1164
  + c.muted(' para ver suas conversas aqui, no app e no celular.'));
1067
1165
  break;
1068
1166
  }
@@ -1111,14 +1209,14 @@ async function handleSlash(text) {
1111
1209
  if (!conta.atual()) { console.log(c.muted(' Você não está conectado.')); break; }
1112
1210
  conta.sair();
1113
1211
  console.log(c.ok(' Desconectado deste computador.')
1114
- + c.dim(' A conta continua intacta; entre de novo com /entrar.'));
1212
+ + c.dim(' A conta continua intacta; entre de novo com /conectar.'));
1115
1213
  break;
1116
1214
  }
1117
1215
 
1118
1216
  case 'conta': {
1119
1217
  const eu = conta.atual();
1120
1218
  if (!eu) {
1121
- console.log(c.muted(' Não conectado. ') + c.brand('/entrar') + c.muted(' para entrar.'));
1219
+ console.log(c.muted(' Não conectado. ') + c.brand('/conectar') + c.muted(' para entrar.'));
1122
1220
  break;
1123
1221
  }
1124
1222
  const sp = new Spinner('conferindo').start();
@@ -1304,6 +1402,23 @@ async function main() {
1304
1402
  pastaPadrao: state.workDirOrigem === 'padrao',
1305
1403
  }));
1306
1404
  rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: makePrompt(), completer: completarSlash });
1405
+
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
+
1307
1422
  rl.prompt();
1308
1423
 
1309
1424
  // Fila: o readline continua emitindo 'line' enquanto um pedido está sendo