primocode 8.10.4 → 8.10.5
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/lib/tools.js +17 -3
- package/package.json +1 -1
- package/studio/arquivos/corte-legado-teste.json +1 -0
- package/studio/arquivos/corte-legado2.json +1 -0
- package/studio/arquivos/corte-ok-1270e.json +22 -0
- package/studio/server.py +19 -0
- package/studio/web/js/player.js +6 -1
- package/studio/web/js/render.js +13 -0
package/lib/tools.js
CHANGED
|
@@ -170,6 +170,9 @@ async function runShell(command, timeout = 60000, cwd) {
|
|
|
170
170
|
// execFile com array de argumentos nunca passa por shell nenhum: "target" vira
|
|
171
171
|
// UM argumento, não texto que um shell reinterpreta — sem isso, um valor tipo
|
|
172
172
|
// `https://x/"; touch /tmp/pwned; echo "` injetava comando (achado de segurança).
|
|
173
|
+
// Quando o auto-abrir do Studio abriu pela última vez — o freio das mil abas.
|
|
174
|
+
let ultimaAberturaStudio = 0;
|
|
175
|
+
|
|
173
176
|
function openInBrowser(target) {
|
|
174
177
|
const p = os.platform();
|
|
175
178
|
const [cmd, args, opener] = p === 'darwin' ? ['open', [target], 'open']
|
|
@@ -511,9 +514,20 @@ async function despachar(name, args, ctx) {
|
|
|
511
514
|
// uma tarefa inteira de tokens para o modelo redescobrir o
|
|
512
515
|
// link. Abrir é determinístico: não se gasta modelo com isso.
|
|
513
516
|
if (r && r.ok !== false && r.ver) {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
+
// UMA aba por tarefa. O modelo às vezes cria três versões
|
|
518
|
+
// seguidas, e abrir todas enchia o navegador do usuário de
|
|
519
|
+
// abas — "abre 1000 versões". A primeira abre; as outras
|
|
520
|
+
// são avisadas a ATUALIZAR a que já existe.
|
|
521
|
+
const agora = Date.now();
|
|
522
|
+
if (agora - ultimaAberturaStudio > 45000) {
|
|
523
|
+
ultimaAberturaStudio = agora;
|
|
524
|
+
try { openInBrowser(r.ver); } catch {}
|
|
525
|
+
r.abertoNoNavegador = true;
|
|
526
|
+
r.aviso = 'JÁ ABERTO no navegador do usuário. Chame finish AGORA dizendo o que foi criado — não abra de novo, não leia de volta, não ajuste sem pedido.';
|
|
527
|
+
} else {
|
|
528
|
+
r.aviso = `Você JÁ criou uma versão nesta tarefa, e ela está aberta no navegador. NÃO crie outra: `
|
|
529
|
+
+ `para mudar algo, use studio_atualizar com o id "${r.id}". Chame finish AGORA.`;
|
|
530
|
+
}
|
|
517
531
|
}
|
|
518
532
|
return r;
|
|
519
533
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "8.10.
|
|
3
|
+
"version": "8.10.5",
|
|
4
4
|
"description": "PrimoCode — agente de engenharia com IA e cursor próprio. Modelos grátis. 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": {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id": "corte-legado-teste", "ferramenta": "corte", "titulo": "legado", "doc": {"tema": "meia-noite", "largura": 1080, "altura": 1920, "cenas": [{"duracao": "4s", "fundo": "aurora", "elementos": [{"tipo": "titulo", "texto": "Imersão Founders AI"}, {"tipo": "subtitulo", "conteudo": "Paris Group · Chapecó"}]}]}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id": "corte-legado2", "ferramenta": "corte", "titulo": "legado2", "doc": {"tema": "meia-noite", "largura": 1080, "altura": 1920, "cenas": [{"duracao": "4s", "fundo": "aurora", "elementos": [{"tipo": "titulo", "texto": "Imersão Founders AI", "anim": "nenhuma"}, {"tipo": "subtitulo", "conteudo": "Paris Group · Chapecó", "anim": "nenhuma"}]}]}}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "corte-ok-1270e",
|
|
3
|
+
"ferramenta": "corte",
|
|
4
|
+
"titulo": "ok",
|
|
5
|
+
"doc": {
|
|
6
|
+
"tema": "brasa",
|
|
7
|
+
"cenas": [
|
|
8
|
+
{
|
|
9
|
+
"duracao": 3,
|
|
10
|
+
"elementos": [
|
|
11
|
+
{
|
|
12
|
+
"tipo": "texto",
|
|
13
|
+
"papel": "titulo",
|
|
14
|
+
"texto": "Founders AI",
|
|
15
|
+
"anim": "nenhuma"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
"atualizado": "2026-08-16T17:50:21+00:00"
|
|
22
|
+
}
|
package/studio/server.py
CHANGED
|
@@ -324,6 +324,14 @@ def validar_doc(modelo, doc):
|
|
|
324
324
|
'do usuário. O exemplo ensina o FORMATO; o conteúdo é seu: reescreva cada '
|
|
325
325
|
'cena com os fatos da tarefa (a pesquisa que você fez) e crie de novo.' % frase)
|
|
326
326
|
|
|
327
|
+
# Os tipos que o render.js de fato desenha. Elemento de tipo inventado
|
|
328
|
+
# ("titulo", "subtitulo", "video"...) passava na validação e caía no
|
|
329
|
+
# default do renderizador: texto cru de 16px num canvas de 1920 — ou nada,
|
|
330
|
+
# quando o texto estava noutro campo. Era o "vídeo com música mas tudo
|
|
331
|
+
# preto": documento válido na forma, invisível na tela.
|
|
332
|
+
TIPOS_VALIDOS = {"texto", "lista", "cartoes", "numeros", "tabela", "codigo",
|
|
333
|
+
"citacao", "forma", "imagem", "icone", "grafico", "no", "conector"}
|
|
334
|
+
|
|
327
335
|
if modelo == "cena":
|
|
328
336
|
cenas = doc.get("cenas")
|
|
329
337
|
if not isinstance(cenas, list) or not cenas:
|
|
@@ -337,9 +345,20 @@ def validar_doc(modelo, doc):
|
|
|
337
345
|
if not isinstance(els, list) or not els:
|
|
338
346
|
return ('cena %d sem "elementos". Cada cena precisa de elementos:[...] — '
|
|
339
347
|
'ex.: {"tipo":"texto","texto":"..."}. Leia studio_spec e refaça.' % (i + 1))
|
|
348
|
+
dur = c.get("duracao")
|
|
349
|
+
if dur is not None and not isinstance(dur, (int, float)):
|
|
350
|
+
return ('cena %d com "duracao" que não é número (veio %r). Use número de segundos: '
|
|
351
|
+
'"duracao": 3' % (i + 1, dur))
|
|
340
352
|
for j, e in enumerate(els):
|
|
341
353
|
if not isinstance(e, dict) or not e.get("tipo"):
|
|
342
354
|
return 'elemento %d da cena %d sem "tipo" (texto, imagem, lista, numeros, forma...)' % (j + 1, i + 1)
|
|
355
|
+
if e.get("tipo") not in TIPOS_VALIDOS:
|
|
356
|
+
return ('elemento %d da cena %d tem tipo "%s", que NÃO existe. Os tipos válidos são: %s. '
|
|
357
|
+
'Para título use {"tipo":"texto","papel":"titulo","texto":"..."}.'
|
|
358
|
+
% (j + 1, i + 1, e.get("tipo"), ", ".join(sorted(TIPOS_VALIDOS))))
|
|
359
|
+
if e.get("tipo") == "texto" and not (e.get("texto") or "").strip():
|
|
360
|
+
return ('elemento %d da cena %d é "texto" mas o campo "texto" está vazio — o conteúdo '
|
|
361
|
+
'vai SEMPRE no campo "texto".' % (j + 1, i + 1))
|
|
343
362
|
elif modelo == "documento":
|
|
344
363
|
blocos = doc.get("blocos")
|
|
345
364
|
if not isinstance(blocos, list) or not blocos:
|
package/studio/web/js/player.js
CHANGED
|
@@ -30,7 +30,12 @@
|
|
|
30
30
|
return String(Math.floor(s / 60)).padStart(2, "0") + ":" + String(s % 60).padStart(2, "0");
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
function duracaoDe(i) {
|
|
33
|
+
function duracaoDe(i) {
|
|
34
|
+
/* "duracao": "3s" (texto) fazia acc virar concatenação e o relógio dava
|
|
35
|
+
vídeo de 00:00. Número são ou o padrão — nunca corrente de texto. */
|
|
36
|
+
var d = Number(cenas[i] && cenas[i].duracao);
|
|
37
|
+
return (isFinite(d) && d > 0) ? Math.min(60, d) : 4;
|
|
38
|
+
}
|
|
34
39
|
function total() { return cenas.reduce(function (s, _, i) { return s + duracaoDe(i); }, 0); }
|
|
35
40
|
function inicioDe(i) { var s = 0; for (var k = 0; k < i; k++) s += duracaoDe(k); return s; }
|
|
36
41
|
|
package/studio/web/js/render.js
CHANGED
|
@@ -137,9 +137,22 @@ function svgGrafico(e, l, a) {
|
|
|
137
137
|
|
|
138
138
|
/* -------------------------------------------------------------- elementos */
|
|
139
139
|
|
|
140
|
+
var TIPOS_CONHECIDOS = { texto: 1, lista: 1, cartoes: 1, numeros: 1, tabela: 1, codigo: 1,
|
|
141
|
+
citacao: 1, forma: 1, imagem: 1, icone: 1, grafico: 1, no: 1, conector: 1 };
|
|
142
|
+
|
|
140
143
|
function renderElemento(e, ctx) {
|
|
141
144
|
var no = document.createElement("div");
|
|
142
145
|
var tipo = e.tipo || "texto";
|
|
146
|
+
/* Tipo inventado ("titulo", "subtitulo"...) caía num default sem estilo:
|
|
147
|
+
texto de 16px num canvas de 1920 — invisível — ou div vazio quando o
|
|
148
|
+
conteúdo estava noutro campo. O servidor novo recusa; documentos velhos
|
|
149
|
+
e outros clientes ganham a leitura generosa: vira texto de verdade. */
|
|
150
|
+
if (!TIPOS_CONHECIDOS[tipo]) {
|
|
151
|
+
e = { tipo: "texto", papel: /titulo|title/i.test(tipo) ? "titulo" : (e.papel || null),
|
|
152
|
+
texto: e.texto || e.conteudo || e.titulo || e.valor || e.legenda || "",
|
|
153
|
+
tamanho: e.tamanho, estilo: e.estilo, anim: e.anim, x: e.x, y: e.y, l: e.l, a: e.a };
|
|
154
|
+
tipo = "texto";
|
|
155
|
+
}
|
|
143
156
|
no.className = "el " + tipo;
|
|
144
157
|
no.dataset.tipo = tipo;
|
|
145
158
|
|