primocode 8.9.0 → 8.10.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.
@@ -0,0 +1,113 @@
1
+ {
2
+ "id": "traco-estatico-traco-25872",
3
+ "ferramenta": "traco",
4
+ "titulo": "estatico-traco",
5
+ "doc": {
6
+ "tema": "meia-noite",
7
+ "largura": 1920,
8
+ "altura": 1080,
9
+ "cenas": [
10
+ {
11
+ "layout": "livre",
12
+ "fundo": "pontos",
13
+ "elementos": [
14
+ {
15
+ "id": "ped",
16
+ "tipo": "no",
17
+ "texto": "Pedido aberto",
18
+ "x": 120,
19
+ "y": 480,
20
+ "l": 320,
21
+ "a": 140,
22
+ "destaque": true,
23
+ "anim": "nenhuma"
24
+ },
25
+ {
26
+ "id": "val",
27
+ "tipo": "no",
28
+ "texto": "Validação\nfiscal",
29
+ "x": 620,
30
+ "y": 480,
31
+ "l": 320,
32
+ "a": 140,
33
+ "anim": "nenhuma"
34
+ },
35
+ {
36
+ "id": "ger",
37
+ "tipo": "no",
38
+ "texto": "Aprovação\ngerência",
39
+ "x": 1120,
40
+ "y": 300,
41
+ "l": 320,
42
+ "a": 140,
43
+ "anim": "nenhuma"
44
+ },
45
+ {
46
+ "id": "dir",
47
+ "tipo": "no",
48
+ "texto": "Aprovação\ndiretoria",
49
+ "x": 1120,
50
+ "y": 660,
51
+ "l": 320,
52
+ "a": 140,
53
+ "anim": "nenhuma"
54
+ },
55
+ {
56
+ "id": "ok",
57
+ "tipo": "no",
58
+ "texto": "Compra\nemitida",
59
+ "x": 1560,
60
+ "y": 480,
61
+ "l": 280,
62
+ "a": 140,
63
+ "destaque": true,
64
+ "anim": "nenhuma"
65
+ },
66
+ {
67
+ "tipo": "conector",
68
+ "de": "ped",
69
+ "para": "val",
70
+ "anim": "nenhuma"
71
+ },
72
+ {
73
+ "tipo": "conector",
74
+ "de": "val",
75
+ "para": "ger",
76
+ "texto": "até R$ 20 mil",
77
+ "anim": "nenhuma"
78
+ },
79
+ {
80
+ "tipo": "conector",
81
+ "de": "val",
82
+ "para": "dir",
83
+ "texto": "acima de R$ 20 mil",
84
+ "anim": "nenhuma"
85
+ },
86
+ {
87
+ "tipo": "conector",
88
+ "de": "ger",
89
+ "para": "ok",
90
+ "anim": "nenhuma"
91
+ },
92
+ {
93
+ "tipo": "conector",
94
+ "de": "dir",
95
+ "para": "ok",
96
+ "anim": "nenhuma"
97
+ },
98
+ {
99
+ "tipo": "texto",
100
+ "x": 120,
101
+ "y": 120,
102
+ "l": 900,
103
+ "a": 120,
104
+ "texto": "Compras · fluxo vigente",
105
+ "papel": "chapeu",
106
+ "anim": "nenhuma"
107
+ }
108
+ ]
109
+ }
110
+ ]
111
+ },
112
+ "atualizado": "2026-08-16T15:08:28+00:00"
113
+ }
@@ -0,0 +1,102 @@
1
+ {
2
+ "id": "traco-sweep-traco-0bcf4",
3
+ "ferramenta": "traco",
4
+ "titulo": "sweep-traco",
5
+ "doc": {
6
+ "tema": "meia-noite",
7
+ "largura": 1920,
8
+ "altura": 1080,
9
+ "cenas": [
10
+ {
11
+ "layout": "livre",
12
+ "fundo": "pontos",
13
+ "elementos": [
14
+ {
15
+ "id": "ped",
16
+ "tipo": "no",
17
+ "texto": "Pedido aberto",
18
+ "x": 120,
19
+ "y": 480,
20
+ "l": 320,
21
+ "a": 140,
22
+ "destaque": true
23
+ },
24
+ {
25
+ "id": "val",
26
+ "tipo": "no",
27
+ "texto": "Validação\nfiscal",
28
+ "x": 620,
29
+ "y": 480,
30
+ "l": 320,
31
+ "a": 140
32
+ },
33
+ {
34
+ "id": "ger",
35
+ "tipo": "no",
36
+ "texto": "Aprovação\ngerência",
37
+ "x": 1120,
38
+ "y": 300,
39
+ "l": 320,
40
+ "a": 140
41
+ },
42
+ {
43
+ "id": "dir",
44
+ "tipo": "no",
45
+ "texto": "Aprovação\ndiretoria",
46
+ "x": 1120,
47
+ "y": 660,
48
+ "l": 320,
49
+ "a": 140
50
+ },
51
+ {
52
+ "id": "ok",
53
+ "tipo": "no",
54
+ "texto": "Compra\nemitida",
55
+ "x": 1560,
56
+ "y": 480,
57
+ "l": 280,
58
+ "a": 140,
59
+ "destaque": true
60
+ },
61
+ {
62
+ "tipo": "conector",
63
+ "de": "ped",
64
+ "para": "val"
65
+ },
66
+ {
67
+ "tipo": "conector",
68
+ "de": "val",
69
+ "para": "ger",
70
+ "texto": "até R$ 20 mil"
71
+ },
72
+ {
73
+ "tipo": "conector",
74
+ "de": "val",
75
+ "para": "dir",
76
+ "texto": "acima de R$ 20 mil"
77
+ },
78
+ {
79
+ "tipo": "conector",
80
+ "de": "ger",
81
+ "para": "ok"
82
+ },
83
+ {
84
+ "tipo": "conector",
85
+ "de": "dir",
86
+ "para": "ok"
87
+ },
88
+ {
89
+ "tipo": "texto",
90
+ "x": 120,
91
+ "y": 120,
92
+ "l": 900,
93
+ "a": 120,
94
+ "texto": "Compras · fluxo vigente",
95
+ "papel": "chapeu"
96
+ }
97
+ ]
98
+ }
99
+ ]
100
+ },
101
+ "atualizado": "2026-08-16T15:07:33+00:00"
102
+ }
@@ -3,6 +3,68 @@
3
3
  No lugar de CapCut, Premiere e After Effects. Modelo `cena`, com `duracao` em cada
