primocode 7.0.0 → 7.0.2

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/bin/primocode.js CHANGED
@@ -20,6 +20,7 @@ const { c, welcomeBox, Spinner, ProgressBar, askYesNo } = require('../lib/ui');
20
20
  const api = require('../lib/api');
21
21
  const tools = require('../lib/tools');
22
22
  const cfg = require('../lib/config');
23
+ const { createProjectDir, getProjectDir, listProjects } = require('../lib/config');
23
24
  const { runAct } = require('../lib/act');
24
25
 
25
26
  const VERSION = pkg.version;
@@ -30,10 +31,12 @@ const state = {
30
31
  mode: 'primocode',
31
32
  attachedFiles: [],
32
33
  lastResponse: '',
34
+ currentProject: null,
33
35
  };
34
36
 
35
37
  const HELP = `
36
38
  ${c.bold('Comandos')}
39
+ ${c.brand('/project')} lista projetos ou muda: /project <nome>
37
40
  ${c.brand('/mode')} Primo Code / Chat
38
41
  ${c.brand('/attach')} (Caminho do arquivo ou pasta) anexa arquivo
39
42
  ${c.brand('/clear')} limpa conversa
@@ -66,11 +69,14 @@ async function sendPrompt(userText) {
66
69
 
67
70
  if (state.mode === 'agent') {
68
71
  await runAgent(promptWithAttach, userText);
69
- } else if (state.mode === 'act') {
72
+ } else if (state.mode === 'primocode') {
73
+ const projectDir = state.currentProject ? getProjectDir(state.currentProject) : process.cwd();
70
74
  const r = await runAct(promptWithAttach, {
71
75
  server: state.config.server,
72
76
  model: state.config.model,
73
77
  history: state.history,
78
+ projectDir: projectDir,
79
+ projectName: state.currentProject,
74
80
  });
75
81
  state.history.push({ role: 'user', content: userText });
76
82
  state.history.push({ role: 'assistant', content: r.assistantMessage.content });
@@ -351,7 +357,7 @@ async function main() {
351
357
  catch { console.log(c.err(`Não consegui ler: ${args[fileFlag+1]}`)); process.exit(1); }
352
358
  }
353
359
  if (isAgent) await runAgent(promptText, prompt);
354
- else { const r = await runAct(promptText, {server:state.config.server, model:state.config.model, history:[]}); console.log('\n'+c.white(r.assistantMessage.content)); }
360
+ else { const r = await runAct(promptText, {server:state.config.server, model:state.config.model, history:[], projectDir: process.cwd()}); console.log('\n'+c.white(r.assistantMessage.content)); }
355
361
  process.exit(0);
356
362
  }
357
363
  console.log(welcomeBox(VERSION, state.config.server));
package/lib/act.js CHANGED
@@ -1,4 +1,4 @@
1
- // Loop do agente autônomo — vai e volta entre server e execução local.
1
+ // Loop do agente autônomo — streaming via /api/act/stream
2
2
 
3
3
  'use strict';
4
4
 
@@ -29,7 +29,7 @@ function makeLogger() {
29
29
  }
30
30
 
31
31
  async function runAct(userPrompt, opts) {
32
- const { server, model, history } = opts || {};
32
+ const { server, model, history, projectDir, projectName } = opts || {};
33
33
  const logger = makeLogger();
34
34
  const messages = [
35
35
  ...(Array.isArray(history) ? history.filter(m => m && m.role && m.content).slice(-20) : []),
@@ -37,36 +37,68 @@ async function runAct(userPrompt, opts) {
37
37
  ];
38
38
  const toolResults = [];
39
39
  let iteration = 0;
40
+ let plan = '';
41
+ let assistantContent = '';
40
42
 
41
43
  while (iteration < MAX_ITERATIONS) {
42
44
  iteration++;
43
- const sp = new Spinner('pensando').start();
44
- let response;
45
- try { response = await api.requestAct(server, messages, model); }
46
- catch (e) { sp.stop(); console.log(c.err(`\nErro no servidor: ${e.message||e}`)); throw e; }
47
- sp.stop();
45
+ const sp = new Spinner('Primo Code pensando').start();
48
46
 
49
- if (!response.tool_calls || !response.tool_calls.length) {
50
- const content = response.content || '';
51
- console.log('\n' + c.white(content));
52
- return { userMessage: { role: 'user', content: userPrompt }, assistantMessage: { role: 'assistant', content } };
53
- }
47
+ try {
48
+ await api.requestAct(server, messages, model, { projectDir, projectName, plan_cache: plan }, (evt) => {
49
+ if (evt.plan) {
50
+ plan = evt.plan;
51
+ }
52
+ if (evt.tool_call) {
53
+ sp.stop();
54
+ const tc = evt.tool_call;
55
+ console.log(actionHeader(tc.name, tc.arguments));
56
+ logger.event(tc.name, tc.arguments);
54
57
 
55
- for (const tc of response.tool_calls) {
56
- const name = tc.function?.name || tc.name;
57
- let args = {}; try { args = JSON.parse(tc.function?.arguments || tc.arguments || '{}'); } catch {}
58
- console.log(actionHeader(name, args));
59
- logger.event(name, null, null);
60
- let result; try { result = await tools.execute(name, args); } catch (e) { result = { ok: false, error: e.message || String(e) }; }
61
- logger.event(name + '_done', null, result);
62
- toolResults.push({ tool_call_id: tc.id, role: 'tool', name, content: JSON.stringify(result) });
63
- }
58
+ // Executar tool localmente
59
+ tools.execute(tc.name, tc.arguments).then(result => {
60
+ logger.event(tc.name + '_done', null, result);
61
+ toolResults.push({ tool_call_id: tc.id || 'tc_' + iteration, result });
62
+ });
63
+ sp.start();
64
+ }
65
+ if (evt.assistant_message) {
66
+ assistantContent = evt.assistant_message.content || '';
67
+ }
68
+ if (evt.delta) {
69
+ sp.stop();
70
+ process.stdout.write(evt.delta);
71
+ sp.start();
72
+ }
73
+ });
74
+ sp.stop();
75
+
76
+ // Se não teve tool_calls, mostrar conteúdo
77
+ if (toolResults.length === 0 && assistantContent) {
78
+ console.log('\n' + c.white(assistantContent));
79
+ return { userMessage: { role: 'user', content: userPrompt }, assistantMessage: { role: 'assistant', content: assistantContent } };
80
+ }
64
81
 
65
- messages.push({ role: 'assistant', content: response.content || '', tool_calls: response.tool_calls });
66
- for (const tr of toolResults) messages.push({ role: 'tool', tool_call_id: tr.tool_call_id, name: tr.name, content: tr.content });
82
+ // Se teve tool_results, enviar de volta para próximo turno
83
+ if (toolResults.length > 0) {
84
+ messages.push({ role: 'assistant', content: assistantContent, tool_calls: [] });
85
+ for (const tr of toolResults) {
86
+ messages.push({ role: 'tool', tool_call_id: tr.tool_call_id, content: JSON.stringify(tr.result) });
87
+ }
88
+ toolResults.length = 0;
89
+ assistantContent = '';
90
+ continue;
91
+ }
92
+
93
+ break;
94
+ } catch (e) {
95
+ sp.stop();
96
+ console.log(c.err(`\nErro: ${e.message||e}`));
97
+ throw e;
98
+ }
67
99
  }
68
- console.log(c.err('\nLimite de iterações atingido.'));
69
- return { userMessage: { role: 'user', content: userPrompt }, assistantMessage: { role: 'assistant', content: 'Limite de iterações atingido.' } };
100
+
101
+ return { userMessage: { role: 'user', content: userPrompt }, assistantMessage: { role: 'assistant', content: assistantContent || 'Concluído.' } };
70
102
  }
71
103
 
72
104
  module.exports = { runAct };
package/lib/api.js CHANGED
@@ -30,7 +30,10 @@ function requestStream(serverUrl, endpoint, payload, onEvent) {
30
30
  });
31
31
  }
32
32
 
33
- function requestAct(serverUrl, messages, model) { return request(serverUrl, '/api/act', { messages, model }); }
33
+ function requestAct(serverUrl, messages, model, extras, onEvent) {
34
+ // Usa streaming para /api/act/stream
35
+ return requestStream(serverUrl, '/api/act/stream', { messages, model, ...(extras || {}) }, onEvent);
36
+ }
34
37
 
35
38
  function getHealth(serverUrl) {
36
39
  return new Promise((resolve, reject) => { const url=new URL('/health',serverUrl); const client=url.protocol==='https:'?https:http; const req=client.get(url, (res)=>{ let body=''; res.on('data',c=>body+=c); res.on('end',()=>{ try { resolve(JSON.parse(body)); } catch { reject(new Error('Resposta inválida do health check')); } }); }); req.on('error',reject); });
package/lib/config.js CHANGED
@@ -1,4 +1,9 @@
1
1
  // Carrega config de ~/.primocode/config.json + env vars.
2
+ // Estrutura de pastas:
3
+ // ~/primocode/ <- raiz (projetos + you.md)
4
+ // ~/primocode/primocode/ <- codigo interno do CLI
5
+ // ~/primocode/<projeto>/ <- projetos gerados
6
+ // ~/primocode/<projeto>/memory.md <- memorias do projeto
2
7
 
3
8
  'use strict';
4
9
 
@@ -7,31 +12,112 @@ const path = require('path');
7
12
  const os = require('os');
8
13
 
9
14
  const HOME = os.homedir();
15
+ const ROOT_DIR = path.join(HOME, 'primocode');
16
+ const INTERNAL_DIR = path.join(ROOT_DIR, 'primocode');
10
17
  const CONFIG_DIR = path.join(HOME, '.primocode');
11
18
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
12
19
  const HISTORY_FILE = path.join(CONFIG_DIR, 'history.jsonl');
13
20
 
14
- function ensureDir() {
15
- try { fs.mkdirSync(CONFIG_DIR, { recursive: true }); } catch {}
21
+ const YOU_MD_TEMPLATE = `# You - PrimoCode AI
22
+
23
+ ## Sobre mim
24
+ Sou o Kimi Chat, um assistente de IA desenvolvido pela Moonshot AI.
25
+
26
+ ## Capacidades
27
+ - Ajudo na criacao de sistemas complexos
28
+ - Geracao de codigo de alto nivel
29
+ - Automacao de desktop e navegador
30
+ - Controle via CDP (Chrome DevTools Protocol)
31
+
32
+ ## Projetos
33
+ Este arquivo contem informacoes sobre mim e projetos em geral.
34
+ Cada pasta de projeto tem suas memorias em memory.md.
35
+ `;
36
+
37
+ const MEMORY_MD_TEMPLATE = `# Memorias do Projeto
38
+
39
+ ## Descricao
40
+ {nome_do_projeto}
41
+
42
+ ## Tecnologias
43
+ - Tecnologia: {tecnologia}
44
+
45
+ ## Estrutura de Pastas
46
+ - {estrutura}
47
+
48
+ ## Notas
49
+ - Criado em: {data}
50
+
51
+ ## Historico
52
+ - Primeira interacao: {data}
53
+ `;
54
+
55
+ function ensureDir(dir) {
56
+ try { fs.mkdirSync(dir, { recursive: true }); } catch {}
57
+ }
58
+
59
+ function ensureStructure() {
60
+ ensureDir(ROOT_DIR);
61
+ ensureDir(INTERNAL_DIR);
62
+ ensureDir(CONFIG_DIR);
63
+ const youMdPath = path.join(ROOT_DIR, 'you.md');
64
+ if (!fs.existsSync(youMdPath)) {
65
+ fs.writeFileSync(youMdPath, YOU_MD_TEMPLATE, 'utf-8');
66
+ }
67
+ return { rootDir: ROOT_DIR, internalDir: INTERNAL_DIR };
68
+ }
69
+
70
+ function createProjectDir(projectName) {
71
+ ensureStructure();
72
+ const projectDir = path.join(ROOT_DIR, projectName);
73
+ ensureDir(projectDir);
74
+ const memoryPath = path.join(projectDir, 'memory.md');
75
+ if (!fs.existsSync(memoryPath)) {
76
+ const content = MEMORY_MD_TEMPLATE
77
+ .replace(/{nome_do_projeto}/g, projectName)
78
+ .replace(/{tecnologia}/g, 'a definir')
79
+ .replace(/{estrutura}/g, 'a definir')
80
+ .replace(/{data}/g, new Date().toISOString().split('T')[0]);
81
+ fs.writeFileSync(memoryPath, content, 'utf-8');
82
+ }
83
+ return projectDir;
84
+ }
85
+
86
+ function getProjectDir(projectName) {
87
+ if (!projectName) return ROOT_DIR;
88
+ return path.join(ROOT_DIR, projectName);
89
+ }
90
+
91
+ function listProjects() {
92
+ ensureStructure();
93
+ try {
94
+ return fs.readdirSync(ROOT_DIR)
95
+ .filter(name => {
96
+ const fullPath = path.join(ROOT_DIR, name);
97
+ return fs.statSync(fullPath).isDirectory() && name !== 'primocode';
98
+ });
99
+ } catch { return []; }
16
100
  }
17
101
 
18
102
  function loadConfig() {
19
- ensureDir();
103
+ ensureDir(CONFIG_DIR);
104
+ ensureStructure();
20
105
  let file = {};
21
106
  try {
22
107
  if (fs.existsSync(CONFIG_FILE)) file = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
23
108
  } catch { file = {}; }
24
-
25
109
  return {
26
110
  server: process.env.PRIMOCODE_SERVER || file.server || 'https://primocode-server.onrender.com',
27
111
  model: process.env.PRIMOCODE_MODEL || file.model || 'main',
28
112
  autoApply: file.autoApply === true,
113
+ currentProject: file.currentProject || null,
114
+ rootDir: ROOT_DIR,
29
115
  _file: file,
30
116
  };
31
117
  }
32
118
 
33
119
  function saveConfig(partial) {
34
- ensureDir();
120
+ ensureDir(CONFIG_DIR);
35
121
  const cur = (() => { try { return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); } catch { return {}; } })();
36
122
  const next = { ...cur, ...partial };
37
123
  fs.writeFileSync(CONFIG_FILE, JSON.stringify(next, null, 2), 'utf8');
@@ -39,10 +125,16 @@ function saveConfig(partial) {
39
125
  }
40
126
 
41
127
  function appendHistory(entry) {
42
- ensureDir();
128
+ ensureDir(CONFIG_DIR);
43
129
  try {
44
130
  fs.appendFileSync(HISTORY_FILE, JSON.stringify({ ts: Date.now(), ...entry }) + '\n', 'utf8');
45
131
  } catch {}
46
132
  }
47
133
 
48
- module.exports = { loadConfig, saveConfig, appendHistory, CONFIG_DIR, CONFIG_FILE, HISTORY_FILE };
134
+ module.exports = {
135
+ loadConfig, saveConfig, appendHistory,
136
+ CONFIG_DIR, CONFIG_FILE, HISTORY_FILE,
137
+ ROOT_DIR, INTERNAL_DIR,
138
+ ensureStructure, createProjectDir, getProjectDir, listProjects,
139
+ YOU_MD_TEMPLATE, MEMORY_MD_TEMPLATE,
140
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primocode",
3
- "version": "7.0.0",
3
+ "version": "7.0.2",
4
4
  "description": "PrimoCode \u2014 agente de engenharia com IA e cursor pr\u00f3prio. Modo primocode cria arquivos, controla o desktop e navegador. Modo chat \u00e9 conversa pura.",
5
5
  "main": "bin/primocode.js",
6
6
  "bin": "./bin/primocode.js",