primocode 9.8.0-beta.0 → 9.8.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # PrimoCode v9.7.0
1
+ # PrimoCode v9.8.0-beta.1
2
2
 
3
3
  Agente de engenharia brasileiro para o terminal, no estilo Claude Code.
4
4
  Cria arquivos de verdade, roda comandos, **controla o navegador e o desktop** —
@@ -9,7 +9,7 @@ OpenRouter, e cada apelido (`top`/`main`/`fast`) é uma **corrente**: se um mode
9
9
  está no limite de pedidos, o próximo atende. Você não vê o erro, e não paga nada.
10
10
 
11
11
  ```
12
- ▐▛███▜▌ Primo Code v9.7.0
12
+ ▐▛███▜▌ Primo Code v9.8.0-beta.1
13
13
  ▝▜█████▛▘ Bem-vindo de volta, Joel
14
14
  ▘▘ ▝▝ ~/primocode · /dir <pasta> muda
15
15
  ```
@@ -27,7 +27,7 @@ veja `/mascot` e `/name`.
27
27
  | **Memória** | Cada projeto lembra o que foi feito, entre sessões | Esquece ao fechar |
28
28
  | **Instalação** | Node puro, **zero dependências** — segundos | Pacotes e dependências |
29
29
  | **Cara própria** | Mascote, nome e temas que você escolhe | Fixo |
30
- | **Modelos** | Escolhidos pelo produto, não por você: `/effort` regula o esforço e o modelo sai daí | Preso ao provedor do fabricante |
30
+ | **Modelos** | Escolhidos pelo produto (`/effort` regula o esforço), ou a sua própria chave com `/api` | Preso ao provedor do fabricante |
31
31
 
32
32
  Em uma frase: **é o poder de um agente de código no terminal, em português, controlando o seu computador de verdade — sem você precisar configurar chave de API nenhuma.**
33
33
 
@@ -287,7 +287,8 @@ primocode -f app.js "onde está o bug?" # com arquivo anexado
287
287
  | `/voice` | Modo voz: uma orb, você fala, ele faz. O cérebro é o mesmo do terminal |
288
288
  | `/skills` · `/marketplace` | O que ele sabe fazer bem. Tela navegável pelas setas, e "New Skill" para escrever a sua |
289
289
  | `/connectors` | Quem está atendendo os seus pedidos (só mostra — não há o que configurar) |
290
- | `/effort` | O esforço do agente, num slider (←/→, Enter confirma): do `low` ao `ultracode`. O modelo sai daí — não há provedor nem chave para configurar |
290
+ | `/effort` | O esforço do agente, num slider (←/→, Enter confirma): do `low` ao `ultracode`. O modelo sai daí |
291
+ | `/api` | Roda com a SUA chave (Gemini, ChatGPT, Claude, Groq, OpenRouter, DeepSeek), direto do seu computador, sem o limite do servidor. `/api modelo <nome>` troca o modelo · `/api off` volta ao servidor |
291
292
  | `/attach <arquivo>` | Anexa arquivo ao próximo pedido |
292
293
  | `/detach` · `/files` | Remove / lista anexos |
293
294
  | `/all` | Lista as 46 ferramentas que ele sabe usar, por grupo |
package/bin/primocode.js CHANGED
@@ -21,6 +21,7 @@ const api = require('../lib/api');
21
21
  const tools = require('../lib/tools');
22
22
  const cfg = require('../lib/config');
23
23
  const nomeProjeto = require('../lib/nome-projeto');
24
+ const provedorLocal = require('../lib/local/provedor');
24
25
  const { runAct, interromper, ehTetoDeToken, ehTransitorio, esperaDoErro } = require('../lib/act');
25
26
  const fala = require('../lib/fala');
26
27
  const boasVindas = require('../lib/boas-vindas');
