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 +8 -2
- package/lib/act.js +57 -25
- package/lib/api.js +4 -1
- package/lib/config.js +99 -7
- package/package.json +1 -1
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 === '
|
|
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 —
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
-
|
|
69
|
-
return { userMessage: { role: 'user', content: userPrompt }, assistantMessage: { role: 'assistant', content:
|
|
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
|
|
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
|
-
|
|
15
|
-
|
|
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 = {
|
|
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.
|
|
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",
|