primocode 8.34.1 → 8.35.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
@@ -40,7 +40,7 @@
40
40
  <div class="rotulo">Conversas</div>
41
41
  <div class="recentes" id="recentes">
42
42
  <div class="vazio-lista" id="listaVazia">
43
- As conversas aparecem aqui quando você entra na sua conta com <code>/conectar</code>.
43
+ As conversas aparecem aqui quando você entra na sua conta com <code>/connect</code>.
44
44
  </div>
45
45
  </div>
46
46
 
@@ -69,7 +69,7 @@
69
69
  <p>Escreva do jeito que você falaria. Ele lê a pasta, escreve o código,
70
70
  roda o que precisar e conta o que fez.</p>
71
71
  <div class="sugestoes">
72
- <button class="sugestao" data-manda="/tudo">o que ele sabe fazer</button>
72
+ <button class="sugestao" data-manda="/all">o que ele sabe fazer</button>
73
73
  <button class="sugestao" data-manda="/project">meus projetos</button>
74
74
  <button class="sugestao" data-manda="/effort">esforço</button>
75
75
  <button class="sugestao" data-manda="/help">comandos</button>
package/bin/primocode.js CHANGED
@@ -82,31 +82,32 @@ let janelaAberta = null;
82
82
 
83
83
  const HELP = `
84
84
  ${c.bold('Comandos')}
85
- ${c.brand('/dir')} <caminho> trabalha numa pasta que já existe (onde você quiser)
86
- ${c.brand('/project')} lista projetos ou cria em ~/primocode: /project <nome>
87
- ${c.brand('/mode')} /mode primocode|chat|auto
88
- ${c.brand('/effort')} esforço do agente — do rápido ao mais esperto
89
- ${c.brand('/attach')} <arquivo> anexa arquivo ao próximo pedido
90
- ${c.brand('/files')} lista anexos ${c.brand('/detach')} remove
91
- ${c.brand('/tudo')} lista tudo que ele sabe fazer
92
- ${c.brand('/tree')} [n] árvore do projeto
93
- ${c.brand('/read')} <arquivo> mostra um arquivo
94
- ${c.brand('/run')} <comando> executa no shell do projeto
95
- ${c.brand('/open')} <arquivo|url> abre no navegador
96
- ${c.brand('/rc')} abre a janela do PrimoCode (chat, sem terminal)
97
- ${c.brand('/claude')} usa o Claude Code da SUA conta como motor (Opus)
98
- ${c.brand('/gpt')} usa o GPT (Codex) da SUA conta como motor
99
- ${c.brand('/chave')} <provedor> sua própria chave de API (Claude, Codex)
100
- ${c.brand('/conectar')} entra na sua conta Conecta Primo · ${c.brand('/conta')} mostra o plano
101
- ${c.brand('/conversas')} [n] suas conversas salvas — as mesmas do app e do celular
102
- ${c.brand('/sair')} desconecta este computador
103
- ${c.brand('/status')} checa o servidor
104
- ${c.brand('/name')} <nome> como o Primo te chama
105
- ${c.brand('/mascot')} <estilo> mascote: primo|fox|cat
106
- ${c.brand('/config')} mostra suas preferências
107
- ${c.brand('/clear')} limpa a conversa ${c.brand('/cls')} limpa a tela
108
- ${c.brand('/uninstall')} remove o PrimoCode
109
- ${c.brand('/exit')} sair
85
+ ${c.brand('/dir')} <caminho> trabalha numa pasta que já existe (onde você quiser)
86
+ ${c.brand('/project')} lista projetos ou cria em ~/primocode: /project <nome>
87
+ ${c.brand('/mode')} /mode primocode|chat|auto
88
+ ${c.brand('/effort')} esforço do agente — do rápido ao mais esperto
89
+ ${c.brand('/compact')} resume a conversa e devolve o contexto
90
+ ${c.brand('/attach')} <arquivo> anexa arquivo ao próximo pedido
91
+ ${c.brand('/files')} lista anexos ${c.brand('/detach')} remove
92
+ ${c.brand('/all')} lista tudo que ele sabe fazer
93
+ ${c.brand('/tree')} [n] árvore do projeto
94
+ ${c.brand('/read')} <arquivo> mostra um arquivo
95
+ ${c.brand('/run')} <comando> executa no shell do projeto
96
+ ${c.brand('/open')} <arq|url> abre no navegador
97
+ ${c.brand('/rc')} abre a janela do PrimoCode (chat, sem terminal)
98
+ ${c.brand('/claude')} usa o Claude Code da SUA conta como motor (Opus)
99
+ ${c.brand('/gpt')} usa o GPT (Codex) da SUA conta como motor
100
+ ${c.brand('/key')} <provedor> sua própria chave de API (Claude, GPT)
101
+ ${c.brand('/connect')} entra na sua conta · ${c.brand('/account')} mostra o plano
102
+ ${c.brand('/chats')} [n] suas conversas — as mesmas do app e do celular
103
+ ${c.brand('/logout')} desconecta este computador da conta
104
+ ${c.brand('/status')} checa o servidor
105
+ ${c.brand('/name')} <nome> como o Primo te chama
106
+ ${c.brand('/mascot')} <estilo> mascote: primo|fox|cat
107
+ ${c.brand('/config')} mostra suas preferências
108
+ ${c.brand('/clear')} limpa a conversa ${c.brand('/cls')} limpa a tela
109
+ ${c.brand('/uninstall')} remove o PrimoCode
110
+ ${c.brand('/exit')} sai do PrimoCode
110
111
 
111
112
  ${c.bold('Modos')}
112
113
  ${c.warn('Primo Code')} ${c.muted('Agente completo — cria arquivos, roda comandos, abre o navegador')}
@@ -131,10 +132,11 @@ const SLASH_COMANDOS = [
131
132
  ['/dir', 'trabalhar numa pasta sua'],
132
133
  ['/mode', 'primocode | chat | auto'],
133
134
  ['/effort', 'esforço: low → ultracode (←/→ para ajustar)'],
135
+ ['/compact', 'resume a conversa e devolve o contexto'],
134
136
  ['/attach', 'anexa arquivo ao pedido'],
135
137
  ['/detach', 'remove os anexos'],
136
138
  ['/files', 'lista os anexos'],
137
- ['/tudo', 'tudo que ele sabe fazer'],
139
+ ['/all', 'tudo que ele sabe fazer'],
138
140
  ['/tree', 'árvore do projeto'],
139
141
  ['/read', 'mostra um arquivo'],
140
142
  ['/run', 'roda um comando'],
@@ -142,11 +144,11 @@ const SLASH_COMANDOS = [
142
144
  ['/rc', 'abre a janela do PrimoCode'],
143
145
  ['/claude', 'usa o Claude Code da sua conta como motor'],
144
146
  ['/gpt', 'usa o GPT (Codex) da sua conta como motor'],
145
- ['/chave', 'sua própria chave de API (Claude, Codex)'],
146
- ['/conectar', 'entra na sua conta Conecta Primo'],
147
- ['/conta', 'sua conta, plano e computadores conectados'],
148
- ['/conversas', 'suas conversas salvas, as mesmas do app e do celular'],
149
- ['/sair', 'desconecta este computador'],
147
+ ['/key', 'sua própria chave de API (Claude, Codex)'],
148
+ ['/connect', 'entra na sua conta Conecta Primo'],
149
+ ['/account', 'sua conta, plano e computadores conectados'],
150
+ ['/chats', 'suas conversas salvas, as mesmas do app e do celular'],
151
+ ['/logout', 'desconecta este computador da conta'],
150
152
  ['/status', 'checa o servidor'],
151
153
  ['/name', 'como ele te chama'],
152
154
  ['/mascot', 'primo | fox | cat'],
@@ -259,8 +261,22 @@ function makePrompt() {
259
261
  modeTag = state.mode === 'primocode' ? c.warn('Primo Code') : c.brandDim('Chat');
260
262
  }
261
263
  const proj = state.currentProject ? c.dim(` ${state.currentProject}`) : '';
264
+
265
+ /* O CONTEXTO na barra, como o pedido pede. Só aparece depois que há
266
+ conversa: um "ctx 0%" na abertura é ruído, e ruído que aparece sempre é
267
+ o que a pessoa aprende a não enxergar — inclusive quando marcar 90%.
268
+ Fica âmbar a partir de 70% e vermelho a partir de 100%, que é quando
269
+ `/compact` deixa de ser conselho e vira o que fazer. */
270
+ const uso = memoria.usoDoContexto(state.memoria, state.history);
271
+ const ctx = uso.pct < 5 ? '' : c.dim(' · ')
272
+ + (uso.pct >= 100 ? c.err(`ctx ${uso.pct}%`)
273
+ : uso.pct >= 70 ? c.warn(`ctx ${uso.pct}%`)
274
+ : c.dim(`ctx ${uso.pct}%`));
275
+ // O modelo ativo: qual motor está pensando. No padrão é o do servidor.
276
+ const motor = state.engine === 'claude-code' ? c.dim(' · ') + c.dim('claude')
277
+ : state.engine === 'codex' ? c.dim(' · ') + c.dim('gpt') : '';
262
278
  const att = state.attachedFiles.length ? c.muted(` +${state.attachedFiles.length}`) : '';
263
- return `\n${c.muted(modeTag)}${proj}${att} ${c.brand('❯')} `;
279
+ return `\n${c.muted(modeTag)}${motor}${ctx}${proj}${att} ${c.brand('❯')} `;
264
280
  }
265
281
  function refreshPrompt() { if (rl) rl.setPrompt(makePrompt()); }
266
282
 
@@ -458,7 +474,7 @@ async function sendPrompt(userText) {
458
474
  if (state.liberado === false) {
459
475
  console.log(c.err(' O PrimoCode pede o plano Premium ou Super.'));
460
476
  console.log(c.muted(' Assine em ') + c.brand('conecta-primo-ai.vercel.app')
461
- + c.muted(' · já assinou? ') + c.brand('/conectar'));
477
+ + c.muted(' · já assinou? ') + c.brand('/connect'));
462
478
  return;
463
479
  }
464
480
 
@@ -649,8 +665,12 @@ async function dividirTarefa(pedido) {
649
665
  * Silencioso quando não há o que fazer: nas primeiras rodadas a conversa é
650
666
  * curta e o resumo não pagaria a chamada.
651
667
  */
652
- async function comprimirConversa() {
653
- if (!memoria.precisaResumir(state.memoria, state.history)) return;
668
+ /* `forcado` é o `/compact`: a pessoa mandou resumir, e mandar resumir tem de
669
+ resumir. Sem isso o comando responderia "nada a fazer" justamente quando
670
+ ela está vendo a IA se perder — e "não fiz nada" é a pior resposta possível
671
+ para quem acabou de pedir socorro. */
672
+ async function comprimirConversa(forcado = false) {
673
+ if (!forcado && !memoria.precisaResumir(state.memoria, state.history)) return;
654
674
  const sp = new Spinner('resumindo a conversa').start();
655
675
  try {
656
676
  state.memoria = await memoria.atualizar(state.memoria, state.history, ({ resumo, mensagens }) =>
@@ -720,11 +740,49 @@ function motorDaVez() {
720
740
 
721
741
  function isSlash(text) { return text.trim().startsWith('/'); }
722
742
 
743
+ /* ── OS NOMES ANTIGOS ────────────────────────────────────────────────────
744
+ * Os comandos passaram a ser todos em inglês. Metade já era; a outra metade
745
+ * estava em português, e a mistura era o que a especificação chamou de
746
+ * bagunça — `/run` ao lado de `/pasta`, `/tree` ao lado de `/tudo`.
747
+ *
748
+ * O nome antigo NÃO some: ele responde e ensina o novo. Sumir de vez faz quem
749
+ * tem o dedo viciado bater num "comando não existe" e concluir que a
750
+ * atualização quebrou alguma coisa — e essa pessoa não vai procurar no /help,
751
+ * vai desinstalar.
752
+ *
753
+ * Uma tabela, e não um `if` dentro de cada `case`: com dezoito apelidos, o
754
+ * `if` espalhado é o jeito garantido de esquecer um. Aqui, esquecer é não
755
+ * escrever a linha, e a linha some da lista.
756
+ */
757
+ const RENOMEADOS = {
758
+ // o que abre a janela
759
+ app: 'rc',
760
+ // a conta
761
+ entrar: 'connect', login: 'connect', conectar: 'connect',
762
+ conta: 'account',
763
+ sair: 'exit', // sair é SAIR; desconectar a conta é /logout
764
+ // as chaves de API
765
+ chave: 'key', chaves: 'key',
766
+ // o histórico
767
+ conversas: 'chats', historico: 'chats', histórico: 'chats',
768
+ // o resto
769
+ pasta: 'dir',
770
+ esforco: 'effort', esforço: 'effort',
771
+ tudo: 'all', capacidades: 'all',
772
+ };
773
+
723
774
  async function handleSlash(text) {
724
775
  const [cmd] = text.trim().slice(1).split(/\s+/);
725
776
  const rest = text.trim().slice(1 + cmd.length).trim();
726
777
 
727
- switch (cmd.toLowerCase()) {
778
+ let nome = cmd.toLowerCase();
779
+ if (RENOMEADOS[nome]) {
780
+ const novo = RENOMEADOS[nome];
781
+ console.log(c.muted(' Agora é ') + c.brand('/' + novo) + c.muted('.'));
782
+ nome = novo;
783
+ }
784
+
785
+ switch (nome) {
728
786
  case 'help': console.log(HELP); break;
729
787
 
730
788
  case 'project': {
@@ -751,8 +809,7 @@ async function handleSlash(text) {
751
809
  // Antes só existia /project, que SEMPRE cria em ~/primocode/<nome>. Se a
752
810
  // sua pasta já existe em outro lugar, não havia como apontar para ela.
753
811
  case 'dir':
754
- case 'cd':
755
- case 'pasta': {
812
+ case 'cd': {
756
813
  if (!rest) {
757
814
  const de = {
758
815
  padrao: 'a pasta padrão do PrimoCode',
@@ -820,7 +877,7 @@ async function handleSlash(text) {
820
877
  /* A chave de API da própria pessoa. Guardada em ~/.primocode/
821
878
  chaves.json com permissão 600 — nunca no config.json, que é o
822
879
  arquivo que se cola num chamado de suporte. */
823
- case 'chave': case 'chaves': {
880
+ case 'key': case 'keys': {
824
881
  const [qual, ...resto] = rest.split(/\s+/).filter(Boolean);
825
882
  const valor = resto.join(' ');
826
883
 
@@ -831,8 +888,8 @@ async function handleSlash(text) {
831
888
  + (p.tem ? c.ok(p.disfarce) : c.dim('não configurada'))
832
889
  + c.dim(` ${p.variavel}`));
833
890
  }
834
- console.log(c.dim('\n /chave claude sk-ant-… guarda a chave'));
835
- console.log(c.dim(' /chave codex remover apaga a que estiver lá'));
891
+ console.log(c.dim('\n /key claude sk-ant-… guarda a chave'));
892
+ console.log(c.dim(' /key gpt remover apaga a que estiver lá'));
836
893
  console.log(c.muted('\n Sem chave própria, cada motor usa a conta em que você já entrou nele.'));
837
894
  break;
838
895
  }
@@ -848,7 +905,7 @@ async function handleSlash(text) {
848
905
  }
849
906
  if (!valor) {
850
907
  const p = chaves.PROVEDORES[qual];
851
- console.log(c.muted(` Uso: `) + c.brand(`/chave ${qual} ${p.prefixo}…`));
908
+ console.log(c.muted(` Uso: `) + c.brand(`/key ${qual} ${p.prefixo}…`));
852
909
  console.log(c.dim(` Pegue a sua em ${p.onde}`));
853
910
  break;
854
911
  }
@@ -911,7 +968,7 @@ async function handleSlash(text) {
911
968
  // O /model virou /effort: você escolhe o ESFORÇO num slider (←/→,
912
969
  // Enter confirma, Esc cancela) e o modelo sai daí. Com argumento
913
970
  // ("/effort high") aplica direto, para quem já sabe o que quer.
914
- case 'effort': case 'esforco': case 'esforço': {
971
+ case 'effort': {
915
972
  const pedido = rest.toLowerCase().trim();
916
973
  if (pedido) {
917
974
  if (!effort.NOMES.includes(pedido)) {
@@ -964,9 +1021,8 @@ async function handleSlash(text) {
964
1021
  // A lista vem de lib/catalogo.js, e o test/catalogo.test.js cruza aquela
965
1022
  // lista com as tools que o tools.execute realmente atende. Ou seja: esta
966
1023
  // tela não consegue prometer coisa que o programa não faz.
967
- case 'tudo':
968
- case 'tools':
969
- case 'capacidades': {
1024
+ case 'all':
1025
+ case 'tools': {
970
1026
  const cols = Math.max(40, Math.min(process.stdout.columns || 90, 110));
971
1027
  console.log('');
972
1028
  for (const g of catalogo.GRUPOS) {
@@ -1055,6 +1111,29 @@ async function handleSlash(text) {
1055
1111
  // Limpar aqui não apaga o que já subiu: encerra ESTA conversa e a
1056
1112
  // próxima pergunta abre outra. Apagar do histórico é decisão da
1057
1113
  // pessoa, e ela toma no app — não de raspão num comando de limpar tela.
1114
+ /* Resume a conversa por ordem da pessoa. Diferente do /clear: aqui
1115
+ nada é esquecido — o que já passou vira resumo e o fim continua
1116
+ cru, então dá para seguir de onde parou. */
1117
+ case 'compact': {
1118
+ if (!state.history.length) {
1119
+ console.log(c.muted(' A conversa está vazia — não há o que resumir.'));
1120
+ break;
1121
+ }
1122
+ const antes = memoria.usoDoContexto(state.memoria, state.history);
1123
+ await comprimirConversa(true);
1124
+ const depois = memoria.usoDoContexto(state.memoria, state.history);
1125
+ if (depois.tokens >= antes.tokens) {
1126
+ // Pode não haver o que dobrar (conversa curta, ou o servidor
1127
+ // recusou). Dizer isso é melhor que fingir que encolheu.
1128
+ console.log(c.muted(' Não deu para encolher mais desta vez.'));
1129
+ } else {
1130
+ console.log(c.ok(' Conversa resumida.')
1131
+ + c.muted(` ctx ${antes.pct}% → ${depois.pct}%`));
1132
+ }
1133
+ refreshPrompt();
1134
+ break;
1135
+ }
1136
+
1058
1137
  case 'clear': state.history = []; state.memoria = memoria.nova();
1059
1138
  state.gravador.zerar(); console.log(c.ok('Conversa limpa.')); break;
1060
1139
  case 'cls': process.stdout.write('\x1Bc'); break;
@@ -1096,10 +1175,7 @@ async function handleSlash(text) {
1096
1175
  não existe" e acha que a atualização quebrou alguma coisa; o aviso
1097
1176
  custa uma linha e ensina o nome novo na hora em que ele é
1098
1177
  procurado, que é a única hora em que alguém lê. */
1099
- case 'rc': case 'app': {
1100
- if (cmd.toLowerCase() === 'app') {
1101
- console.log(c.muted(' Agora é ') + c.brand('/rc') + c.muted('. Abrindo…'));
1102
- }
1178
+ case 'rc': {
1103
1179
  // `/rc --instalar` põe o PrimoCode no Dock, no menu Iniciar ou no
1104
1180
  // menu de aplicativos, com o ícone do mascote. Uma vez só.
1105
1181
  if (/^--?(instalar|install)$/.test(rest)) {
@@ -1150,7 +1226,7 @@ async function handleSlash(text) {
1150
1226
  // /rc viraria ruído para quem já instalou de propósito.
1151
1227
  if (!require('../lib/instalar-app.js').instalado()) {
1152
1228
  console.log(c.dim(' Quer o ícone no ') + c.dim(os.platform() === 'darwin' ? 'Dock' : 'menu Iniciar')
1153
- + c.dim('? ') + c.brand('/app --instalar'));
1229
+ + c.dim('? ') + c.brand('/rc --instalar'));
1154
1230
  }
1155
1231
  } catch (e) {
1156
1232
  sp.stop();
@@ -1161,15 +1237,12 @@ async function handleSlash(text) {
1161
1237
 
1162
1238
  /* A conta do Conecta Primo. O login inteiro mora em lib/conta.js;
1163
1239
  aqui é a tela dele. */
1164
- case 'conectar': case 'entrar': case 'login': {
1165
- if (/^(entrar|login)$/.test(cmd.toLowerCase())) {
1166
- console.log(c.muted(' Agora é ') + c.brand('/conectar') + c.muted('.'));
1167
- }
1240
+ case 'connect': {
1168
1241
  const jaEsta = conta.atual();
1169
1242
  if (jaEsta && rest !== 'trocar') {
1170
1243
  console.log(c.muted(' Já conectado como ') + c.brand(jaEsta.uid.slice(0, 12) + '…')
1171
1244
  + c.muted(` · plano ${jaEsta.plano}`));
1172
- console.log(c.dim(' Para entrar com outra conta: ') + c.brand('/conectar trocar'));
1245
+ console.log(c.dim(' Para entrar com outra conta: ') + c.brand('/connect trocar'));
1173
1246
  break;
1174
1247
  }
1175
1248
  if (rest === 'trocar') conta.sair();
@@ -1185,11 +1258,20 @@ async function handleSlash(text) {
1185
1258
  console.log(' ' + c.brand(msg));
1186
1259
  primeiro = false;
1187
1260
  } else {
1188
- // O código aparece AQUI para a pessoa comparar com o
1189
- // da página. Sem essa comparação, um link mandado por
1190
- // outra pessoa conectaria o computador dela à conta
1191
- // de quem clicou.
1192
- console.log(c.muted(' Confira o código na página: ') + c.bold(c.warn(msg)));
1261
+ /* O código é para DIGITAR na página, e é ele que
1262
+ protege: como não vai no link, quem não está olhando
1263
+ este terminal não tem o que digitar — e um link
1264
+ mandado por outra pessoa não conecta o computador
1265
+ dela à sua conta.
1266
+
1267
+ Por isso ele aparece grande e sozinho, com o traço
1268
+ no meio: o que se lê de uma tela e se digita noutra
1269
+ erra menos partido em dois. */
1270
+ const meio = msg.length === 8 ? msg.slice(0, 4) + '-' + msg.slice(4) : msg;
1271
+ console.log('');
1272
+ console.log(c.muted(' Digite este código na página:'));
1273
+ console.log(' ' + c.bold(c.brand(meio.split('').join(' '))));
1274
+ console.log('');
1193
1275
  console.log(c.dim(' Esperando você aprovar…'));
1194
1276
  }
1195
1277
  },
@@ -1203,9 +1285,9 @@ async function handleSlash(text) {
1203
1285
 
1204
1286
  /* O histórico da conta — o MESMO que o app desktop e o celular
1205
1287
  mostram. Sem número, lista; com número, abre e continua dali. */
1206
- case 'conversas': case 'historico': {
1288
+ case 'chats': {
1207
1289
  if (!conta.atual()) {
1208
- console.log(c.muted(' Não conectado. ') + c.brand('/conectar')
1290
+ console.log(c.muted(' Não conectado. ') + c.brand('/connect')
1209
1291
  + c.muted(' para ver suas conversas aqui, no app e no celular.'));
1210
1292
  break;
1211
1293
  }
@@ -1226,7 +1308,7 @@ async function handleSlash(text) {
1226
1308
  + ' ' + c.white(x.titulo.slice(0, 52).padEnd(52))
1227
1309
  + c.dim(`${String(x.mensagens).padStart(3)} msg ${quando}`));
1228
1310
  });
1229
- console.log(c.dim('\n /conversas 1 abre a primeira e continua dela.'));
1311
+ console.log(c.dim('\n /chats 1 abre a primeira e continua dela.'));
1230
1312
  break;
1231
1313
  }
1232
1314
  const n = Number(rest);
@@ -1250,18 +1332,18 @@ async function handleSlash(text) {
1250
1332
  break;
1251
1333
  }
1252
1334
 
1253
- case 'sair': case 'logout': {
1335
+ case 'logout': {
1254
1336
  if (!conta.atual()) { console.log(c.muted(' Você não está conectado.')); break; }
1255
1337
  conta.sair();
1256
1338
  console.log(c.ok(' Desconectado deste computador.')
1257
- + c.dim(' A conta continua intacta; entre de novo com /conectar.'));
1339
+ + c.dim(' A conta continua intacta; entre de novo com /connect.'));
1258
1340
  break;
1259
1341
  }
1260
1342
 
1261
- case 'conta': {
1343
+ case 'account': {
1262
1344
  const eu = conta.atual();
1263
1345
  if (!eu) {
1264
- console.log(c.muted(' Não conectado. ') + c.brand('/conectar') + c.muted(' para entrar.'));
1346
+ console.log(c.muted(' Não conectado. ') + c.brand('/connect') + c.muted(' para entrar.'));
1265
1347
  break;
1266
1348
  }
1267
1349
  const sp = new Spinner('conferindo').start();
@@ -1582,7 +1664,7 @@ async function main() {
1582
1664
  cores: c,
1583
1665
  interativo: !!(process.stdin.isTTY && process.stdout.isTTY),
1584
1666
  conectado: () => conta.atual(),
1585
- conectar: () => handleSlash('/conectar').then(() => conta.atual()),
1667
+ conectar: () => handleSlash('/connect').then(() => conta.atual()),
1586
1668
  sessao: () => conta.sessao(),
1587
1669
  aplicar: (cmd) => handleSlash(cmd),
1588
1670
  }).then((r) => {
package/lib/icone.js CHANGED
@@ -82,56 +82,72 @@ function caixa(t, x0, y0, l, a, r, cor) {
82
82
 
83
83
  // ── o mascote ────────────────────────────────────────────────────────────
84
84
 
85
- /* O Primo, o mesmo do terminal: cabeça de topo arredondado, ombros largos,
86
- * dois pezinhos. Lá ele é feito de glifos de bloco:
85
+ /* O DESENHO VEM DE UM LUGAR SÓ.
87
86
  *
88
- * ▐▛███▜▌ a cabeça
89
- * ▝▜█████▛▘ o corpo, mais largo
90
- * ▘▘ ▝▝ os pés
87
+ * Antes esta função desenhava um bicho à mão — cabeça arredondada, ombros,
88
+ * dois pés, olhos no negativo — e o app e o site desenhavam OUTRO, a matriz de
89
+ * pixels do logo que o dono mandou. Dois desenhos com o mesmo nome: o ícone no
90
+ * Dock não era o bicho que aparecia dentro do programa.
91
91
  *
92
- * Aqui são as mesmas três peças em proporção, e não uma transliteração dos
93
- * glifos: meio bloco de terminal vira escada num ícone de 512px. O que faz
94
- * reconhecer é a SILHUETA — topo redondo, ombro saliente, dois pés —, e ela é
95
- * a mesma.
96
- */
92
+ * Agora os dois leem a mesma `MATRIZ` do `app/primo-marca.js`. Mudar o logo é
93
+ * mudar onze linhas de texto, e o Dock, a janela e o site mudam juntos.
94
+ *
95
+ * (Aquele arquivo é escrito para o navegador, mas se pendura no `globalThis`
96
+ * quando não há `window` — então dá para carregá-lo aqui sem uma segunda
97
+ * versão dele.) */
98
+ require('../app/primo-marca.js');
99
+ const MARCA = (typeof globalThis !== 'undefined' && globalThis.PrimoMarca) || null;
100
+
101
+ /** #5ec8ef → [94, 200, 239] */
102
+ function doHex(h) {
103
+ const n = parseInt(String(h).replace('#', ''), 16);
104
+ return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
105
+ }
106
+
97
107
  function desenharPrimo(n) {
98
108
  const t = tela(n);
99
- const u = n / 32; // a grade do desenho, em pixels
100
109
 
101
- // A base: o quadrado escuro do app, com o mesmo arredondado do ícone de
102
- // sistema (22% do lado é o raio que o macOS e o Windows usam hoje).
110
+ // A base: o quadrado escuro do app, com o mesmo arredondado que o macOS e
111
+ // o Windows usam hoje (22% do lado). É o formato de azulejo do estilo
112
+ // pedido — glifo chapado, centrado, sobre cor sólida.
103
113
  caixa(t, 0, 0, n, n, n * 0.22, FUNDO);
104
114
 
105
- // Um brilho fraco atrás do bicho — o mesmo halo do fundo do app.
115
+ // Um brilho fraco atrás do bicho, para o azulejo não ficar chapado.
106
116
  for (let y = 0; y < n; y++) {
107
117
  for (let x = 0; x < n; x++) {
108
- const dx = (x - n / 2) / (n * 0.6);
109
- const dy = (y - n * 0.34) / (n * 0.6);
118
+ const dx = (x - n / 2) / (n * 0.62);
119
+ const dy = (y - n * 0.42) / (n * 0.62);
110
120
  const d = Math.sqrt(dx * dx + dy * dy);
111
- if (d < 1) ponto(t, x, y, AZUL, 0.11 * (1 - d) * (1 - d));
121
+ if (d < 1) ponto(t, x, y, AZUL, 0.10 * (1 - d) * (1 - d));
112
122
  }
113
123
  }
114
124
 
115
- // Cabeça: topo arredondado, ombros retos embaixo (raio só em cima é o que
116
- // dá a silhueta do ▐▛███▜▌ — redondo por cima, quadrado por baixo).
117
- caixa(t, 9 * u, 7 * u, 14 * u, 11 * u, 3.4 * u, AZUL);
118
- caixa(t, 9 * u, 14 * u, 14 * u, 4 * u, 0.6 * u, AZUL);
119
-
120
- // Corpo: mais largo que a cabeça, que é o ombro marcado do desenho.
121
- caixa(t, 6 * u, 17.6 * u, 20 * u, 7.6 * u, 2.2 * u, AZUL);
125
+ if (!MARCA) return t; // sem a matriz, fica o azulejo
122
126
 
123
- // Os dois pés.
124
- caixa(t, 10 * u, 24.6 * u, 4.4 * u, 2.6 * u, 1 * u, AZUL_FUNDO);
125
- caixa(t, 17.6 * u, 24.6 * u, 4.4 * u, 2.6 * u, 1 * u, AZUL_FUNDO);
127
+ const linhas = MARCA.MATRIZ;
128
+ const colunas = linhas[0].length;
129
+ const corpo = doHex(MARCA.CORPO);
130
+ const detalhe = doHex(MARCA.DETALHE);
126
131
 
127
- // Os olhos, no negativo — é o que faz o bloco virar bicho.
128
- caixa(t, 12.4 * u, 10.6 * u, 2.6 * u, 3.2 * u, 1.1 * u, FUNDO);
129
- caixa(t, 17 * u, 10.6 * u, 2.6 * u, 3.2 * u, 1.1 * u, FUNDO);
132
+ /* O bicho ocupa 62% do azulejo. Mais que isso ele encosta na borda
133
+ * arredondada e o ícone fica apertado; menos, e some no tamanho de 16px da
134
+ * barra de tarefas — que é onde ele mais aparece. */
135
+ const lado = Math.floor((n * 0.62) / colunas);
136
+ const largura = lado * colunas;
137
+ const altura = lado * linhas.length;
138
+ const x0 = Math.round((n - largura) / 2);
139
+ const y0 = Math.round((n - altura) / 2);
130
140
 
131
- // A luz de cima: uma faixa clara na testa e no ombro. Sem ela o bicho é
132
- // uma silhueta chapada — é o mesmo bisel do "vidro" do estúdio.
133
- caixa(t, 10.4 * u, 8.2 * u, 11.2 * u, 1.4 * u, 0.7 * u, BRILHO);
134
- caixa(t, 7.6 * u, 18.6 * u, 16.8 * u, 1.2 * u, 0.6 * u, BRILHO);
141
+ for (let f = 0; f < linhas.length; f++) {
142
+ for (let cIdx = 0; cIdx < colunas; cIdx++) {
143
+ const ch = linhas[f][cIdx];
144
+ if (ch !== 'C' && ch !== 'D') continue;
145
+ // Sem raio: pixel art com canto redondo deixa de ser pixel art, e
146
+ // vira um monte de bolinhas quando o quadrado é pequeno.
147
+ caixa(t, x0 + cIdx * lado, y0 + f * lado, lado, lado, 0,
148
+ ch === 'C' ? corpo : detalhe);
149
+ }
150
+ }
135
151
 
136
152
  return t;
137
153
  }
@@ -115,10 +115,33 @@ function paraEnviar(memoria, history) {
115
115
  }
116
116
 
117
117
  /** Quanto este jeito economiza — para o /status e para o teste provarem. */
118
+ /* ── O QUANTO DA CONVERSA JÁ ESTÁ NA MESA ────────────────────────────────
119
+ * A barra do terminal mostra isso em porcentagem, e a porcentagem precisa de
120
+ * um teto para ser porcentagem DE alguma coisa.
121
+ *
122
+ * O teto de verdade é do servidor e muda com o modelo e com o minuto (ele
123
+ * divide o orçamento entre prompt, ferramentas e conversa a cada pedido). O
124
+ * CLI não vê esse número, e fingir que vê seria pior que não mostrar: a barra
125
+ * diria 20% no exato momento em que o pedido não coubesse.
126
+ *
127
+ * Então o teto aqui é declarado e é DA CONVERSA: quanto de histórico vale a
128
+ * pena carregar antes de resumir. É uma régua honesta — quem chega perto de
129
+ * 100% está mesmo com a conversa pesada, e `/compact` resolve. */
130
+ const TETO_CONVERSA = 4000;
131
+
132
+ function usoDoContexto(memoria, history) {
133
+ const tokens = tokensDe(paraEnviar(memoria, history));
134
+ return {
135
+ tokens,
136
+ teto: TETO_CONVERSA,
137
+ pct: Math.min(999, Math.round((tokens / TETO_CONVERSA) * 100)),
138
+ };
139
+ }
140
+
118
141
  function economia(memoria, history) {
119
142
  const cheio = tokensDe((history || []).slice(-20));
120
143
  const enxuto = tokensDe(paraEnviar(memoria, history));
121
144
  return { cheio, enxuto, poupado: Math.max(0, cheio - enxuto) };
122
145
  }
123
146
 
124
- module.exports = { nova, pendentes, precisaResumir, atualizar, paraEnviar, economia, tokensDe, inicioSeguro, CRUAS, LIMITE_TOKENS };
147
+ module.exports = { nova, pendentes, usoDoContexto, TETO_CONVERSA, precisaResumir, atualizar, paraEnviar, economia, tokensDe, inicioSeguro, CRUAS, LIMITE_TOKENS };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primocode",
3
- "version": "8.34.1",
3
+ "version": "8.35.0",
4
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": {