primocode 9.8.0-beta.2 → 9.8.0-beta.4
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 +20 -5
- package/bin/primocode.js +322 -53
- package/comandos/explicar.md +14 -0
- package/comandos/publicar.md +10 -0
- package/comandos/revisar.md +13 -0
- package/kits/landing/index.html +162 -0
- package/kits/landing/script.js +199 -0
- package/kits/landing/style.css +219 -0
- package/lib/act.js +43 -10
- package/lib/checkpoint.js +134 -0
- package/lib/comandos.js +104 -0
- package/lib/config.js +18 -4
- package/lib/kits.js +95 -0
- package/lib/mencoes.js +98 -0
- package/lib/perigo.js +256 -0
- package/package.json +4 -2
- package/skills/design-de-verdade/LICENSE.txt +177 -0
- package/skills/design-de-verdade/SKILL.md +40 -0
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 kits = require('../lib/kits');
|
|
24
25
|
const provedorLocal = require('../lib/local/provedor');
|
|
25
26
|
const { runAct, interromper, ehTetoDeToken, ehTransitorio, esperaDoErro } = require('../lib/act');
|
|
26
27
|
const fala = require('../lib/fala');
|
|
@@ -31,13 +32,17 @@ const effort = require('../lib/effort');
|
|
|
31
32
|
const memoria = require('../lib/memoria-conversa');
|
|
32
33
|
const registro = require('../lib/registro');
|
|
33
34
|
const permissao = require('../lib/permissao');
|
|
35
|
+
const perigo = require('../lib/perigo');
|
|
34
36
|
const skills = require('../lib/skills');
|
|
37
|
+
const checkpoint = require('../lib/checkpoint');
|
|
38
|
+
const comandosLib = require('../lib/comandos');
|
|
35
39
|
const marketplace = require('../lib/marketplace');
|
|
36
40
|
const vozModo = require('../lib/voz');
|
|
37
41
|
const appLib = require('../lib/app');
|
|
38
42
|
const conta = require('../lib/conta');
|
|
39
43
|
const nuvem = require('../lib/nuvem');
|
|
40
44
|
const compat = require('../lib/compatibilidade');
|
|
45
|
+
const mencoesLib = require('../lib/mencoes');
|
|
41
46
|
|
|
42
47
|
// Rede de segurança de último recurso: sem isto, uma exceção fora de um
|
|
43
48
|
// try/catch (ex.: dentro de um callback de socket, como o parser de SSE) não
|
|
@@ -98,10 +103,13 @@ ${c.bold('Comandos')}
|
|
|
98
103
|
${c.brand('/project')} lista projetos ou cria em ~/primocode: /project <nome>
|
|
99
104
|
${c.brand('/mode')} /mode primocode|chat|auto
|
|
100
105
|
${c.brand('/effort')} esforço do agente — do rápido ao mais esperto
|
|
106
|
+
${c.brand('/permissoes')} quando ele pergunta antes de agir: auto|perguntar|livre
|
|
101
107
|
${c.brand('/compact')} resume a conversa e devolve o contexto
|
|
102
|
-
${c.brand('/
|
|
108
|
+
${c.brand('/init')} estuda o projeto e escreve o PRIMO.md
|
|
109
|
+
${c.brand('/attach')} <arquivo> anexa arquivo ao próximo pedido · ou @arquivo no pedido
|
|
103
110
|
${c.brand('/files')} lista anexos ${c.brand('/detach')} remove
|
|
104
111
|
${c.brand('/all')} lista tudo que ele sabe fazer
|
|
112
|
+
${c.brand('/desfazer')} [lista|N] desfaz o que o último pedido (ou os N últimos) mudou
|
|
105
113
|
${c.brand('/tree')} [n] árvore do projeto
|
|
106
114
|
${c.brand('/read')} <arquivo> mostra um arquivo
|
|
107
115
|
${c.brand('/run')} <comando> executa no shell do projeto
|
|
@@ -110,6 +118,7 @@ ${c.bold('Comandos')}
|
|
|
110
118
|
${c.brand('/login')} entra na sua conta · ${c.brand('/account')} mostra o plano
|
|
111
119
|
${c.brand('/chats')} [n] suas conversas — as mesmas do app e do celular
|
|
112
120
|
${c.brand('/logout')} desconecta este computador da conta
|
|
121
|
+
${c.brand('/comandos')} seus comandos (/revisar, /explicar…) · /comandos novo <nome>
|
|
113
122
|
${c.brand('/status')} checa o servidor
|
|
114
123
|
${c.brand('/name')} <nome> como o Primo te chama
|
|
115
124
|
${c.brand('/mascot')} <estilo> mascote: primo|fox|cat
|
|
@@ -141,11 +150,14 @@ const SLASH_COMANDOS = [
|
|
|
141
150
|
['/dir', 'trabalhar numa pasta sua'],
|
|
142
151
|
['/mode', 'primocode | chat | auto'],
|
|
143
152
|
['/effort', 'esforço: low → ultracode (←/→ para ajustar)'],
|
|
153
|
+
['/permissoes', 'auto | perguntar | livre — quando ele pede licença'],
|
|
144
154
|
['/compact', 'resume a conversa e devolve o contexto'],
|
|
155
|
+
['/init', 'estuda o projeto e escreve o PRIMO.md (as instruções dele)'],
|
|
145
156
|
['/attach', 'anexa arquivo ao pedido'],
|
|
146
157
|
['/detach', 'remove os anexos'],
|
|
147
158
|
['/files', 'lista os anexos'],
|
|
148
159
|
['/all', 'tudo que ele sabe fazer'],
|
|
160
|
+
['/desfazer', 'desfaz o que o último pedido mudou nos arquivos (lista · N)'],
|
|
149
161
|
['/tree', 'árvore do projeto'],
|
|
150
162
|
['/read', 'mostra um arquivo'],
|
|
151
163
|
['/run', 'roda um comando'],
|
|
@@ -161,6 +173,7 @@ const SLASH_COMANDOS = [
|
|
|
161
173
|
['/skills', 'o que ele sabe fazer bem — ligue e desligue'],
|
|
162
174
|
['/marketplace', 'a tela das skills: navegue, ligue, crie'],
|
|
163
175
|
['/install-skill', 'instala uma skill de um arquivo'],
|
|
176
|
+
['/comandos', 'seus comandos: um .md vira /nome · /comandos novo <nome>'],
|
|
164
177
|
['/api', 'usa a SUA chave (Gemini, ChatGPT, Claude…) direto daqui, sem o limite do servidor'],
|
|
165
178
|
['/status', 'checa o servidor'],
|
|
166
179
|
['/connectors', 'quem está atendendo os seus pedidos'],
|
|
@@ -312,9 +325,34 @@ function aplicarEffort(nome) {
|
|
|
312
325
|
// Enquanto o slider está no ar, o fantasma do autocompletar não desenha.
|
|
313
326
|
let sliderAtivo = false;
|
|
314
327
|
|
|
328
|
+
/* Os comandos da pessoa (lib/comandos.js), com o do projeto atual por cima.
|
|
329
|
+
A caixa de digitar pede a lista a cada tecla para desenhar o fantasma; um
|
|
330
|
+
cache de dois segundos poupa ler três pastas por letra digitada, e ainda
|
|
331
|
+
pega na hora um arquivo que a pessoa acabou de salvar. */
|
|
332
|
+
let cacheComandos = { em: 0, dir: null, lista: [] };
|
|
333
|
+
function comandosDaPessoa() {
|
|
334
|
+
const agora = Date.now();
|
|
335
|
+
if (agora - cacheComandos.em > 2000 || cacheComandos.dir !== state.workDir) {
|
|
336
|
+
let lista = [];
|
|
337
|
+
try { lista = comandosLib.carregar({ projectDir: state.workDir }); } catch { /* disco estranho não trava a caixa */ }
|
|
338
|
+
cacheComandos = { em: agora, dir: state.workDir, lista };
|
|
339
|
+
}
|
|
340
|
+
return cacheComandos.lista;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/* O menu e o fantasma enxergam os dois tipos. O embutido vem primeiro e,
|
|
344
|
+
com o mesmo nome, o da pessoa nem aparece: quem ganha é sempre o embutido. */
|
|
345
|
+
function comandosDoMenu() {
|
|
346
|
+
const embutidos = new Set(SLASH_COMANDOS.map(([n]) => n));
|
|
347
|
+
const deles = comandosDaPessoa()
|
|
348
|
+
.filter((x) => !embutidos.has('/' + x.nome))
|
|
349
|
+
.map((x) => ['/' + x.nome, x.descricao || `seu comando (${x.origem})`]);
|
|
350
|
+
return SLASH_COMANDOS.concat(deles);
|
|
351
|
+
}
|
|
352
|
+
|
|
315
353
|
function mostrarMenuSlash() {
|
|
316
354
|
console.log('');
|
|
317
|
-
for (const [nome, desc] of
|
|
355
|
+
for (const [nome, desc] of comandosDoMenu()) {
|
|
318
356
|
console.log(` ${c.brand(ui.padRight(nome, 12))}${c.muted(desc)}`);
|
|
319
357
|
}
|
|
320
358
|
console.log(c.dim('\n Tab completa · metade do comando já serve: /eff vira /effort'));
|
|
@@ -554,14 +592,18 @@ const RE_PROJETO_NOVO = /\b(novo projeto|projeto novo|outro projeto|do zero|come
|
|
|
554
592
|
const RE_CRIAR_UM = /\b(crie|cria|criar|fa[cç]a|faz|fazer|monte|monta|montar|gere|gera|desenvolva|construa|programe|quero|preciso de)\s+(um|uma|outro|outra|novo|nova)\b/;
|
|
555
593
|
const RE_REFERE_ATUAL = /\b(isso|isto|esse|essa|este|esta|nele|nela|dele|dela|no projeto|nesse|nessa|neste|nesta|continua|continue|edita|edite|muda|mude|melhora|melhore|adiciona|adicione|coloca|coloque|p[oõ]e|corrige|corrija|arruma|arrume|ajusta|ajuste)\b/;
|
|
556
594
|
|
|
595
|
+
/* "crie uma nova seção", "cria um botão", "faz uma tela de login": é um
|
|
596
|
+
PEDAÇO do projeto em que se está, não outro projeto. */
|
|
597
|
+
const RE_PEDACO = /\b(um|uma|outro|outra|novo|nova)\s+(nov[ao]\s+)?(secao|secoes|parte|botao|botoes|tela|aba|menu|rodape|cabecalho|formulario|fase|funcao|funcionalidade|campo|imagem|animacao|efeito|cor|fonte|pagina interna|area|bloco|card|modal|link)\b/;
|
|
598
|
+
|
|
557
599
|
function pedeProjetoNovo(texto) {
|
|
558
600
|
const t = semAcento(texto);
|
|
559
601
|
if (RE_PROJETO_NOVO.test(t)) return true;
|
|
560
|
-
if (!RE_CRIAR_UM.test(t) || RE_REFERE_ATUAL.test(t)) return false;
|
|
602
|
+
if (!RE_CRIAR_UM.test(t) || RE_REFERE_ATUAL.test(t) || RE_PEDACO.test(t)) return false;
|
|
561
603
|
// Mesmo assunto do projeto atual ("crie uma fase nova pro jogo de carro")
|
|
562
604
|
// é continuação, não projeto novo.
|
|
563
|
-
const atual = new Set(cfg.
|
|
564
|
-
const pedido = cfg.
|
|
605
|
+
const atual = new Set(cfg.tokensDoAssunto(state.currentProject || ''));
|
|
606
|
+
const pedido = cfg.tokensDoAssunto(deriveProjectName(texto));
|
|
565
607
|
return !pedido.some((x) => atual.has(x));
|
|
566
608
|
}
|
|
567
609
|
|
|
@@ -642,10 +684,30 @@ function batizarDepois(plan, userText) {
|
|
|
642
684
|
}, (evt) => { if (evt.delta) resposta += evt.delta; });
|
|
643
685
|
return resposta;
|
|
644
686
|
};
|
|
645
|
-
nomeProjeto.escolher({ pedido: userText, nomes: cfg.listProjects(), perguntar })
|
|
687
|
+
plan.batizando = nomeProjeto.escolher({ pedido: userText, nomes: cfg.listProjects(), perguntar })
|
|
646
688
|
.then((nome) => { if (nome) plan.nomeEscolhido = nome; });
|
|
647
689
|
}
|
|
648
690
|
|
|
691
|
+
/* O kit de partida (lib/kits.js): pedido de landing numa pasta nova começa
|
|
692
|
+
de uma página pronta, e o agente só adapta. A pasta nasce AGORA, e não na
|
|
693
|
+
primeira escrita, porque o kit precisa estar lá antes de o agente ler. */
|
|
694
|
+
async function prepararKit(plan, userText) {
|
|
695
|
+
const kit = kits.escolher(userText, { provedor: provedorLocal.atual() });
|
|
696
|
+
if (!kit) return '';
|
|
697
|
+
let dir = plan.dir;
|
|
698
|
+
if (plan.pending) {
|
|
699
|
+
await Promise.race([plan.batizando, new Promise((r) => setTimeout(r, 8000))]);
|
|
700
|
+
dir = createPendingProject(plan);
|
|
701
|
+
plan.pending = false;
|
|
702
|
+
plan.dir = dir;
|
|
703
|
+
}
|
|
704
|
+
if (!dir || path.resolve(dir) === path.resolve(cfg.ROOT_DIR)) return '';
|
|
705
|
+
const copiados = kits.aplicar(kit, dir);
|
|
706
|
+
if (!copiados.length) return '';
|
|
707
|
+
console.log(c.dim(` ⎿ começando do kit de ${kit.nome} (${copiados.join(', ')}) — o agente só adapta`));
|
|
708
|
+
return kit.instrucao;
|
|
709
|
+
}
|
|
710
|
+
|
|
649
711
|
function createPendingProject(plan) {
|
|
650
712
|
const dir = cfg.createProjectDir(plan.nomeEscolhido || plan.name);
|
|
651
713
|
entrarNoProjeto(dir, 'projeto');
|
|
@@ -726,8 +788,15 @@ function prepararPasta(userText) {
|
|
|
726
788
|
* Enter sozinho é NÃO. O padrão de quem apertou Enter sem ler não pode ser
|
|
727
789
|
* entregar o mouse e o teclado.
|
|
728
790
|
*/
|
|
729
|
-
async function autorizarControle(oQueVaiFazer, { primeira } = {}) {
|
|
730
|
-
|
|
791
|
+
async function autorizarControle(oQueVaiFazer, { primeira, tipo } = {}) {
|
|
792
|
+
/* O mesmo portão serve ao /permissoes (lib/perigo.js): a pergunta muda de
|
|
793
|
+
cabeçalho, as três respostas são as mesmas. */
|
|
794
|
+
if (tipo) {
|
|
795
|
+
console.log('');
|
|
796
|
+
console.log(' ' + c.warn('▸▸') + ' ' + c.bold(c.white(tipo === 'perigo'
|
|
797
|
+
? 'Isto não tem volta. O Primo quer:'
|
|
798
|
+
: 'O Primo quer (você escolheu /permissoes perguntar):')));
|
|
799
|
+
} else if (primeira) {
|
|
731
800
|
console.log('');
|
|
732
801
|
console.log(' ' + c.warn('▸▸') + ' ' + c.bold(c.white('O Primo quer usar o seu computador.')));
|
|
733
802
|
console.log(' ' + c.muted('Ele vai mexer no mouse e no teclado de verdade, no programa que'));
|
|
@@ -758,7 +827,15 @@ async function autorizarControle(oQueVaiFazer, { primeira } = {}) {
|
|
|
758
827
|
* simplesmente travava. Abrir o /voice e PEDIR em voz para ele mexer no
|
|
759
828
|
* computador já é a autorização — é o produto funcionando como o Jarvis. Fica
|
|
760
829
|
* dito no terminal, a cada ação, o que ele fez; e fechar o PrimoCode esquece. */
|
|
761
|
-
async function autorizarPorVoz(oQueVaiFazer, { primeira } = {}) {
|
|
830
|
+
async function autorizarPorVoz(oQueVaiFazer, { primeira, tipo } = {}) {
|
|
831
|
+
/* Pedir em voz é autorizar o que foi pedido — mas não o que não tem volta.
|
|
832
|
+
Ninguém está lendo o terminal para ver um `rm -rf` passar: o perigoso
|
|
833
|
+
é recusado, e o agente conta em voz o que ficou por fazer. */
|
|
834
|
+
if (tipo === 'perigo') {
|
|
835
|
+
console.log(' ' + c.warn('▸▸ recusado no modo voz: ') + c.muted(oQueVaiFazer));
|
|
836
|
+
return 'nao';
|
|
837
|
+
}
|
|
838
|
+
if (tipo) return 'agora';
|
|
762
839
|
if (primeira) {
|
|
763
840
|
console.log(' ' + c.warn('▸▸') + ' ' + c.muted('Modo voz: o Primo vai usar o mouse e o teclado. Você vê o cursor dele na tela.'));
|
|
764
841
|
}
|
|
@@ -936,8 +1013,12 @@ async function sendPrompt(userText) {
|
|
|
936
1013
|
}
|
|
937
1014
|
|
|
938
1015
|
let prompt = userText;
|
|
939
|
-
|
|
940
|
-
|
|
1016
|
+
// "@index.html" no pedido é um /attach só deste pedido: vai junto dos
|
|
1017
|
+
// anexos, mas não fica em state.attachedFiles para o próximo.
|
|
1018
|
+
const citados = mencoesDoPedido(userText);
|
|
1019
|
+
const anexos = [...state.attachedFiles, ...citados];
|
|
1020
|
+
if (anexos.length) {
|
|
1021
|
+
const parts = anexos.map((f) => `Arquivo: ${f.path}\n\`\`\`\n${f.content}\n\`\`\``);
|
|
941
1022
|
prompt = parts.join('\n\n') + '\n\n' + userText;
|
|
942
1023
|
}
|
|
943
1024
|
|
|
@@ -976,6 +1057,7 @@ async function sendPrompt(userText) {
|
|
|
976
1057
|
// num plano limitado por REQUISIÇÕES (o grátis da Groq são 30 por
|
|
977
1058
|
// minuto) cada chamada extra custa tanto quanto token.
|
|
978
1059
|
const passos = await dividirTarefa(prompt);
|
|
1060
|
+
const instrucaoKit = await prepararKit(plan, userText);
|
|
979
1061
|
|
|
980
1062
|
let r = null;
|
|
981
1063
|
let jaFeito = '';
|
|
@@ -984,6 +1066,8 @@ async function sendPrompt(userText) {
|
|
|
984
1066
|
interessa é o do pedido inteiro: quem pediu um vídeo não quer três
|
|
985
1067
|
balanços, quer saber o que existe no disco quando ele terminar. */
|
|
986
1068
|
const livro = registro.novo();
|
|
1069
|
+
// Um checkpoint por PEDIDO (não por passo): é o que o /desfazer volta.
|
|
1070
|
+
checkpoint.iniciar(userText);
|
|
987
1071
|
for (let i = 0; i < passos.length; i++) {
|
|
988
1072
|
if (i > 0) console.log(c.dim(` ⎿ ${passos[i].titulo}`));
|
|
989
1073
|
// Só fato de passo CONCLUÍDO entra aqui. Num pedido real o passo de
|
|
@@ -1013,11 +1097,15 @@ async function sendPrompt(userText) {
|
|
|
1013
1097
|
ensureWorkDir: plan.pending ? () => createPendingProject(plan) : null,
|
|
1014
1098
|
// O portão do computador: quem pergunta é o terminal, quem
|
|
1015
1099
|
// lembra da resposta é a sessão.
|
|
1016
|
-
|
|
1100
|
+
// No pedido único (`primo "…"`) não há ninguém para responder:
|
|
1101
|
+
// sem `autorizar`, o que pede licença é recusado (lib/perigo.js).
|
|
1102
|
+
autorizar: state.semTerminal ? undefined : autorizarControle,
|
|
1017
1103
|
controle: state.controle,
|
|
1104
|
+
permissoes: state.config.permissoes,
|
|
1018
1105
|
// Pedido de mexer no computador leva a dica que libera as
|
|
1019
1106
|
// ferramentas desktop_* no servidor (ver DICA_COMPUTADOR).
|
|
1020
|
-
instrucao: i === 0 && querComputador(userText) ? vozModo.DICA_COMPUTADOR : ''
|
|
1107
|
+
instrucao: [instrucaoKit, i === 0 && querComputador(userText) ? vozModo.DICA_COMPUTADOR : '']
|
|
1108
|
+
.filter(Boolean).join('\n\n'),
|
|
1021
1109
|
});
|
|
1022
1110
|
|
|
1023
1111
|
r = await rodarPasso();
|
|
@@ -1058,6 +1146,7 @@ async function sendPrompt(userText) {
|
|
|
1058
1146
|
: '';
|
|
1059
1147
|
if (r.parado) break; // Esc Esc no meio do plano para o plano inteiro
|
|
1060
1148
|
}
|
|
1149
|
+
checkpoint.encerrar();
|
|
1061
1150
|
|
|
1062
1151
|
/* O balanço, depois da resposta. Depois, e não antes, de propósito: o
|
|
1063
1152
|
que a pessoa quer ler primeiro é o que ele TEM A DIZER; a lista de
|
|
@@ -1251,6 +1340,31 @@ async function esperarResumo(tetoMs = 15000) {
|
|
|
1251
1340
|
/* A linha que vai junto do resumo: em que projeto se está. É o que faz
|
|
1252
1341
|
"continua o jogo" e "muda a cor" caírem no mesmo projeto em vez de inventar
|
|
1253
1342
|
outro. */
|
|
1343
|
+
function mencoesDoPedido(userText) {
|
|
1344
|
+
const { arquivos, ignorados } = mencoesLib.mencoes(userText, projectDir());
|
|
1345
|
+
const lidos = [];
|
|
1346
|
+
for (const a of arquivos) {
|
|
1347
|
+
try {
|
|
1348
|
+
lidos.push({ path: a.rel, content: fs.readFileSync(a.abs, 'utf-8') });
|
|
1349
|
+
console.log(c.dim(` ⎿ anexado: ${a.rel} (${tools.fmtBytes(a.bytes)})`));
|
|
1350
|
+
} catch { console.log(c.dim(` ⎿ não consegui ler: ${a.rel}`)); }
|
|
1351
|
+
}
|
|
1352
|
+
for (const i of ignorados) console.log(c.dim(` ⎿ não anexei ${i.rel}: ${i.motivo}`));
|
|
1353
|
+
return lidos;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
/* O pedido do /init. Passa pelo MESMO caminho de um pedido digitado
|
|
1357
|
+
(sendPrompt → runAct), então usa as ferramentas e o provedor de agora — um
|
|
1358
|
+
/init que falasse direto com o modelo não enxergaria os arquivos. */
|
|
1359
|
+
const PEDIDO_INIT = 'Estude este projeto (a pasta atual) e escreva um arquivo PRIMO.md na raiz dela. '
|
|
1360
|
+
+ 'Primeiro olhe de verdade: liste os arquivos, leia o README, o package.json (ou o equivalente) e os arquivos principais. '
|
|
1361
|
+
+ 'Depois escreva o PRIMO.md em português, curto (menos de 60 linhas), com estas seções: '
|
|
1362
|
+
+ 'o que é o projeto (2 ou 3 frases); como rodar (os comandos exatos que você viu, de instalar, rodar e testar); '
|
|
1363
|
+
+ 'a stack (linguagem, frameworks, banco); as convenções que o código segue (nomes, estilo, onde ficam os testes); '
|
|
1364
|
+
+ 'e onde está cada coisa (as pastas e arquivos que importam, uma linha cada). '
|
|
1365
|
+
+ 'Só escreva o que você conferiu nos arquivos — nada de inventar comando ou dependência. '
|
|
1366
|
+
+ 'Esse arquivo vai ser lido no começo de todo pedido neste projeto, então cada linha precisa valer o espaço.';
|
|
1367
|
+
|
|
1254
1368
|
function contextoDoProjeto() {
|
|
1255
1369
|
if (!state.currentProject) return '';
|
|
1256
1370
|
return `Projeto atual: ${state.currentProject} (pasta ${shortPath(projectDir())}). `
|
|
@@ -1258,6 +1372,14 @@ function contextoDoProjeto() {
|
|
|
1258
1372
|
+ 'trabalhe nos arquivos que já existem lá, não crie outro projeto.';
|
|
1259
1373
|
}
|
|
1260
1374
|
|
|
1375
|
+
/* O chat não passa pelo runAct, e é no runAct (buildMemoryContext) que as
|
|
1376
|
+
instruções do projeto entram. Sem isto o PRIMO.md valeria para o agente e
|
|
1377
|
+
seria ignorado quando o modo auto manda o pedido para a conversa. */
|
|
1378
|
+
function comInstrucoes(history) {
|
|
1379
|
+
const texto = mencoesLib.instrucoesDoProjeto(state.currentProject || state.workDir ? projectDir() : null);
|
|
1380
|
+
return texto ? [{ role: 'system', content: texto }, ...history] : history;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1261
1383
|
// O chat também precisa parar no Esc Esc. Sem isto o texto continuava saindo
|
|
1262
1384
|
// na tela depois de o usuário ter mandado parar.
|
|
1263
1385
|
let chatEmVoo = null;
|
|
@@ -1284,7 +1406,7 @@ async function runChat(promptForModel, userTextForMemory) {
|
|
|
1284
1406
|
try {
|
|
1285
1407
|
await api.requestChat(state.config.server, {
|
|
1286
1408
|
prompt: promptForModel,
|
|
1287
|
-
history: memoria.paraEnviar(state.memoria, state.history, contextoDoProjeto()),
|
|
1409
|
+
history: comInstrucoes(memoria.paraEnviar(state.memoria, state.history, contextoDoProjeto())),
|
|
1288
1410
|
model: state.config.model,
|
|
1289
1411
|
token: state.config.token,
|
|
1290
1412
|
sinal: chatEmVoo.signal,
|
|
@@ -1411,6 +1533,49 @@ const RENOMEADOS = {
|
|
|
1411
1533
|
tudo: 'all', capacidades: 'all',
|
|
1412
1534
|
};
|
|
1413
1535
|
|
|
1536
|
+
/* Abrir uma conversa salva e seguir dela. Uma função só, para o /chats n e o
|
|
1537
|
+
`primo -c` carregarem a conversa do MESMO jeito: dois caminhos divergiriam
|
|
1538
|
+
no primeiro detalhe (a memória, o gravador) e um deles gravaria errado. */
|
|
1539
|
+
async function abrirConversa(alvo) {
|
|
1540
|
+
const sp = new Spinner('abrindo').start();
|
|
1541
|
+
const conv = await nuvem.lerConversa(alvo.id);
|
|
1542
|
+
sp.stop();
|
|
1543
|
+
if (!conv.ok) return conv;
|
|
1544
|
+
// A memória entra ZERADA: o resumo antigo não veio junto, e um
|
|
1545
|
+
// resumo de outra conversa colado aqui faria o modelo responder
|
|
1546
|
+
// sobre coisa que não foi dita nesta.
|
|
1547
|
+
state.history = conv.mensagens.map((m) => ({
|
|
1548
|
+
role: m.papel, content: m.texto,
|
|
1549
|
+
}));
|
|
1550
|
+
state.memoria = memoria.nova();
|
|
1551
|
+
state.gravador.retomar(alvo.id, alvo.titulo, conv.mensagens.length);
|
|
1552
|
+
return conv;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
/* `primo --continue` / `primo -c`: volta na última conversa, sem passar pelo
|
|
1556
|
+
/chats. A lista do servidor já vem da mais recente para a mais antiga, então
|
|
1557
|
+
a última é a primeira. Sem conta ou sem conversa, começa do zero e diz. */
|
|
1558
|
+
async function continuarUltima() {
|
|
1559
|
+
if (!conta.atual()) {
|
|
1560
|
+
console.log(c.dim(' ⎿ sem conta conectada, não há conversa para continuar — começando do zero'));
|
|
1561
|
+
return false;
|
|
1562
|
+
}
|
|
1563
|
+
const l = await nuvem.listarConversas({ limite: 1 });
|
|
1564
|
+
const alvo = l.ok && l.conversas[0];
|
|
1565
|
+
if (!alvo) {
|
|
1566
|
+
console.log(c.dim(' ⎿ nenhuma conversa salva ainda — começando do zero'));
|
|
1567
|
+
return false;
|
|
1568
|
+
}
|
|
1569
|
+
const conv = await abrirConversa(alvo);
|
|
1570
|
+
if (!conv.ok) {
|
|
1571
|
+
console.log(c.dim(` ⎿ não consegui abrir a última conversa (${conv.error}) — começando do zero`));
|
|
1572
|
+
return false;
|
|
1573
|
+
}
|
|
1574
|
+
const quando = alvo.atualizada ? new Date(alvo.atualizada).toLocaleString('pt-BR') : 'data desconhecida';
|
|
1575
|
+
console.log(c.dim(` ⎿ continuando: ${alvo.titulo.slice(0, 60)} (${quando})`));
|
|
1576
|
+
return true;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1414
1579
|
async function handleSlash(text) {
|
|
1415
1580
|
const [cmd] = text.trim().slice(1).split(/\s+/);
|
|
1416
1581
|
const rest = text.trim().slice(1 + cmd.length).trim();
|
|
@@ -1496,6 +1661,38 @@ async function handleSlash(text) {
|
|
|
1496
1661
|
break;
|
|
1497
1662
|
}
|
|
1498
1663
|
|
|
1664
|
+
/* Quando ele pergunta antes de agir. Os três modos e o porquê de cada
|
|
1665
|
+
um estão em lib/perigo.js. */
|
|
1666
|
+
case 'permissoes':
|
|
1667
|
+
case 'permissions': {
|
|
1668
|
+
const DIZ = {
|
|
1669
|
+
auto: 'roda tudo, mas pergunta antes do que não tem volta (apagar fora do projeto, push --force, sudo…)',
|
|
1670
|
+
perguntar: 'pergunta antes de todo comando e de toda escrita de arquivo',
|
|
1671
|
+
livre: 'nunca pergunta — nem antes do que não tem volta',
|
|
1672
|
+
};
|
|
1673
|
+
const atual = perigo.normalizarModo(state.config.permissoes);
|
|
1674
|
+
if (!rest) {
|
|
1675
|
+
console.log(c.muted(' Agora: ') + c.brand(atual) + c.muted(' — ' + DIZ[atual]));
|
|
1676
|
+
for (const m of perigo.MODOS) console.log(` ${m === atual ? c.brand('● ' + m) : c.muted('○ ' + m)} ${c.dim(DIZ[m])}`);
|
|
1677
|
+
console.log(c.dim(' Use /permissoes auto|perguntar|livre'));
|
|
1678
|
+
break;
|
|
1679
|
+
}
|
|
1680
|
+
const pedido = rest.toLowerCase();
|
|
1681
|
+
if (!perigo.MODOS.includes(perigo.normalizarModo(pedido)) || (perigo.normalizarModo(pedido) === 'auto' && pedido !== 'auto')) {
|
|
1682
|
+
console.log(c.err('Uso: /permissoes auto|perguntar|livre'));
|
|
1683
|
+
break;
|
|
1684
|
+
}
|
|
1685
|
+
const m = perigo.normalizarModo(pedido);
|
|
1686
|
+
state.config.permissoes = m;
|
|
1687
|
+
cfg.saveConfig({ permissoes: m });
|
|
1688
|
+
console.log(c.ok(` Permissões: ${m}`) + c.muted(' — ' + DIZ[m]));
|
|
1689
|
+
if (m === 'livre') {
|
|
1690
|
+
console.log(' ' + c.warn('▸▸ Atenção: ele vai apagar, forçar push e usar sudo sem te perguntar.'));
|
|
1691
|
+
console.log(' ' + c.warn(' O que der errado pode não ter volta. /permissoes auto desfaz.'));
|
|
1692
|
+
}
|
|
1693
|
+
break;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1499
1696
|
case 'mode': {
|
|
1500
1697
|
const m = rest.toLowerCase();
|
|
1501
1698
|
if (['primocode', 'chat', 'auto'].includes(m)) {
|
|
@@ -1556,6 +1753,20 @@ async function handleSlash(text) {
|
|
|
1556
1753
|
break;
|
|
1557
1754
|
}
|
|
1558
1755
|
|
|
1756
|
+
case 'init': {
|
|
1757
|
+
const alvo = path.join(projectDir(), 'PRIMO.md');
|
|
1758
|
+
if (fs.existsSync(alvo) && !(await confirm(`Já existe um PRIMO.md em ${shortPath(projectDir())}. Escrever outro por cima?`))) {
|
|
1759
|
+
console.log(c.muted(' Deixei o PRIMO.md como estava.'));
|
|
1760
|
+
break;
|
|
1761
|
+
}
|
|
1762
|
+
// Escrever arquivo pede o agente: no modo chat (ou num auto que
|
|
1763
|
+
// achasse que é conversa) o /init só responderia um texto.
|
|
1764
|
+
const modoAntes = state.mode;
|
|
1765
|
+
state.mode = 'primocode';
|
|
1766
|
+
try { await sendPrompt(PEDIDO_INIT); } finally { state.mode = modoAntes; }
|
|
1767
|
+
break;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1559
1770
|
case 'detach': state.attachedFiles = []; console.log(c.ok('Anexos removidos.')); refreshPrompt(); break;
|
|
1560
1771
|
|
|
1561
1772
|
case 'files':
|
|
@@ -1581,6 +1792,35 @@ async function handleSlash(text) {
|
|
|
1581
1792
|
break;
|
|
1582
1793
|
}
|
|
1583
1794
|
|
|
1795
|
+
/* ── /desfazer ────────────────────────────────────────────────
|
|
1796
|
+
* Quem não sabe git não tem como voltar uma edição ruim. Aqui volta:
|
|
1797
|
+
* cada pedido guardou o original dos arquivos que mexeu. */
|
|
1798
|
+
case 'desfazer': case 'undo': case 'rewind': {
|
|
1799
|
+
const cps = checkpoint.lista(projectDir());
|
|
1800
|
+
if (!cps.length) { console.log(c.muted(' Nada para desfazer nesta pasta.')); break; }
|
|
1801
|
+
const quando = (cp) => new Date(cp.quando).toLocaleString('pt-BR', { dateStyle: 'short', timeStyle: 'short' });
|
|
1802
|
+
if (/^(lista|list|ls)$/i.test(rest)) {
|
|
1803
|
+
cps.forEach((cp, i) => console.log(` ${c.brand(String(i + 1).padStart(2))} ${c.dim(quando(cp))} `
|
|
1804
|
+
+ `${c.white(cp.pedido.slice(0, 50) + (cp.pedido.length > 50 ? '…' : ''))} ${c.muted(cp.arquivos.length + ' arq.')}`));
|
|
1805
|
+
console.log(c.muted('\n /desfazer N volta os N últimos pedidos.'));
|
|
1806
|
+
break;
|
|
1807
|
+
}
|
|
1808
|
+
const n = rest ? parseInt(rest, 10) : 1;
|
|
1809
|
+
if (!(n >= 1)) { console.log(c.err('Uso: /desfazer · /desfazer lista · /desfazer N')); break; }
|
|
1810
|
+
const alvo = cps.slice(0, n);
|
|
1811
|
+
console.log(c.muted(`\n Volta ${alvo.length === 1 ? 'o último pedido' : `os ${alvo.length} últimos pedidos`}:`));
|
|
1812
|
+
for (const cp of alvo) {
|
|
1813
|
+
console.log(` ${c.dim(quando(cp))} ${c.white(cp.pedido.slice(0, 60))}`);
|
|
1814
|
+
for (const a of cp.arquivos) console.log(c.muted(` ${a.existia ? 'volta ao original' : 'apaga (era novo)'} `) + shortPath(a.caminho));
|
|
1815
|
+
}
|
|
1816
|
+
if (!(await confirm('Desfazer?'))) { console.log(c.muted('Cancelado.')); break; }
|
|
1817
|
+
for (const f of checkpoint.desfazer(projectDir(), n)) {
|
|
1818
|
+
const rotulo = f.acao === 'falhou' ? c.err(` falhou: ${f.erro} `) : c.ok(` ${f.acao} `);
|
|
1819
|
+
console.log(rotulo + shortPath(f.caminho));
|
|
1820
|
+
}
|
|
1821
|
+
break;
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1584
1824
|
case 'tree': console.log(tools.treeDir(projectDir(), parseInt(rest, 10) || 2)); break;
|
|
1585
1825
|
|
|
1586
1826
|
case 'read': {
|
|
@@ -1853,6 +2093,7 @@ async function handleSlash(text) {
|
|
|
1853
2093
|
return resposta;
|
|
1854
2094
|
}
|
|
1855
2095
|
const plan = prepararPasta(texto);
|
|
2096
|
+
checkpoint.iniciar(texto);
|
|
1856
2097
|
const resultado = await runAct(texto, {
|
|
1857
2098
|
instrucao: vozModo.COMO_FALAR,
|
|
1858
2099
|
server: state.config.server,
|
|
@@ -1865,6 +2106,7 @@ async function handleSlash(text) {
|
|
|
1865
2106
|
ensureWorkDir: plan.pending ? () => createPendingProject(plan) : null,
|
|
1866
2107
|
autorizar: autorizarPorVoz,
|
|
1867
2108
|
controle: state.controle,
|
|
2109
|
+
permissoes: state.config.permissoes,
|
|
1868
2110
|
});
|
|
1869
2111
|
const resposta = (resultado.assistantMessage && resultado.assistantMessage.content) || '';
|
|
1870
2112
|
// A conversa é a MESMA do terminal: sair do modo voz e
|
|
@@ -2067,18 +2309,8 @@ async function handleSlash(text) {
|
|
|
2067
2309
|
const n = Number(rest);
|
|
2068
2310
|
const alvo = l.conversas[n - 1];
|
|
2069
2311
|
if (!alvo) { console.log(c.err(` Não achei a conversa ${rest}.`)); break; }
|
|
2070
|
-
const
|
|
2071
|
-
const conv = await nuvem.lerConversa(alvo.id);
|
|
2072
|
-
sp2.stop();
|
|
2312
|
+
const conv = await abrirConversa(alvo);
|
|
2073
2313
|
if (!conv.ok) { console.log(c.err(` ${conv.error}`)); break; }
|
|
2074
|
-
// A memória entra ZERADA: o resumo antigo não veio junto, e um
|
|
2075
|
-
// resumo de outra conversa colado aqui faria o modelo responder
|
|
2076
|
-
// sobre coisa que não foi dita nesta.
|
|
2077
|
-
state.history = conv.mensagens.map((m) => ({
|
|
2078
|
-
role: m.papel, content: m.texto,
|
|
2079
|
-
}));
|
|
2080
|
-
state.memoria = memoria.nova();
|
|
2081
|
-
state.gravador.retomar(alvo.id, alvo.titulo, conv.mensagens.length);
|
|
2082
2314
|
console.log(c.ok(` ${alvo.titulo}`)
|
|
2083
2315
|
+ c.muted(` · ${conv.mensagens.length} mensagens carregadas`));
|
|
2084
2316
|
console.log(c.dim(' Pode continuar daqui — o que você disser volta pro app e pro celular.'));
|
|
@@ -2154,6 +2386,29 @@ async function handleSlash(text) {
|
|
|
2154
2386
|
break;
|
|
2155
2387
|
}
|
|
2156
2388
|
|
|
2389
|
+
/* Os comandos da pessoa: lista, e explica como fazer o dela. */
|
|
2390
|
+
case 'comandos': {
|
|
2391
|
+
const novo = rest.match(/^(novo|new|criar)\s+(\S+)$/i);
|
|
2392
|
+
if (novo) {
|
|
2393
|
+
const r = comandosLib.criar(novo[2]);
|
|
2394
|
+
if (!r.ok) { console.log(c.err(` ${r.error}`)); break; }
|
|
2395
|
+
cacheComandos.em = 0;
|
|
2396
|
+
console.log(c.ok(` Criado: /${path.basename(r.arquivo, '.md')}`) + c.muted(' abra e escreva o pedido:'));
|
|
2397
|
+
console.log(' ' + c.white(r.arquivo));
|
|
2398
|
+
break;
|
|
2399
|
+
}
|
|
2400
|
+
const lista = comandosDaPessoa();
|
|
2401
|
+
console.log(c.white('\n Seus comandos'));
|
|
2402
|
+
for (const x of lista) {
|
|
2403
|
+
console.log(` ${c.brand(ui.padRight('/' + x.nome, 14))}${c.dim(ui.padRight(x.origem, 11))}${c.muted(x.descricao)}`);
|
|
2404
|
+
}
|
|
2405
|
+
console.log(c.dim('\n Um arquivo .md vira um comando: revisar.md é /revisar.'));
|
|
2406
|
+
console.log(c.dim(' ~/.primocode/comandos/ vale em todo projeto · <projeto>/.primocode/comandos/ só naquele.'));
|
|
2407
|
+
console.log(c.dim(' $ARGUMENTOS no texto recebe o que você digitar depois: /revisar o login.'));
|
|
2408
|
+
console.log(c.dim(' /comandos novo <nome> cria um para você editar.'));
|
|
2409
|
+
break;
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2157
2412
|
case 'claude': case 'gpt': case 'codex': case 'key': case 'keys': {
|
|
2158
2413
|
console.log(c.muted(' Não existe mais provedor para configurar.'));
|
|
2159
2414
|
console.log(c.muted(' A API já vem com o PrimoCode: entrou na conta, pode usar.'));
|
|
@@ -2165,7 +2420,18 @@ async function handleSlash(text) {
|
|
|
2165
2420
|
// "/" sozinho abre o menu; um prefixo único autopreenche e executa
|
|
2166
2421
|
// ("/eff" → /effort); prefixo ambíguo mostra as opções.
|
|
2167
2422
|
if (!cmd) { mostrarMenuSlash(); break; }
|
|
2168
|
-
|
|
2423
|
+
// Chegou aqui, não é embutido: o switch já atendeu todos eles.
|
|
2424
|
+
// Por isso um /help.md da pessoa nunca troca o /help de verdade.
|
|
2425
|
+
const proprio = comandosDaPessoa().find((x) => x.nome === nome);
|
|
2426
|
+
if (proprio) {
|
|
2427
|
+
console.log(c.dim(` ⎿ /${proprio.nome} · ${proprio.origem}`));
|
|
2428
|
+
// O MESMO caminho de um pedido digitado: modo, pasta, plano,
|
|
2429
|
+
// tudo. Um atalho que pulasse o sendPrompt pularia junto o
|
|
2430
|
+
// portão do plano e a escolha da pasta.
|
|
2431
|
+
await sendPrompt(comandosLib.expandir(proprio, rest));
|
|
2432
|
+
break;
|
|
2433
|
+
}
|
|
2434
|
+
const casam = comandosDoMenu().map(([nome]) => nome.slice(1)).filter((n) => n.startsWith(cmd.toLowerCase()));
|
|
2169
2435
|
if (casam.length === 1) {
|
|
2170
2436
|
console.log(c.dim(` → /${casam[0]}`));
|
|
2171
2437
|
await handleSlash('/' + casam[0] + (rest ? ' ' + rest : ''));
|
|
@@ -2174,7 +2440,7 @@ async function handleSlash(text) {
|
|
|
2174
2440
|
if (casam.length > 1) {
|
|
2175
2441
|
console.log(c.muted(` /${cmd}… pode ser:`));
|
|
2176
2442
|
for (const n of casam) {
|
|
2177
|
-
const [, desc] =
|
|
2443
|
+
const [, desc] = comandosDoMenu().find(([nome]) => nome === '/' + n);
|
|
2178
2444
|
console.log(` ${c.brand(ui.padRight('/' + n, 12))}${c.dim(desc)}`);
|
|
2179
2445
|
}
|
|
2180
2446
|
break;
|
|
@@ -2247,7 +2513,11 @@ async function main() {
|
|
|
2247
2513
|
// mesmo que uma refatoração.
|
|
2248
2514
|
state.mode = ['primocode', 'chat', 'auto'].includes(state.config.mode) ? state.config.mode : 'auto';
|
|
2249
2515
|
|
|
2250
|
-
|
|
2516
|
+
// `-c` / `--continue` pode vir em qualquer posição e sai da lista: o
|
|
2517
|
+
// resto (one-shot, -f) segue sendo lido do jeito de sempre.
|
|
2518
|
+
const todos = process.argv.slice(2);
|
|
2519
|
+
const continuar = todos.some((a) => a === '-c' || a === '--continue');
|
|
2520
|
+
const args = todos.filter((a) => a !== '-c' && a !== '--continue');
|
|
2251
2521
|
|
|
2252
2522
|
// Flags padrão de CLI — funcionam em qualquer máquina, úteis em scripts/CI.
|
|
2253
2523
|
if (args[0] === '--version' || args[0] === '-v' || args[0] === '-V') {
|
|
@@ -2312,28 +2582,23 @@ async function main() {
|
|
|
2312
2582
|
const fileFlag = args.indexOf('-f');
|
|
2313
2583
|
const words = fileFlag >= 0 ? args.slice(0, fileFlag) : args;
|
|
2314
2584
|
const promptText = words.join(' ');
|
|
2315
|
-
let fullPrompt = promptText;
|
|
2316
2585
|
|
|
2317
2586
|
if (fileFlag >= 0 && args[fileFlag + 1]) {
|
|
2318
2587
|
try {
|
|
2319
2588
|
const fc = fs.readFileSync(path.resolve(args[fileFlag + 1]), 'utf-8');
|
|
2320
|
-
|
|
2589
|
+
state.attachedFiles.push({ path: args[fileFlag + 1], content: fc });
|
|
2321
2590
|
} catch { console.log(c.err(`Não consegui ler: ${args[fileFlag + 1]}`)); process.exit(1); }
|
|
2322
2591
|
}
|
|
2323
2592
|
|
|
2324
|
-
//
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
projectDir: plan.dir,
|
|
2334
|
-
projectName: plan.name || state.currentProject,
|
|
2335
|
-
ensureWorkDir: plan.pending ? () => createPendingProject(plan) : null,
|
|
2336
|
-
});
|
|
2593
|
+
// Com -c, o pedido continua a última conversa em vez de chegar sozinho.
|
|
2594
|
+
if (continuar) await continuarUltima();
|
|
2595
|
+
/* O MESMO caminho do pedido digitado (sendPrompt). Este modo tinha um
|
|
2596
|
+
runAct próprio, e por isso ficava sem tudo o que vive lá: o kit de
|
|
2597
|
+
partida, o plano em passos, a retomada automática, o @arquivo, o
|
|
2598
|
+
checkpoint do /desfazer. Medido em 24/09: `primo "crie uma landing…"`
|
|
2599
|
+
não pegou o kit. */
|
|
2600
|
+
state.semTerminal = true;
|
|
2601
|
+
await sendPrompt(promptText);
|
|
2337
2602
|
process.exit(0);
|
|
2338
2603
|
}
|
|
2339
2604
|
|
|
@@ -2345,7 +2610,7 @@ async function main() {
|
|
|
2345
2610
|
}));
|
|
2346
2611
|
rl = entrada.criar({
|
|
2347
2612
|
status: barraDeStatus,
|
|
2348
|
-
comandos:
|
|
2613
|
+
comandos: comandosDoMenu,
|
|
2349
2614
|
vazio: 'peça alguma coisa, ou / para os comandos',
|
|
2350
2615
|
});
|
|
2351
2616
|
|
|
@@ -2484,15 +2749,19 @@ async function main() {
|
|
|
2484
2749
|
// o primocode-server, que tem a chave do provedor.
|
|
2485
2750
|
state.liberado = !r || r.liberado !== false;
|
|
2486
2751
|
state.motivoBloqueio = r && r.motivo;
|
|
2487
|
-
}).catch(() => { state.liberado = true; })
|
|
2488
|
-
//
|
|
2489
|
-
//
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2752
|
+
}).catch(() => { state.liberado = true; })
|
|
2753
|
+
// Depois da abertura, porque é nela que a pessoa entra na conta — e
|
|
2754
|
+
// sem conta não há conversa para achar.
|
|
2755
|
+
.then(() => (continuar ? continuarUltima().catch(() => false) : null))
|
|
2756
|
+
.then(() => {
|
|
2757
|
+
// O que sobrou de adiantado não era resposta de nada: vira comando,
|
|
2758
|
+
// na ordem em que foi digitado. Nada se perde, só esperou a vez.
|
|
2759
|
+
segurando = false;
|
|
2760
|
+
while (adiantadas.length) queue.push(adiantadas.shift());
|
|
2761
|
+
if (queue.length) drain();
|
|
2762
|
+
else if (!closed) rl.prompt();
|
|
2763
|
+
conferirServidor();
|
|
2764
|
+
});
|
|
2496
2765
|
}
|
|
2497
2766
|
|
|
2498
2767
|
main().catch((e) => { console.error(c.err(e.message || e)); process.exit(1); });
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
descricao: explica este projeto (ou um arquivo) para quem não programa
|
|
3
|
+
---
|
|
4
|
+
Me explique como se eu nunca tivesse programado: $ARGUMENTOS
|
|
5
|
+
|
|
6
|
+
Se eu não disse o que explicar, explique o projeto inteiro desta pasta. Leia os arquivos antes de falar, não chute.
|
|
7
|
+
|
|
8
|
+
Responda nesta ordem, em frases curtas:
|
|
9
|
+
1. o que isto faz, em uma frase;
|
|
10
|
+
2. as partes principais e o papel de cada uma (use comparações do dia a dia);
|
|
11
|
+
3. como eu rodo e vejo funcionando;
|
|
12
|
+
4. onde eu mexo para mudar o que é mais provável que eu queira mudar.
|
|
13
|
+
|
|
14
|
+
Sem jargão. Se uma palavra técnica for inevitável, explique ela na hora.
|