primocode 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -29
- package/bin/primocode.js +135 -133
- package/lib/config.js +16 -4
- package/lib/jarvis.js +51 -649
- package/package.json +10 -5
package/README.md
CHANGED
|
@@ -1,11 +1,33 @@
|
|
|
1
|
-
# PrimoCode
|
|
1
|
+
# PrimoCode v3.1
|
|
2
2
|
|
|
3
|
-
Assistente de engenharia com
|
|
3
|
+
Assistente de engenharia com IA multi-agente, RAG e memoria de longo prazo.
|
|
4
|
+
|
|
5
|
+
## Sistema Multi-Agente + RAG
|
|
6
|
+
|
|
7
|
+
### Pipeline de 5 Agentes
|
|
8
|
+
|
|
9
|
+
1. **Prompt Engineer** — Estrutura e otimiza o prompt do usuario
|
|
10
|
+
2. **Construtor 1** — Gera prototipo funcional rapidamente
|
|
11
|
+
3. **Construtor 2** — Refina e otimiza o codigo
|
|
12
|
+
4. **Analisador** — Encontra bugs e vulnerabilidades
|
|
13
|
+
5. **Revisor** — Code review final e aprovacao
|
|
14
|
+
|
|
15
|
+
### RAG (Retrieval Augmented Generation)
|
|
16
|
+
|
|
17
|
+
- **Indexacao automatica** do projeto a cada hora
|
|
18
|
+
- **Contexto enriquecido** com arquivos relevantes
|
|
19
|
+
- **Busca semantica** via TF-IDF + cosine similarity
|
|
20
|
+
|
|
21
|
+
### Memoria de Longo Prazo
|
|
22
|
+
|
|
23
|
+
- **Aprende preferencias**: stack tecnologico, estilo UI, complexidade
|
|
24
|
+
- **Contexto do projeto**: tecnologias, estrutura de arquivos
|
|
25
|
+
- **Historico indexado**: codigo gerado disponivel para consulta
|
|
4
26
|
|
|
5
27
|
## Modos
|
|
6
28
|
|
|
7
|
-
- **Conecta Primo AI** —
|
|
8
|
-
- **Chat** — Conversa direta
|
|
29
|
+
- **Conecta Primo AI** — Pipeline multi-agente completo com RAG
|
|
30
|
+
- **Chat** — Conversa direta com modelo unico
|
|
9
31
|
- **JARVIS** — Interacao por voz e visao
|
|
10
32
|
|
|
11
33
|
## Instalacao
|
|
@@ -14,49 +36,31 @@ Assistente de engenharia com inteligencia artificial.
|
|
|
14
36
|
npm install -g primocode
|
|
15
37
|
```
|
|
16
38
|
|
|
17
|
-
Ou via script:
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
# Windows
|
|
21
|
-
iwr -useb https://gist.githubusercontent.com/ConectaPrimoAI/primocode-install/raw/install.cmd | iex
|
|
22
|
-
|
|
23
|
-
# Linux / macOS / WSL / Termux
|
|
24
|
-
curl -fsSL https://gist.githubusercontent.com/ConectaPrimoAI/primocode-install/raw/install.sh | bash
|
|
25
|
-
```
|
|
26
|
-
|
|
27
39
|
## Uso
|
|
28
40
|
|
|
29
41
|
```bash
|
|
30
|
-
primocode # Modo interativo
|
|
31
|
-
primocode "crie um site"
|
|
42
|
+
primocode # Modo interativo (Multi-Agente + RAG)
|
|
43
|
+
primocode "crie um site completo" # Pipeline completo com contexto
|
|
32
44
|
primocode --chat "explique REST" # Modo conversa
|
|
33
45
|
primocode --jarvis # Voz e visao
|
|
34
|
-
primocode -f app.js "analise isto" # Anexa arquivo
|
|
35
46
|
```
|
|
36
47
|
|
|
37
48
|
## Comandos
|
|
38
49
|
|
|
39
50
|
| Comando | Funcao |
|
|
40
51
|
|---------|--------|
|
|
41
|
-
| `/mode` | Alterna entre
|
|
52
|
+
| `/mode` | Alterna entre modos |
|
|
42
53
|
| `/attach` | Anexa arquivo ao contexto |
|
|
43
|
-
| `/clear` | Limpa historico
|
|
54
|
+
| `/clear` | Limpa historico |
|
|
55
|
+
| `/status` | Mostra status do servidor, RAG e memoria |
|
|
44
56
|
| `/uninstall` | Remove PrimoCode |
|
|
45
|
-
| `/help` |
|
|
57
|
+
| `/help` | Ajuda |
|
|
46
58
|
| `/exit` | Encerra |
|
|
47
59
|
|
|
48
|
-
## Configuracao
|
|
49
|
-
|
|
50
|
-
Defina variaveis de ambiente:
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
export PRIMOCODE_SERVER=https://seu-servidor.com
|
|
54
|
-
export PRIMOCODE_MODEL=main
|
|
55
|
-
```
|
|
56
|
-
|
|
57
60
|
## Requisitos
|
|
58
61
|
|
|
59
62
|
- Node.js 18.17+
|
|
63
|
+
- Servidor PrimoCode com API Groq
|
|
60
64
|
|
|
61
65
|
---
|
|
62
66
|
|
package/bin/primocode.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* PrimoCode
|
|
4
|
-
* Conecta Primo AI
|
|
5
|
-
* Chat → conversa direta
|
|
6
|
-
* JARVIS → voz e visão
|
|
3
|
+
* PrimoCode v3.0 — Cliente Multi-Agente
|
|
4
|
+
* Conecta Primo AI: Engenharia completa com pipeline de 5 agentes
|
|
7
5
|
*/
|
|
8
6
|
|
|
9
7
|
'use strict';
|
|
@@ -12,15 +10,13 @@ const readline = require('readline');
|
|
|
12
10
|
const path = require('path');
|
|
13
11
|
const fs = require('fs');
|
|
14
12
|
const os = require('os');
|
|
15
|
-
const { spawn } = require('child_process');
|
|
13
|
+
const { spawn, execSync } = require('child_process');
|
|
16
14
|
|
|
17
15
|
const { c, welcomeBox, Spinner } = require('../lib/ui');
|
|
18
16
|
const api = require('../lib/api');
|
|
19
17
|
const tools = require('../lib/tools');
|
|
20
18
|
const cfg = require('../lib/config');
|
|
21
|
-
const { runAct } = require('../lib/act');
|
|
22
19
|
|
|
23
|
-
// ---- Estado ----
|
|
24
20
|
const state = {
|
|
25
21
|
config: cfg.loadConfig(),
|
|
26
22
|
history: [],
|
|
@@ -29,7 +25,6 @@ const state = {
|
|
|
29
25
|
lastResponse: '',
|
|
30
26
|
};
|
|
31
27
|
|
|
32
|
-
// ---- Ajuda ----
|
|
33
28
|
const HELP = `
|
|
34
29
|
${c.bold('Comandos')}
|
|
35
30
|
/mode <nome> Conecta Primo AI | Chat | JARVIS
|
|
@@ -41,7 +36,7 @@ ${c.bold('Comandos')}
|
|
|
41
36
|
/exit sair
|
|
42
37
|
|
|
43
38
|
${c.bold('Modos')}
|
|
44
|
-
${c.brand('Conecta Primo AI')} Engenharia completa
|
|
39
|
+
${c.brand('Conecta Primo AI')} Engenharia completa (5 agentes)
|
|
45
40
|
${c.brand('Chat')} Conversa direta
|
|
46
41
|
${c.brand('JARVIS')} Voz e visao
|
|
47
42
|
`;
|
|
@@ -55,34 +50,90 @@ function makePrompt() {
|
|
|
55
50
|
}
|
|
56
51
|
function refreshPrompt() { if (rl) rl.setPrompt(makePrompt()); }
|
|
57
52
|
|
|
58
|
-
// ----
|
|
59
|
-
async function
|
|
60
|
-
|
|
61
|
-
if (state.attachedFiles.length) {
|
|
62
|
-
const parts = state.attachedFiles.map(f => `Arquivo: ${f.path}\n\`\`\`\n${f.content}\n\`\`\``);
|
|
63
|
-
promptWithAttach = parts.join('\n\n') + '\n\n' + userText;
|
|
64
|
-
}
|
|
53
|
+
// ---- Multi-Agent Pipeline ----
|
|
54
|
+
async function runMultiAgent(userText) {
|
|
55
|
+
const sp = new Spinner('Iniciando pipeline multi-agente').start();
|
|
65
56
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
57
|
+
try {
|
|
58
|
+
sp.update('Analisando contexto do projeto...');
|
|
59
|
+
|
|
60
|
+
const response = await fetch(state.config.server + '/api/multi-agent', {
|
|
61
|
+
method: 'POST',
|
|
62
|
+
headers: { 'Content-Type': 'application/json' },
|
|
63
|
+
body: JSON.stringify({
|
|
64
|
+
prompt: userText,
|
|
65
|
+
maxIterations: 3,
|
|
66
|
+
projectPath: process.cwd(),
|
|
67
|
+
}),
|
|
71
68
|
});
|
|
72
|
-
state.history.push({ role: 'user', content: userText });
|
|
73
|
-
state.history.push({ role: 'assistant', content: r.assistantMessage.content });
|
|
74
|
-
state.lastResponse = r.assistantMessage.content;
|
|
75
|
-
cfg.appendHistory({ mode: 'Conecta Primo AI', prompt: userText, response: r.assistantMessage.content });
|
|
76
|
-
} else {
|
|
77
|
-
await runChat(promptWithAttach, userText);
|
|
78
|
-
}
|
|
79
69
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
70
|
+
sp.stop();
|
|
71
|
+
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
throw new Error(`HTTP ${response.status}`);
|
|
74
|
+
}
|
|
83
75
|
|
|
84
|
-
|
|
85
|
-
|
|
76
|
+
const result = await response.json();
|
|
77
|
+
|
|
78
|
+
if (result.error) {
|
|
79
|
+
console.log(c.err(`\nErro: ${result.error}`));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Mostrar contexto RAG
|
|
84
|
+
if (result.ragContext) {
|
|
85
|
+
console.log(c.muted('\n ── Contexto RAG ──'));
|
|
86
|
+
console.log(c.muted(` ${result.ragContext.substring(0, 120)}...`));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Mostrar progresso dos agentes
|
|
90
|
+
console.log(c.muted('\n ── Pipeline Multi-Agente ──'));
|
|
91
|
+
for (const phase of result.phases) {
|
|
92
|
+
const icon = phase.agent.includes('Revisor') ? '✓' :
|
|
93
|
+
phase.agent.includes('Analisador') ? '🔍' :
|
|
94
|
+
phase.agent.includes('Correcao') ? '🔧' : '●';
|
|
95
|
+
console.log(c.muted(` ${icon} ${phase.agent}`));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Mostrar resultado do revisor
|
|
99
|
+
const reviewPhase = result.phases.find(p => p.agent === 'Revisor Final');
|
|
100
|
+
if (reviewPhase) {
|
|
101
|
+
const approved = reviewPhase.output.includes('APROVADO');
|
|
102
|
+
const score = reviewPhase.output.match(/nota\s*(\d+)/)?.[1] || '?';
|
|
103
|
+
console.log(c.muted(` ──`));
|
|
104
|
+
console.log(approved ? c.ok(` ✓ Aprovado (nota ${score}/10)`) : c.warn(` ⚠ Revisado (nota ${score}/10)`));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Extrair e mostrar codigo
|
|
108
|
+
const code = result.finalCode;
|
|
109
|
+
state.lastResponse = code;
|
|
110
|
+
|
|
111
|
+
// Mostrar arquivos gerados
|
|
112
|
+
if (result.files && result.files.length) {
|
|
113
|
+
console.log(c.muted(`\n Arquivos gerados:`));
|
|
114
|
+
for (const f of result.files) {
|
|
115
|
+
console.log(c.muted(` 📄 ${f.path}`));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Mostrar codigo (primeiros 2000 chars)
|
|
120
|
+
console.log('\n' + c.white(code.substring(0, 2000)));
|
|
121
|
+
if (code.length > 2000) {
|
|
122
|
+
console.log(c.muted(`\n ... +${code.length - 2000} caracteres`));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Perguntar se quer deploy
|
|
126
|
+
console.log(c.muted(`\n Use /deploy para abrir no navegador ou instalar dependencias`));
|
|
127
|
+
|
|
128
|
+
cfg.appendHistory({ mode: 'Conecta Primo AI', prompt: userText, response: code });
|
|
129
|
+
|
|
130
|
+
} catch (err) {
|
|
131
|
+
sp.stop();
|
|
132
|
+
console.log(c.err(`\nFalha no pipeline: ${err.message}`));
|
|
133
|
+
console.log(c.muted(' Tentando modo normal...'));
|
|
134
|
+
await runChat(userText, userText);
|
|
135
|
+
}
|
|
136
|
+
}async function runChat(promptForModel, userTextForMemory) {
|
|
86
137
|
const sp = new Spinner('Processando').start();
|
|
87
138
|
let started = false;
|
|
88
139
|
let text = '';
|
|
@@ -90,7 +141,7 @@ async function runChat(promptForModel, userTextForMemory) {
|
|
|
90
141
|
await api.requestStream(state.config.server, '/api/normal/stream', {
|
|
91
142
|
prompt: promptForModel,
|
|
92
143
|
history: state.history.slice(-20),
|
|
93
|
-
model: state.
|
|
144
|
+
model: cfg.resolveModel(state.mode),
|
|
94
145
|
}, (evt) => {
|
|
95
146
|
if (evt.erro) {
|
|
96
147
|
if (!started) sp.stop();
|
|
@@ -117,7 +168,25 @@ async function runChat(promptForModel, userTextForMemory) {
|
|
|
117
168
|
cfg.appendHistory({ mode: 'Chat', prompt: userTextForMemory, response: text });
|
|
118
169
|
}
|
|
119
170
|
|
|
120
|
-
// ----
|
|
171
|
+
// ---- Envio Principal ----
|
|
172
|
+
async function sendPrompt(userText) {
|
|
173
|
+
let promptWithAttach = userText;
|
|
174
|
+
if (state.attachedFiles.length) {
|
|
175
|
+
const parts = state.attachedFiles.map(f => `Arquivo: ${f.path}\n\`\`\`\n${f.content}\n\`\`\``);
|
|
176
|
+
promptWithAttach = parts.join('\n\n') + '\n\n' + userText;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (state.mode === 'Conecta Primo AI') {
|
|
180
|
+
await runMultiAgent(promptWithAttach);
|
|
181
|
+
} else {
|
|
182
|
+
await runChat(promptWithAttach, userText);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
state.attachedFiles = [];
|
|
186
|
+
refreshPrompt();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ---- Slash Commands ----
|
|
121
190
|
async function handleSlash(line) {
|
|
122
191
|
const [cmd, ...rest] = line.trim().split(/\s+/);
|
|
123
192
|
const arg = rest.join(' ').trim();
|
|
@@ -132,22 +201,30 @@ async function handleSlash(line) {
|
|
|
132
201
|
console.log(c.muted(' Conversa limpa'));
|
|
133
202
|
return;
|
|
134
203
|
case '/version':
|
|
135
|
-
console.log(c.muted(` PrimoCode`));
|
|
204
|
+
console.log(c.muted(` PrimoCode Multi-Agent v3.0`));
|
|
136
205
|
console.log(c.muted(` Servidor: ${state.config.server}`));
|
|
137
206
|
return;
|
|
138
|
-
|
|
207
|
+
case '/status': {
|
|
139
208
|
const sp = new Spinner('Verificando').start();
|
|
140
209
|
try {
|
|
141
|
-
const
|
|
210
|
+
const res = await fetch(state.config.server + '/health');
|
|
211
|
+
const data = await res.json();
|
|
142
212
|
sp.stop();
|
|
143
|
-
|
|
213
|
+
if (data.status === 'ok') {
|
|
214
|
+
console.log(c.ok(' Online') + c.muted(` ${state.config.server}`));
|
|
215
|
+
console.log(c.muted(` Sistema: ${data.system} v${data.version}`));
|
|
216
|
+
console.log(c.muted(` Agentes: ${data.agents?.join(', ')}`));
|
|
217
|
+
console.log(c.muted(` RAG: Indexacao automatica ativa`));
|
|
218
|
+
console.log(c.muted(` Memory: Aprendizado de padroes ativo`));
|
|
219
|
+
} else {
|
|
220
|
+
console.log(c.err(' Indisponivel'));
|
|
221
|
+
}
|
|
144
222
|
} catch (e) {
|
|
145
223
|
sp.stop();
|
|
146
224
|
console.log(c.err(' Indisponivel'));
|
|
147
225
|
}
|
|
148
226
|
return;
|
|
149
|
-
}
|
|
150
|
-
case '/jarvis': {
|
|
227
|
+
}case '/jarvis': {
|
|
151
228
|
try {
|
|
152
229
|
const jarvis = require('../lib/jarvis');
|
|
153
230
|
console.log(c.muted(' Iniciando JARVIS...'));
|
|
@@ -158,117 +235,44 @@ async function handleSlash(line) {
|
|
|
158
235
|
return;
|
|
159
236
|
}
|
|
160
237
|
case '/mode': {
|
|
161
|
-
const valid = ['Conecta Primo AI', 'Chat', 'JARVIS'
|
|
238
|
+
const valid = ['Conecta Primo AI', 'Chat', 'JARVIS'];
|
|
162
239
|
let target = arg;
|
|
163
|
-
if (
|
|
164
|
-
console.log(c.
|
|
165
|
-
|
|
240
|
+
if (!valid.includes(target)) {
|
|
241
|
+
console.log(c.warn(' /mode Conecta Primo AI | Chat | JARVIS'));
|
|
242
|
+
return;
|
|
166
243
|
}
|
|
167
|
-
if (!valid.includes(target)) { console.log(c.warn(' /mode Conecta Primo AI | Chat | JARVIS')); return; }
|
|
168
244
|
state.mode = target;
|
|
245
|
+
state.config.model = cfg.resolveModel(target);
|
|
169
246
|
console.log(c.muted(` Modo: ${target}`));
|
|
170
247
|
refreshPrompt();
|
|
171
248
|
return;
|
|
172
249
|
}
|
|
173
|
-
case '/model':
|
|
174
|
-
if (!arg) { console.log(c.muted(` Modelo: ${state.config.model}`)); return; }
|
|
175
|
-
state.config.model = arg;
|
|
176
|
-
cfg.saveConfig({ model: arg });
|
|
177
|
-
console.log(c.muted(` Modelo: ${arg}`));
|
|
178
|
-
return;
|
|
179
250
|
case '/attach': {
|
|
180
251
|
if (!arg) { console.log(c.warn(' /attach <arquivo>')); return; }
|
|
181
252
|
try {
|
|
182
253
|
const content = tools.readFileSafe(arg);
|
|
183
254
|
state.attachedFiles.push({ path: arg, content });
|
|
184
|
-
console.log(c.muted(` Anexado: ${arg}
|
|
255
|
+
console.log(c.muted(` Anexado: ${arg}`));
|
|
185
256
|
refreshPrompt();
|
|
186
257
|
} catch (e) { console.log(c.err(` ${e.message}`)); }
|
|
187
258
|
return;
|
|
188
259
|
}
|
|
189
260
|
case '/detach':
|
|
190
261
|
state.attachedFiles = []; console.log(c.muted(' Anexos removidos')); refreshPrompt(); return;
|
|
191
|
-
case '/files':
|
|
192
|
-
if (!state.attachedFiles.length) console.log(c.muted(' (Nenhum)'));
|
|
193
|
-
else for (const f of state.attachedFiles) console.log(c.muted(` ${f.path} (${tools.fmtBytes(Buffer.byteLength(f.content, 'utf8'))})`));
|
|
194
|
-
return;
|
|
195
|
-
case '/tree': {
|
|
196
|
-
const d = parseInt(arg, 10) || 2;
|
|
197
|
-
const r = tools.listDir('.', d);
|
|
198
|
-
console.log(r.ok ? c.muted(r.tree) : c.err(` ${r.error}`));
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
case '/read': {
|
|
202
|
-
if (!arg) { console.log(c.warn(' /read <arquivo>')); return; }
|
|
203
|
-
try {
|
|
204
|
-
const content = tools.readFileSafe(arg);
|
|
205
|
-
for (const line of content.split('\n')) console.log(c.muted(' ') + c.white(line));
|
|
206
|
-
} catch (e) { console.log(c.err(` ${e.message}`)); }
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
case '/write': {
|
|
210
|
-
if (!arg) { console.log(c.warn(' /write <arquivo>')); return; }
|
|
211
|
-
if (!state.lastResponse) { console.log(c.warn(' Sem resposta anterior')); return; }
|
|
212
|
-
const blocks = tools.extractCodeBlocks(state.lastResponse);
|
|
213
|
-
const content = blocks.length ? blocks[0].content : state.lastResponse;
|
|
214
|
-
const r = tools.writeFile(arg, content);
|
|
215
|
-
if (r.ok) console.log(c.muted(` ${r.created ? 'Criado' : 'Atualizado'} · ${r.path} · ${tools.fmtBytes(r.bytes)}`));
|
|
216
|
-
else console.log(c.err(` ${r.error}`));
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
case '/apply': {
|
|
220
|
-
if (!state.lastResponse) { console.log(c.warn(' Sem resposta anterior')); return; }
|
|
221
|
-
const blocks = tools.extractCodeBlocks(state.lastResponse);
|
|
222
|
-
const withFile = blocks.filter(b => b.filename);
|
|
223
|
-
if (!withFile.length) { console.log(c.warn(' Nenhum bloco com // filename:')); return; }
|
|
224
|
-
for (const b of withFile) {
|
|
225
|
-
const r = tools.writeFile(b.filename, b.content);
|
|
226
|
-
if (r.ok) console.log(c.muted(` ${r.created ? 'Criado' : 'Atualizado'} · ${r.path} · ${tools.fmtBytes(r.bytes)}`));
|
|
227
|
-
else console.log(c.err(` ${b.filename}: ${r.error}`));
|
|
228
|
-
}
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
case '/run': {
|
|
232
|
-
if (!arg) { console.log(c.warn(' /run <cmd>')); return; }
|
|
233
|
-
console.log(c.muted(` $ ${arg}`));
|
|
234
|
-
const r = tools.runShellSync(arg);
|
|
235
|
-
if (r.stdout) process.stdout.write(r.stdout);
|
|
236
|
-
if (r.stderr) process.stdout.write(c.err(r.stderr));
|
|
237
|
-
console.log(r.ok ? c.muted(' Concluido') : c.err(` Falhou`));
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
case '/open': {
|
|
241
|
-
if (!arg) { console.log(c.warn(' /open <arquivo|url>')); return; }
|
|
242
|
-
const r = tools.openInBrowser(arg);
|
|
243
|
-
console.log(r.ok ? c.muted(` ${r.url}`) : c.err(` ${r.error}`));
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
262
|
case '/uninstall': {
|
|
247
263
|
console.log(c.muted(' Removendo PrimoCode...'));
|
|
248
|
-
const installDir = path.join(os.homedir(), '.primocode');
|
|
249
|
-
const globalLink = path.join(os.homedir(), '.local/bin/primocode');
|
|
250
264
|
try {
|
|
265
|
+
const installDir = path.join(os.homedir(), '.primocode');
|
|
266
|
+
const globalLink = path.join(os.homedir(), '.local/bin/primocode');
|
|
251
267
|
if (fs.existsSync(globalLink)) fs.unlinkSync(globalLink);
|
|
252
|
-
if (fs.existsSync(installDir)) {
|
|
253
|
-
fs.rmSync(installDir, { recursive: true, force: true });
|
|
254
|
-
}
|
|
268
|
+
if (fs.existsSync(installDir)) fs.rmSync(installDir, { recursive: true, force: true });
|
|
255
269
|
console.log(c.ok(' PrimoCode removido'));
|
|
256
|
-
|
|
257
|
-
} catch (e) {
|
|
258
|
-
console.log(c.err(` Erro: ${e.message}`));
|
|
259
|
-
}
|
|
270
|
+
} catch (e) { console.log(c.err(` Erro: ${e.message}`)); }
|
|
260
271
|
return;
|
|
261
272
|
}
|
|
262
|
-
case '/
|
|
263
|
-
if (!
|
|
264
|
-
|
|
265
|
-
server: state.config.server,
|
|
266
|
-
model: state.config.model,
|
|
267
|
-
history: state.history,
|
|
268
|
-
});
|
|
269
|
-
state.history.push({ role: 'user', content: arg });
|
|
270
|
-
state.history.push({ role: 'assistant', content: r.assistantMessage.content });
|
|
271
|
-
state.lastResponse = r.assistantMessage.content;
|
|
273
|
+
case '/deploy': {
|
|
274
|
+
if (!state.lastResponse) { console.log(c.warn(' Sem codigo para deploy')); return; }
|
|
275
|
+
console.log(c.muted(' Deploy em desenvolvimento...'));
|
|
272
276
|
return;
|
|
273
277
|
}
|
|
274
278
|
default:
|
|
@@ -290,9 +294,8 @@ async function runOneShot(argv) {
|
|
|
290
294
|
else if (a === '--jarvis' || a === '-j') mode = 'JARVIS';
|
|
291
295
|
else if (a === '--attach' || a === '-f') attach.push(args[++i]);
|
|
292
296
|
else if (a === '--help' || a === '-h') { printCliHelp(); process.exit(0); }
|
|
293
|
-
else if (a === '--version' || a === '-v') { console.log('PrimoCode'); process.exit(0); }
|
|
297
|
+
else if (a === '--version' || a === '-v') { console.log('PrimoCode Multi-Agent v3.0'); process.exit(0); }
|
|
294
298
|
else if (a === '--server') state.config.server = args[++i];
|
|
295
|
-
else if (a === '--model') state.config.model = args[++i];
|
|
296
299
|
else prompt.push(a);
|
|
297
300
|
}
|
|
298
301
|
if (!prompt.length) return false;
|
|
@@ -308,11 +311,10 @@ async function runOneShot(argv) {
|
|
|
308
311
|
function printCliHelp() {
|
|
309
312
|
console.log(welcomeBox(state.config));
|
|
310
313
|
console.log(`${c.bold('Uso')}`);
|
|
311
|
-
console.log(` primocode Interativo (
|
|
312
|
-
console.log(` primocode "crie um site"
|
|
314
|
+
console.log(` primocode Interativo (Multi-Agente)`);
|
|
315
|
+
console.log(` primocode "crie um site" Pipeline completo`);
|
|
313
316
|
console.log(` primocode --chat "duvida" Modo conversa`);
|
|
314
317
|
console.log(` primocode --jarvis Voz e visao`);
|
|
315
|
-
console.log(` primocode -f app.js "analise" Anexa arquivo`);
|
|
316
318
|
console.log(HELP);
|
|
317
319
|
}
|
|
318
320
|
|
|
@@ -367,7 +369,7 @@ function startInteractive() {
|
|
|
367
369
|
const versionFlag = flags.some(a => a === '-v' || a === '--version');
|
|
368
370
|
const jarvisFlag = flags.some(a => a === '--jarvis' || a === '-j');
|
|
369
371
|
if (helpFlag) { printCliHelp(); process.exit(0); }
|
|
370
|
-
if (versionFlag) { console.log('PrimoCode'); process.exit(0); }
|
|
372
|
+
if (versionFlag) { console.log('PrimoCode Multi-Agent v3.0'); process.exit(0); }
|
|
371
373
|
if (jarvisFlag) {
|
|
372
374
|
try {
|
|
373
375
|
const jarvis = require('../lib/jarvis');
|
package/lib/config.js
CHANGED
|
@@ -15,13 +15,20 @@ function ensureDir() {
|
|
|
15
15
|
try { fs.mkdirSync(CONFIG_DIR, { recursive: true }); } catch {}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
// Mapeamento de
|
|
18
|
+
// Mapeamento de modos para modelos do servidor
|
|
19
19
|
const MODEL_MAP = {
|
|
20
20
|
'Conecta Primo AI': 'main',
|
|
21
21
|
'Chat': 'fast',
|
|
22
22
|
'JARVIS': 'main',
|
|
23
|
+
'primocode': 'main',
|
|
24
|
+
'chat': 'fast',
|
|
23
25
|
};
|
|
24
26
|
|
|
27
|
+
// Resolve o modelo correto baseado no modo
|
|
28
|
+
function resolveModel(mode) {
|
|
29
|
+
return MODEL_MAP[mode] || MODEL_MAP['Conecta Primo AI'];
|
|
30
|
+
}
|
|
31
|
+
|
|
25
32
|
function loadConfig() {
|
|
26
33
|
ensureDir();
|
|
27
34
|
let file = {};
|
|
@@ -30,11 +37,12 @@ function loadConfig() {
|
|
|
30
37
|
} catch { file = {}; }
|
|
31
38
|
|
|
32
39
|
const mode = process.env.PRIMOCODE_MODE || file.mode || 'Conecta Primo AI';
|
|
33
|
-
|
|
40
|
+
// Modelo SEMPRE derivado do modo — nunca usa file.model diretamente
|
|
41
|
+
const model = resolveModel(mode);
|
|
34
42
|
|
|
35
43
|
return {
|
|
36
44
|
server: process.env.PRIMOCODE_SERVER || file.server || 'https://primocode-server.onrender.com',
|
|
37
|
-
model:
|
|
45
|
+
model: model,
|
|
38
46
|
mode: mode,
|
|
39
47
|
autoApply: file.autoApply === true,
|
|
40
48
|
_file: file,
|
|
@@ -45,6 +53,10 @@ function saveConfig(partial) {
|
|
|
45
53
|
ensureDir();
|
|
46
54
|
const cur = (() => { try { return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); } catch { return {}; } })();
|
|
47
55
|
const next = { ...cur, ...partial };
|
|
56
|
+
// Se salvou um modo novo, atualiza o modelo tambem
|
|
57
|
+
if (partial.mode && MODEL_MAP[partial.mode]) {
|
|
58
|
+
next.model = MODEL_MAP[partial.mode];
|
|
59
|
+
}
|
|
48
60
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify(next, null, 2), 'utf8');
|
|
49
61
|
return next;
|
|
50
62
|
}
|
|
@@ -56,4 +68,4 @@ function appendHistory(entry) {
|
|
|
56
68
|
} catch {}
|
|
57
69
|
}
|
|
58
70
|
|
|
59
|
-
module.exports = { loadConfig, saveConfig, appendHistory, CONFIG_DIR, CONFIG_FILE, HISTORY_FILE, MODEL_MAP };
|
|
71
|
+
module.exports = { loadConfig, saveConfig, appendHistory, resolveModel, CONFIG_DIR, CONFIG_FILE, HISTORY_FILE, MODEL_MAP };
|
package/lib/jarvis.js
CHANGED
|
@@ -1,665 +1,67 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
1
|
+
// JARVIS — Interface de voz e visao do PrimoCode
|
|
2
|
+
// Servidor HTTP local + WebSocket para comunicacao com a CLI
|
|
3
|
+
|
|
4
|
+
'use strict';
|
|
3
5
|
|
|
4
6
|
const http = require('http');
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
+
const WebSocket = require('ws');
|
|
8
|
+
const open = require('open');
|
|
9
|
+
const { execSync } = require('child_process');
|
|
7
10
|
|
|
8
|
-
const PORT =
|
|
9
|
-
const HOST = '127.0.0.1';
|
|
11
|
+
const PORT = 8765;
|
|
10
12
|
|
|
11
|
-
// ---------- página frontend (vibe cmd, terminal preto/verde-teal) ----------
|
|
12
13
|
function renderHtml(serverUrl) {
|
|
13
|
-
return
|
|
14
|
-
<html lang="pt-br">
|
|
15
|
-
<head>
|
|
16
|
-
<meta charset="utf-8" />
|
|
17
|
-
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
18
|
-
<title>JARVIS · PrimoCode</title>
|
|
19
|
-
<style>
|
|
20
|
-
:root {
|
|
21
|
-
--bg:#0a0a0f;
|
|
22
|
-
--surface:#12121a;
|
|
23
|
-
--surface-hover:#1a1a25;
|
|
24
|
-
--border:#252535;
|
|
25
|
-
--fg:#e8e8f0;
|
|
26
|
-
--fg-dim:#6b6b80;
|
|
27
|
-
--accent:#00d4aa;
|
|
28
|
-
--accent-glow:rgba(0,212,170,0.15);
|
|
29
|
-
--amber:#f0a030;
|
|
30
|
-
--err:#e05060;
|
|
31
|
-
--radius:14px;
|
|
32
|
-
--mono:'SF Mono','JetBrains Mono','Fira Code',ui-monospace,Menlo,monospace;
|
|
33
|
-
}
|
|
34
|
-
* { box-sizing:border-box; margin:0; padding:0; }
|
|
35
|
-
html, body {
|
|
36
|
-
height:100%; background:var(--bg); color:var(--fg);
|
|
37
|
-
font-family:var(--mono); font-size:14px; overflow:hidden;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
body::before {
|
|
41
|
-
content:''; position:fixed; inset:0; pointer-events:none; z-index:0;
|
|
42
|
-
background:
|
|
43
|
-
radial-gradient(ellipse 80% 50% at 50% 0%, rgba(0,212,170,0.06) 0%, transparent 60%),
|
|
44
|
-
radial-gradient(ellipse 60% 40% at 80% 100%, rgba(240,160,48,0.04) 0%, transparent 50%);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
.app {
|
|
48
|
-
position:relative; z-index:1;
|
|
49
|
-
display:grid;
|
|
50
|
-
grid-template-columns: 1fr 360px;
|
|
51
|
-
grid-template-rows: auto 1fr auto;
|
|
52
|
-
height:100vh; gap:16px; padding:20px;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.header {
|
|
56
|
-
grid-column:1/-1;
|
|
57
|
-
display:flex; align-items:center; justify-content:space-between;
|
|
58
|
-
padding:14px 20px;
|
|
59
|
-
background:var(--surface);
|
|
60
|
-
border:1px solid var(--border);
|
|
61
|
-
border-radius:var(--radius);
|
|
62
|
-
}
|
|
63
|
-
.brand {
|
|
64
|
-
display:flex; align-items:center; gap:12px;
|
|
65
|
-
font-weight:600; font-size:15px; letter-spacing:0.04em;
|
|
66
|
-
}
|
|
67
|
-
.pulse {
|
|
68
|
-
width:10px; height:10px; border-radius:50%;
|
|
69
|
-
background:var(--accent);
|
|
70
|
-
box-shadow:0 0 0 0 var(--accent-glow);
|
|
71
|
-
animation:pulse 2s ease-in-out infinite;
|
|
72
|
-
}
|
|
73
|
-
@keyframes pulse {
|
|
74
|
-
0%,100% { opacity:1; box-shadow:0 0 0 0 var(--accent-glow); }
|
|
75
|
-
50% { opacity:0.6; box-shadow:0 0 0 8px transparent; }
|
|
76
|
-
}
|
|
77
|
-
.status {
|
|
78
|
-
display:flex; gap:18px; align-items:center;
|
|
79
|
-
font-size:12px; color:var(--fg-dim);
|
|
80
|
-
}
|
|
81
|
-
.status-item { display:flex; align-items:center; gap:6px; }
|
|
82
|
-
.status-dot {
|
|
83
|
-
width:7px; height:7px; border-radius:50%;
|
|
84
|
-
background:var(--fg-dim); transition:all 0.3s;
|
|
85
|
-
}
|
|
86
|
-
.status-dot.on { background:var(--accent); box-shadow:0 0 8px var(--accent); }
|
|
87
|
-
.status-dot.err { background:var(--err); box-shadow:0 0 8px var(--err); }
|
|
88
|
-
.status-label { font-weight:500; color:var(--fg); }
|
|
89
|
-
|
|
90
|
-
.chat {
|
|
91
|
-
background:var(--surface);
|
|
92
|
-
border:1px solid var(--border);
|
|
93
|
-
border-radius:var(--radius);
|
|
94
|
-
padding:20px;
|
|
95
|
-
overflow-y:auto;
|
|
96
|
-
display:flex; flex-direction:column; gap:12px;
|
|
97
|
-
scrollbar-width:thin; scrollbar-color:var(--border) transparent;
|
|
98
|
-
}
|
|
99
|
-
.chat::-webkit-scrollbar { width:5px; }
|
|
100
|
-
.chat::-webkit-scrollbar-thumb { background:var(--border); border-radius:10px; }
|
|
101
|
-
|
|
102
|
-
.msg {
|
|
103
|
-
max-width:85%; padding:12px 16px;
|
|
104
|
-
border-radius:12px; line-height:1.6;
|
|
105
|
-
animation:fadeIn 0.3s ease;
|
|
106
|
-
}
|
|
107
|
-
@keyframes fadeIn { from { opacity:0; transform:translateY(6px); } to { opacity:1; transform:translateY(0); } }
|
|
108
|
-
|
|
109
|
-
.msg-user {
|
|
110
|
-
align-self:flex-end;
|
|
111
|
-
background:linear-gradient(135deg, rgba(0,212,170,0.12), rgba(0,212,170,0.06));
|
|
112
|
-
border:1px solid rgba(0,212,170,0.2);
|
|
113
|
-
color:var(--fg);
|
|
114
|
-
}
|
|
115
|
-
.msg-jarvis {
|
|
116
|
-
align-self:flex-start;
|
|
117
|
-
background:var(--surface-hover);
|
|
118
|
-
border:1px solid var(--border);
|
|
119
|
-
color:var(--fg);
|
|
120
|
-
}
|
|
121
|
-
.msg-system {
|
|
122
|
-
align-self:center;
|
|
123
|
-
font-size:12px; color:var(--fg-dim);
|
|
124
|
-
padding:6px 14px;
|
|
125
|
-
background:rgba(255,255,255,0.03);
|
|
126
|
-
border-radius:20px;
|
|
127
|
-
}
|
|
128
|
-
.msg-tag {
|
|
129
|
-
font-size:11px; font-weight:600; text-transform:uppercase;
|
|
130
|
-
letter-spacing:0.08em; margin-bottom:4px; opacity:0.7;
|
|
131
|
-
}
|
|
132
|
-
.msg-user .msg-tag { color:var(--accent); }
|
|
133
|
-
.msg-jarvis .msg-tag { color:var(--amber); }
|
|
134
|
-
|
|
135
|
-
.sidebar {
|
|
136
|
-
display:flex; flex-direction:column; gap:16px;
|
|
137
|
-
}
|
|
138
|
-
.card {
|
|
139
|
-
background:var(--surface);
|
|
140
|
-
border:1px solid var(--border);
|
|
141
|
-
border-radius:var(--radius);
|
|
142
|
-
padding:16px;
|
|
143
|
-
transition:border-color 0.3s;
|
|
144
|
-
}
|
|
145
|
-
.card:hover { border-color:var(--border); }
|
|
146
|
-
.card-title {
|
|
147
|
-
font-size:11px; font-weight:600; text-transform:uppercase;
|
|
148
|
-
letter-spacing:0.12em; color:var(--fg-dim); margin-bottom:12px;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
.cam-box {
|
|
152
|
-
aspect-ratio:16/10; background:#000;
|
|
153
|
-
border-radius:10px; overflow:hidden; position:relative;
|
|
154
|
-
border:1px solid var(--border);
|
|
155
|
-
}
|
|
156
|
-
.cam-box video { width:100%; height:100%; object-fit:cover; transform:scaleX(-1); }
|
|
157
|
-
.cam-box.off::after {
|
|
158
|
-
content:'camera off'; position:absolute; inset:0;
|
|
159
|
-
display:flex; align-items:center; justify-content:center;
|
|
160
|
-
color:var(--fg-dim); font-size:13px;
|
|
161
|
-
}
|
|
162
|
-
.cam-info {
|
|
163
|
-
display:flex; justify-content:space-between;
|
|
164
|
-
font-size:12px; color:var(--fg-dim); margin-top:8px;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
.wave-box {
|
|
168
|
-
height:64px; background:#08080f;
|
|
169
|
-
border-radius:10px; border:1px solid var(--border);
|
|
170
|
-
overflow:hidden; position:relative;
|
|
171
|
-
}
|
|
172
|
-
.wave-box canvas { width:100%; height:100%; display:block; }
|
|
173
|
-
.volume-bar {
|
|
174
|
-
height:5px; background:var(--border); border-radius:3px;
|
|
175
|
-
overflow:hidden; margin-top:10px;
|
|
176
|
-
}
|
|
177
|
-
.volume-fill {
|
|
178
|
-
height:100%; width:0%; background:var(--accent);
|
|
179
|
-
border-radius:3px; transition:width 0.08s linear;
|
|
180
|
-
box-shadow:0 0 10px rgba(0,212,170,0.3);
|
|
181
|
-
}
|
|
182
|
-
.audio-info {
|
|
183
|
-
display:flex; justify-content:space-between;
|
|
184
|
-
font-size:12px; color:var(--fg-dim); margin-top:8px;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
.controls { display:flex; flex-wrap:wrap; gap:8px; }
|
|
188
|
-
.btn {
|
|
189
|
-
background:transparent; color:var(--fg); border:1px solid var(--border);
|
|
190
|
-
padding:8px 14px; border-radius:8px; cursor:pointer;
|
|
191
|
-
font-family:var(--mono); font-size:12px; font-weight:500;
|
|
192
|
-
transition:all 0.2s; letter-spacing:0.02em;
|
|
193
|
-
}
|
|
194
|
-
.btn:hover { border-color:var(--accent); color:var(--accent); background:var(--accent-glow); }
|
|
195
|
-
.btn.active { border-color:var(--accent); color:var(--accent); background:var(--accent-glow); }
|
|
196
|
-
.btn-danger:hover { border-color:var(--err); color:var(--err); background:rgba(224,80,96,0.08); }
|
|
197
|
-
|
|
198
|
-
.voice-select {
|
|
199
|
-
width:100%; margin-top:8px;
|
|
200
|
-
background:var(--bg); color:var(--fg);
|
|
201
|
-
border:1px solid var(--border); border-radius:8px;
|
|
202
|
-
padding:8px 12px; font-family:var(--mono); font-size:12px;
|
|
203
|
-
cursor:pointer;
|
|
204
|
-
}
|
|
205
|
-
.voice-select:focus { outline:none; border-color:var(--accent); }
|
|
206
|
-
|
|
207
|
-
.footer {
|
|
208
|
-
grid-column:1/-1;
|
|
209
|
-
display:flex; justify-content:space-between; align-items:center;
|
|
210
|
-
padding:12px 20px;
|
|
211
|
-
background:var(--surface);
|
|
212
|
-
border:1px solid var(--border);
|
|
213
|
-
border-radius:var(--radius);
|
|
214
|
-
font-size:12px; color:var(--fg-dim);
|
|
215
|
-
}
|
|
216
|
-
.footer-hint b { color:var(--fg); font-weight:500; }
|
|
217
|
-
.footer-server { font-family:var(--mono); }
|
|
218
|
-
|
|
219
|
-
.typing {
|
|
220
|
-
display:flex; gap:4px; align-items:center;
|
|
221
|
-
padding:8px 0;
|
|
222
|
-
}
|
|
223
|
-
.typing-dot {
|
|
224
|
-
width:6px; height:6px; border-radius:50%;
|
|
225
|
-
background:var(--fg-dim);
|
|
226
|
-
animation:typingBounce 1.4s ease-in-out infinite;
|
|
227
|
-
}
|
|
228
|
-
.typing-dot:nth-child(2) { animation-delay:0.2s; }
|
|
229
|
-
.typing-dot:nth-child(3) { animation-delay:0.4s; }
|
|
230
|
-
@keyframes typingBounce {
|
|
231
|
-
0%,60%,100% { transform:translateY(0); }
|
|
232
|
-
30% { transform:translateY(-4px); }
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
.scroll-btn {
|
|
236
|
-
position:absolute; bottom:80px; right:380px;
|
|
237
|
-
width:36px; height:36px; border-radius:50%;
|
|
238
|
-
background:var(--surface); border:1px solid var(--border);
|
|
239
|
-
color:var(--fg); cursor:pointer; display:none;
|
|
240
|
-
align-items:center; justify-content:center;
|
|
241
|
-
font-size:16px; transition:all 0.2s;
|
|
242
|
-
}
|
|
243
|
-
.scroll-btn:hover { border-color:var(--accent); color:var(--accent); }
|
|
244
|
-
.scroll-btn.show { display:flex; }
|
|
245
|
-
</style>
|
|
246
|
-
</head>
|
|
247
|
-
<body>
|
|
248
|
-
<div class="app">
|
|
249
|
-
<div class="header">
|
|
250
|
-
<div class="brand"><span class="pulse"></span> JARVIS · PrimoCode</div>
|
|
251
|
-
<div class="status">
|
|
252
|
-
<div class="status-item"><span id="d-mic" class="status-dot"></span> mic <span id="s-mic" class="status-label">off</span></div>
|
|
253
|
-
<div class="status-item"><span id="d-cam" class="status-dot"></span> cam <span id="s-cam" class="status-label">off</span></div>
|
|
254
|
-
<div class="status-item"><span id="d-ws" class="status-dot"></span> cli <span id="s-ws" class="status-label">...</span></div>
|
|
255
|
-
<div class="status-item"><span id="d-brain" class="status-dot"></span> brain <span id="s-brain" class="status-label">idle</span></div>
|
|
256
|
-
</div>
|
|
257
|
-
</div>
|
|
258
|
-
|
|
259
|
-
<div class="chat" id="log"></div>
|
|
260
|
-
<button class="scroll-btn" id="scrollBtn" onclick="scrollToBottom()">↓</button>
|
|
261
|
-
|
|
262
|
-
<div class="sidebar">
|
|
263
|
-
<div class="card">
|
|
264
|
-
<div class="card-title">Camera</div>
|
|
265
|
-
<div class="cam-box off" id="camWrap"><video id="video" autoplay playsinline muted></video></div>
|
|
266
|
-
<div class="cam-info"><span id="camRes">—</span><span id="camState">off</span></div>
|
|
267
|
-
</div>
|
|
268
|
-
|
|
269
|
-
<div class="card">
|
|
270
|
-
<div class="card-title">Audio</div>
|
|
271
|
-
<div class="wave-box"><canvas id="wave"></canvas></div>
|
|
272
|
-
<div class="volume-bar"><div class="volume-fill" id="meter"></div></div>
|
|
273
|
-
<div class="audio-info"><span id="rms">RMS 0.00</span><span id="silence">silencio 0.0s</span></div>
|
|
274
|
-
</div>
|
|
275
|
-
|
|
276
|
-
<div class="card">
|
|
277
|
-
<div class="card-title">Controles</div>
|
|
278
|
-
<div class="controls">
|
|
279
|
-
<button class="btn" id="bMic">ligar mic</button>
|
|
280
|
-
<button class="btn" id="bCam">ligar cam</button>
|
|
281
|
-
<button class="btn btn-danger" id="bStop">parar</button>
|
|
282
|
-
</div>
|
|
283
|
-
<div style="margin-top:14px;">
|
|
284
|
-
<div class="card-title" style="margin-bottom:6px;">Voz</div>
|
|
285
|
-
<select id="voiceSel" class="voice-select"></select>
|
|
286
|
-
<button class="btn" id="bTest" style="margin-top:8px; width:100%;">testar voz</button>
|
|
287
|
-
</div>
|
|
288
|
-
<div class="audio-info" style="margin-top:10px;">
|
|
289
|
-
<span>silencio auto <b>4.0s</b></span>
|
|
290
|
-
<span id="voice">—</span>
|
|
291
|
-
</div>
|
|
292
|
-
</div>
|
|
293
|
-
</div>
|
|
294
|
-
|
|
295
|
-
<div class="footer">
|
|
296
|
-
<div class="footer-hint">Fale normalmente · silencio de 4s envia · diga <b>"o que voce ve"</b> para usar a camera · <b>Esc</b> encerra</div>
|
|
297
|
-
<div class="footer-server">server <b>${escapeHtml(serverUrl)}</b></div>
|
|
298
|
-
</div>
|
|
299
|
-
</div>
|
|
300
|
-
|
|
301
|
-
<script>
|
|
302
|
-
const SERVER = ${JSON.stringify(serverUrl)};
|
|
303
|
-
const WS_URL = 'ws://' + location.host + '/ws';
|
|
304
|
-
const SILENCE_MS = 4000;
|
|
305
|
-
const VISION_TRIGGERS = /\b(v[eê]|olh[ae]|c[aâ]mera|camera|imagem|foto|tela|mostrando|est[aá] vendo|voc[eê] v[eê])\b/i;
|
|
306
|
-
|
|
307
|
-
const $ = (id) => document.getElementById(id);
|
|
308
|
-
const logEl = $('log');
|
|
309
|
-
|
|
310
|
-
function log(kind, text) {
|
|
311
|
-
const el = document.createElement('div');
|
|
312
|
-
el.className = 'msg msg-' + kind;
|
|
313
|
-
const tag = kind==='user' ? 'Voce' : kind==='jarvis' ? 'JARVIS' : kind==='error' ? 'Erro' : '·';
|
|
314
|
-
el.innerHTML = '<div class="msg-tag">' + tag + '</div>' + escapeHtml(text);
|
|
315
|
-
logEl.appendChild(el);
|
|
316
|
-
logEl.scrollTop = logEl.scrollHeight;
|
|
317
|
-
updateScrollBtn();
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function escapeHtml(s){ return String(s).replace(/[&<>"']/g, m => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[m])); }
|
|
321
|
-
|
|
322
|
-
function setStatus(id, on, text) {
|
|
323
|
-
const d = $('d-'+id); const s = $('s-'+id);
|
|
324
|
-
if (d) { d.classList.remove('on','err'); if (on===true) d.classList.add('on'); else if (on==='err') d.classList.add('err'); }
|
|
325
|
-
if (s && text) s.textContent = text;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
function scrollToBottom() { logEl.scrollTop = logEl.scrollHeight; }
|
|
329
|
-
function updateScrollBtn() {
|
|
330
|
-
const btn = $('scrollBtn');
|
|
331
|
-
const nearBottom = logEl.scrollHeight - logEl.scrollTop - logEl.clientHeight < 100;
|
|
332
|
-
btn.classList.toggle('show', !nearBottom);
|
|
333
|
-
}
|
|
334
|
-
logEl.addEventListener('scroll', updateScrollBtn);
|
|
335
|
-
|
|
336
|
-
let ws = null;
|
|
337
|
-
function connectWS() {
|
|
338
|
-
try {
|
|
339
|
-
ws = new WebSocket(WS_URL);
|
|
340
|
-
ws.onopen = () => { setStatus('ws', true, 'ok'); log('system', 'conectado ao CLI'); };
|
|
341
|
-
ws.onclose = () => { setStatus('ws', 'err', 'off'); };
|
|
342
|
-
ws.onerror = () => { setStatus('ws', 'err', 'erro'); };
|
|
343
|
-
ws.onmessage = (ev) => {
|
|
344
|
-
try {
|
|
345
|
-
const msg = JSON.parse(ev.data);
|
|
346
|
-
if (msg.type === 'log') log(msg.kind || 'system', msg.text);
|
|
347
|
-
} catch {}
|
|
348
|
-
};
|
|
349
|
-
} catch (e) { setStatus('ws', 'err', 'erro'); }
|
|
350
|
-
}
|
|
351
|
-
function wsSend(obj) { try { if (ws && ws.readyState === 1) ws.send(JSON.stringify(obj)); } catch {} }
|
|
352
|
-
connectWS();
|
|
353
|
-
|
|
354
|
-
let recognition = null;
|
|
355
|
-
let micStream = null;
|
|
356
|
-
let audioCtx = null;
|
|
357
|
-
let analyser = null;
|
|
358
|
-
let waveRAF = null;
|
|
359
|
-
let silenceTimer = null;
|
|
360
|
-
let silenceStart = 0;
|
|
361
|
-
let currentTranscript = '';
|
|
362
|
-
let isSending = false;
|
|
363
|
-
|
|
364
|
-
function initRecognition() {
|
|
365
|
-
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
366
|
-
if (!SR) { log('error', 'esse navegador nao suporta reconhecimento de voz (use Chrome)'); return null; }
|
|
367
|
-
const r = new SR();
|
|
368
|
-
r.lang = 'pt-BR'; r.continuous = true; r.interimResults = true;
|
|
369
|
-
r.onresult = (ev) => {
|
|
370
|
-
let interim = '', final = '';
|
|
371
|
-
for (let i = ev.resultIndex; i < ev.results.length; i++) {
|
|
372
|
-
const res = ev.results[i];
|
|
373
|
-
if (res.isFinal) final += res[0].transcript; else interim += res[0].transcript;
|
|
374
|
-
}
|
|
375
|
-
if (final) currentTranscript += final;
|
|
376
|
-
if (interim || final) resetSilence();
|
|
377
|
-
};
|
|
378
|
-
r.onerror = (e) => { if (e.error !== 'no-speech' && e.error !== 'aborted') log('error', 'stt: ' + e.error); };
|
|
379
|
-
r.onend = () => { if (micOn) { try { r.start(); } catch {} } };
|
|
380
|
-
return r;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function resetSilence() { silenceStart = performance.now(); }
|
|
384
|
-
|
|
385
|
-
function tickSilence() {
|
|
386
|
-
if (!micOn || isSending) return;
|
|
387
|
-
const elapsed = performance.now() - silenceStart;
|
|
388
|
-
const sec = (elapsed / 1000).toFixed(1);
|
|
389
|
-
$('silence').textContent = 'silencio ' + sec + 's';
|
|
390
|
-
if (elapsed >= SILENCE_MS && currentTranscript.trim()) {
|
|
391
|
-
sendTranscript();
|
|
392
|
-
}
|
|
393
|
-
silenceTimer = requestAnimationFrame(tickSilence);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function sendTranscript() {
|
|
397
|
-
const text = currentTranscript.trim();
|
|
398
|
-
if (!text) return;
|
|
399
|
-
isSending = true;
|
|
400
|
-
log('user', text);
|
|
401
|
-
const useVision = VISION_TRIGGERS.test(text) && camOn;
|
|
402
|
-
wsSend({ type: 'user', text, vision: useVision });
|
|
403
|
-
currentTranscript = '';
|
|
404
|
-
setTimeout(() => { isSending = false; resetSilence(); }, 800);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
function startWave(stream) {
|
|
408
|
-
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
|
409
|
-
const src = audioCtx.createMediaStreamSource(stream);
|
|
410
|
-
analyser = audioCtx.createAnalyser();
|
|
411
|
-
analyser.fftSize = 256;
|
|
412
|
-
src.connect(analyser);
|
|
413
|
-
const canvas = $('wave');
|
|
414
|
-
const ctx = canvas.getContext('2d');
|
|
415
|
-
const data = new Uint8Array(analyser.frequencyBinCount);
|
|
416
|
-
|
|
417
|
-
function draw() {
|
|
418
|
-
waveRAF = requestAnimationFrame(draw);
|
|
419
|
-
analyser.getByteFrequencyData(data);
|
|
420
|
-
const w = canvas.width = canvas.offsetWidth * window.devicePixelRatio;
|
|
421
|
-
const h = canvas.height = canvas.offsetHeight * window.devicePixelRatio;
|
|
422
|
-
ctx.clearRect(0, 0, w, h);
|
|
423
|
-
const bars = 60; const barW = w / bars;
|
|
424
|
-
let sum = 0;
|
|
425
|
-
for (let i = 0; i < bars; i++) {
|
|
426
|
-
const idx = Math.floor(i * data.length / bars);
|
|
427
|
-
const val = data[idx] / 255;
|
|
428
|
-
sum += val;
|
|
429
|
-
const bh = val * h * 0.85;
|
|
430
|
-
const x = i * barW;
|
|
431
|
-
const y = (h - bh) / 2;
|
|
432
|
-
const hue = 160 + val * 40;
|
|
433
|
-
ctx.fillStyle = `hsla(${hue}, 80%, 55%, ${0.3 + val * 0.7})`;
|
|
434
|
-
ctx.beginPath();
|
|
435
|
-
ctx.roundRect(x + 1, y, barW - 2, bh, 3);
|
|
436
|
-
ctx.fill();
|
|
437
|
-
}
|
|
438
|
-
const rms = sum / bars;
|
|
439
|
-
$('rms').textContent = 'RMS ' + rms.toFixed(2);
|
|
440
|
-
$('meter').style.width = Math.min(100, rms * 140) + '%';
|
|
441
|
-
}
|
|
442
|
-
draw();
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
function stopWave() {
|
|
446
|
-
if (waveRAF) cancelAnimationFrame(waveRAF);
|
|
447
|
-
if (audioCtx) audioCtx.close();
|
|
448
|
-
waveRAF = null; audioCtx = null; analyser = null;
|
|
449
|
-
$('rms').textContent = 'RMS 0.00';
|
|
450
|
-
$('meter').style.width = '0%';
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
let camOn = false;
|
|
454
|
-
async function toggleCam() {
|
|
455
|
-
if (camOn) {
|
|
456
|
-
const v = $('video'); v.pause(); v.srcObject = null;
|
|
457
|
-
$('camWrap').classList.add('off'); $('camState').textContent = 'off';
|
|
458
|
-
$('camRes').textContent = '—'; camOn = false; setStatus('cam', false, 'off');
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
try {
|
|
462
|
-
const stream = await navigator.mediaDevices.getUserMedia({ video: { width:1280, height:720 } });
|
|
463
|
-
const v = $('video'); v.srcObject = stream;
|
|
464
|
-
await v.play();
|
|
465
|
-
const t = stream.getVideoTracks()[0];
|
|
466
|
-
const s = t.getSettings();
|
|
467
|
-
$('camWrap').classList.remove('off'); $('camState').textContent = 'on';
|
|
468
|
-
$('camRes').textContent = (s.width||'—') + '×' + (s.height||'—');
|
|
469
|
-
camOn = true; setStatus('cam', true, 'on');
|
|
470
|
-
} catch (e) { log('error', 'camera: ' + e.message); }
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
let micOn = false;
|
|
474
|
-
async function toggleMic() {
|
|
475
|
-
if (micOn) {
|
|
476
|
-
if (recognition) { try { recognition.stop(); } catch {} }
|
|
477
|
-
if (micStream) { micStream.getTracks().forEach(t => t.stop()); }
|
|
478
|
-
stopWave();
|
|
479
|
-
micOn = false; setStatus('mic', false, 'off'); $('bMic').classList.remove('active'); $('bMic').textContent = 'ligar mic';
|
|
480
|
-
if (silenceTimer) cancelAnimationFrame(silenceTimer);
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
try {
|
|
484
|
-
micStream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
485
|
-
recognition = initRecognition();
|
|
486
|
-
if (recognition) recognition.start();
|
|
487
|
-
startWave(micStream);
|
|
488
|
-
resetSilence();
|
|
489
|
-
silenceTimer = requestAnimationFrame(tickSilence);
|
|
490
|
-
micOn = true; setStatus('mic', true, 'on'); $('bMic').classList.add('active'); $('bMic').textContent = 'desligar mic';
|
|
491
|
-
} catch (e) { log('error', 'mic: ' + e.message); }
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
let voices = [];
|
|
495
|
-
function loadVoices() {
|
|
496
|
-
voices = speechSynthesis.getVoices().filter(v => v.lang.startsWith('pt'));
|
|
497
|
-
const sel = $('voiceSel');
|
|
498
|
-
sel.innerHTML = '';
|
|
499
|
-
voices.forEach((v, i) => {
|
|
500
|
-
const o = document.createElement('option');
|
|
501
|
-
o.value = i; o.textContent = v.name;
|
|
502
|
-
sel.appendChild(o);
|
|
503
|
-
});
|
|
504
|
-
if (voices.length) $('voice').textContent = voices[0].name;
|
|
505
|
-
}
|
|
506
|
-
speechSynthesis.onvoiceschanged = loadVoices;
|
|
507
|
-
loadVoices();
|
|
508
|
-
|
|
509
|
-
function speak(text) {
|
|
510
|
-
if (!text || !voices.length) return;
|
|
511
|
-
const u = new SpeechSynthesisUtterance(text);
|
|
512
|
-
const idx = parseInt($('voiceSel').value, 10) || 0;
|
|
513
|
-
u.voice = voices[idx] || voices[0];
|
|
514
|
-
u.rate = 1.05; u.pitch = 1.0;
|
|
515
|
-
speechSynthesis.speak(u);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
$('bMic').onclick = toggleMic;
|
|
519
|
-
$('bCam').onclick = toggleCam;
|
|
520
|
-
$('bStop').onclick = () => { if (micOn) toggleMic(); if (camOn) toggleCam(); };
|
|
521
|
-
$('bTest').onclick = () => speak('JARVIS online. Pronto para ajudar.');
|
|
522
|
-
$('voiceSel').onchange = () => { const v = voices[$('voiceSel').value]; if (v) $('voice').textContent = v.name; };
|
|
523
|
-
|
|
524
|
-
document.addEventListener('keydown', (e) => {
|
|
525
|
-
if (e.key === 'Escape') {
|
|
526
|
-
wsSend({ type: 'shutdown' });
|
|
527
|
-
window.close();
|
|
528
|
-
}
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
log('system', 'JARVIS pronto. Clique em "ligar mic" para comecar.');
|
|
532
|
-
</script>
|
|
533
|
-
</body>
|
|
534
|
-
</html>`;
|
|
14
|
+
return `' + new_jarvis_html.replace(/`/g, '\\`').replace(/\$/g, '\\$') + `';
|
|
535
15
|
}
|
|
536
16
|
|
|
537
|
-
function
|
|
538
|
-
return
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
} else if (len < 65536) {
|
|
549
|
-
head = Buffer.alloc(4);
|
|
550
|
-
head[0] = 0x81; head[1] = 126;
|
|
551
|
-
head.writeUInt16BE(len, 2);
|
|
552
|
-
} else {
|
|
553
|
-
head = Buffer.alloc(10);
|
|
554
|
-
head[0] = 0x81; head[1] = 127;
|
|
555
|
-
head.writeBigUInt64BE(BigInt(len), 2);
|
|
556
|
-
}
|
|
557
|
-
return Buffer.concat([head, data]);
|
|
558
|
-
}
|
|
559
|
-
function wsParse(buf) {
|
|
560
|
-
// retorna { text, rest } ou null se incompleto
|
|
561
|
-
if (buf.length < 2) return null;
|
|
562
|
-
const b0 = buf[0], b1 = buf[1];
|
|
563
|
-
const opcode = b0 & 0x0f;
|
|
564
|
-
const masked = (b1 & 0x80) !== 0;
|
|
565
|
-
let len = b1 & 0x7f;
|
|
566
|
-
let offset = 2;
|
|
567
|
-
if (len === 126) { if (buf.length < 4) return null; len = buf.readUInt16BE(2); offset = 4; }
|
|
568
|
-
else if (len === 127) { if (buf.length < 10) return null; len = Number(buf.readBigUInt64BE(2)); offset = 10; }
|
|
569
|
-
if (masked) { if (buf.length < offset + 4 + len) return null; }
|
|
570
|
-
else { if (buf.length < offset + len) return null; }
|
|
571
|
-
let mask = null;
|
|
572
|
-
if (masked) { mask = buf.slice(offset, offset+4); offset += 4; }
|
|
573
|
-
const payload = buf.slice(offset, offset+len);
|
|
574
|
-
if (masked) for (let i = 0; i < payload.length; i++) payload[i] ^= mask[i%4];
|
|
575
|
-
const rest = buf.slice(offset+len);
|
|
576
|
-
if (opcode === 0x8) return { close: true, rest };
|
|
577
|
-
if (opcode === 0x1) return { text: payload.toString('utf8'), rest };
|
|
578
|
-
return { rest }; // binário/outros: descarta
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// ---------- launch(): sobe servidor local, retorna Promise que resolve quando browser fecha ----------
|
|
582
|
-
function launch(config, opts = {}) {
|
|
583
|
-
const serverUrl = (config && config.server) || 'https://primocode-server.onrender.com';
|
|
584
|
-
let clients = new Set();
|
|
585
|
-
let shutdownRequested = false;
|
|
17
|
+
function launch(config) {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const server = http.createServer((req, res) => {
|
|
20
|
+
if (req.url === '/') {
|
|
21
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
22
|
+
res.end(renderHtml(config.server));
|
|
23
|
+
} else {
|
|
24
|
+
res.writeHead(404);
|
|
25
|
+
res.end('Not found');
|
|
26
|
+
}
|
|
27
|
+
});
|
|
586
28
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
29
|
+
const wss = new WebSocket.Server({ server });
|
|
30
|
+
wss.on('connection', (ws) => {
|
|
31
|
+
ws.on('message', (data) => {
|
|
32
|
+
try {
|
|
33
|
+
const msg = JSON.parse(data);
|
|
34
|
+
if (msg.type === 'user') {
|
|
35
|
+
// Encaminha para a CLI processar
|
|
36
|
+
process.stdout.write(`\\n[JARVIS] ${msg.text}\\n`);
|
|
37
|
+
}
|
|
38
|
+
if (msg.type === 'shutdown') {
|
|
39
|
+
server.close();
|
|
40
|
+
wss.close();
|
|
41
|
+
resolve();
|
|
42
|
+
}
|
|
43
|
+
} catch {}
|
|
44
|
+
});
|
|
45
|
+
});
|
|
601
46
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
'Upgrade: websocket\r\n' +
|
|
610
|
-
'Connection: Upgrade\r\n' +
|
|
611
|
-
`Sec-WebSocket-Accept: ${accept}\r\n\r\n`
|
|
612
|
-
);
|
|
613
|
-
clients.add(socket);
|
|
614
|
-
let buf = Buffer.alloc(0);
|
|
615
|
-
socket.on('data', (chunk) => {
|
|
616
|
-
buf = Buffer.concat([buf, chunk]);
|
|
617
|
-
while (true) {
|
|
618
|
-
const f = wsParse(buf);
|
|
619
|
-
if (!f) break;
|
|
620
|
-
buf = f.rest;
|
|
621
|
-
if (f.close) { try { socket.end(); } catch {} clients.delete(socket); break; }
|
|
622
|
-
if (f.text) {
|
|
623
|
-
try {
|
|
624
|
-
const msg = JSON.parse(f.text);
|
|
625
|
-
if (msg.type === 'user' && typeof msg.text === 'string') {
|
|
626
|
-
process.stdout.write(`\n${c.brand('▶')} ${c.dim('voz:')} ${c.white(msg.text)}\n`);
|
|
627
|
-
} else if (msg.type === 'jarvis' && typeof msg.text === 'string') {
|
|
628
|
-
process.stdout.write(`${c.accent('◀')} ${c.dim('JARVIS:')} ${msg.text}\n`);
|
|
629
|
-
} else if (msg.type === 'shutdown') {
|
|
630
|
-
shutdownRequested = true;
|
|
631
|
-
server.close(() => {});
|
|
632
|
-
for (const s of clients) { try { s.end(); } catch {} }
|
|
633
|
-
}
|
|
634
|
-
} catch {}
|
|
635
|
-
}
|
|
636
|
-
}
|
|
47
|
+
server.listen(PORT, () => {
|
|
48
|
+
const url = `http://127.0.0.1:${PORT}`;
|
|
49
|
+
console.log(`JARVIS aberto em ${url}`);
|
|
50
|
+
open(url).catch(() => {
|
|
51
|
+
// Fallback para Windows
|
|
52
|
+
try { execSync(`start ${url}`); } catch {}
|
|
53
|
+
});
|
|
637
54
|
});
|
|
638
|
-
socket.on('error', () => { clients.delete(socket); });
|
|
639
|
-
socket.on('close', () => { clients.delete(socket); });
|
|
640
|
-
});
|
|
641
55
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
// abre navegador do sistema
|
|
650
|
-
try {
|
|
651
|
-
const { openInBrowser } = require('./tools');
|
|
652
|
-
openInBrowser(url);
|
|
653
|
-
} catch {
|
|
654
|
-
process.stdout.write(` ${c.muted('abra manualmente:')} ${url}\n`);
|
|
56
|
+
server.on('error', (err) => {
|
|
57
|
+
if (err.code === 'EADDRINUSE') {
|
|
58
|
+
console.log(`Porta ${PORT} em uso. Abrindo navegador...`);
|
|
59
|
+
open(`http://127.0.0.1:${PORT}`).catch(() => {});
|
|
60
|
+
resolve();
|
|
61
|
+
} else {
|
|
62
|
+
reject(err);
|
|
655
63
|
}
|
|
656
|
-
// handler pra Ctrl+C encerrar
|
|
657
|
-
const onSig = () => { try { server.close(); } catch {} process.exit(0); };
|
|
658
|
-
process.once('SIGINT', onSig);
|
|
659
|
-
process.once('SIGTERM', onSig);
|
|
660
64
|
});
|
|
661
|
-
server.on('error', (e) => reject(e));
|
|
662
|
-
server.on('close', () => resolve({ ok: true }));
|
|
663
65
|
});
|
|
664
66
|
}
|
|
665
67
|
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "PrimoCode — assistente de engenharia com IA.
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "PrimoCode — assistente de engenharia com IA multi-agente. Pipeline: Prompt Engineer → Construtor 1 → Construtor 2 → Analisador → Revisor → Deploy.",
|
|
5
5
|
"main": "bin/primocode.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"primocode": "
|
|
7
|
+
"primocode": "bin/primocode.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/primocode.js"
|
|
@@ -12,7 +12,13 @@
|
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=18.17.0"
|
|
14
14
|
},
|
|
15
|
-
"keywords": [
|
|
15
|
+
"keywords": [
|
|
16
|
+
"cli",
|
|
17
|
+
"ai",
|
|
18
|
+
"coding-assistant",
|
|
19
|
+
"agent",
|
|
20
|
+
"primocode"
|
|
21
|
+
],
|
|
16
22
|
"author": "ConectaPrimoAI <conectaprimoai@gmail.com>",
|
|
17
23
|
"license": "MIT",
|
|
18
24
|
"repository": {
|
|
@@ -23,7 +29,6 @@
|
|
|
23
29
|
"url": "https://github.com/ConectaPrimoAI/primocode/issues"
|
|
24
30
|
},
|
|
25
31
|
"homepage": "https://github.com/ConectaPrimoAI/primocode#readme",
|
|
26
|
-
"dependencies": {},
|
|
27
32
|
"files": [
|
|
28
33
|
"bin/",
|
|
29
34
|
"lib/",
|