4
4
  cena, trilha sonora, transições e gravação em `.webm`.
5
5
 
6
+ <!-- O EXEMPLO FICA NO COMEÇO DE PROPÓSITO: quando o plano de tokens é
7
+ apertado, o agente recebe só os primeiros ~3500 caracteres desta spec. Um
8
+ vídeo inteiro saiu PRETO porque o formato do documento estava depois do
9
+ corte e o agente inventou a estrutura. Formato primeiro; detalhe depois. -->
10
+
11
+ ## Exemplo completo
12
+
13
+ ```json
14
+ {
15
+ "ferramenta": "corte",
16
+ "titulo": "Reels — por que sua conta de luz subiu",
17
+ "doc": {
18
+ "tema": "brasa",
19
+ "largura": 1080, "altura": 1920,
20
+ "acabamento": "grao",
21
+ "trilha": { "tipo": "gerada", "estilo": "pulso", "volume": 0.7 },
22
+ "cenas": [
23
+ { "layout": "centro", "duracao": 3, "fundo": "aurora", "transicao": "flash",
24
+ "elementos": [
25
+ { "tipo": "texto", "papel": "titulo", "texto": "Sua conta subiu\nde novo.",
26
+ "tamanho": 124, "estilo": "gradiente", "anim": "desfoque" }]},
27
+
28
+ { "layout": "centro", "duracao": 3, "fundo": "vinheta", "transicao": "limpar",
29
+ "elementos": [
30
+ { "tipo": "texto", "papel": "titulo", "texto": "E não foi o seu chuveiro.",
31
+ "tamanho": 78, "estilo": "marcador", "anim": "palavra" }]},
32
+
33
+ { "layout": "centro", "duracao": 4, "transicao": "zoom", "acabamento": "vinheta",
34
+ "fundo": { "imagem": "/midia/torre.jpg", "escurecer": 0.5 },
35
+ "elementos": [
36
+ { "tipo": "numeros", "contar": true, "anim": "saltar", "tamanho": 180,
37
+ "itens": ["+38% | bandeira tarifária em 12 meses"] }]},
38
+
39
+ { "layout": "livre", "duracao": 4, "transicao": "deslizar",
40
+ "elementos": [
41
+ { "tipo": "imagem", "src": "/midia/campo.jpg", "x": 0, "y": 0,
42
+ "l": 1080, "a": 1920, "raio": 0, "movimento": "aproximar", "filtro": "contraste" },
43
+ { "tipo": "forma", "forma": "retangulo", "x": 0, "y": 1180, "l": 1080, "a": 740,
44
+ "preenchimento": "rgba(10,5,6,.72)" },
45
+ { "tipo": "texto", "x": 90, "y": 1320, "l": 900, "a": 420,
46
+ "texto": "O custo está\nna transmissão", "tamanho": 104, "peso": 750,
47
+ "estilo": "sombra", "anim": "cortina" }]},
48
+
49
+ { "layout": "centro", "duracao": 5, "fundo": "raios", "transicao": "subir",
50
+ "elementos": [
51
+ { "tipo": "lista", "revelar": true, "tamanho": 52, "intervalo": 0.9,
52
+ "itens": ["Linhas saturadas no Nordeste",
53
+ "Usina gerando sem escoar",
54
+ "A conta chega em quem consome"] }]},
55
+
56
+ { "layout": "centro", "duracao": 3, "fundo": "brilho", "transicao": "fade",
57
+ "acabamento": "luz",
58
+ "elementos": [
59
+ { "tipo": "texto", "papel": "titulo", "texto": "Segue para\nentender a próxima.",
60
+ "tamanho": 96, "estilo": "contorno-tom", "anim": "elastico" }]}
61
+ ]
62
+ }
63
+ }
64
+ ```
65
+
66
+ ---
67
+
6
68
  ## Como pensar um vídeo curto