@@ -160,6 +161,7 @@ const SLASH_COMANDOS = [
160
161
  ['/skills', 'o que ele sabe fazer bem — ligue e desligue'],
161
162
  ['/marketplace', 'a tela das skills: navegue, ligue, crie'],
162
163
  ['/install-skill', 'instala uma skill de um arquivo'],
164
+ ['/api', 'usa a SUA chave (Gemini, ChatGPT, Claude…) direto daqui, sem o limite do servidor'],
163
165
  ['/status', 'checa o servidor'],
164
166
  ['/connectors', 'quem está atendendo os seus pedidos'],
165
167
  ['/name', 'como ele te chama'],
@@ -172,6 +174,56 @@ const SLASH_COMANDOS = [
172
174
  ['/exit', 'sair'],
173
175
  ];
174
176
 
177
+ /* ── /api: a chave da própria pessoa ──────────────────────────────
178
+ * "Fica batendo no limite. Coloca um /provider onde você seleciona se é
179
+ * Gemini, ChatGPT etc., cola a key e ela roda local." (23/09/2026)
180
+ * Com provedor, o agente fala direto com a API da pessoa (lib/local/motor.js);
181
+ * sem, tudo segue pelo servidor como sempre. */
182
+ async function comandoApi(rest) {
183
+ const P = provedorLocal.PROVEDORES;
184
+ const ids = Object.keys(P);
185
+ const arg = String(rest || '').trim();
186
+ const atual = provedorLocal.atual();
187
+
188
+ if (/^(off|desliga[r]?|servidor|nenhum|sair)$/i.test(arg)) {
189
+ provedorLocal.desligar();
190
+ console.log(c.ok(' De volta ao servidor do PrimoCode.') + c.muted(' (a chave foi apagada desta máquina)'));
191
+ return;
192
+ }
193
+ const trocaModelo = arg.match(/^(modelo|model)\s+(\S+)$/i);
194
+ if (trocaModelo) {
195
+ if (!atual) { console.log(c.warn(' Nenhum provedor configurado. Rode /api primeiro.')); return; }
196
+ provedorLocal.trocarModelo(trocaModelo[2]);
197
+ console.log(c.ok(` ${atual.nome} · modelo ${trocaModelo[2]}`));
198
+ return;
199
+ }
200
+
201
+ let id = ids.find((k) => k === arg.toLowerCase() || P[k].nome.toLowerCase().startsWith(arg.toLowerCase()) && arg);
202
+ if (!id) {
203
+ console.log('');
204
+ console.log(' ' + c.bold('Com qual IA você quer rodar?') + c.muted(' a sua chave, direto daqui, sem o limite do servidor'));
205
+ if (atual) console.log(' ' + c.muted('agora: ') + c.white(`${atual.nome} · ${atual.modelo} · chave ${provedorLocal.mascarar(atual.chave)}`));
206
+ ids.forEach((k, i) => console.log(` ${c.brand(String(i + 1))} ${c.white(P[k].nome.padEnd(24))}${c.dim(P[k].dica)}`));
207
+ console.log(` ${c.brand('0')} ${c.white('Servidor do PrimoCode'.padEnd(24))}${c.dim('sem chave, com o limite do plano')}`);
208
+ const escolha = String(await ask(` ${c.brand('?')} Número: `)).trim();
209
+ if (escolha === '0') return comandoApi('off');
210
+ id = ids[Number(escolha) - 1];
211
+ if (!id) { console.log(c.muted(' Nada mudou.')); return; }
212
+ }
213
+
214
+ const p = P[id];
215
+ console.log(c.muted(` Pegue a chave em `) + c.white(p.onde));
216
+ const chave = String(await askSecret(` ${c.brand('?')} Cole a chave do ${p.nome} (não aparece na tela): `)).trim();
217
+ if (!chave) { console.log(c.muted(' Nada mudou.')); return; }
218
+ const sp = new Spinner('conferindo a chave').start();
219
+ const r = await provedorLocal.conferir(id, chave);
220
+ sp.stop();
221
+ if (!r.ok) { console.log(c.warn(` ${r.error}`)); return; }
222
+ const salvo = provedorLocal.salvar({ provedor: id, chave, modelo: r.modelo });
223
+ console.log(c.ok(` Pronto: ${salvo.nome} · ${salvo.modelo}`) + c.muted(` · chave ${provedorLocal.mascarar(chave)}`));
224
+ console.log(c.dim(' Agora os pedidos vão direto daqui, com a sua cota. /api modelo <nome> troca o modelo · /api off volta ao servidor'));
225
+ }
226
+
175
227
  // ── /effort: o slider de esforço (estilo Claude Code) ──────────────────
176
228
  // Régua com os níveis, o marcador ▲ embaixo do escolhido, "Faster" à
177
229
  // esquerda e "Smarter" à direita. ←/→ move, Enter confirma, Esc cancela.
@@ -2084,6 +2136,11 @@ async function handleSlash(text) {
2084
2136
  * Eles não somem calados. Quem tem o dedo viciado em `/claude` e
2085
2137
  * bate num "comando não existe" conclui que a atualização quebrou
2086
2138
  * alguma coisa — e não vai procurar no /help, vai desinstalar. */
2139
+ case 'api': {
2140
+ await comandoApi(rest);
2141
+ break;
2142
+ }
2143
+
2087
2144
  case 'claude': case 'gpt': case 'codex': case 'key': case 'keys': {
2088
2145
  console.log(c.muted(' Não existe mais provedor para configurar.'));
2089
2146
  console.log(c.muted(' A API já vem com o PrimoCode: entrou na conta, pode usar.'));
@@ -2141,6 +2198,9 @@ function conferirServidor() {
2141
2198
  jaConferiu = true;
2142
2199
  const minimo = (pkg.primocode && pkg.primocode.servidorMinimo) || '';
2143
2200
  if (!minimo) return;
2201
+ // Com a chave própria (/api), quem pensa não é o servidor: a versão dele
2202
+ // não muda nada no que a pessoa pede.
2203
+ if (provedorLocal.atual()) return;
2144
2204
  const servidor = state.config.server;
2145
2205
  Promise.resolve()
2146
2206
  .then(() => api.getHealth(servidor, state.config.token))
package/lib/api.js CHANGED
@@ -201,14 +201,28 @@ function requestStream(serverUrl, endpoint, payload, onEvent, token, sinal) {
201
201
  });
202
202
  }
203
203
 
204
+ /* Com um provedor configurado (/api), estas quatro rotas rodam AQUI,
205
+ com a chave da pessoa, em vez de ir ao servidor — ver lib/local/motor.js.
206
+ O resto (login, transcrição, conectores) continua no servidor. */
207
+ function local() {
208
+ try {
209
+ const motor = require('./local/motor.js');
210
+ return motor.ativo() ? motor : null;
211
+ } catch { return null; }
212
+ }
213
+
204
214
  // Agente autônomo. `payload` carrega a conversa completa (messages).
205
215
  function requestAct(serverUrl, payload, onEvent) {
216
+ const m = local();
217
+ if (m) return m.requestAct(serverUrl, payload, onEvent);
206
218
  const { token, sinal, ...body } = payload;
207
219
  return requestStream(serverUrl, '/api/act/stream', body, onEvent, token, sinal);
208
220
  }
209
221
 
210
222
  // Chat puro.
211
223
  function requestChat(serverUrl, payload, onEvent) {
224
+ const m = local();
225
+ if (m) return m.requestChat(serverUrl, payload, onEvent);
212
226
  const { token, sinal, ...body } = payload;
213
227
  return requestStream(serverUrl, '/api/chat/stream', body, onEvent, token, sinal);
214
228
  }
@@ -216,6 +230,8 @@ function requestChat(serverUrl, payload, onEvent) {
216
230
  // Resumo incremental da conversa. Vai e volta pequeno: manda o resumo de
217
231
  // antes + o que é novo, recebe o resumo atualizado.
218
232
  function requestResumo(serverUrl, payload) {
233
+ const m = local();
234
+ if (m) return m.requestResumo(serverUrl, payload);
219
235
  const { token, ...body } = payload;
220
236
  return request(serverUrl, '/api/resumir', body, token);
221
237
  }
@@ -236,6 +252,8 @@ function requestRefino(serverUrl, payload) {
236
252
 
237
253
  // O plano: a tarefa grande dividida em passos, para uma chamada por pedaço.
238
254
  function requestPlano(serverUrl, payload) {
255
+ const m = local();
256
+ if (m) return m.requestPlano(serverUrl, payload);
239
257
  const { token, ...body } = payload;
240
258
  return request(serverUrl, '/api/plano', body, token);
241
259
  }