primocode 9.9.0 → 10.0.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.
Files changed (63) hide show
  1. package/README.md +30 -6
  2. package/bin/primocode.js +100 -341
  3. package/lib/act.js +245 -23
  4. package/lib/browser.js +17 -0
  5. package/lib/casar.js +108 -0
  6. package/lib/catalogo.js +6 -2
  7. package/lib/config.js +10 -3
  8. package/lib/cursor.js +10 -1
  9. package/lib/entrada.js +4 -2
  10. package/lib/estudio/diretor.js +9 -1
  11. package/lib/estudio/ende.js +2 -0
  12. package/lib/estudio/estilo.js +51 -0
  13. package/lib/estudio/glifos.js +215 -0
  14. package/lib/estudio/index.js +63 -11
  15. package/lib/estudio/kit/base.jsx +285 -0
  16. package/lib/estudio/kit/index.jsx +11 -0
  17. package/lib/estudio/kit/marca.jsx +741 -0
  18. package/lib/estudio/kit/vox.jsx +357 -0
  19. package/lib/estudio/kit.js +72 -0
  20. package/lib/estudio/lancamento.js +413 -0
  21. package/lib/estudio/morfar.js +191 -0
  22. package/lib/estudio/motion.js +333 -0
  23. package/lib/estudio/plano.js +54 -7
  24. package/lib/estudio/remotion.js +167 -52
  25. package/lib/estudio/ritmo.js +77 -0
  26. package/lib/estudio/sons-leves.js +62 -0
  27. package/lib/estudio/vox.js +385 -0
  28. package/lib/fala.js +1 -94
  29. package/lib/pipeline.js +55 -56
  30. package/lib/repeticao.js +3 -1
  31. package/lib/skills.js +8 -2
  32. package/lib/snapshot.js +111 -0
  33. package/lib/tools.js +79 -20
  34. package/lib/ui.js +13 -6
  35. package/lib/video/analise.js +89 -0
  36. package/lib/video/cli.js +74 -0
  37. package/lib/video/delta.js +90 -0
  38. package/lib/video/edits.js +379 -0
  39. package/lib/video/filters.js +247 -0
  40. package/lib/video/ops.js +322 -0
  41. package/lib/video/projeto.js +133 -0
  42. package/lib/video/render.js +221 -0
  43. package/lib/video/texto.js +142 -0
  44. package/lib/video/timeline.js +310 -0
  45. package/lib/video/transcript.js +124 -0
  46. package/lib/voz.js +75 -223
  47. package/package.json +2 -3
  48. package/skills/motion-de-primeira/SKILL.md +21 -1
  49. package/studio/texto.py +208 -0
  50. package/voz/primo_voz/__main__.py +96 -236
  51. package/voz/primo_voz/transcrever.py +31 -1
  52. package/app/ansi.js +0 -55
  53. package/app/app.css +0 -500
  54. package/app/app.js +0 -1017
  55. package/app/icone.svg +0 -6
  56. package/app/index.html +0 -220
  57. package/app/primo-marca.js +0 -192
  58. package/lib/app.js +0 -615
  59. package/lib/icone.js +0 -263
  60. package/lib/instalar-app.js +0 -231
  61. package/lib/painel.js +0 -291
  62. package/voz/primo_voz/desperta.py +0 -195
  63. package/voz/primo_voz/orbe.py +0 -406
