primocode 9.2.2 → 9.2.3
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 +2 -2
- package/lib/estudio/index.js +31 -6
- package/lib/estudio/projeto.js +51 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# PrimoCode v9.2.
|
|
1
|
+
# PrimoCode v9.2.3
|
|
2
2
|
|
|
3
3
|
Agente de engenharia brasileiro para o terminal, no estilo Claude Code.
|
|
4
4
|
Cria arquivos de verdade, roda comandos, **controla o navegador e o desktop** —
|
|
@@ -9,7 +9,7 @@ OpenRouter, e cada apelido (`top`/`main`/`fast`) é uma **corrente**: se um mode
|
|
|
9
9
|
está no limite de pedidos, o próximo atende. Você não vê o erro, e não paga nada.
|
|
10
10
|
|
|
11
11
|
```
|
|
12
|
-
▐▛███▜▌ Primo Code v9.2.
|
|
12
|
+
▐▛███▜▌ Primo Code v9.2.3
|
|
13
13
|
▝▜█████▛▘ Bem-vindo de volta, Joel
|
|
14
14
|
▘▘ ▝▝ ~/primocode · /dir <pasta> muda
|
|
15
15
|
```
|
package/lib/estudio/index.js
CHANGED
|
@@ -81,6 +81,31 @@ function rodarPython(script, args, { timeout = 20 * 60000 } = {}) {
|
|
|
81
81
|
|
|
82
82
|
// ── estudio_projeto ──────────────────────────────────────────────────────
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* A queixa quando o projeto não aparece — e ela muda conforme o motivo.
|
|
86
|
+
*
|
|
87
|
+
* "não achei o projeto 'undefined'" é o que o agente recebia ao chamar a
|
|
88
|
+
* ferramenta SEM id, e não ensina nada: ele não sabe se inventou o id, se o
|
|
89
|
+
* projeto sumiu, ou se esqueceu o argumento. Visto em produção — o modelo
|
|
90
|
+
* chamou `estudio_plano` sem id e ficou repetindo a chamada.
|
|
91
|
+
*/
|
|
92
|
+
function semProjeto(id) {
|
|
93
|
+
if (id == null || String(id).trim() === '') {
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
error: 'faltou dizer em qual projeto (id).',
|
|
97
|
+
comoResolver: 'crie um com estudio_projeto acao:"criar" e use o id que ele devolve, '
|
|
98
|
+
+ 'ou ache o existente com acao:"listar" / acao:"procurar".',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
ok: false,
|
|
103
|
+
error: `não achei o projeto "${id}".`,
|
|
104
|
+
comoResolver: 'liste com estudio_projeto acao:"listar" e use um id de lá — '
|
|
105
|
+
+ 'o id é o nome da pasta (ex: "2026-09-22-anuncio-picpay"), não o título.',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
84
109
|
async function ferramentaProjeto(args) {
|
|
85
110
|
const acao = String(args.acao || 'listar').toLowerCase();
|
|
86
111
|
|
|
@@ -93,7 +118,7 @@ async function ferramentaProjeto(args) {
|
|
|
93
118
|
|
|
94
119
|
if (acao === 'abrir' || acao === 'ler') {
|
|
95
120
|
const p = projeto.ler(args.id);
|
|
96
|
-
if (!p) return
|
|
121
|
+
if (!p) return semProjeto(args.id);
|
|
97
122
|
const pl = projeto.lerPlano(args.id);
|
|
98
123
|
return {
|
|
99
124
|
ok: true, projeto: p,
|
|
@@ -142,7 +167,7 @@ function listarArquivos(pasta) {
|
|
|
142
167
|
|
|
143
168
|
async function ferramentaPlano(args) {
|
|
144
169
|
const p = projeto.ler(args.id);
|
|
145
|
-
if (!p) return
|
|
170
|
+
if (!p) return semProjeto(args.id);
|
|
146
171
|
|
|
147
172
|
if (args.voltarPara) return projeto.voltarPara(args.id, args.voltarPara);
|
|
148
173
|
|
|
@@ -193,7 +218,7 @@ async function ferramentaPlano(args) {
|
|
|
193
218
|
async function ferramentaMidia(args) {
|
|
194
219
|
const acao = String(args.acao || 'imagem').toLowerCase();
|
|
195
220
|
const p = args.id ? projeto.ler(args.id) : null;
|
|
196
|
-
if (args.id && !p) return
|
|
221
|
+
if (args.id && !p) return semProjeto(args.id);
|
|
197
222
|
|
|
198
223
|
if (acao === 'fontes') {
|
|
199
224
|
if (args.conferir) return assets.conferirFonte(args.conferir);
|
|
@@ -282,7 +307,7 @@ async function ferramentaNarrar(args) {
|
|
|
282
307
|
if (args.vozes) return rodarPython('narrar.py', ['vozes'], { timeout: 30000 });
|
|
283
308
|
|
|
284
309
|
const p = projeto.ler(args.id);
|
|
285
|
-
if (!p) return
|
|
310
|
+
if (!p) return semProjeto(args.id);
|
|
286
311
|
const texto = String(args.texto || '').trim();
|
|
287
312
|
if (!texto) return { ok: false, error: 'não há texto para narrar.' };
|
|
288
313
|
|
|
@@ -313,7 +338,7 @@ async function ferramentaNarrar(args) {
|
|
|
313
338
|
async function ferramentaVideo(args, ctx = {}) {
|
|
314
339
|
const acao = String(args.acao || 'medir').toLowerCase();
|
|
315
340
|
const p = projeto.ler(args.id);
|
|
316
|
-
if (!p) return
|
|
341
|
+
if (!p) return semProjeto(args.id);
|
|
317
342
|
|
|
318
343
|
const pronto = video.conferir();
|
|
319
344
|
if (!pronto.ok) return pronto;
|
|
@@ -436,7 +461,7 @@ async function transcrever(p, alvo, ctx) {
|
|
|
436
461
|
|
|
437
462
|
async function ferramentaRender(args, ctx = {}) {
|
|
438
463
|
const p = projeto.ler(args.id);
|
|
439
|
-
if (!p) return
|
|
464
|
+
if (!p) return semProjeto(args.id);
|
|
440
465
|
const pl = projeto.lerPlano(args.id);
|
|
441
466
|
if (!pl) return { ok: false, error: 'não há plano neste projeto.' };
|
|
442
467
|
|
package/lib/estudio/projeto.js
CHANGED
|
@@ -103,13 +103,38 @@ function lerJSON(arquivo, padrao = null) {
|
|
|
103
103
|
try { return JSON.parse(fs.readFileSync(arquivo, 'utf8')); } catch { return padrao; }
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
/**
|
|
107
|
+
* A pasta de um projeto — ou null, quando o id não serve.
|
|
108
|
+
*
|
|
109
|
+
* ── POR QUE ISTO VALIDA, EM VEZ DE SÓ CONCATENAR ────────────────────────
|
|
110
|
+
* O `id` vem do MODELO, e o modelo erra de dois jeitos que doem aqui:
|
|
111
|
+
*
|
|
112
|
+
* 1. Não manda nada. Visto em produção: o agente chamou `estudio_plano` sem
|
|
113
|
+
* `id`, o `path.join` recebeu `undefined` e a tool explodiu com
|
|
114
|
+
* 'The "path" argument must be of type string. Received undefined' — uma
|
|
115
|
+
* mensagem que não fala de projeto nenhum, e que o agente não tem como
|
|
116
|
+
* corrigir porque não diz o que faltou. A checagem de "achei o projeto?"
|
|
117
|
+
* vinha DEPOIS, e nunca era alcançada.
|
|
118
|
+
*
|
|
119
|
+
* 2. Manda um id com caminho dentro ("../../etc/passwd", "/tmp/x"). O
|
|
120
|
+
* `path.join` obedece: sobe de pasta e sai da casa dos projetos. O id de
|
|
121
|
+
* um projeto é um nome de pasta, não um caminho — então tudo que tem
|
|
122
|
+
* barra ou `..` é recusado, em vez de resolvido.
|
|
123
|
+
*/
|
|
124
|
+
function pastaDe(id) {
|
|
125
|
+
if (typeof id !== 'string' || !id.trim()) return null;
|
|
126
|
+
const limpo = id.trim();
|
|
127
|
+
if (limpo.includes('/') || limpo.includes('\\') || limpo.includes('..')) return null;
|
|
128
|
+
return path.join(PROJETOS, limpo);
|
|
129
|
+
}
|
|
107
130
|
|
|
108
131
|
/** O meta de um projeto, ou null. */
|
|
109
132
|
function ler(id) {
|
|
110
|
-
const
|
|
133
|
+
const pasta = pastaDe(id);
|
|
134
|
+
if (!pasta) return null;
|
|
135
|
+
const meta = lerJSON(path.join(pasta, 'projeto.json'));
|
|
111
136
|
if (!meta) return null;
|
|
112
|
-
return { ...meta, pasta
|
|
137
|
+
return { ...meta, pasta };
|
|
113
138
|
}
|
|
114
139
|
|
|
115
140
|
function salvar(id, mudancas) {
|
|
@@ -142,17 +167,21 @@ function listar({ limite = 50 } = {}) {
|
|
|
142
167
|
* perde um caderno quando o processo morre no meio.
|
|
143
168
|
*/
|
|
144
169
|
function anotar(id, tipo, texto, extra = {}) {
|
|
170
|
+
const pasta = pastaDe(id);
|
|
171
|
+
if (!pasta) return false;
|
|
145
172
|
const linha = JSON.stringify({ quando: agora(), tipo, texto: String(texto || ''), ...extra });
|
|
146
173
|
try {
|
|
147
|
-
fs.mkdirSync(
|
|
148
|
-
fs.appendFileSync(path.join(
|
|
174
|
+
fs.mkdirSync(pasta, { recursive: true });
|
|
175
|
+
fs.appendFileSync(path.join(pasta, 'caderno.jsonl'), linha + '\n', 'utf8');
|
|
149
176
|
return true;
|
|
150
177
|
} catch { return false; }
|
|
151
178
|
}
|
|
152
179
|
|
|
153
180
|
function caderno(id, { ultimas = 40 } = {}) {
|
|
181
|
+
const pasta = pastaDe(id);
|
|
182
|
+
if (!pasta) return [];
|
|
154
183
|
let bruto = '';
|
|
155
|
-
try { bruto = fs.readFileSync(path.join(
|
|
184
|
+
try { bruto = fs.readFileSync(path.join(pasta, 'caderno.jsonl'), 'utf8'); } catch { return []; }
|
|
156
185
|
const linhas = bruto.split('\n').filter(Boolean).slice(-ultimas);
|
|
157
186
|
return linhas.map((l) => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
|
|
158
187
|
}
|
|
@@ -193,9 +222,11 @@ function procurar(texto, { limite = 5 } = {}) {
|
|
|
193
222
|
|
|
194
223
|
// ── o plano ──────────────────────────────────────────────────────────────
|
|
195
224
|
|
|
196
|
-
|
|
225
|
+
// null quando o id não serve — e `lerJSON(null)` devolve null, que é o que
|
|
226
|
+
// todo mundo aqui já sabe tratar.
|
|
227
|
+
const planoDe = (id) => { const p = pastaDe(id); return p ? path.join(p, 'plano.json') : null; };
|
|
197
228
|
|
|
198
|
-
function lerPlano(id) {
|
|
229
|
+
function lerPlano(id) { const a = planoDe(id); return a ? lerJSON(a) : null; }
|
|
199
230
|
|
|
200
231
|
/**
|
|
201
232
|
* Grava o plano e guarda a versão anterior.
|
|
@@ -205,9 +236,11 @@ function lerPlano(id) { return lerJSON(planoDe(id)); }
|
|
|
205
236
|
* nada é sobrescrito sem uma cópia ficar para trás.
|
|
206
237
|
*/
|
|
207
238
|
function salvarPlano(id, plano, motivo = '') {
|
|
239
|
+
const pasta = pastaDe(id);
|
|
240
|
+
if (!pasta) return { ok: false, error: `Projeto "${id}" não existe.` };
|
|
208
241
|
const anterior = lerPlano(id);
|
|
209
242
|
if (anterior) {
|
|
210
|
-
const versoes = path.join(
|
|
243
|
+
const versoes = path.join(pasta, 'versoes');
|
|
211
244
|
fs.mkdirSync(versoes, { recursive: true });
|
|
212
245
|
const n = fs.readdirSync(versoes).filter((f) => f.endsWith('.json')).length + 1;
|
|
213
246
|
escreverJSON(path.join(versoes, `${String(n).padStart(3, '0')}.json`), anterior);
|
|
@@ -218,12 +251,16 @@ function salvarPlano(id, plano, motivo = '') {
|
|
|
218
251
|
}
|
|
219
252
|
|
|
220
253
|
function versoes(id) {
|
|
221
|
-
const
|
|
254
|
+
const pasta = pastaDe(id);
|
|
255
|
+
if (!pasta) return [];
|
|
256
|
+
const dir = path.join(pasta, 'versoes');
|
|
222
257
|
try { return fs.readdirSync(dir).filter((f) => f.endsWith('.json')).sort(); } catch { return []; }
|
|
223
258
|
}
|
|
224
259
|
|
|
225
260
|
function voltarPara(id, versao) {
|
|
226
|
-
const
|
|
261
|
+
const pasta = pastaDe(id);
|
|
262
|
+
if (!pasta) return { ok: false, error: `Projeto "${id}" não existe.` };
|
|
263
|
+
const arquivo = path.join(pasta, 'versoes', String(versao || '').replace(/[^\w.-]+/g, ''));
|
|
227
264
|
const plano = lerJSON(arquivo);
|
|
228
265
|
if (!plano) return { ok: false, error: `Versão "${versao}" não existe.` };
|
|
229
266
|
salvarPlano(id, plano, `voltou para a versão ${versao}`);
|
|
@@ -232,9 +269,11 @@ function voltarPara(id, versao) {
|
|
|
232
269
|
|
|
233
270
|
/** Onde guardar uma mídia baixada, com nome que não colide. */
|
|
234
271
|
function caminhoDeMidia(id, nome) {
|
|
272
|
+
const pasta = pastaDe(id);
|
|
273
|
+
if (!pasta) return null;
|
|
235
274
|
const limpo = String(nome || 'arquivo').replace(/[^\w.-]+/g, '-').slice(-60);
|
|
236
275
|
const marca = crypto.randomBytes(3).toString('hex');
|
|
237
|
-
return path.join(
|
|
276
|
+
return path.join(pasta, 'midia', `${marca}-${limpo}`);
|
|
238
277
|
}
|
|
239
278
|
|
|
240
279
|
module.exports = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "9.2.
|
|
3
|
+
"version": "9.2.3",
|
|
4
4
|
"description": "PrimoCode — agente de engenharia com IA e cursor próprio. Requer conta Conecta Primo AI (Premium ou Super). Cria arquivos, roda comandos, controla navegador e desktop: abre apps, clica em botões e ícones pelo nome, digita e usa atalhos.",
|
|
5
5
|
"main": "bin/primocode.js",
|
|
6
6
|
"bin": {
|