7
69
 
8
70
  Um Reels de 30 s são 7 a 9 cenas de 3 a 5 segundos. A regra é dura:
@@ -152,63 +214,6 @@ Em `lista`, `"revelar": true` distribui os itens dentro da duração da cena;
152
214
 
153
215
  ---
154
216
 
155
- ## Exemplo completo
156
-
157
- ```json
158
- {
159
- "ferramenta": "corte",
160
- "titulo": "Reels — por que sua conta de luz subiu",
161
- "doc": {
162
- "tema": "brasa",
163
- "largura": 1080, "altura": 1920,
164
- "acabamento": "grao",
165
- "trilha": { "tipo": "gerada", "estilo": "pulso", "volume": 0.7 },
166
- "cenas": [
167
- { "layout": "centro", "duracao": 3, "fundo": "aurora", "transicao": "flash",
168
- "elementos": [
169
- { "tipo": "texto", "papel": "titulo", "texto": "Sua conta subiu\nde novo.",
170
- "tamanho": 124, "estilo": "gradiente", "anim": "desfoque" }]},
171
-
172
- { "layout": "centro", "duracao": 3, "fundo": "vinheta", "transicao": "limpar",
173
- "elementos": [
174
- { "tipo": "texto", "papel": "titulo", "texto": "E não foi o seu chuveiro.",
175
- "tamanho": 78, "estilo": "marcador", "anim": "palavra" }]},
176
-
177
- { "layout": "centro", "duracao": 4, "transicao": "zoom", "acabamento": "vinheta",
178
- "fundo": { "imagem": "/midia/torre.jpg", "escurecer": 0.5 },
179
- "elementos": [
180
- { "tipo": "numeros", "contar": true, "anim": "saltar", "tamanho": 180,
181
- "itens": ["+38% | bandeira tarifária em 12 meses"] }]},
182
-
183
- { "layout": "livre", "duracao": 4, "transicao": "deslizar",
184
- "elementos": [
185
- { "tipo": "imagem", "src": "/midia/campo.jpg", "x": 0, "y": 0,
186
- "l": 1080, "a": 1920, "raio": 0, "movimento": "aproximar", "filtro": "contraste" },
187
- { "tipo": "forma", "forma": "retangulo", "x": 0, "y": 1180, "l": 1080, "a": 740,
188
- "preenchimento": "rgba(10,5,6,.72)" },
189
- { "tipo": "texto", "x": 90, "y": 1320, "l": 900, "a": 420,
190
- "texto": "O custo está\nna transmissão", "tamanho": 104, "peso": 750,
191
- "estilo": "sombra", "anim": "cortina" }]},
192
-
193
- { "layout": "centro", "duracao": 5, "fundo": "raios", "transicao": "subir",
194
- "elementos": [
195
- { "tipo": "lista", "revelar": true, "tamanho": 52, "intervalo": 0.9,
196
- "itens": ["Linhas saturadas no Nordeste",
197
- "Usina gerando sem escoar",
198
- "A conta chega em quem consome"] }]},
199
-
200
- { "layout": "centro", "duracao": 3, "fundo": "brilho", "transicao": "fade",
201
- "acabamento": "luz",
202
- "elementos": [
203
- { "tipo": "texto", "papel": "titulo", "texto": "Segue para\nentender a próxima.",
204
- "tamanho": 96, "estilo": "contorno-tom", "anim": "elastico" }]}
205
- ]
206
- }
207
- }
208
- ```
209
-
210
- ---
211
-
212
217
  ## No modo Abrir
