primocode 1.0.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 ADDED
@@ -0,0 +1,63 @@
1
+ # PrimoCode
2
+
3
+ Assistente de engenharia com inteligencia artificial.
4
+
5
+ ## Modos
6
+
7
+ - **Conecta Primo AI** — Engenharia completa: codigo, arquivos, terminal, navegador
8
+ - **Chat** — Conversa direta e objetiva
9
+ - **JARVIS** — Interacao por voz e visao
10
+
11
+ ## Instalacao
12
+
13
+ ```bash
14
+ npm install -g primocode
15
+ ```
16
+
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
+ ## Uso
28
+
29
+ ```bash
30
+ primocode # Modo interativo
31
+ primocode "crie um site" # Tarefa direta
32
+ primocode --chat "explique REST" # Modo conversa
33
+ primocode --jarvis # Voz e visao
34
+ primocode -f app.js "analise isto" # Anexa arquivo
35
+ ```
36
+
37
+ ## Comandos
38
+
39
+ | Comando | Funcao |
40
+ |---------|--------|
41
+ | `/mode` | Alterna entre Conecta Primo AI, Chat, JARVIS |
42
+ | `/attach` | Anexa arquivo ao contexto |
43
+ | `/clear` | Limpa historico da conversa |
44
+ | `/uninstall` | Remove PrimoCode |
45
+ | `/help` | Exibe ajuda |
46
+ | `/exit` | Encerra |
47
+
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
+ ## Requisitos
58
+
59
+ - Node.js 18.17+
60
+
61
+ ---
62
+
63
+ Desenvolvido por ConectaPrimoAI
@@ -0,0 +1,386 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * PrimoCode
4
+ * Conecta Primo AI → engenharia completa: código, arquivos, terminal, navegador
5
+ * Chat → conversa direta
6
+ * JARVIS → voz e visão
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const readline = require('readline');
12
+ const path = require('path');
13
+ const fs = require('fs');
14
+ const os = require('os');
15
+ const { spawn } = require('child_process');
16
+
17
+ const { c, welcomeBox, Spinner } = require('../lib/ui');
18
+ const api = require('../lib/api');
19
+ const tools = require('../lib/tools');
20
+ const cfg = require('../lib/config');
21
+ const { runAct } = require('../lib/act');
22
+
23
+ // ---- Estado ----
24
+ const state = {
25
+ config: cfg.loadConfig(),
26
+ history: [],
27
+ mode: 'Conecta Primo AI',
28
+ attachedFiles: [],
29
+ lastResponse: '',
30
+ };
31
+
32
+ // ---- Ajuda ----
33
+ const HELP = `
34
+ ${c.bold('Comandos')}
35
+ /mode <nome> Conecta Primo AI | Chat | JARVIS
36
+ /attach <arquivo> anexa arquivo
37
+ /clear limpa conversa
38
+ /cls limpa tela
39
+ /uninstall remove PrimoCode
40
+ /help ajuda
41
+ /exit sair
42
+
43
+ ${c.bold('Modos')}
44
+ ${c.brand('Conecta Primo AI')} Engenharia completa
45
+ ${c.brand('Chat')} Conversa direta
46
+ ${c.brand('JARVIS')} Voz e visao
47
+ `;
48
+
49
+ let rl = null;
50
+
51
+ function makePrompt() {
52
+ const tag = state.mode === 'Conecta Primo AI' ? c.magenta('PrimoCode') : c.brand(state.mode);
53
+ const att = state.attachedFiles.length ? c.muted(` +${state.attachedFiles.length}`) : '';
54
+ return `\n${c.muted(tag + att)} ${c.brand('❯')} `;
55
+ }
56
+ function refreshPrompt() { if (rl) rl.setPrompt(makePrompt()); }
57
+
58
+ // ---- Envio ----
59
+ async function sendPrompt(userText) {
60
+ let promptWithAttach = userText;
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
+ }
65
+
66
+ if (state.mode === 'Conecta Primo AI') {
67
+ const r = await runAct(promptWithAttach, {
68
+ server: state.config.server,
69
+ model: state.config.model,
70
+ history: state.history,
71
+ });
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
+
80
+ state.attachedFiles = [];
81
+ refreshPrompt();
82
+ }
83
+
84
+ // Chat
85
+ async function runChat(promptForModel, userTextForMemory) {
86
+ const sp = new Spinner('Processando').start();
87
+ let started = false;
88
+ let text = '';
89
+ try {
90
+ await api.requestStream(state.config.server, '/api/normal/stream', {
91
+ prompt: promptForModel,
92
+ history: state.history.slice(-20),
93
+ model: state.config.model,
94
+ }, (evt) => {
95
+ if (evt.erro) {
96
+ if (!started) sp.stop();
97
+ console.log(c.err(`\nErro: ${evt.erro}`));
98
+ return;
99
+ }
100
+ if (evt.delta) {
101
+ if (!started) { sp.stop(); process.stdout.write('\n'); started = true; }
102
+ text += evt.delta;
103
+ process.stdout.write(c.white(evt.delta));
104
+ }
105
+ if (evt.done && started) process.stdout.write('\n');
106
+ });
107
+ } catch (err) {
108
+ sp.stop();
109
+ console.log(c.err(`\nFalha de conexao: ${err.message}`));
110
+ return;
111
+ }
112
+ if (!started) { sp.stop(); console.log(c.muted('\n(Sem resposta)')); return; }
113
+
114
+ state.history.push({ role: 'user', content: userTextForMemory });
115
+ state.history.push({ role: 'assistant', content: text });
116
+ state.lastResponse = text;
117
+ cfg.appendHistory({ mode: 'Chat', prompt: userTextForMemory, response: text });
118
+ }
119
+
120
+ // ---- Slash commands ----
121
+ async function handleSlash(line) {
122
+ const [cmd, ...rest] = line.trim().split(/\s+/);
123
+ const arg = rest.join(' ').trim();
124
+
125
+ switch (cmd.toLowerCase()) {
126
+ case '/help': process.stdout.write(HELP); return;
127
+ case '/exit': case '/quit':
128
+ console.log(c.muted('\nEncerrado.\n')); process.exit(0);
129
+ case '/cls': console.clear(); process.stdout.write(welcomeBox(state.config)); return;
130
+ case '/clear':
131
+ state.history = [];
132
+ console.log(c.muted(' Conversa limpa'));
133
+ return;
134
+ case '/version':
135
+ console.log(c.muted(` PrimoCode`));
136
+ console.log(c.muted(` Servidor: ${state.config.server}`));
137
+ return;
138
+ case '/status': {
139
+ const sp = new Spinner('Verificando').start();
140
+ try {
141
+ const h = await api.getHealth(state.config.server);
142
+ sp.stop();
143
+ console.log(h.ok ? c.ok(' Online') + c.muted(` ${state.config.server}`) : c.err(' Indisponivel'));
144
+ } catch (e) {
145
+ sp.stop();
146
+ console.log(c.err(' Indisponivel'));
147
+ }
148
+ return;
149
+ }
150
+ case '/jarvis': {
151
+ try {
152
+ const jarvis = require('../lib/jarvis');
153
+ console.log(c.muted(' Iniciando JARVIS...'));
154
+ await jarvis.launch(state.config);
155
+ } catch (e) {
156
+ console.log(c.err(` JARVIS: ${e.message}`));
157
+ }
158
+ return;
159
+ }
160
+ case '/mode': {
161
+ const valid = ['Conecta Primo AI', 'Chat', 'JARVIS', 'primocode', 'chat'];
162
+ let target = arg;
163
+ if (target.toLowerCase() === 'act' || target.toLowerCase() === 'agent' || target.toLowerCase() === 'primocode') {
164
+ console.log(c.muted(' Modo: Conecta Primo AI'));
165
+ target = 'Conecta Primo AI';
166
+ }
167
+ if (!valid.includes(target)) { console.log(c.warn(' /mode Conecta Primo AI | Chat | JARVIS')); return; }
168
+ state.mode = target;
169
+ console.log(c.muted(` Modo: ${target}`));
170
+ refreshPrompt();
171
+ return;
172
+ }
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
+ case '/attach': {
180
+ if (!arg) { console.log(c.warn(' /attach <arquivo>')); return; }
181
+ try {
182
+ const content = tools.readFileSafe(arg);
183
+ state.attachedFiles.push({ path: arg, content });
184
+ console.log(c.muted(` Anexado: ${arg} (${tools.fmtBytes(Buffer.byteLength(content, 'utf8'))})`));
185
+ refreshPrompt();
186
+ } catch (e) { console.log(c.err(` ${e.message}`)); }
187
+ return;
188
+ }
189
+ case '/detach':
190
+ 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
+ case '/uninstall': {
247
+ 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
+ try {
251
+ if (fs.existsSync(globalLink)) fs.unlinkSync(globalLink);
252
+ if (fs.existsSync(installDir)) {
253
+ fs.rmSync(installDir, { recursive: true, force: true });
254
+ }
255
+ console.log(c.ok(' PrimoCode removido'));
256
+ console.log(c.muted(' Para reinstalar: npm install -g primocode'));
257
+ } catch (e) {
258
+ console.log(c.err(` Erro: ${e.message}`));
259
+ }
260
+ return;
261
+ }
262
+ case '/act': {
263
+ if (!arg) { console.log(c.warn(' /act <tarefa>')); return; }
264
+ const r = await runAct(arg, {
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;
272
+ return;
273
+ }
274
+ default:
275
+ console.log(c.warn(` Comando desconhecido: ${cmd}`));
276
+ console.log(c.muted(' /help'));
277
+ }
278
+ }
279
+
280
+ // ---- One-shot ----
281
+ async function runOneShot(argv) {
282
+ const args = argv.slice(2);
283
+ let mode = 'Conecta Primo AI';
284
+ let attach = [];
285
+ const prompt = [];
286
+ for (let i = 0; i < args.length; i++) {
287
+ const a = args[i];
288
+ if (a === '--agent' || a === '-a' || a === '--act' || a === '-x' || a === '--primocode' || a === '-p') mode = 'Conecta Primo AI';
289
+ else if (a === '--chat' || a === '-c') mode = 'Chat';
290
+ else if (a === '--jarvis' || a === '-j') mode = 'JARVIS';
291
+ else if (a === '--attach' || a === '-f') attach.push(args[++i]);
292
+ else if (a === '--help' || a === '-h') { printCliHelp(); process.exit(0); }
293
+ else if (a === '--version' || a === '-v') { console.log('PrimoCode'); process.exit(0); }
294
+ else if (a === '--server') state.config.server = args[++i];
295
+ else if (a === '--model') state.config.model = args[++i];
296
+ else prompt.push(a);
297
+ }
298
+ if (!prompt.length) return false;
299
+ state.mode = mode;
300
+ for (const p of attach) {
301
+ try { state.attachedFiles.push({ path: p, content: tools.readFileSafe(p) }); }
302
+ catch (e) { console.log(c.err(` Anexo ${p}: ${e.message}`)); }
303
+ }
304
+ await sendPrompt(prompt.join(' '));
305
+ return true;
306
+ }
307
+
308
+ function printCliHelp() {
309
+ console.log(welcomeBox(state.config));
310
+ console.log(`${c.bold('Uso')}`);
311
+ console.log(` primocode Interativo (Conecta Primo AI)`);
312
+ console.log(` primocode "crie um site" Executa tarefa`);
313
+ console.log(` primocode --chat "duvida" Modo conversa`);
314
+ console.log(` primocode --jarvis Voz e visao`);
315
+ console.log(` primocode -f app.js "analise" Anexa arquivo`);
316
+ console.log(HELP);
317
+ }
318
+
319
+ // ---- Interactive ----
320
+ function startInteractive() {
321
+ process.stdout.write(welcomeBox(state.config));
322
+
323
+ rl = readline.createInterface({
324
+ input: process.stdin,
325
+ output: process.stdout,
326
+ prompt: makePrompt(),
327
+ historySize: 200,
328
+ });
329
+ rl.prompt();
330
+
331
+ const queue = [];
332
+ let busy = false;
333
+ async function drain() {
334
+ if (busy) return;
335
+ busy = true;
336
+ while (queue.length) {
337
+ const input = queue.shift();
338
+ try {
339
+ if (input.startsWith('/')) await handleSlash(input);
340
+ else await sendPrompt(input);
341
+ } catch (e) { console.log(c.err(`Erro: ${e.message}`)); }
342
+ }
343
+ busy = false;
344
+ rl.prompt();
345
+ }
346
+ rl.on('line', (line) => {
347
+ const input = line.trim();
348
+ if (!input) { if (!busy) rl.prompt(); return; }
349
+ queue.push(input);
350
+ drain();
351
+ });
352
+ rl.on('SIGINT', () => { console.log(c.muted('\nUse /exit para sair')); rl.prompt(); });
353
+ rl.on('close', () => {
354
+ const check = () => {
355
+ if (!busy && queue.length === 0) { console.log(c.muted('\nEncerrado.\n')); process.exit(0); }
356
+ setTimeout(check, 100);
357
+ };
358
+ check();
359
+ });
360
+ }
361
+
362
+ // ---- Entry ----
363
+ (async () => {
364
+ const flags = process.argv.slice(2);
365
+ const hasPrompt = flags.some(a => !a.startsWith('-'));
366
+ const helpFlag = flags.some(a => a === '-h' || a === '--help');
367
+ const versionFlag = flags.some(a => a === '-v' || a === '--version');
368
+ const jarvisFlag = flags.some(a => a === '--jarvis' || a === '-j');
369
+ if (helpFlag) { printCliHelp(); process.exit(0); }
370
+ if (versionFlag) { console.log('PrimoCode'); process.exit(0); }
371
+ if (jarvisFlag) {
372
+ try {
373
+ const jarvis = require('../lib/jarvis');
374
+ await jarvis.launch(state.config);
375
+ process.exit(0);
376
+ } catch (e) {
377
+ console.log(c.err(`JARVIS: ${e.message}`));
378
+ process.exit(1);
379
+ }
380
+ }
381
+ if (hasPrompt) { await runOneShot(process.argv); process.exit(0); }
382
+ startInteractive();
383
+ })().catch((err) => {
384
+ console.error(c.err('Erro fatal: ') + err.message);
385
+ process.exit(1);
386
+ });