primocode 7.1.0 → 7.2.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/bin/primocode.js +54 -25
- package/lib/act.js +12 -6
- package/lib/config.js +9 -8
- package/lib/ui.js +18 -11
- package/package.json +1 -1
package/bin/primocode.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
const readline = require('readline');
|
|
11
11
|
const path = require('path');
|
|
12
12
|
const fs = require('fs');
|
|
13
|
+
const os = require('os');
|
|
13
14
|
|
|
14
15
|
const pkg = require('../package.json');
|
|
15
16
|
const { c, welcomeBox, Spinner } = require('../lib/ui');
|
|
@@ -27,6 +28,7 @@ const state = {
|
|
|
27
28
|
attachedFiles: [],
|
|
28
29
|
lastResponse: '',
|
|
29
30
|
currentProject: null,
|
|
31
|
+
workDir: null, // pasta onde o agente trabalha nesta sessão
|
|
30
32
|
};
|
|
31
33
|
|
|
32
34
|
const HELP = `
|
|
@@ -96,33 +98,40 @@ function deriveProjectName(text) {
|
|
|
96
98
|
return picked || 'projeto';
|
|
97
99
|
}
|
|
98
100
|
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
// Como o Claude Code: se você abriu o PrimoCode dentro de uma pasta de
|
|
102
|
+
// trabalho, é ali que ele trabalha. Home e raiz de disco não contam — ninguém
|
|
103
|
+
// quer um agente soltando arquivo no meio de C:\Users\você.
|
|
104
|
+
function isUsableCwd(dir) {
|
|
105
|
+
const home = os.homedir();
|
|
106
|
+
const resolved = path.resolve(dir);
|
|
107
|
+
if (resolved === path.resolve(home)) return false;
|
|
108
|
+
if (resolved === path.parse(resolved).root) return false;
|
|
109
|
+
if (resolved.startsWith(path.join(home, 'primocode'))) return false;
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
103
112
|
|
|
113
|
+
// Escolhe onde trabalhar SEM criar nada. A pasta só é criada de verdade em
|
|
114
|
+
// createPendingProject(), quando o agente for mexer em arquivo.
|
|
115
|
+
function planWorkDir(userText) {
|
|
116
|
+
if (state.workDir) return { dir: state.workDir, pending: false };
|
|
104
117
|
const proposed = deriveProjectName(userText);
|
|
105
118
|
const similar = cfg.findSimilarProject(proposed);
|
|
119
|
+
const name = similar ? similar.name : proposed;
|
|
120
|
+
return { dir: cfg.getProjectDir(name), pending: true, name, similar: !!similar };
|
|
121
|
+
}
|
|
106
122
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
state.currentProject = similar.name;
|
|
111
|
-
console.log(c.ok(` Projeto: ${similar.name}`));
|
|
112
|
-
refreshPrompt();
|
|
113
|
-
return state.currentProject;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const dir = cfg.createProjectDir(proposed);
|
|
123
|
+
function createPendingProject(plan) {
|
|
124
|
+
const dir = cfg.createProjectDir(plan.name);
|
|
125
|
+
state.workDir = dir;
|
|
118
126
|
state.currentProject = path.basename(dir);
|
|
119
|
-
|
|
127
|
+
const rotulo = plan.similar ? 'Continuando em' : 'Projeto novo:';
|
|
128
|
+
console.log(c.ok(` ${rotulo} ${state.currentProject}`) + c.muted(` (${dir})`));
|
|
120
129
|
refreshPrompt();
|
|
121
|
-
return
|
|
130
|
+
return dir;
|
|
122
131
|
}
|
|
123
132
|
|
|
124
133
|
function projectDir() {
|
|
125
|
-
return state.
|
|
134
|
+
return state.workDir || process.cwd();
|
|
126
135
|
}
|
|
127
136
|
|
|
128
137
|
// ── Envio ──────────────────────────────────────────────────────────────
|
|
@@ -135,14 +144,16 @@ async function sendPrompt(userText) {
|
|
|
135
144
|
}
|
|
136
145
|
|
|
137
146
|
if (state.mode === 'primocode') {
|
|
138
|
-
|
|
147
|
+
const plan = planWorkDir(userText);
|
|
139
148
|
const r = await runAct(prompt, {
|
|
140
149
|
server: state.config.server,
|
|
141
150
|
model: state.config.model,
|
|
142
151
|
token: state.config.token,
|
|
143
152
|
history: state.history,
|
|
144
|
-
projectDir:
|
|
145
|
-
projectName: state.currentProject,
|
|
153
|
+
projectDir: plan.dir,
|
|
154
|
+
projectName: plan.pending ? plan.name : state.currentProject,
|
|
155
|
+
// Só chamado se o agente for mesmo mexer em arquivo.
|
|
156
|
+
ensureWorkDir: plan.pending ? () => createPendingProject(plan) : null,
|
|
146
157
|
});
|
|
147
158
|
state.history.push({ role: 'user', content: userText });
|
|
148
159
|
state.history.push({ role: 'assistant', content: r.assistantMessage.content });
|
|
@@ -214,6 +225,7 @@ async function handleSlash(text) {
|
|
|
214
225
|
break;
|
|
215
226
|
}
|
|
216
227
|
const dir = cfg.createProjectDir(rest);
|
|
228
|
+
state.workDir = dir;
|
|
217
229
|
state.currentProject = path.basename(dir);
|
|
218
230
|
console.log(c.ok(`Projeto: ${state.currentProject}`) + c.muted(` (${dir})`));
|
|
219
231
|
refreshPrompt();
|
|
@@ -314,8 +326,24 @@ async function handleSlash(text) {
|
|
|
314
326
|
|
|
315
327
|
// ── Main ───────────────────────────────────────────────────────────────
|
|
316
328
|
|
|
329
|
+
function shortPath(p) {
|
|
330
|
+
// Normaliza as barras antes de comparar — no Windows o cwd vem com "\"
|
|
331
|
+
// e a home pode vir com "/", e aí o prefixo nunca batia.
|
|
332
|
+
const norm = (s) => path.resolve(s).replace(/\\/g, '/');
|
|
333
|
+
const home = norm(os.homedir());
|
|
334
|
+
const full = norm(p);
|
|
335
|
+
return full.startsWith(home + '/') ? '~' + full.slice(home.length) : full;
|
|
336
|
+
}
|
|
337
|
+
|
|
317
338
|
async function main() {
|
|
318
339
|
cfg.ensureStructure();
|
|
340
|
+
|
|
341
|
+
// Abriu dentro de uma pasta de trabalho? Então é ali que ele atua.
|
|
342
|
+
if (isUsableCwd(process.cwd())) {
|
|
343
|
+
state.workDir = process.cwd();
|
|
344
|
+
state.currentProject = path.basename(state.workDir);
|
|
345
|
+
}
|
|
346
|
+
|
|
319
347
|
const args = process.argv.slice(2);
|
|
320
348
|
|
|
321
349
|
// Modo one-shot: primocode "crie um jogo de carro"
|
|
@@ -332,19 +360,20 @@ async function main() {
|
|
|
332
360
|
} catch { console.log(c.err(`Não consegui ler: ${args[fileFlag + 1]}`)); process.exit(1); }
|
|
333
361
|
}
|
|
334
362
|
|
|
335
|
-
|
|
363
|
+
const plan = planWorkDir(promptText);
|
|
336
364
|
await runAct(fullPrompt, {
|
|
337
365
|
server: state.config.server,
|
|
338
366
|
model: state.config.model,
|
|
339
367
|
token: state.config.token,
|
|
340
368
|
history: [],
|
|
341
|
-
projectDir:
|
|
342
|
-
projectName: state.currentProject,
|
|
369
|
+
projectDir: plan.dir,
|
|
370
|
+
projectName: plan.pending ? plan.name : state.currentProject,
|
|
371
|
+
ensureWorkDir: plan.pending ? () => createPendingProject(plan) : null,
|
|
343
372
|
});
|
|
344
373
|
process.exit(0);
|
|
345
374
|
}
|
|
346
375
|
|
|
347
|
-
console.log(welcomeBox(VERSION, state.
|
|
376
|
+
console.log(welcomeBox(VERSION, state.workDir ? shortPath(state.workDir) : null));
|
|
348
377
|
rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: makePrompt() });
|
|
349
378
|
rl.prompt();
|
|
350
379
|
|
package/lib/act.js
CHANGED
|
@@ -49,21 +49,24 @@ function summarizeForModel(result) {
|
|
|
49
49
|
return json.length > 4000 ? json.slice(0, 4000) + '…(truncado)' : json;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
function buildMemoryContext(projectName) {
|
|
52
|
+
function buildMemoryContext(projectDir, projectName) {
|
|
53
53
|
const parts = [];
|
|
54
54
|
const geral = cfg.readGeneralMemory();
|
|
55
55
|
if (geral.trim()) parts.push(`[memory-geral.md]\n${geral}`);
|
|
56
|
-
const projeto = cfg.readProjectMemory(
|
|
56
|
+
const projeto = cfg.readProjectMemory(projectDir);
|
|
57
57
|
if (projeto.trim()) parts.push(`[${projectName}/memory.md]\n${projeto}`);
|
|
58
58
|
return parts.join('\n\n');
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
async function runAct(userPrompt, opts) {
|
|
62
|
-
const { server, model, token, history, projectDir, projectName } = opts || {};
|
|
62
|
+
const { server, model, token, history, projectDir, projectName, ensureWorkDir } = opts || {};
|
|
63
|
+
// A pasta só nasce quando o agente for mesmo mexer em arquivo. Um "oi"
|
|
64
|
+
// não deve deixar diretório vazio espalhado pelo disco.
|
|
65
|
+
let workDirReady = !ensureWorkDir;
|
|
63
66
|
|
|
64
67
|
// Conversa completa desta tarefa: histórico recente + memória + pedido.
|
|
65
68
|
const messages = [];
|
|
66
|
-
const memory = buildMemoryContext(projectName);
|
|
69
|
+
const memory = buildMemoryContext(projectDir, projectName);
|
|
67
70
|
if (memory) messages.push({ role: 'system', content: `Memória persistente:\n\n${memory}` });
|
|
68
71
|
for (const m of (history || []).slice(-20)) {
|
|
69
72
|
if (m && m.role && m.content) messages.push({ role: m.role, content: m.content });
|
|
@@ -133,6 +136,8 @@ async function runAct(userPrompt, opts) {
|
|
|
133
136
|
continue;
|
|
134
137
|
}
|
|
135
138
|
|
|
139
|
+
if (!workDirReady) { ensureWorkDir(); workDirReady = true; }
|
|
140
|
+
|
|
136
141
|
console.log(actionHeader(tc.name, tc.arguments));
|
|
137
142
|
const result = await tools.execute(tc.name, tc.arguments, { projectDir });
|
|
138
143
|
logResult(tc.name, result);
|
|
@@ -144,8 +149,9 @@ async function runAct(userPrompt, opts) {
|
|
|
144
149
|
|
|
145
150
|
if (!finalText) finalText = 'Cheguei ao limite de passos sem concluir. Refaça o pedido em partes menores.';
|
|
146
151
|
|
|
147
|
-
|
|
148
|
-
|
|
152
|
+
// Nada de arquivo mexido = nada para lembrar.
|
|
153
|
+
if (projectName && workDirReady) {
|
|
154
|
+
cfg.appendProjectMemory(projectDir, `**Pedido:** ${userPrompt.slice(0, 300)}\n**Resultado:** ${finalText.slice(0, 500)}`);
|
|
149
155
|
}
|
|
150
156
|
|
|
151
157
|
return {
|
package/lib/config.js
CHANGED
|
@@ -135,18 +135,19 @@ function readGeneralMemory(limit = 2000) {
|
|
|
135
135
|
catch { return ''; }
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
// Recebem o DIRETÓRIO de trabalho, não o nome do projeto: quando o PrimoCode
|
|
139
|
+
// roda dentro de uma pasta sua, a memória fica lá, junto do código.
|
|
140
|
+
function readProjectMemory(dir, limit = 2000) {
|
|
141
|
+
if (!dir) return '';
|
|
142
|
+
try { return fs.readFileSync(path.join(dir, 'memory.md'), 'utf-8').slice(0, limit); }
|
|
141
143
|
catch { return ''; }
|
|
142
144
|
}
|
|
143
145
|
|
|
144
|
-
function appendProjectMemory(
|
|
145
|
-
if (!
|
|
146
|
-
const memoryPath = path.join(getProjectDir(projectName), 'memory.md');
|
|
146
|
+
function appendProjectMemory(dir, entry) {
|
|
147
|
+
if (!dir || !entry) return;
|
|
147
148
|
try {
|
|
148
|
-
ensureDir(
|
|
149
|
-
fs.appendFileSync(
|
|
149
|
+
ensureDir(dir);
|
|
150
|
+
fs.appendFileSync(path.join(dir, 'memory.md'), `\n### ${today()}\n${entry}\n`, 'utf-8');
|
|
150
151
|
} catch {}
|
|
151
152
|
}
|
|
152
153
|
|
package/lib/ui.js
CHANGED
|
@@ -36,24 +36,31 @@ const c = {
|
|
|
36
36
|
bold: (s) => `${PALETTE.bold}${s}${PALETTE.reset}`,
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
// Primo — o mascote.
|
|
40
|
-
|
|
39
|
+
// Primo — o mascote. Antena que brilha, olhos acesos, sorriso discreto.
|
|
40
|
+
function mascot() {
|
|
41
|
+
return [
|
|
42
|
+
` ${c.dim('·')} ${c.magenta('✦')} ${c.dim('·')} `,
|
|
43
|
+
` ${c.brand('╭─────╮')} `,
|
|
44
|
+
`${c.brandDim('─')}${c.brand('┤')} ${c.cyan('◉')} ${c.cyan('◉')} ${c.brand('├')}${c.brandDim('─')}`,
|
|
45
|
+
` ${c.brand('│')} ${c.magenta('‿')} ${c.brand('│')} `,
|
|
46
|
+
` ${c.brand('╰─┬─┬─╯')} `,
|
|
47
|
+
];
|
|
48
|
+
}
|
|
41
49
|
|
|
42
|
-
function welcomeBox(version,
|
|
50
|
+
function welcomeBox(version, contexto) {
|
|
43
51
|
const side = [
|
|
44
|
-
|
|
45
|
-
c.
|
|
46
|
-
c.muted(
|
|
52
|
+
'',
|
|
53
|
+
`${c.bold(c.brand('P R I M O C O D E'))}`,
|
|
54
|
+
`${c.muted('agente de engenharia')} ${c.dim('v' + version)}`,
|
|
55
|
+
contexto ? c.brandDim(contexto) : c.dim('nenhuma pasta ativa'),
|
|
56
|
+
'',
|
|
47
57
|
];
|
|
48
|
-
const rows =
|
|
49
|
-
const text = i === 0 ? '' : side[i - 1] || '';
|
|
50
|
-
return ` ${c.brand(art)} ${text}`;
|
|
51
|
-
});
|
|
58
|
+
const rows = mascot().map((art, i) => ` ${art} ${side[i] || ''}`);
|
|
52
59
|
return [
|
|
53
60
|
'',
|
|
54
61
|
...rows,
|
|
55
62
|
'',
|
|
56
|
-
` ${c.magenta('●')} ${c.muted('Primo Code')} ${c.brand('●')} ${c.muted('Chat')} ${c.dim('/help
|
|
63
|
+
` ${c.magenta('●')} ${c.muted('Primo Code')} ${c.brand('●')} ${c.muted('Chat')} ${c.dim('/help')}`,
|
|
57
64
|
'',
|
|
58
65
|
].join('\n');
|
|
59
66
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.2.0",
|
|
4
4
|
"description": "PrimoCode — agente de engenharia com IA e cursor próprio. Modo primocode cria arquivos, controla o desktop e navegador. Modo chat é conversa pura.",
|
|
5
5
|
"main": "bin/primocode.js",
|
|
6
6
|
"bin": {
|