primocode 9.8.0-beta.5 → 9.8.0-beta.7
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 +15 -4
- package/app/ansi.js +55 -0
- package/app/ide.css +345 -0
- package/app/ide.html +183 -0
- package/app/ide.js +639 -0
- package/bin/primocode.js +338 -88
- package/kits/acolhedor/index.html +130 -0
- package/kits/acolhedor/script.js +25 -0
- package/kits/acolhedor/style.css +146 -0
- package/kits/editorial/index.html +115 -0
- package/kits/editorial/script.js +19 -0
- package/kits/editorial/style.css +112 -0
- package/kits/premium/index.html +83 -0
- package/kits/premium/script.js +11 -0
- package/kits/premium/style.css +87 -0
- package/kits/vitrine/index.html +108 -0
- package/kits/vitrine/script.js +20 -0
- package/kits/vitrine/style.css +117 -0
- package/lib/act.js +66 -7
- package/lib/app.js +41 -6
- package/lib/browser.js +79 -3
- package/lib/checkpoint-git.js +218 -0
- package/lib/checkpoint.js +159 -44
- package/lib/comandos-extra.js +481 -0
- package/lib/config.js +5 -2
- package/lib/ide.js +337 -0
- package/lib/kits.js +165 -54
- package/lib/pre-leitura.js +118 -0
- package/lib/repeticao.js +4 -2
- package/lib/skills.js +6 -1
- package/lib/tempos.js +62 -0
- package/lib/ui.js +171 -17
- package/package.json +2 -2
- package/skills/apresentacao-pptx/SKILL.md +52 -0
- package/skills/cartaz-de-arte/LICENSE.txt +202 -0
- package/skills/cartaz-de-arte/SKILL.md +47 -0
- package/skills/criar-skill/LICENSE.txt +202 -0
- package/skills/criar-skill/SKILL.md +49 -0
- package/skills/depurar-de-verdade/LICENSE.txt +21 -0
- package/skills/depurar-de-verdade/SKILL.md +14 -5
- package/skills/documento-word/SKILL.md +57 -0
- package/skills/interface-revisada/SKILL.md +47 -0
- package/skills/pdf-bonito/SKILL.md +48 -0
- package/skills/planilha-excel/SKILL.md +47 -0
- package/skills/react-sem-dor/SKILL.md +48 -0
- package/skills/revisao-antes-de-entregar/LICENSE.txt +21 -0
- package/skills/revisao-antes-de-entregar/SKILL.md +6 -1
- package/skills/temas-prontos/LICENSE.txt +202 -0
- package/skills/temas-prontos/SKILL.md +50 -0
- package/skills/testar-o-site/LICENSE.txt +202 -0
- package/skills/testar-o-site/SKILL.md +46 -0
- package/skills/video-no-remotion/SKILL.md +48 -0
package/bin/primocode.js
CHANGED
|
@@ -43,6 +43,17 @@ const conta = require('../lib/conta');
|
|
|
43
43
|
const nuvem = require('../lib/nuvem');
|
|
44
44
|
const compat = require('../lib/compatibilidade');
|
|
45
45
|
const mencoesLib = require('../lib/mencoes');
|
|
46
|
+
const extra = require('../lib/comandos-extra');
|
|
47
|
+
|
|
48
|
+
/* O /custo: toda conversa com o modelo passa pelo lib/api.js, e o contador
|
|
49
|
+
embrulha as funções dele uma vez, aqui. Quem atende é o provedor do /api
|
|
50
|
+
quando há um, senão o servidor — é por eles que o /custo separa. */
|
|
51
|
+
const contadorDaSessao = extra.novoContador();
|
|
52
|
+
extra.instalarContador(api, contadorDaSessao, () => {
|
|
53
|
+
const p = provedorLocal.atual();
|
|
54
|
+
return p ? p.nome : 'Servidor PrimoCode';
|
|
55
|
+
});
|
|
56
|
+
const tempos = require('../lib/tempos');
|
|
46
57
|
|
|
47
58
|
// Rede de segurança de último recurso: sem isto, uma exceção fora de um
|
|
48
59
|
// try/catch (ex.: dentro de um callback de socket, como o parser de SSE) não
|
|
@@ -97,10 +108,34 @@ const state = {
|
|
|
97
108
|
// existe para a frente em vez de subir outro servidor ao lado.
|
|
98
109
|
let janelaAberta = null;
|
|
99
110
|
|
|
111
|
+
/* O que a janela do /rc, a IDE do /ide e o `primo --app` passam ao
|
|
112
|
+
lib/app.js. Uma função só porque os três sobem o MESMO servidor: se cada um
|
|
113
|
+
montasse o seu, o /ide aberto depois do /rc acharia um servidor sem a pasta
|
|
114
|
+
do projeto — e a IDE nasceria sem árvore. */
|
|
115
|
+
function opcoesDaJanela(extra) {
|
|
116
|
+
return Object.assign({
|
|
117
|
+
versao: VERSION,
|
|
118
|
+
enviar: (t) => sendPrompt(t),
|
|
119
|
+
barra: (t) => handleSlash(t),
|
|
120
|
+
interromper,
|
|
121
|
+
estado: () => ({
|
|
122
|
+
pasta: state.workDir ? shortPath(state.workDir) : null,
|
|
123
|
+
projeto: state.currentProject,
|
|
124
|
+
modo: state.mode,
|
|
125
|
+
esforco: state.config.effort,
|
|
126
|
+
}),
|
|
127
|
+
// Lida na hora, não guardada: o /dir e o /project trocam a pasta
|
|
128
|
+
// com a IDE aberta, e ela tem de acompanhar.
|
|
129
|
+
pastaProjeto: () => projectDir(),
|
|
130
|
+
}, extra || {});
|
|
131
|
+
}
|
|
132
|
+
|
|
100
133
|
const HELP = `
|
|
101
134
|
${c.bold('Comandos')}
|
|
102
135
|
${c.brand('/dir')} <caminho> trabalha numa pasta que já existe (onde você quiser)
|
|
103
136
|
${c.brand('/project')} lista projetos ou cria em ~/primocode: /project <nome>
|
|
137
|
+
${c.brand('/projetos')} [n] projetos do mais recente, com tamanho · /projetos 2 entra
|
|
138
|
+
${c.brand('/novo')} <descrição> começa um projeto NOVO do zero, em pasta própria
|
|
104
139
|
${c.brand('/mode')} /mode primocode|chat|auto
|
|
105
140
|
${c.brand('/effort')} esforço do agente — do rápido ao mais esperto
|
|
106
141
|
${c.brand('/permissoes')} quando ele pergunta antes de agir: auto|perguntar|livre
|
|
@@ -114,14 +149,21 @@ ${c.bold('Comandos')}
|
|
|
114
149
|
${c.brand('/read')} <arquivo> mostra um arquivo
|
|
115
150
|
${c.brand('/run')} <comando> executa no shell do projeto
|
|
116
151
|
${c.brand('/open')} <arq|url> abre no navegador
|
|
152
|
+
${c.brand('/abrir')} abre o projeto: o servidor de pé, ou a index.html
|
|
153
|
+
${c.brand('/copiar')} copia a última resposta
|
|
154
|
+
${c.brand('/historico')} [termo] procura nos seus pedidos antigos
|
|
117
155
|
${c.brand('/rc')} abre a janela do PrimoCode (chat, sem terminal)
|
|
156
|
+
${c.brand('/ide')} abre a IDE: arquivos, editor, prévia do site e o chat
|
|
118
157
|
${c.brand('/login')} entra na sua conta · ${c.brand('/account')} mostra o plano
|
|
119
158
|
${c.brand('/chats')} [n] suas conversas — as mesmas do app e do celular
|
|
120
159
|
${c.brand('/logout')} desconecta este computador da conta
|
|
121
160
|
${c.brand('/comandos')} seus comandos (/revisar, /explicar…) · /comandos novo <nome>
|
|
122
161
|
${c.brand('/status')} checa o servidor
|
|
162
|
+
${c.brand('/doctor')} confere tudo (servidor, /api, Chrome, disco…) e diz como consertar
|
|
163
|
+
${c.brand('/custo')} tokens e pedidos desta sessão, por provedor
|
|
164
|
+
${c.brand('/atalhos')} as teclas que ajudam
|
|
123
165
|
${c.brand('/name')} <nome> como o Primo te chama
|
|
124
|
-
${c.brand('/mascot')}
|
|
166
|
+
${c.brand('/mascot')} [nome|n] galeria dos mascotes · /mascot reacoes on|off
|
|
125
167
|
${c.brand('/config')} mostra suas preferências
|
|
126
168
|
${c.brand('/clear')} limpa a conversa ${c.brand('/cls')} limpa a tela
|
|
127
169
|
${c.brand('/uninstall')} remove o PrimoCode
|
|
@@ -147,6 +189,8 @@ ${c.bold('Onde ele trabalha')}
|
|
|
147
189
|
const SLASH_COMANDOS = [
|
|
148
190
|
['/help', 'todos os comandos'],
|
|
149
191
|
['/project', 'lista/cria projetos em ~/primocode'],
|
|
192
|
+
['/projetos', 'projetos do mais recente, com tamanho · /projetos 2 entra'],
|
|
193
|
+
['/novo', 'começa um projeto novo do zero: /novo site da padaria'],
|
|
150
194
|
['/dir', 'trabalhar numa pasta sua'],
|
|
151
195
|
['/mode', 'primocode | chat | auto'],
|
|
152
196
|
['/effort', 'esforço: low → ultracode (←/→ para ajustar)'],
|
|
@@ -162,7 +206,11 @@ const SLASH_COMANDOS = [
|
|
|
162
206
|
['/read', 'mostra um arquivo'],
|
|
163
207
|
['/run', 'roda um comando'],
|
|
164
208
|
['/open', 'abre no navegador'],
|
|
209
|
+
['/abrir', 'abre o projeto atual: o servidor de pé, ou a index.html'],
|
|
210
|
+
['/copiar', 'copia a última resposta'],
|
|
211
|
+
['/historico', 'procura nos seus pedidos antigos: /historico login'],
|
|
165
212
|
['/rc', 'abre a janela do PrimoCode'],
|
|
213
|
+
['/ide', 'abre a IDE: arquivos, editor, prévia e o chat, lado a lado'],
|
|
166
214
|
['/login', 'entra na sua conta Conecta Primo'],
|
|
167
215
|
['/account', 'sua conta, plano e computadores conectados'],
|
|
168
216
|
['/chats', 'suas conversas salvas, as mesmas do app e do celular'],
|
|
@@ -176,9 +224,12 @@ const SLASH_COMANDOS = [
|
|
|
176
224
|
['/comandos', 'seus comandos: um .md vira /nome · /comandos novo <nome>'],
|
|
177
225
|
['/api', 'usa a SUA chave (Gemini, ChatGPT, Claude…) direto daqui, sem o limite do servidor'],
|
|
178
226
|
['/status', 'checa o servidor'],
|
|
227
|
+
['/doctor', 'confere se está tudo certo e diz como consertar'],
|
|
228
|
+
['/custo', 'tokens e pedidos desta sessão, por provedor'],
|
|
229
|
+
['/atalhos', 'as teclas que ajudam'],
|
|
179
230
|
['/connectors', 'quem está atendendo os seus pedidos'],
|
|
180
231
|
['/name', 'como ele te chama'],
|
|
181
|
-
['/mascot', '
|
|
232
|
+
['/mascot', 'galeria dos mascotes · escolha por nome ou número'],
|
|
182
233
|
['/config', 'suas preferências'],
|
|
183
234
|
['/clear', 'limpa a conversa'],
|
|
184
235
|
['/cls', 'limpa a tela'],
|
|
@@ -654,6 +705,8 @@ function ehSoEstudio(texto) {
|
|
|
654
705
|
}
|
|
655
706
|
|
|
656
707
|
function planWorkDir(userText) {
|
|
708
|
+
// O /novo já criou a pasta e entrou nela: nenhuma heurística decide outra.
|
|
709
|
+
if (state.projetoFixo) return { dir: state.workDir, pending: false, name: state.currentProject };
|
|
657
710
|
const escolhida = ['/dir', 'detectada', 'projeto'].includes(state.workDirOrigem);
|
|
658
711
|
// Pasta escolhida a dedo (/dir, ou dita no pedido) manda sempre. Projeto
|
|
659
712
|
// em andamento manda também — a menos que o pedido abra outra coisa.
|
|
@@ -675,36 +728,27 @@ function planWorkDir(userText) {
|
|
|
675
728
|
paralelo com o trabalho: a pasta só nasce na primeira escrita, e até lá a
|
|
676
729
|
resposta já chegou. Só pergunta quando o pedido é de CRIAR algo — pedir
|
|
677
730
|
para abrir o Chrome não gasta chamada com nome de pasta. */
|
|
731
|
+
async function perguntarNome(texto) {
|
|
732
|
+
let resposta = '';
|
|
733
|
+
await api.requestChat(state.config.server, {
|
|
734
|
+
prompt: texto, history: [], model: state.config.model, token: state.config.token,
|
|
735
|
+
}, (evt) => { if (evt.delta) resposta += evt.delta; });
|
|
736
|
+
return resposta;
|
|
737
|
+
}
|
|
738
|
+
|
|
678
739
|
function batizarDepois(plan, userText) {
|
|
679
740
|
if (!plan.pending || !(RE_CRIAR_UM.test(semAcento(userText)) || PEDIDO_DE_CODIGO.test(userText))) return;
|
|
680
|
-
|
|
681
|
-
let resposta = '';
|
|
682
|
-
await api.requestChat(state.config.server, {
|
|
683
|
-
prompt: texto, history: [], model: state.config.model, token: state.config.token,
|
|
684
|
-
}, (evt) => { if (evt.delta) resposta += evt.delta; });
|
|
685
|
-
return resposta;
|
|
686
|
-
};
|
|
687
|
-
plan.batizando = nomeProjeto.escolher({ pedido: userText, nomes: cfg.listProjects(), perguntar })
|
|
741
|
+
plan.batizando = nomeProjeto.escolher({ pedido: userText, nomes: cfg.listProjects(), perguntar: perguntarNome })
|
|
688
742
|
.then((nome) => { if (nome) plan.nomeEscolhido = nome; });
|
|
689
743
|
}
|
|
690
744
|
|
|
691
|
-
/*
|
|
692
|
-
|
|
693
|
-
|
|
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. */
|
|
694
748
|
async function prepararKit(plan, userText) {
|
|
695
749
|
const kit = kits.escolher(userText, { provedor: provedorLocal.atual() });
|
|
696
750
|
if (!kit) return '';
|
|
697
|
-
|
|
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`));
|
|
751
|
+
console.log(c.dim(` ⎿ inspiração visual: ${kit.titulo}`));
|
|
708
752
|
return kit.instrucao;
|
|
709
753
|
}
|
|
710
754
|
|
|
@@ -1012,6 +1056,16 @@ async function sendPrompt(userText) {
|
|
|
1012
1056
|
return;
|
|
1013
1057
|
}
|
|
1014
1058
|
|
|
1059
|
+
// PRIMOCODE_TEMPOS=1: uma linha no fim com o custo de cada fase.
|
|
1060
|
+
tempos.comecar();
|
|
1061
|
+
try { await enviarPedido(userText); }
|
|
1062
|
+
finally {
|
|
1063
|
+
const linha = tempos.relatorio();
|
|
1064
|
+
if (linha) console.log(c.dim(` ⏱ ${linha}`));
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
async function enviarPedido(userText) {
|
|
1015
1069
|
let prompt = userText;
|
|
1016
1070
|
// "@index.html" no pedido é um /attach só deste pedido: vai junto dos
|
|
1017
1071
|
// anexos, mas não fica em state.attachedFiles para o próximo.
|
|
@@ -1048,15 +1102,22 @@ async function sendPrompt(userText) {
|
|
|
1048
1102
|
|
|
1049
1103
|
// O resumo da rodada anterior é feito em segundo plano logo depois
|
|
1050
1104
|
// dela; aqui só se espera ele terminar, para não sair com memória velha.
|
|
1051
|
-
|
|
1052
|
-
|
|
1105
|
+
//
|
|
1053
1106
|
// O pedido passa por uma cabeça antes de virar trabalho: conserta o
|
|
1054
1107
|
// que está torto, diz qual é o entregável e o que conta como pronto.
|
|
1055
1108
|
// UMA chamada faz as duas coisas: arruma o pedido torto e, se fizer
|
|
1056
1109
|
// sentido, divide em passos. Eram duas idas ao modelo por pedido, e
|
|
1057
1110
|
// num plano limitado por REQUISIÇÕES (o grátis da Groq são 30 por
|
|
1058
1111
|
// minuto) cada chamada extra custa tanto quanto token.
|
|
1059
|
-
|
|
1112
|
+
//
|
|
1113
|
+
// As duas esperas correm JUNTAS. Uma não lê o que a outra escreve: o
|
|
1114
|
+
// plano só olha o texto do pedido, e o resumo só a conversa de antes.
|
|
1115
|
+
// Em fila, o pedido de duas fases pagava o resumo e DEPOIS o plano —
|
|
1116
|
+
// duas idas ao modelo em série antes da primeira rodada.
|
|
1117
|
+
const [, passos] = await Promise.all([
|
|
1118
|
+
tempos.medir('espera do resumo', () => esperarResumo()),
|
|
1119
|
+
tempos.medir('plano', () => dividirTarefa(prompt)),
|
|
1120
|
+
]);
|
|
1060
1121
|
const instrucaoKit = await prepararKit(plan, userText);
|
|
1061
1122
|
|
|
1062
1123
|
let r = null;
|
|
@@ -1067,7 +1128,9 @@ async function sendPrompt(userText) {
|
|
|
1067
1128
|
balanços, quer saber o que existe no disco quando ele terminar. */
|
|
1068
1129
|
const livro = registro.novo();
|
|
1069
1130
|
// Um checkpoint por PEDIDO (não por passo): é o que o /desfazer volta.
|
|
1070
|
-
|
|
1131
|
+
// Com a pasta: a foto do projeto inteiro sai AGORA, antes de qualquer
|
|
1132
|
+
// tool — é ela que dá volta ao `rm`/`mv` que o run_shell fizer.
|
|
1133
|
+
checkpoint.iniciar(userText, projectDir());
|
|
1071
1134
|
for (let i = 0; i < passos.length; i++) {
|
|
1072
1135
|
if (i > 0) console.log(c.dim(` ⎿ ${passos[i].titulo}`));
|
|
1073
1136
|
// Só fato de passo CONCLUÍDO entra aqui. Num pedido real o passo de
|
|
@@ -1153,6 +1216,12 @@ async function sendPrompt(userText) {
|
|
|
1153
1216
|
arquivos é a conferência, e conferência se lê por último. */
|
|
1154
1217
|
const balanco = registro.resumo(livro, { colunas: process.stdout.columns || 80 });
|
|
1155
1218
|
if (balanco) console.log(balanco);
|
|
1219
|
+
// O rostinho do mascote, por último e numa linha só. O humor sai do
|
|
1220
|
+
// que aconteceu, não de adivinhar pelo texto da resposta.
|
|
1221
|
+
if (state.config.reacoes !== false && r) {
|
|
1222
|
+
const humor = r.parado ? 'parado' : r.concluiu === false ? 'preocupado' : 'feliz';
|
|
1223
|
+
console.log(ui.reacao(state.config.mascot, humor));
|
|
1224
|
+
}
|
|
1156
1225
|
state.history.push({ role: 'user', content: userText });
|
|
1157
1226
|
state.history.push({ role: 'assistant', content: r.assistantMessage.content });
|
|
1158
1227
|
resumirDepois();
|
|
@@ -1163,8 +1232,8 @@ async function sendPrompt(userText) {
|
|
|
1163
1232
|
state.gravador.turno(userText, r.assistantMessage.content,
|
|
1164
1233
|
{ modo: 'primocode', projeto: state.currentProject || null });
|
|
1165
1234
|
} else {
|
|
1166
|
-
await esperarResumo();
|
|
1167
|
-
await runChat(prompt, userText);
|
|
1235
|
+
await tempos.medir('espera do resumo', () => esperarResumo());
|
|
1236
|
+
await tempos.medir('chat', () => runChat(prompt, userText));
|
|
1168
1237
|
}
|
|
1169
1238
|
|
|
1170
1239
|
state.attachedFiles = [];
|
|
@@ -1526,7 +1595,10 @@ const RENOMEADOS = {
|
|
|
1526
1595
|
conta: 'account',
|
|
1527
1596
|
sair: 'exit', // sair é SAIR; desconectar a conta é /logout
|
|
1528
1597
|
// o histórico
|
|
1529
|
-
|
|
1598
|
+
// /historico deixou de ser apelido do /chats: agora procura nos pedidos
|
|
1599
|
+
// guardados nesta máquina (lib/comandos-extra.js). /chats segue sendo a
|
|
1600
|
+
// conversa inteira, da conta.
|
|
1601
|
+
conversas: 'chats',
|
|
1530
1602
|
// o resto
|
|
1531
1603
|
pasta: 'dir',
|
|
1532
1604
|
esforco: 'effort', esforço: 'effort',
|
|
@@ -1590,16 +1662,33 @@ async function handleSlash(text) {
|
|
|
1590
1662
|
switch (nome) {
|
|
1591
1663
|
case 'help': console.log(HELP); break;
|
|
1592
1664
|
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1665
|
+
/* /projetos e /project sem nome são a MESMA lista: antes o /project só
|
|
1666
|
+
listava os nomes, em ordem alfabética, e quem procurava "o de ontem"
|
|
1667
|
+
tinha de abrir pasta por pasta. Agora vem do mais recente, com
|
|
1668
|
+
quando mexeu e o tamanho, e um número para entrar. */
|
|
1669
|
+
case 'projetos': case 'project': {
|
|
1670
|
+
const lista = extra.listarProjetos(cfg.ROOT_DIR);
|
|
1671
|
+
const numero = /^\d+$/.test(rest) ? Number(rest) : 0;
|
|
1672
|
+
if (!rest || (numero && nome === 'projetos') || (numero && numero <= lista.length)) {
|
|
1673
|
+
if (!lista.length) { console.log(c.muted(' Nenhum projeto ainda. Peça algo e eu crio o primeiro.')); break; }
|
|
1674
|
+
if (numero) {
|
|
1675
|
+
const alvo = lista[numero - 1];
|
|
1676
|
+
if (!alvo) { console.log(c.err(` Não há projeto ${numero}. São ${lista.length}.`)); break; }
|
|
1677
|
+
entrarNoProjeto(alvo.dir, 'projeto');
|
|
1678
|
+
console.log(c.ok(` Trabalhando em ${alvo.nome}`) + c.muted(` (${shortPath(alvo.dir)})`));
|
|
1679
|
+
refreshPrompt();
|
|
1680
|
+
break;
|
|
1601
1681
|
}
|
|
1602
|
-
console.log(
|
|
1682
|
+
console.log('');
|
|
1683
|
+
const largura = Math.min(34, Math.max(...lista.map((x) => x.nome.length)));
|
|
1684
|
+
lista.forEach((x, i) => {
|
|
1685
|
+
const aqui = x.nome === state.currentProject;
|
|
1686
|
+
const nomeCol = ui.padRight(ui.truncate(aqui ? c.brand(x.nome) : c.white(x.nome), largura), largura);
|
|
1687
|
+
console.log(` ${aqui ? c.brand('●') : ' '} ${c.dim(String(i + 1).padStart(2))} ${nomeCol} `
|
|
1688
|
+
+ c.muted(ui.padRight(extra.quandoFoi(x.modificado), 11))
|
|
1689
|
+
+ c.dim(`${extra.fmtTamanho(x.bytes)}${x.cortado ? '+' : ''} · ${x.arquivos} arq.`));
|
|
1690
|
+
});
|
|
1691
|
+
console.log(c.dim(`\n ${shortPath(cfg.ROOT_DIR)} · /projetos 1 entra no primeiro · /novo <descrição> começa outro`));
|
|
1603
1692
|
break;
|
|
1604
1693
|
}
|
|
1605
1694
|
const dir = cfg.createProjectDir(rest);
|
|
@@ -1794,25 +1883,31 @@ async function handleSlash(text) {
|
|
|
1794
1883
|
|
|
1795
1884
|
/* ── /desfazer ────────────────────────────────────────────────
|
|
1796
1885
|
* Quem não sabe git não tem como voltar uma edição ruim. Aqui volta:
|
|
1797
|
-
* cada pedido
|
|
1886
|
+
* cada pedido tirou uma foto do projeto (com git) ou guardou o
|
|
1887
|
+
* original dos arquivos que mexeu (sem git). */
|
|
1798
1888
|
case 'desfazer': case 'undo': case 'rewind': {
|
|
1799
1889
|
const cps = checkpoint.lista(projectDir());
|
|
1800
1890
|
if (!cps.length) { console.log(c.muted(' Nada para desfazer nesta pasta.')); break; }
|
|
1801
1891
|
const quando = (cp) => new Date(cp.quando).toLocaleString('pt-BR', { dateStyle: 'short', timeStyle: 'short' });
|
|
1802
1892
|
if (/^(lista|list|ls)$/i.test(rest)) {
|
|
1803
1893
|
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 ? '…' : ''))}
|
|
1894
|
+
+ `${c.white(cp.pedido.slice(0, 50) + (cp.pedido.length > 50 ? '…' : ''))} `
|
|
1895
|
+
+ c.muted(`${cp.mudou.length} ${cp.mudou.length === 1 ? 'arquivo mudou' : 'arquivos mudaram'}`)));
|
|
1805
1896
|
console.log(c.muted('\n /desfazer N volta os N últimos pedidos.'));
|
|
1897
|
+
if (checkpoint.modo(projectDir()) === 'arquivo') {
|
|
1898
|
+
console.log(c.muted(' Sem git aqui: só volta o que o agente escreveu com as ferramentas de arquivo (não o que um comando apagou).'));
|
|
1899
|
+
}
|
|
1806
1900
|
break;
|
|
1807
1901
|
}
|
|
1808
1902
|
const n = rest ? parseInt(rest, 10) : 1;
|
|
1809
1903
|
if (!(n >= 1)) { console.log(c.err('Uso: /desfazer · /desfazer lista · /desfazer N')); break; }
|
|
1810
1904
|
const alvo = cps.slice(0, n);
|
|
1811
1905
|
console.log(c.muted(`\n Volta ${alvo.length === 1 ? 'o último pedido' : `os ${alvo.length} últimos pedidos`}:`));
|
|
1812
|
-
for (const cp of alvo) {
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
}
|
|
1906
|
+
for (const cp of alvo) console.log(` ${c.dim(quando(cp))} ${c.white(cp.pedido.slice(0, 60))}`);
|
|
1907
|
+
const previa = checkpoint.previa(projectDir(), n);
|
|
1908
|
+
if (!previa.length) console.log(c.muted(' (o disco já está como estava antes)'));
|
|
1909
|
+
for (const a of previa.slice(0, 40)) console.log(c.muted(` ${a.acao} `) + shortPath(a.caminho));
|
|
1910
|
+
if (previa.length > 40) console.log(c.muted(` … e mais ${previa.length - 40}`));
|
|
1816
1911
|
if (!(await confirm('Desfazer?'))) { console.log(c.muted('Cancelado.')); break; }
|
|
1817
1912
|
for (const f of checkpoint.desfazer(projectDir(), n)) {
|
|
1818
1913
|
const rotulo = f.acao === 'falhou' ? c.err(` falhou: ${f.erro} `) : c.ok(` ${f.acao} `);
|
|
@@ -1843,8 +1938,57 @@ async function handleSlash(text) {
|
|
|
1843
1938
|
break;
|
|
1844
1939
|
}
|
|
1845
1940
|
|
|
1846
|
-
|
|
1847
|
-
|
|
1941
|
+
/* /novo: projeto novo por ORDEM da pessoa. Sem ele, "crie um site de
|
|
1942
|
+
receitas" pode cair dentro de "site-receitas-velho" porque o nome
|
|
1943
|
+
parece (findSimilarProject) — e quem quer começar do zero não tem
|
|
1944
|
+
como dizer isso. Aqui a pasta nasce antes, com nome que não colide,
|
|
1945
|
+
e o pedido roda preso nela. */
|
|
1946
|
+
case 'novo': case 'new': {
|
|
1947
|
+
if (!rest) { console.log(c.err('Uso: /novo <o que você quer criar>') + c.dim(' ex.: /novo site da padaria')); break; }
|
|
1948
|
+
const nomes = cfg.listProjects();
|
|
1949
|
+
const sp = new Spinner('escolhendo o nome da pasta').start();
|
|
1950
|
+
const sugerido = await nomeProjeto.escolher({ pedido: rest, nomes, perguntar: perguntarNome }).catch(() => null);
|
|
1951
|
+
sp.stop();
|
|
1952
|
+
const nomeNovo = extra.nomeDoNovo({
|
|
1953
|
+
descricao: rest, nomes, sugerido, derivar: deriveProjectName,
|
|
1954
|
+
slugify: cfg.slugify, semColisao: nomeProjeto.semColisao,
|
|
1955
|
+
});
|
|
1956
|
+
const dir = cfg.createProjectDir(nomeNovo);
|
|
1957
|
+
entrarNoProjeto(dir, 'projeto');
|
|
1958
|
+
// Conversa nova junto: o que se falou do projeto anterior puxaria o
|
|
1959
|
+
// agente de volta para ele.
|
|
1960
|
+
state.history = []; state.memoria = memoria.nova(); state.gravador.zerar();
|
|
1961
|
+
console.log(c.ok(` Projeto novo: ${state.currentProject}`) + c.muted(` (${shortPath(dir)})`));
|
|
1962
|
+
refreshPrompt();
|
|
1963
|
+
const modoAntes = state.mode;
|
|
1964
|
+
state.mode = 'primocode';
|
|
1965
|
+
state.projetoFixo = true;
|
|
1966
|
+
try { await sendPrompt(extra.pedidoDoNovo(rest, state.currentProject)); }
|
|
1967
|
+
finally { state.mode = modoAntes; state.projetoFixo = false; }
|
|
1968
|
+
break;
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
/* /open sem nada (e /abrir) acha sozinho o que abrir: o servidor do
|
|
1972
|
+
projeto de pé, ou a index.html. Antes pedia o caminho, e quem não
|
|
1973
|
+
sabe que a página se chama index.html ficava parado aqui. */
|
|
1974
|
+
case 'abrir': case 'open': {
|
|
1975
|
+
if (!rest) {
|
|
1976
|
+
const pastaAtual = projectDir();
|
|
1977
|
+
if (path.resolve(pastaAtual) === path.resolve(cfg.ROOT_DIR)) {
|
|
1978
|
+
console.log(c.muted(' Nenhum projeto aberto.') + c.dim(' /projetos para escolher um'));
|
|
1979
|
+
break;
|
|
1980
|
+
}
|
|
1981
|
+
const alvo = await extra.acharAlvoAbrir(pastaAtual);
|
|
1982
|
+
if (!alvo) {
|
|
1983
|
+
console.log(c.muted(` Não achei página nem servidor em ${shortPath(pastaAtual)}.`));
|
|
1984
|
+
console.log(c.dim(' Peça "roda o projeto" ao Primo, ou /open <arquivo>.'));
|
|
1985
|
+
break;
|
|
1986
|
+
}
|
|
1987
|
+
const r = await tools.openInBrowser(alvo.url || alvo.caminho);
|
|
1988
|
+
if (r && r.ok === false) { console.log(c.err(` Não consegui abrir: ${r.error}`)); break; }
|
|
1989
|
+
console.log(c.ok(` Aberto: ${alvo.url || shortPath(alvo.caminho)}`) + c.dim(` (${alvo.porque})`));
|
|
1990
|
+
break;
|
|
1991
|
+
}
|
|
1848
1992
|
const target = /^https?:\/\//i.test(rest) ? rest : path.resolve(projectDir(), rest);
|
|
1849
1993
|
await tools.openInBrowser(target);
|
|
1850
1994
|
console.log(c.ok(`Aberto: ${rest}`));
|
|
@@ -1859,32 +2003,47 @@ async function handleSlash(text) {
|
|
|
1859
2003
|
break;
|
|
1860
2004
|
}
|
|
1861
2005
|
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
2006
|
+
/* A galeria: todos lado a lado, número e nome em cima. Escolhe por
|
|
2007
|
+
nome ou número — na hora, se há teclado; senão pelo /mascot <n>.
|
|
2008
|
+
Num pipe não pergunta: a "resposta" seria o próximo comando. */
|
|
2009
|
+
case 'mascot': case 'mascote': {
|
|
2010
|
+
const reac = rest.match(/^(reac(?:oes|ões)?|rea[cç][aã]o|reactions?)\s*(on|off|liga[r]?|desliga[r]?|sim|n[aã]o)?$/i);
|
|
2011
|
+
if (reac) {
|
|
2012
|
+
const quer = reac[2] ? /^(on|liga|ligar|sim)$/i.test(reac[2]) : state.config.reacoes === false;
|
|
2013
|
+
state.config.reacoes = quer;
|
|
2014
|
+
cfg.saveConfig({ reacoes: quer });
|
|
2015
|
+
console.log(c.ok(` Reações do mascote: ${quer ? 'ligadas' : 'desligadas'}`)
|
|
2016
|
+
+ (quer ? ' ' + ui.reacao(state.config.mascot, 'feliz').trim() : ''));
|
|
2017
|
+
break;
|
|
2018
|
+
}
|
|
2019
|
+
let pedido = rest;
|
|
2020
|
+
if (!pedido) {
|
|
1866
2021
|
console.log('');
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
2022
|
+
console.log(ui.galeriaMascotes(state.config.mascot || 'primo', process.stdout.columns || 80));
|
|
2023
|
+
if (!process.stdin.isTTY || state.semTerminal) {
|
|
2024
|
+
console.log(c.muted(' Escolha: /mascot <nome ou número>') + c.dim(' · /mascot reacoes off desliga o rostinho'));
|
|
2025
|
+
break;
|
|
1871
2026
|
}
|
|
1872
|
-
|
|
1873
|
-
break;
|
|
2027
|
+
pedido = String(await ask(` ${c.brand('?')} Nome ou número ${c.dim('(Enter mantém)')}: `)).trim();
|
|
2028
|
+
if (!pedido) { console.log(c.muted(' Nada mudou.')); break; }
|
|
1874
2029
|
}
|
|
1875
|
-
|
|
2030
|
+
const m = ui.resolverMascote(pedido);
|
|
2031
|
+
if (!m) { console.log(c.err(` Não conheço "${pedido}".`) + c.dim(` São: ${ui.MASCOTES.join(', ')}`)); break; }
|
|
1876
2032
|
state.config.mascot = m;
|
|
1877
2033
|
cfg.saveConfig({ mascot: m });
|
|
2034
|
+
const info = ui.mascoteInfo(m);
|
|
1878
2035
|
console.log('');
|
|
1879
|
-
ui.mascot(m)
|
|
1880
|
-
console.log(c.
|
|
2036
|
+
const arte = ui.mascot(m);
|
|
2037
|
+
arte.forEach((l, i) => console.log(' ' + l + (i === 0 ? ' ' + c.bold(c.white(info.nome))
|
|
2038
|
+
: i === 1 ? ' ' + c.dim(`"${info.frase}"`) : '')));
|
|
2039
|
+
console.log(c.ok(`\n Mascote: ${m}`));
|
|
1881
2040
|
break;
|
|
1882
2041
|
}
|
|
1883
2042
|
|
|
1884
2043
|
case 'config': {
|
|
1885
2044
|
console.log('');
|
|
1886
2045
|
console.log(` ${c.muted('nome')} ${c.white(state.config.userName || '(nenhum)')}`);
|
|
1887
|
-
console.log(` ${c.muted('mascote')} ${c.white(state.config.mascot || 'primo')}`);
|
|
2046
|
+
console.log(` ${c.muted('mascote')} ${c.white(state.config.mascot || 'primo')}${c.dim(state.config.reacoes === false ? ' · sem reações' : ' · com reações')}`);
|
|
1888
2047
|
console.log(` ${c.muted('esforço')} ${c.white(state.config.effort)}${c.dim(' · /effort muda')}`);
|
|
1889
2048
|
console.log(` ${c.muted('modelo')} ${c.white(state.config.model)}${c.dim(' (vem do esforço)')}`);
|
|
1890
2049
|
console.log(` ${c.muted('modo')} ${c.white(state.mode)}`);
|
|
@@ -1923,6 +2082,78 @@ async function handleSlash(text) {
|
|
|
1923
2082
|
state.gravador.zerar(); console.log(c.ok('Conversa limpa.')); break;
|
|
1924
2083
|
case 'cls': process.stdout.write('\x1Bc'); break;
|
|
1925
2084
|
|
|
2085
|
+
/* O /status olha só o servidor. O /doctor olha tudo o que costuma
|
|
2086
|
+
ser o motivo de "não funciona" — e cada ✗ diz como consertar. */
|
|
2087
|
+
case 'doctor': case 'medico': case 'médico': {
|
|
2088
|
+
const sp = new Spinner('conferindo').start();
|
|
2089
|
+
const linhas = await extra.diagnosticar({
|
|
2090
|
+
saude: () => api.getHealth(state.config.server, state.config.token),
|
|
2091
|
+
provedor: () => provedorLocal.atual(),
|
|
2092
|
+
conferirChave: (id, chave) => provedorLocal.conferir(id, chave),
|
|
2093
|
+
cliInstalado: (id) => require('../lib/local/motor-cli').instalado(id),
|
|
2094
|
+
acharChrome: () => require('../lib/browser').findChrome(),
|
|
2095
|
+
raiz: cfg.ROOT_DIR,
|
|
2096
|
+
arquivoConfig: cfg.CONFIG_FILE,
|
|
2097
|
+
});
|
|
2098
|
+
sp.stop();
|
|
2099
|
+
console.log('');
|
|
2100
|
+
for (const l of linhas) {
|
|
2101
|
+
const marca = l.ok === true ? c.ok('✓') : l.ok === false ? c.err('✗') : c.dim('·');
|
|
2102
|
+
console.log(` ${marca} ${ui.padRight(c.white(l.item), 18)}${ui.truncate(c.muted(l.detalhe || ''), Math.max(20, (process.stdout.columns || 80) - 23))}`);
|
|
2103
|
+
if (l.conserto) console.log(` ${c.dim('→ ' + l.conserto)}`);
|
|
2104
|
+
}
|
|
2105
|
+
const ruins = linhas.filter((l) => l.ok === false).length;
|
|
2106
|
+
console.log(ruins ? c.warn(`\n ${ruins} ${ruins === 1 ? 'coisa para arrumar' : 'coisas para arrumar'}.`) : c.ok('\n Tudo certo.'));
|
|
2107
|
+
break;
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
case 'custo': case 'cost': case 'tokens': {
|
|
2111
|
+
const { linhas, total } = extra.resumoCusto(contadorDaSessao);
|
|
2112
|
+
if (!linhas.length) { console.log(c.muted(' Nenhum pedido ao modelo nesta sessão ainda.')); break; }
|
|
2113
|
+
console.log('');
|
|
2114
|
+
for (const l of linhas) {
|
|
2115
|
+
console.log(` ${c.white(ui.padRight(l.provedor, 22))}${c.muted(ui.padRight(l.pedidos + (l.pedidos === 1 ? ' pedido' : ' pedidos'), 12))}`
|
|
2116
|
+
+ `${c.dim('enviou ')}${c.white(extra.fmtNumero(l.enviados))}${c.dim(' · recebeu ')}${c.white(extra.fmtNumero(l.recebidos))}${c.dim(' tokens' + (l.exato ? '' : ' (estimado)'))}`);
|
|
2117
|
+
}
|
|
2118
|
+
if (linhas.length > 1) {
|
|
2119
|
+
console.log(` ${c.white(ui.padRight('Total', 22))}${c.muted(ui.padRight(total.pedidos + ' pedidos', 12))}`
|
|
2120
|
+
+ `${c.dim('enviou ')}${c.white(extra.fmtNumero(total.enviados))}${c.dim(' · recebeu ')}${c.white(extra.fmtNumero(total.recebidos))}`);
|
|
2121
|
+
}
|
|
2122
|
+
if (linhas.some((l) => !l.exato)) console.log(c.dim('\n Estimado = caracteres ÷ 4. Conta cada pedido inteiro, com a conversa que vai junto.'));
|
|
2123
|
+
break;
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
case 'copiar': case 'copy': {
|
|
2127
|
+
const r = extra.copiar(state.lastResponse);
|
|
2128
|
+
if (r.ok) console.log(c.ok(' Copiado.') + c.dim(` ${state.lastResponse.length} caracteres · é só colar`));
|
|
2129
|
+
else console.log(c.muted(` Não copiei: ${r.error}.`));
|
|
2130
|
+
break;
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
case 'historico': case 'histórico': {
|
|
2134
|
+
const achados = extra.buscarHistorico(cfg.HISTORY_FILE, rest, 20);
|
|
2135
|
+
if (!achados.length) {
|
|
2136
|
+
console.log(c.muted(rest ? ` Nada com "${rest}" nos seus pedidos.` : ' Nenhum pedido guardado ainda.'));
|
|
2137
|
+
console.log(c.dim(' /chats mostra as conversas inteiras da sua conta.'));
|
|
2138
|
+
break;
|
|
2139
|
+
}
|
|
2140
|
+
console.log('');
|
|
2141
|
+
const largura = Math.max(30, (process.stdout.columns || 80) - 34);
|
|
2142
|
+
for (const e of achados) {
|
|
2143
|
+
const quando = new Date(e.ts).toLocaleString('pt-BR', { dateStyle: 'short', timeStyle: 'short' });
|
|
2144
|
+
console.log(` ${c.dim(quando)} ${c.muted(ui.padRight(ui.truncate(e.project || e.mode || '', 14), 14))} `
|
|
2145
|
+
+ ui.truncate(c.white(String(e.prompt).replace(/\s+/g, ' ')), largura));
|
|
2146
|
+
}
|
|
2147
|
+
console.log(c.dim(`\n ${achados.length === 20 ? 'Os 20 mais recentes' : achados.length + ' pedido(s)'}${rest ? ` com "${rest}"` : ''} · /historico <termo> filtra · /chats abre conversas`));
|
|
2148
|
+
break;
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
case 'atalhos': case 'shortcuts': case 'teclas': {
|
|
2152
|
+
console.log('');
|
|
2153
|
+
for (const [tecla, faz] of extra.ATALHOS) console.log(` ${c.brand(ui.padRight(tecla, 20))}${c.muted(faz)}`);
|
|
2154
|
+
break;
|
|
2155
|
+
}
|
|
2156
|
+
|
|
1926
2157
|
case 'status': {
|
|
1927
2158
|
const sp = new Spinner('verificando').start();
|
|
1928
2159
|
try {
|
|
@@ -2093,7 +2324,8 @@ async function handleSlash(text) {
|
|
|
2093
2324
|
return resposta;
|
|
2094
2325
|
}
|
|
2095
2326
|
const plan = prepararPasta(texto);
|
|
2096
|
-
|
|
2327
|
+
// Pasta pendente (projeto que ainda vai nascer) é o ~/primocode: sem foto.
|
|
2328
|
+
checkpoint.iniciar(texto, plan.pending ? null : plan.dir);
|
|
2097
2329
|
const resultado = await runAct(texto, {
|
|
2098
2330
|
instrucao: vozModo.COMO_FALAR,
|
|
2099
2331
|
server: state.config.server,
|
|
@@ -2108,6 +2340,7 @@ async function handleSlash(text) {
|
|
|
2108
2340
|
controle: state.controle,
|
|
2109
2341
|
permissoes: state.config.permissoes,
|
|
2110
2342
|
});
|
|
2343
|
+
checkpoint.encerrar();
|
|
2111
2344
|
const resposta = (resultado.assistantMessage && resultado.assistantMessage.content) || '';
|
|
2112
2345
|
// A conversa é a MESMA do terminal: sair do modo voz e
|
|
2113
2346
|
// continuar digitando não pode começar do zero.
|
|
@@ -2217,18 +2450,7 @@ async function handleSlash(text) {
|
|
|
2217
2450
|
}
|
|
2218
2451
|
const sp = new Spinner('abrindo a janela').start();
|
|
2219
2452
|
try {
|
|
2220
|
-
janelaAberta = await appLib.abrir(
|
|
2221
|
-
versao: VERSION,
|
|
2222
|
-
enviar: (t) => sendPrompt(t),
|
|
2223
|
-
barra: (t) => handleSlash(t),
|
|
2224
|
-
interromper,
|
|
2225
|
-
estado: () => ({
|
|
2226
|
-
pasta: state.workDir ? shortPath(state.workDir) : null,
|
|
2227
|
-
projeto: state.currentProject,
|
|
2228
|
-
modo: state.mode,
|
|
2229
|
-
esforco: state.config.effort,
|
|
2230
|
-
}),
|
|
2231
|
-
});
|
|
2453
|
+
janelaAberta = await appLib.abrir(opcoesDaJanela());
|
|
2232
2454
|
sp.stop();
|
|
2233
2455
|
const j = janelaAberta.janela || {};
|
|
2234
2456
|
console.log(c.ok(' Janela aberta.')
|
|
@@ -2252,6 +2474,37 @@ async function handleSlash(text) {
|
|
|
2252
2474
|
break;
|
|
2253
2475
|
}
|
|
2254
2476
|
|
|
2477
|
+
/* A IDE: "um app ao invés de CLI, aí fica uma IDE e uma CLI".
|
|
2478
|
+
Mora no MESMO servidor do /rc (lib/app.js + lib/ide.js): com a
|
|
2479
|
+
janela do chat já aberta, o /ide só abre outra tela nele — mesma
|
|
2480
|
+
chave, mesmo agente, a mesma conversa nas duas. */
|
|
2481
|
+
case 'ide': {
|
|
2482
|
+
if (janelaAberta && janelaAberta.urlIde) {
|
|
2483
|
+
await appLib.abrirJanela(janelaAberta.urlIde);
|
|
2484
|
+
console.log(c.ok(' IDE aberta.') + c.muted(' (no mesmo servidor da janela que já estava aberta)'));
|
|
2485
|
+
console.log(c.dim(` ${janelaAberta.urlIde}`));
|
|
2486
|
+
break;
|
|
2487
|
+
}
|
|
2488
|
+
const sp = new Spinner('abrindo a IDE').start();
|
|
2489
|
+
try {
|
|
2490
|
+
janelaAberta = await appLib.abrir(opcoesDaJanela({ abrirEm: 'ide' }));
|
|
2491
|
+
sp.stop();
|
|
2492
|
+
const j = janelaAberta.janela || {};
|
|
2493
|
+
console.log(c.ok(' IDE aberta.')
|
|
2494
|
+
+ c.muted(j.modo === 'janela' ? ` (${j.navegador})`
|
|
2495
|
+
: j.modo === 'aba' ? ' (aba do navegador padrão)' : ''));
|
|
2496
|
+
console.log(c.dim(` ${janelaAberta.urlIde}`));
|
|
2497
|
+
if (j.modo === 'nenhum') {
|
|
2498
|
+
console.log(c.warn(' Não consegui abrir sozinho — copie o endereço acima no navegador.'));
|
|
2499
|
+
}
|
|
2500
|
+
console.log(c.muted(` Pasta: ${shortPath(projectDir())} · o terminal continua funcionando, os dois mostram a mesma coisa.`));
|
|
2501
|
+
} catch (e) {
|
|
2502
|
+
sp.stop();
|
|
2503
|
+
console.log(c.err(` Não subiu: ${e.message}`));
|
|
2504
|
+
}
|
|
2505
|
+
break;
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2255
2508
|
/* A conta do Conecta Primo, sem navegador: e-mail e senha aqui
|
|
2256
2509
|
mesmo. O login inteiro mora em lib/conta.js; aqui é a tela dele. */
|
|
2257
2510
|
case 'login': {
|
|
@@ -2555,18 +2808,7 @@ async function main() {
|
|
|
2555
2808
|
process.exit(r.ok ? 0 : 1);
|
|
2556
2809
|
}
|
|
2557
2810
|
|
|
2558
|
-
const janela = await appLib.abrir(
|
|
2559
|
-
versao: VERSION,
|
|
2560
|
-
enviar: (t) => sendPrompt(t),
|
|
2561
|
-
barra: (t) => handleSlash(t),
|
|
2562
|
-
interromper,
|
|
2563
|
-
estado: () => ({
|
|
2564
|
-
pasta: state.workDir ? shortPath(state.workDir) : null,
|
|
2565
|
-
projeto: state.currentProject,
|
|
2566
|
-
modo: state.mode,
|
|
2567
|
-
esforco: state.config.effort,
|
|
2568
|
-
}),
|
|
2569
|
-
});
|
|
2811
|
+
const janela = await appLib.abrir(opcoesDaJanela());
|
|
2570
2812
|
janelaAberta = janela;
|
|
2571
2813
|
console.log(c.brand(` PrimoCode`) + c.muted(` ${janela.url.split('?')[0]}`));
|
|
2572
2814
|
// Sem readline não há nada segurando o laço de eventos além do
|
|
@@ -2577,8 +2819,13 @@ async function main() {
|
|
|
2577
2819
|
return;
|
|
2578
2820
|
}
|
|
2579
2821
|
|
|
2822
|
+
/* `primo ide`: o terminal de sempre, com a IDE já aberta ao lado.
|
|
2823
|
+
Só a palavra SOZINHA: `primo ide para gatos` continua sendo um pedido
|
|
2824
|
+
ao agente (modo um-tiro), como sempre foi. */
|
|
2825
|
+
const abrirIde = args.length === 1 && args[0].toLowerCase() === 'ide';
|
|
2826
|
+
|
|
2580
2827
|
// Modo one-shot: primocode "crie um jogo de carro"
|
|
2581
|
-
if (args.length && !args[0].startsWith('-')) {
|
|
2828
|
+
if (args.length && !args[0].startsWith('-') && !abrirIde) {
|
|
2582
2829
|
const fileFlag = args.indexOf('-f');
|
|
2583
2830
|
const words = fileFlag >= 0 ? args.slice(0, fileFlag) : args;
|
|
2584
2831
|
const promptText = words.join(' ');
|
|
@@ -2757,6 +3004,9 @@ async function main() {
|
|
|
2757
3004
|
// O que sobrou de adiantado não era resposta de nada: vira comando,
|
|
2758
3005
|
// na ordem em que foi digitado. Nada se perde, só esperou a vez.
|
|
2759
3006
|
segurando = false;
|
|
3007
|
+
// Depois da abertura (e do login dela): a IDE sem conta abriria
|
|
3008
|
+
// mandando pedidos que o servidor recusa.
|
|
3009
|
+
if (abrirIde) queue.push('/ide');
|
|
2760
3010
|
while (adiantadas.length) queue.push(adiantadas.shift());
|
|
2761
3011
|
if (queue.length) drain();
|
|
2762
3012
|
else if (!closed) rl.prompt();
|