213
218
 
214
219
  Toca sozinho ao carregar, com barra de transporte embaixo:
package/studio/server.py CHANGED
@@ -244,6 +244,17 @@ def criar(dados):
244
244
  if doc is not None and not isinstance(doc, dict):
245
245
  raise ValueError("doc precisa ser um objeto")
246
246
 
247
+ # ── O doc torto não pode virar arquivo ───────────────────────────────
248
+ # Quem cria é um agente de IA, e quando ele não viu a spec ele INVENTA a
249
+ # estrutura. Antes o arquivo era gravado assim mesmo: o player não achava
250
+ # cena nenhuma e o "vídeo" saía uma tela preta — o usuário viu exatamente
251
+ # isso. Recusar com o formato certo na mensagem transforma o erro mudo em
252
+ # correção na rodada seguinte.
253
+ if doc:
254
+ problema = validar_doc(POR_SLUG[ferramenta]["modelo"], doc)
255
+ if problema:
256
+ raise ValueError(problema)
257
+
247
258
  arq_id = ferramenta + "-" + slugify(titulo) + "-" + uuid.uuid4().hex[:5]
248
259
  return gravar({
249
260
  "id": arq_id,
@@ -253,6 +264,41 @@ def criar(dados):
253
264
  })
254
265
 
255
266
 
267
+ def validar_doc(modelo, doc):
268
+ """None quando o doc serve; senão, a frase que ENSINA o formato certo.
269
+
270
+ A mensagem repete a estrutura mínima de propósito: ela volta para o agente
271
+ como resultado da tool, e é a única chance de ele se corrigir sem o
272
+ usuário ver um vídeo preto."""
273
+ if modelo == "cena":
274
+ cenas = doc.get("cenas")
275
+ if not isinstance(cenas, list) or not cenas:
276
+ return ('doc sem "cenas". O formato é: {"tema":"...", "cenas":[{"layout":"centro",'
277
+ '"duracao":3,"elementos":[{"tipo":"texto","texto":"..."}]}]}. '
278
+ 'Leia studio_spec da ferramenta e refaça.')
279
+ for i, c in enumerate(cenas):
280
+ if not isinstance(c, dict):
281
+ return "cena %d não é um objeto" % (i + 1)
282
+ els = c.get("elementos")
283
+ if not isinstance(els, list) or not els:
284
+ return ('cena %d sem "elementos". Cada cena precisa de elementos:[...] — '
285
+ 'ex.: {"tipo":"texto","texto":"..."}. Leia studio_spec e refaça.' % (i + 1))
286
+ for j, e in enumerate(els):
287
+ if not isinstance(e, dict) or not e.get("tipo"):
288
+ return 'elemento %d da cena %d sem "tipo" (texto, imagem, lista, numeros, forma...)' % (j + 1, i + 1)
289
+ elif modelo == "documento":
290
+ blocos = doc.get("blocos")
291
+ if not isinstance(blocos, list) or not blocos:
292
+ return ('doc sem "blocos". O formato é: {"tema":"papel","blocos":[{"tipo":"titulo",'
293
+ '"texto":"..."},{"tipo":"paragrafo","texto":"..."}]}. Leia studio_spec e refaça.')
294
+ elif modelo == "planilha":
295
+ abas = doc.get("abas")
296
+ if not isinstance(abas, list) or not abas:
297
+ return ('doc sem "abas". O formato é: {"abas":[{"nome":"...","colunas":[...],'
298
+ '"linhas":[[...]]}]}. Leia studio_spec e refaça.')
299
+ return None
300
+
301
+
256
302
  def exemplo(ferramenta):
257
303
  """Documento inicial de cada ferramenta — o mínimo que já fica bonito."""
258
304
  if ferramenta == "prosa":