primocode 9.8.0-beta.7 → 9.8.0-beta.9
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 +6 -5
- package/app/app.css +482 -281
- package/app/app.js +888 -315
- package/app/index.html +176 -101
- package/app/primo-marca.js +58 -1
- package/bin/primocode.js +99 -39
- package/lib/act.js +5 -0
- package/lib/app.js +297 -89
- package/lib/fala.js +95 -1
- package/lib/offline.js +168 -0
- package/lib/{ide.js → painel.js} +41 -87
- package/package.json +2 -3
- package/skills/design-de-verdade/SKILL.md +2 -0
- package/skills/motion-de-primeira/SKILL.md +1 -1
- package/skills/site-com-motion/SKILL.md +39 -0
- package/app/ide.css +0 -345
- package/app/ide.html +0 -183
- package/app/ide.js +0 -639
- package/kits/acolhedor/index.html +0 -130
- package/kits/acolhedor/script.js +0 -25
- package/kits/acolhedor/style.css +0 -146
- package/kits/editorial/index.html +0 -115
- package/kits/editorial/script.js +0 -19
- package/kits/editorial/style.css +0 -112
- package/kits/landing/index.html +0 -162
- package/kits/landing/script.js +0 -199
- package/kits/landing/style.css +0 -219
- package/kits/premium/index.html +0 -83
- package/kits/premium/script.js +0 -11
- package/kits/premium/style.css +0 -87
- package/kits/vitrine/index.html +0 -108
- package/kits/vitrine/script.js +0 -20
- package/kits/vitrine/style.css +0 -117
- package/lib/kits.js +0 -206
package/bin/primocode.js
CHANGED
|
@@ -21,7 +21,6 @@ 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 kits = require('../lib/kits');
|
|
25
24
|
const provedorLocal = require('../lib/local/provedor');
|
|
26
25
|
const { runAct, interromper, ehTetoDeToken, ehTransitorio, esperaDoErro } = require('../lib/act');
|
|
27
26
|
const fala = require('../lib/fala');
|
|
@@ -44,6 +43,7 @@ const nuvem = require('../lib/nuvem');
|
|
|
44
43
|
const compat = require('../lib/compatibilidade');
|
|
45
44
|
const mencoesLib = require('../lib/mencoes');
|
|
46
45
|
const extra = require('../lib/comandos-extra');
|
|
46
|
+
const offline = require('../lib/offline');
|
|
47
47
|
|
|
48
48
|
/* O /custo: toda conversa com o modelo passa pelo lib/api.js, e o contador
|
|
49
49
|
embrulha as funções dele uma vez, aqui. Quem atende é o provedor do /api
|
|
@@ -108,10 +108,10 @@ const state = {
|
|
|
108
108
|
// existe para a frente em vez de subir outro servidor ao lado.
|
|
109
109
|
let janelaAberta = null;
|
|
110
110
|
|
|
111
|
-
/* O que
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
/* O que o /rc, o /ide e o `primo --app` passam ao lib/app.js. Uma função
|
|
112
|
+
só porque os três sobem a MESMA janela: se cada um montasse a sua, o /ide
|
|
113
|
+
aberto depois do /rc acharia um servidor sem a pasta do projeto — e o
|
|
114
|
+
painel da prévia nasceria vazio. */
|
|
115
115
|
function opcoesDaJanela(extra) {
|
|
116
116
|
return Object.assign({
|
|
117
117
|
versao: VERSION,
|
|
@@ -123,10 +123,37 @@ function opcoesDaJanela(extra) {
|
|
|
123
123
|
projeto: state.currentProject,
|
|
124
124
|
modo: state.mode,
|
|
125
125
|
esforco: state.config.effort,
|
|
126
|
+
permissoes: perigo.normalizarModo(state.config.permissoes),
|
|
127
|
+
voz: !!vozEmEspera,
|
|
128
|
+
usuario: state.config.userName || null,
|
|
129
|
+
mascote: state.config.mascot || 'primo',
|
|
130
|
+
anexos: state.attachedFiles.map((f) => f.path),
|
|
126
131
|
}),
|
|
127
132
|
// Lida na hora, não guardada: o /dir e o /project trocam a pasta
|
|
128
|
-
// com a
|
|
133
|
+
// com a janela aberta, e o painel tem de acompanhar.
|
|
129
134
|
pastaProjeto: () => projectDir(),
|
|
135
|
+
/* As ações da janela passam pelos MESMOS caminhos dos comandos — o
|
|
136
|
+
/projetos, o /clear, o /permissoes, o /effort, o /attach — para a
|
|
137
|
+
janela e o terminal nunca discordarem sobre onde se está. */
|
|
138
|
+
entrarProjeto: (dir) => {
|
|
139
|
+
entrarNoProjeto(dir, 'projeto');
|
|
140
|
+
console.log(c.ok(` Trabalhando em ${state.currentProject}`) + c.muted(` (${shortPath(dir)})`));
|
|
141
|
+
refreshPrompt();
|
|
142
|
+
},
|
|
143
|
+
novoChat: () => handleSlash('/clear'),
|
|
144
|
+
mudarPermissoes: (m) => handleSlash('/permissoes ' + m),
|
|
145
|
+
mudarEsforco: (n) => aplicarEffort(n),
|
|
146
|
+
anexar: (f) => {
|
|
147
|
+
state.attachedFiles.push(f);
|
|
148
|
+
console.log(c.ok(`Anexado: ${f.path} (${tools.fmtBytes(f.content.length)})`) + c.muted(' pela janela'));
|
|
149
|
+
refreshPrompt();
|
|
150
|
+
},
|
|
151
|
+
desanexar: (nome) => {
|
|
152
|
+
state.attachedFiles = nome ? state.attachedFiles.filter((f) => f.path !== nome) : [];
|
|
153
|
+
refreshPrompt();
|
|
154
|
+
},
|
|
155
|
+
voz: (ligar) => handleSlash(ligar ? '/voice' : '/voice parar'),
|
|
156
|
+
continuarConversa: (alvo) => abrirConversa(alvo),
|
|
130
157
|
}, extra || {});
|
|
131
158
|
}
|
|
132
159
|
|
|
@@ -153,12 +180,13 @@ ${c.bold('Comandos')}
|
|
|
153
180
|
${c.brand('/copiar')} copia a última resposta
|
|
154
181
|
${c.brand('/historico')} [termo] procura nos seus pedidos antigos
|
|
155
182
|
${c.brand('/rc')} abre a janela do PrimoCode (chat, sem terminal)
|
|
156
|
-
${c.brand('/ide')} abre a
|
|
183
|
+
${c.brand('/ide')} abre a janela com a prévia do site e os arquivos ao lado
|
|
157
184
|
${c.brand('/login')} entra na sua conta · ${c.brand('/account')} mostra o plano
|
|
158
185
|
${c.brand('/chats')} [n] suas conversas — as mesmas do app e do celular
|
|
159
186
|
${c.brand('/logout')} desconecta este computador da conta
|
|
160
187
|
${c.brand('/comandos')} seus comandos (/revisar, /explicar…) · /comandos novo <nome>
|
|
161
188
|
${c.brand('/status')} checa o servidor
|
|
189
|
+
${c.brand('/offline')} roda o servidor no seu computador (temporário) · /offline off volta
|
|
162
190
|
${c.brand('/doctor')} confere tudo (servidor, /api, Chrome, disco…) e diz como consertar
|
|
163
191
|
${c.brand('/custo')} tokens e pedidos desta sessão, por provedor
|
|
164
192
|
${c.brand('/atalhos')} as teclas que ajudam
|
|
@@ -210,7 +238,7 @@ const SLASH_COMANDOS = [
|
|
|
210
238
|
['/copiar', 'copia a última resposta'],
|
|
211
239
|
['/historico', 'procura nos seus pedidos antigos: /historico login'],
|
|
212
240
|
['/rc', 'abre a janela do PrimoCode'],
|
|
213
|
-
['/ide', 'abre a
|
|
241
|
+
['/ide', 'abre a janela com a prévia do site e os arquivos ao lado'],
|
|
214
242
|
['/login', 'entra na sua conta Conecta Primo'],
|
|
215
243
|
['/account', 'sua conta, plano e computadores conectados'],
|
|
216
244
|
['/chats', 'suas conversas salvas, as mesmas do app e do celular'],
|
|
@@ -224,6 +252,7 @@ const SLASH_COMANDOS = [
|
|
|
224
252
|
['/comandos', 'seus comandos: um .md vira /nome · /comandos novo <nome>'],
|
|
225
253
|
['/api', 'usa a SUA chave (Gemini, ChatGPT, Claude…) direto daqui, sem o limite do servidor'],
|
|
226
254
|
['/status', 'checa o servidor'],
|
|
255
|
+
['/offline', 'roda o servidor aqui no seu computador, com as chaves do Render'],
|
|
227
256
|
['/doctor', 'confere se está tudo certo e diz como consertar'],
|
|
228
257
|
['/custo', 'tokens e pedidos desta sessão, por provedor'],
|
|
229
258
|
['/atalhos', 'as teclas que ajudam'],
|
|
@@ -742,16 +771,6 @@ function batizarDepois(plan, userText) {
|
|
|
742
771
|
.then((nome) => { if (nome) plan.nomeEscolhido = nome; });
|
|
743
772
|
}
|
|
744
773
|
|
|
745
|
-
/* A inspiração visual (lib/kits.js): pedido de landing leva a ficha da
|
|
746
|
-
direção que combina com o negócio. Nada é copiado para a pasta — o agente
|
|
747
|
-
escreve a página dele, inspirado, e não troca texto num molde. */
|
|
748
|
-
async function prepararKit(plan, userText) {
|
|
749
|
-
const kit = kits.escolher(userText, { provedor: provedorLocal.atual() });
|
|
750
|
-
if (!kit) return '';
|
|
751
|
-
console.log(c.dim(` ⎿ inspiração visual: ${kit.titulo}`));
|
|
752
|
-
return kit.instrucao;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
774
|
function createPendingProject(plan) {
|
|
756
775
|
const dir = cfg.createProjectDir(plan.nomeEscolhido || plan.name);
|
|
757
776
|
entrarNoProjeto(dir, 'projeto');
|
|
@@ -1118,7 +1137,6 @@ async function enviarPedido(userText) {
|
|
|
1118
1137
|
tempos.medir('espera do resumo', () => esperarResumo()),
|
|
1119
1138
|
tempos.medir('plano', () => dividirTarefa(prompt)),
|
|
1120
1139
|
]);
|
|
1121
|
-
const instrucaoKit = await prepararKit(plan, userText);
|
|
1122
1140
|
|
|
1123
1141
|
let r = null;
|
|
1124
1142
|
let jaFeito = '';
|
|
@@ -1167,8 +1185,7 @@ async function enviarPedido(userText) {
|
|
|
1167
1185
|
permissoes: state.config.permissoes,
|
|
1168
1186
|
// Pedido de mexer no computador leva a dica que libera as
|
|
1169
1187
|
// ferramentas desktop_* no servidor (ver DICA_COMPUTADOR).
|
|
1170
|
-
instrucao:
|
|
1171
|
-
.filter(Boolean).join('\n\n'),
|
|
1188
|
+
instrucao: i === 0 && querComputador(userText) ? vozModo.DICA_COMPUTADOR : '',
|
|
1172
1189
|
});
|
|
1173
1190
|
|
|
1174
1191
|
r = await rodarPasso();
|
|
@@ -2474,26 +2491,26 @@ async function handleSlash(text) {
|
|
|
2474
2491
|
break;
|
|
2475
2492
|
}
|
|
2476
2493
|
|
|
2477
|
-
/*
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2494
|
+
/* O /ide virou a MESMA janela do /rc, abrindo com a prévia do site
|
|
2495
|
+
à direita. O editor de código saiu ("remove a IDE ou deixa
|
|
2496
|
+
idêntico ao Codex"): quem não programa precisa ver o site
|
|
2497
|
+
nascendo, não de um editor para quebrar o que o agente fez. O
|
|
2498
|
+
comando continua existindo para o dedo viciado não bater em nada. */
|
|
2481
2499
|
case 'ide': {
|
|
2482
|
-
if (janelaAberta
|
|
2483
|
-
await appLib.abrirJanela(janelaAberta.
|
|
2484
|
-
console.log(c.ok('
|
|
2485
|
-
console.log(c.dim(` ${janelaAberta.urlIde}`));
|
|
2500
|
+
if (janelaAberta) {
|
|
2501
|
+
await appLib.abrirJanela(janelaAberta.urlPainel || janelaAberta.url);
|
|
2502
|
+
console.log(c.ok(' Janela com a prévia aberta.') + c.muted(' (no mesmo servidor que já estava de pé)'));
|
|
2486
2503
|
break;
|
|
2487
2504
|
}
|
|
2488
|
-
const sp = new Spinner('abrindo a
|
|
2505
|
+
const sp = new Spinner('abrindo a janela').start();
|
|
2489
2506
|
try {
|
|
2490
2507
|
janelaAberta = await appLib.abrir(opcoesDaJanela({ abrirEm: 'ide' }));
|
|
2491
2508
|
sp.stop();
|
|
2492
2509
|
const j = janelaAberta.janela || {};
|
|
2493
|
-
console.log(c.ok('
|
|
2510
|
+
console.log(c.ok(' Janela aberta, com a prévia do site ao lado.')
|
|
2494
2511
|
+ c.muted(j.modo === 'janela' ? ` (${j.navegador})`
|
|
2495
2512
|
: j.modo === 'aba' ? ' (aba do navegador padrão)' : ''));
|
|
2496
|
-
console.log(c.dim(` ${janelaAberta.
|
|
2513
|
+
console.log(c.dim(` ${janelaAberta.urlPainel}`));
|
|
2497
2514
|
if (j.modo === 'nenhum') {
|
|
2498
2515
|
console.log(c.warn(' Não consegui abrir sozinho — copie o endereço acima no navegador.'));
|
|
2499
2516
|
}
|
|
@@ -2639,6 +2656,45 @@ async function handleSlash(text) {
|
|
|
2639
2656
|
break;
|
|
2640
2657
|
}
|
|
2641
2658
|
|
|
2659
|
+
/* O servidor aqui, no computador da pessoa, até a sessão acabar
|
|
2660
|
+
(lib/offline.js). "Enquanto não tenho acesso ao Railway, eu testo
|
|
2661
|
+
com o projeto rodando localmente." (24/09/2026) */
|
|
2662
|
+
case 'offline': case 'local': {
|
|
2663
|
+
if (/^(off|sair|desligar|parar)$/i.test(rest)) {
|
|
2664
|
+
if (!state.servidorAntes) { console.log(c.muted(' O /offline não está ligado.')); break; }
|
|
2665
|
+
offline.desligar();
|
|
2666
|
+
state.config.server = state.servidorAntes;
|
|
2667
|
+
state.servidorAntes = null;
|
|
2668
|
+
console.log(c.ok(' Voltei ao servidor de antes: ') + c.dim(state.config.server));
|
|
2669
|
+
break;
|
|
2670
|
+
}
|
|
2671
|
+
if (state.servidorAntes) { console.log(c.muted(' Já está no servidor local: ') + c.dim(state.config.server) + c.muted(' · /offline off volta')); break; }
|
|
2672
|
+
const chaves = offline.prepararEnv();
|
|
2673
|
+
if (chaves.criado || !chaves.temChave) {
|
|
2674
|
+
console.log(c.warn(` ${chaves.criado ? 'Criei' : 'Falta chave em'} ${chaves.arquivo}`));
|
|
2675
|
+
console.log(c.muted(' Cole nele as chaves do Render (primocode-server → Environment), uma por linha:'));
|
|
2676
|
+
console.log(c.dim(' GROQ_API_KEY=… OPENROUTER_API_KEY=… GEMINI_API_KEY=…'));
|
|
2677
|
+
console.log(c.muted(' Depois rode ') + c.brand('/offline') + c.muted(' de novo.'));
|
|
2678
|
+
break;
|
|
2679
|
+
}
|
|
2680
|
+
const dir = offline.acharServidor({ pedido: rest });
|
|
2681
|
+
const sp = new Spinner('subindo o servidor aqui').start();
|
|
2682
|
+
const r = await offline.ligar({ dir, env: chaves.env });
|
|
2683
|
+
sp.stop();
|
|
2684
|
+
if (!r.ok) {
|
|
2685
|
+
console.log(c.err(' ' + r.error));
|
|
2686
|
+
if (r.comoResolver) console.log(c.dim(' → ' + r.comoResolver));
|
|
2687
|
+
break;
|
|
2688
|
+
}
|
|
2689
|
+
state.servidorAntes = state.config.server;
|
|
2690
|
+
state.config.server = r.url;
|
|
2691
|
+
console.log(c.ok(` Servidor ${r.versao || ''} ${r.reaproveitado ? 'que já estava de pé' : 'rodando aqui'}: `) + c.dim(r.url));
|
|
2692
|
+
console.log(c.dim(` ${dir || ''}${dir ? ' · ' : ''}chaves: ${chaves.nomes.join(', ')}${r.reaproveitado ? '' : ' · log em ' + offline.ARQUIVO_LOG}`));
|
|
2693
|
+
if (provedorLocal.atual()) console.log(c.warn(' Atenção: o /api está ligado e fala direto com a IA. Para usar o servidor local, /api off.'));
|
|
2694
|
+
console.log(c.muted(' Vale até fechar o PrimoCode. ') + c.brand('/offline off') + c.muted(' volta antes.'));
|
|
2695
|
+
break;
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2642
2698
|
/* Os comandos da pessoa: lista, e explica como fazer o dela. */
|
|
2643
2699
|
case 'comandos': {
|
|
2644
2700
|
const novo = rest.match(/^(novo|new|criar)\s+(\S+)$/i);
|
|
@@ -2819,7 +2875,7 @@ async function main() {
|
|
|
2819
2875
|
return;
|
|
2820
2876
|
}
|
|
2821
2877
|
|
|
2822
|
-
/* `primo ide`: o terminal de sempre, com a
|
|
2878
|
+
/* `primo ide`: o terminal de sempre, com a janela (e a prévia) já aberta.
|
|
2823
2879
|
Só a palavra SOZINHA: `primo ide para gatos` continua sendo um pedido
|
|
2824
2880
|
ao agente (modo um-tiro), como sempre foi. */
|
|
2825
2881
|
const abrirIde = args.length === 1 && args[0].toLowerCase() === 'ide';
|
|
@@ -2840,10 +2896,8 @@ async function main() {
|
|
|
2840
2896
|
// Com -c, o pedido continua a última conversa em vez de chegar sozinho.
|
|
2841
2897
|
if (continuar) await continuarUltima();
|
|
2842
2898
|
/* O MESMO caminho do pedido digitado (sendPrompt). Este modo tinha um
|
|
2843
|
-
runAct próprio, e por isso ficava sem tudo o que vive lá: o
|
|
2844
|
-
|
|
2845
|
-
checkpoint do /desfazer. Medido em 24/09: `primo "crie uma landing…"`
|
|
2846
|
-
não pegou o kit. */
|
|
2899
|
+
runAct próprio, e por isso ficava sem tudo o que vive lá: o plano em
|
|
2900
|
+
passos, a retomada automática, o @arquivo, o checkpoint do /desfazer. */
|
|
2847
2901
|
state.semTerminal = true;
|
|
2848
2902
|
await sendPrompt(promptText);
|
|
2849
2903
|
process.exit(0);
|
|
@@ -2939,7 +2993,13 @@ async function main() {
|
|
|
2939
2993
|
try {
|
|
2940
2994
|
if (ehBash(text)) await rodarBash(text);
|
|
2941
2995
|
else if (isSlash(text)) await handleSlash(text);
|
|
2942
|
-
else
|
|
2996
|
+
else {
|
|
2997
|
+
// Com a janela aberta, o pedido digitado AQUI também vira
|
|
2998
|
+
// balão lá — senão a janela mostraria a resposta de uma
|
|
2999
|
+
// pergunta que ninguém fez.
|
|
3000
|
+
if (janelaAberta && janelaAberta.pedidoDoTerminal) janelaAberta.pedidoDoTerminal(text);
|
|
3001
|
+
await sendPrompt(text);
|
|
3002
|
+
}
|
|
2943
3003
|
} catch (e) {
|
|
2944
3004
|
fala.problema(e);
|
|
2945
3005
|
}
|
|
@@ -3004,7 +3064,7 @@ async function main() {
|
|
|
3004
3064
|
// O que sobrou de adiantado não era resposta de nada: vira comando,
|
|
3005
3065
|
// na ordem em que foi digitado. Nada se perde, só esperou a vez.
|
|
3006
3066
|
segurando = false;
|
|
3007
|
-
// Depois da abertura (e do login dela): a
|
|
3067
|
+
// Depois da abertura (e do login dela): a janela sem conta abriria
|
|
3008
3068
|
// mandando pedidos que o servidor recusa.
|
|
3009
3069
|
if (abrirIde) queue.push('/ide');
|
|
3010
3070
|
while (adiantadas.length) queue.push(adiantadas.shift());
|
package/lib/act.js
CHANGED
|
@@ -138,6 +138,9 @@ function limparParaOModelo(result) {
|
|
|
138
138
|
|
|
139
139
|
function logResult(kind, result, profundidade) {
|
|
140
140
|
const p = fala.resultado(profundidade);
|
|
141
|
+
// A linha da janela (lib/app.js) fecha aqui: o mesmo resultado que o
|
|
142
|
+
// terminal desenha embaixo, resumido. Ver o canal em lib/fala.js.
|
|
143
|
+
fala.avisar('resultado', fala.resumoDoResultado(kind, result));
|
|
141
144
|
if (!result) return;
|
|
142
145
|
// Uma tool que falhou é informação para o AGENTE, que vai consertar na
|
|
143
146
|
// volta seguinte — não é o erro do usuário. Âmbar, uma linha, sem o
|
|
@@ -1131,6 +1134,7 @@ async function runAct(userPrompt, opts) {
|
|
|
1131
1134
|
const porta = await permissao.pedir(portaoControle, tc.name, tc.arguments, autorizar);
|
|
1132
1135
|
if (!porta.ok) {
|
|
1133
1136
|
fala.acao(tc.name, tc.arguments, profundidade);
|
|
1137
|
+
fala.avisar('resultado', { nome: tc.name, ok: false, resumo: 'não autorizado' });
|
|
1134
1138
|
console.log(fala.resultado(profundidade) + c.warn('não autorizado'));
|
|
1135
1139
|
registro.anotar(livro, tc.name, tc.arguments, { ok: false, error: porta.error });
|
|
1136
1140
|
messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify({ ok: false, error: porta.error }) });
|
|
@@ -1148,6 +1152,7 @@ async function runAct(userPrompt, opts) {
|
|
|
1148
1152
|
});
|
|
1149
1153
|
if (!risco.ok) {
|
|
1150
1154
|
fala.acao(tc.name, tc.arguments, profundidade);
|
|
1155
|
+
fala.avisar('resultado', { nome: tc.name, ok: false, resumo: `não autorizado — ${risco.motivo}` });
|
|
1151
1156
|
console.log(fala.resultado(profundidade) + c.warn(`não autorizado — ${risco.motivo}`));
|
|
1152
1157
|
registro.anotar(livro, tc.name, tc.arguments, { ok: false, error: risco.error });
|
|
1153
1158
|
messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify({ ok: false, error: risco.error }) });
|