@@ -1,406 +0,0 @@
1
- """orbe.py — a única coisa que aparece na tela no modo voz.
2
-
3
- "Quero que ele fique na parte inferior da tela, bem centralizado, um pouco
4
- maior [...] mas ele tá rodando em uma caixa, deve ser totalmente
5
- transparente." (23/09/2026)
6
-
7
- ── O QUE ERA A "CAIXA" ─────────────────────────────────────────────────────
8
- Medido com uma captura da própria janela, no Tk 9 do macOS:
9
-
10
- 1. `overrideredirect(True)` NÃO tira a moldura: a janela nascia com barra de
11
- título, os três botões e o nome "Primo Code Voice".
12
- 2. O fundo `systemTransparent` saía PRETO.
13
- 3. O Canvas não apaga o quadro anterior sobre fundo transparente: cada
14
- partícula deixava um rastro, e em segundos a janela virava um borrão.
15
-
16
- Nenhum dos três se conserta de dentro do Tk. Então, no macOS, a orb é
17
- desenhada pelo próprio sistema (PyObjC): uma NSWindow sem borda, sem fundo e
18
- sem sombra, e uma NSView que limpa e pinta cada quadro com transparência de
19
- verdade — o brilho é um gradiente com alfa, não discos misturados contra uma
20
- cor de fundo adivinhada. Só a orb aparece; o resto da janela não existe.
21
-
22
- O Tk continua sendo quem roda o laço de eventos (é ele que o resto do modo
23
- voz já usa), e segue como desenho de reserva fora do macOS ou sem PyObjC.
24
-
25
- ── ONDE ELA MORA ───────────────────────────────────────────────────────────
26
- Embaixo, centralizada na ÁREA ÚTIL da tela — acima do Dock, não atrás dele.
27
- A posição é aplicada depois de a janela existir: dada cedo demais, a janela
28
- sem moldura ignora.
29
-
30
- ── O QUE ELA DESENHA ───────────────────────────────────────────────────────
31
- Um núcleo com brilho suave e um enxame de partículas em órbita. A cor diz o
32
- estado; o MOVIMENTO diz a intensidade — as partículas se afastam e aceleram
33
- com a voz, e respiram devagar quando ele está pensando. Nenhuma palavra: quem
34
- está falando não lê.
35
- """
36
- from __future__ import annotations
37
-
38
- import math
39
- import queue
40
- import random
41
- import sys
42
- import tkinter as tk
43
- from typing import Callable, List, Optional, Tuple
44
-
45
- # Os quatro estados, e a cor de cada um. A paleta é a do PrimoCode (ciano de
46
- # marca, âmbar de aviso) para o modo voz não parecer outro produto.
47
- CORES = {
48
- "esperando": ("#22b8d8", "#0e3a4a"), # ciano calmo: pronto, sem pressa
49
- "ouvindo": ("#22d3ee", "#0e7490"), # ciano vivo: a vez é sua
50
- "pensando": ("#fbbf24", "#78350f"), # âmbar: ele está trabalhando
51
- "falando": ("#67e8f9", "#0891b2"), # ciano claro: a voz é dele
52
- }
53
-
54
- LARGURA = 360 # só a área da orb e do brilho — o resto é transparente
55
- ALTURA = 220
56
- RAIO_BASE = 34 # o núcleo
57
- MARGEM_INFERIOR = 24 # acima da área útil (o Dock já fica de fora)
58
- PARTICULAS = 44
59
- FUNDO_FALSO = "#05070a" # só no desenho de reserva, onde não há alfa
60
-
61
-
62
- def _rgb(cor: str) -> Tuple[float, float, float]:
63
- return int(cor[1:3], 16) / 255, int(cor[3:5], 16) / 255, int(cor[5:7], 16) / 255
64
-
65
-
66
- def _mistura(a: str, b: str, quanto: float) -> str:
67
- """Duas cores hex, uma fração — a cor no meio do caminho."""
68
- quanto = max(0.0, min(1.0, quanto))
69
- ra, ga, ba = int(a[1:3], 16), int(a[3:5], 16), int(a[5:7], 16)
70
- rb, gb, bb = int(b[1:3], 16), int(b[3:5], 16), int(b[5:7], 16)
71
- return "#%02x%02x%02x" % (
72
- int(ra + (rb - ra) * quanto),
73
- int(ga + (gb - ga) * quanto),
74
- int(ba + (bb - ba) * quanto),
75
- )
76
-
77
-
78
- class Particula:
79
- """Um ponto em órbita.
80
-
81
- Cada uma tem ângulo, raio e velocidade PRÓPRIOS, sorteados uma vez. É o
82
- que separa um enxame de um relógio: com os mesmos valores para todas,
83
- elas giram em formação e o olho lê como engrenagem, não como vida.
84
- """
85
-
86
- def __init__(self, rnd: random.Random) -> None:
87
- self.ang = rnd.uniform(0, math.tau)
88
- self.raio = rnd.uniform(1.25, 2.3) # múltiplo do raio do núcleo
89
- self.giro = rnd.uniform(-0.9, 0.9) or 0.4
90
- self.tam = rnd.uniform(1.3, 3.0)
91
- self.fase = rnd.uniform(0, math.tau)
92
- self.brilho = rnd.uniform(0.45, 1.0)
93
-
94
-
95
- class Quadro:
96
- """Um quadro da animação, sem saber quem vai pintá-lo."""
97
-
98
- def __init__(self, vivo: str, fundo: str, raio: float, pulso: float,
99
- particulas: List[Tuple[float, float, float, float]]):
100
- self.vivo, self.fundo = vivo, fundo
101
- self.raio, self.pulso = raio, pulso
102
- self.particulas = particulas # (x, y, tamanho, intensidade 0..1)
103
-
104
-
105
- # ── O desenho nativo do macOS ────────────────────────────────────────────
106
-
107
- def _criar_nativa(orbe: "Orbe"):
108
- """A janela sem borda e sem fundo, desenhada pelo macOS. None se não der."""
109
- if sys.platform != "darwin":
110
- return None
111
- try:
112
- import objc
113
- from AppKit import (NSBackingStoreBuffered, NSBezierPath, NSColor,
114
- NSCompositingOperationClear, NSGradient, NSMakeRect,
115
- NSRectFillUsingOperation, NSScreen, NSView, NSWindow)
116
- except Exception:
117
- return None
118
-
119
- def cor(hexa: str, alfa: float):
120
- r, g, b = _rgb(hexa)
121
- return NSColor.colorWithSRGBRed_green_blue_alpha_(r, g, b, max(0.0, min(1.0, alfa)))
122
-
123
- # A classe Objective-C só pode ser registrada uma vez por processo.
124
- try:
125
- Vista = objc.lookUpClass("PrimoOrbeVista")
126
- except objc.nosuchclass_error:
127
- class PrimoOrbeVista(NSView):
128
- def isOpaque(self):
129
- return False
130
-
131
- def acceptsFirstMouse_(self, evento):
132
- return True
133
-
134
- def drawRect_(self, rect):
135
- NSColor.clearColor().set()
136
- NSRectFillUsingOperation(self.bounds(), NSCompositingOperationClear)
137
- q = getattr(self, "quadro", None)
138
- if q is None:
139
- return
140
- cx, cy = LARGURA / 2, ALTURA / 2
141
- centro = (cx, cy)
142
-
143
- # BRILHO: um gradiente radial com alfa, que some no nada. É o
144
- # que o Tk não sabia fazer — e o que tira o "disco escuro".
145
- alcance = q.raio * (2.6 + q.pulso * 0.9)
146
- brilho = NSGradient.alloc().initWithColors_(
147
- [cor(q.vivo, 0.34 + q.pulso * 0.20), cor(q.vivo, 0.10), cor(q.vivo, 0.0)])
148
- brilho.drawFromCenter_radius_toCenter_radius_options_(centro, q.raio * 0.6, centro, alcance, 0)
149
-
150
- # PARTÍCULAS
151
- for (px, py, t, forca) in q.particulas:
152
- cor(q.vivo, 0.25 + forca * 0.75).set()
153
- NSBezierPath.bezierPathWithOvalInRect_(NSMakeRect(px - t, py - t, 2 * t, 2 * t)).fill()
154
-
155
- # NÚCLEO: gradiente do claro ao vivo, com um anel fino por fora.
156
- r = q.raio
157
- nucleo = NSGradient.alloc().initWithColors_(
158
- [cor(_mistura(q.vivo, "#ffffff", 0.55), 1.0), cor(q.vivo, 1.0),
159
- cor(_mistura(q.vivo, q.fundo, 0.45), 1.0)])
160
- nucleo.drawFromCenter_radius_toCenter_radius_options_(
161
- (cx - r * 0.25, cy + r * 0.25), 0, centro, r, 0)
162
- cor(q.vivo, 0.55).set()
163
- anel = NSBezierPath.bezierPathWithOvalInRect_(
164
- NSMakeRect(cx - r * 1.18, cy - r * 1.18, r * 2.36, r * 2.36))
165
- anel.setLineWidth_(1.5)
166
- anel.stroke()
167
-
168
- Vista = PrimoOrbeVista
169
-
170
- try:
171
- janela = NSWindow.alloc().initWithContentRect_styleMask_backing_defer_(
172
- NSMakeRect(0, 0, LARGURA, ALTURA), 0, NSBackingStoreBuffered, False) # 0 = sem borda
173
- janela.setOpaque_(False)
174
- janela.setBackgroundColor_(NSColor.clearColor())
175
- janela.setHasShadow_(False)
176
- janela.setLevel_(25) # NSStatusWindowLevel: por cima de tudo
177
- # O clique ATRAVESSA a orb: ela mora no pé da tela, em cima do Dock e
178
- # do que o cursor do Primo precisa clicar. Uma orb que segura o clique
179
- # faria o agente clicar nela em vez de no app.
180
- janela.setIgnoresMouseEvents_(True)
181
- janela.setCollectionBehavior_(1 | 16) # todos os espaços; fora do Exposé
182
- janela.setReleasedWhenClosed_(False)
183
- vista = Vista.alloc().initWithFrame_(NSMakeRect(0, 0, LARGURA, ALTURA))
184
- janela.setContentView_(vista)
185
-
186
- # Embaixo, centralizada na área útil (acima do Dock). O Cocoa conta
187
- # de baixo para cima, então a margem é somada ao pé da área útil.
188
- util = NSScreen.mainScreen().visibleFrame()
189
- x = util.origin.x + (util.size.width - LARGURA) / 2
190
- y = util.origin.y + MARGEM_INFERIOR
191
- janela.setFrameOrigin_((x, y))
192
- janela.orderFrontRegardless()
193
- return janela, vista
194
- except Exception:
195
- return None
196
-
197
-
198
- class Orbe:
199
- """A janela da orb. Roda na thread principal, como toda interface."""
200
-
201
- def __init__(self, ao_fechar: Optional[Callable] = None):
202
- self.raiz = tk.Tk()
203
- self.raiz.title("Primo Code Voice")
204
- self.estado = "esperando"
205
- self.visivel = True
206
- self.nivel = 0.0 # 0..1, o quanto a pessoa está falando
207
- self._fase = 0.0
208
- self._fila = queue.Queue()
209
- self._ao_fechar = ao_fechar
210
- self._vivo = True
211
- self._rnd = random.Random(7) # semente fixa: o enxame é sempre o mesmo
212
- self._particulas = [Particula(self._rnd) for _ in range(PARTICULAS)]
213
-
214
- nativa = _criar_nativa(self)
215
- if nativa:
216
- # O macOS desenha; a janela do Tk fica escondida e só roda o laço.
217
- self.janela, self.vista = nativa
218
- self.nativa = True
219
- self.raiz.withdraw()
220
- else:
221
- self.janela = self.vista = None
222
- self.nativa = False
223
- self._montar_tk()
224
-
225
- self.raiz.protocol("WM_DELETE_WINDOW", self.fechar)
226
- self.raiz.bind("<Escape>", lambda e: self.fechar())
227
- self._pintar()
228
-
229
- # ── a reserva em Tk (fora do macOS, ou sem PyObjC) ───────────────────
230
- def _montar_tk(self) -> None:
231
- self.raiz.overrideredirect(True)
232
- self.fundo = FUNDO_FALSO
233
- if sys.platform == "win32":
234
- # No Windows o Tk dá transparência por cor-chave, e ela funciona.
235
- try:
236
- self.raiz.attributes("-transparentcolor", FUNDO_FALSO)
237
- except tk.TclError:
238
- pass
239
- try:
240
- self.raiz.attributes("-topmost", True)
241
- except tk.TclError:
242
- pass
243
- self.raiz.configure(bg=self.fundo)
244
- self.tela = tk.Canvas(self.raiz, width=LARGURA, height=ALTURA,
245
- bg=self.fundo, highlightthickness=0, bd=0)
246
- self.tela.pack()
247
- self.raiz.update_idletasks()
248
- self._posicionar_tk()
249
- self.raiz.after(120, self._posicionar_tk)
250
- self.raiz.bind("<Button-1>", self._pegar)
251
- self.raiz.bind("<B1-Motion>", self._arrastar)
252
- self._pego = (0, 0)
253
-
254
- def _posicionar_tk(self) -> None:
255
- x = (self.raiz.winfo_screenwidth() - LARGURA) // 2
256
- y = self.raiz.winfo_screenheight() - ALTURA - MARGEM_INFERIOR - 48
257
- try:
258
- self.raiz.geometry(f"{LARGURA}x{ALTURA}+{x}+{max(0, y)}")
259
- except tk.TclError:
260
- pass
261
-
262
- def _pegar(self, e) -> None:
263
- self._pego = (e.x_root - self.raiz.winfo_x(), e.y_root - self.raiz.winfo_y())
264
-
265
- def _arrastar(self, e) -> None:
266
- self.raiz.geometry(f"+{e.x_root - self._pego[0]}+{e.y_root - self._pego[1]}")
267
-
268
- # ── a animação ───────────────────────────────────────────────────────
269
- def _quadro(self) -> Quadro:
270
- self._fase += 0.045
271
- vivo, fundo = CORES.get(self.estado, CORES["esperando"])
272
- cx, cy = LARGURA / 2, ALTURA / 2
273
-
274
- # A respiração de cada estado. Quem espera respira devagar; quem
275
- # pensa, num ritmo próprio; quem ouve pulsa com a VOZ, não com o
276
- # relógio — é o que faz a orb parecer estar escutando de verdade.
277
- if self.estado == "ouvindo":
278
- pulso = 0.25 + self.nivel * 1.15
279
- elif self.estado == "pensando":
280
- pulso = 0.42 + 0.30 * math.sin(self._fase * 2.1)
281
- elif self.estado == "falando":
282
- pulso = 0.55 + 0.38 * math.sin(self._fase * 3.4)
283
- else:
284
- pulso = 0.16 + 0.10 * math.sin(self._fase * 0.9)
285
- raio = RAIO_BASE * (1 + pulso * 0.30)
286
-
287
- pontos = []
288
- for p in self._particulas:
289
- p.ang += p.giro * 0.012 * (0.6 + pulso)
290
- # O raio respira junto com a voz: quanto mais alto se fala, mais
291
- # longe o enxame se abre.
292
- osc = math.sin(self._fase * 1.7 + p.fase)
293
- r = raio * (p.raio + 0.25 * osc + pulso * 0.40)
294
- # Elipse leve, mais larga que alta — e dentro da janela: o que
295
- # passa da borda é cortado seco, e partícula cortada é defeito.
296
- px = cx + math.cos(p.ang) * r * 1.2
297
- py = cy + math.sin(p.ang) * r * 0.74
298
- t = p.tam * (0.75 + pulso * 0.6)
299
- pontos.append((px, py, t, p.brilho * (0.5 + pulso * 0.5)))
300
- return Quadro(vivo, fundo, raio, pulso, pontos)
301
-
302
- def _pintar(self) -> None:
303
- if not self._vivo:
304
- return
305
- while True:
306
- try:
307
- estado, nivel = self._fila.get_nowait()
308
- if estado == "__esconder__":
309
- self._aplicarVisibilidade(False)
310
- continue
311
- if estado == "__mostrar__":
312
- self._aplicarVisibilidade(True)
313
- continue
314
- if estado:
315
- self.estado = estado
316
- if nivel is not None:
317
- self.nivel = max(0.0, min(1.0, nivel))
318
- except queue.Empty:
319
- break
320
-
321
- q = self._quadro()
322
- if self.nativa:
323
- self.vista.quadro = q
324
- self.vista.setNeedsDisplay_(True)
325
- else:
326
- self._pintar_tk(q)
327
- self.raiz.after(33, self._pintar)
328
-
329
- def _pintar_tk(self, q: Quadro) -> None:
330
- """A reserva: forma chapada, sem alfa. Misturas contra o fundo."""
331
- cx, cy = LARGURA / 2, ALTURA / 2
332
- self.tela.delete("all")
333
- for i in range(6, 0, -1):
334
- f = i / 6
335
- r = q.raio * (1 + f * 1.3)
336
- self.tela.create_oval(cx - r, cy - r, cx + r, cy + r,
337
- outline=_mistura(self.fundo, q.vivo, (1 - f) ** 1.6 * 0.7), width=2)
338
- for (px, py, t, forca) in q.particulas:
339
- self.tela.create_oval(px - t, py - t, px + t, py + t,
340
- fill=_mistura(self.fundo, q.vivo, 0.3 + forca * 0.7), outline="")
341
- r = q.raio
342
- self.tela.create_oval(cx - r, cy - r, cx + r, cy + r,
343
- fill=_mistura(q.fundo, q.vivo, 0.8), outline="")
344
- rn = r * 0.55
345
- self.tela.create_oval(cx - rn, cy - rn, cx + rn, cy + rn,
346
- fill=_mistura(q.vivo, "#ffffff", 0.4), outline="")
347
-
348
- # ── visibilidade ─────────────────────────────────────────────────────
349
- def esconder(self) -> None:
350
- """Some da tela sem morrer.
351
-
352
- No modo de espera ele fica horas sem nada acontecer, e uma orb parada
353
- na tela todo esse tempo é estorvo. Destruir e recriar a janela a cada
354
- despertar pisca e, em alguns sistemas, rouba o foco de onde a pessoa
355
- estava digitando. Esconder guarda tudo de pé.
356
- """
357
- self._fila.put(("__esconder__", None))
358
-
359
- def mostrar(self) -> None:
360
- self._fila.put(("__mostrar__", None))
361
-
362
- def _aplicarVisibilidade(self, visivel: bool) -> None:
363
- # Só na thread da interface: nem o Tk nem o AppKit perdoam chamada de fora.
364
- try:
365
- if self.nativa:
366
- if visivel:
367
- self.janela.orderFrontRegardless()
368
- else:
369
- self.janela.orderOut_(None)
370
- elif visivel:
371
- self.raiz.deiconify()
372
- self.raiz.lift()
373
- if sys.platform != "linux":
374
- self.raiz.attributes("-topmost", True)
375
- else:
376
- self.raiz.withdraw()
377
- except Exception:
378
- pass # janela já fechando: nada a fazer
379
- self.visivel = visivel
380
-
381
- # ── ciclo de vida ────────────────────────────────────────────────────
382
- def mudar(self, estado: Optional[str] = None, nivel: Optional[float] = None) -> None:
383
- self._fila.put((estado, nivel))
384
-
385
- def fechar(self) -> None:
386
- if not self._vivo:
387
- return
388
- self._vivo = False
389
- try:
390
- if self._ao_fechar:
391
- self._ao_fechar()
392
- except Exception:
393
- pass
394
- try:
395
- if self.janela is not None:
396
- self.janela.orderOut_(None)
397
- self.janela.close()
398
- except Exception:
399
- pass
400
- try:
401
- self.raiz.destroy()
402
- except Exception:
403
- pass
404
-
405
- def rodar(self) -> None:
406
- self.raiz.mainloop()