primocode 8.42.1 → 9.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 (102) hide show
  1. package/README.md +41 -37
  2. package/bin/primocode.js +599 -327
  3. package/lib/acervo.js +8 -1
  4. package/lib/act.js +205 -114
  5. package/lib/api.js +35 -4
  6. package/lib/boas-vindas.js +31 -192
  7. package/lib/catalogo.js +60 -60
  8. package/lib/config.js +1 -1
  9. package/lib/conta.js +3 -24
  10. package/lib/diff.js +161 -0
  11. package/lib/diretor.js +37 -47
  12. package/lib/entrada.js +545 -0
  13. package/lib/especialistas.js +2 -2
  14. package/lib/estudio/assets.js +343 -0
  15. package/lib/estudio/identidade.js +560 -0
  16. package/lib/estudio/index.js +565 -0
  17. package/lib/estudio/plano.js +327 -0
  18. package/lib/estudio/projeto.js +246 -0
  19. package/lib/estudio/remotion.js +606 -0
  20. package/lib/estudio/slides.js +190 -0
  21. package/lib/estudio/video.js +296 -0
  22. package/lib/fala.js +13 -1
  23. package/lib/marca.js +4 -1
  24. package/lib/marketplace.js +189 -0
  25. package/lib/permissao.js +137 -0
  26. package/lib/registro.js +229 -0
  27. package/lib/skills.js +329 -0
  28. package/lib/tools.js +51 -181
  29. package/lib/ui.js +7 -3
  30. package/lib/voz.js +239 -0
  31. package/package.json +12 -3
  32. package/skills/acessibilidade/SKILL.md +28 -0
  33. package/skills/api-que-nao-quebra/SKILL.md +27 -0
  34. package/skills/depurar-de-verdade/SKILL.md +29 -0
  35. package/skills/editar-meu-video/SKILL.md +59 -0
  36. package/skills/escrever-em-portugues/SKILL.md +28 -0
  37. package/skills/git-limpo/SKILL.md +28 -0
  38. package/skills/motion-de-primeira/SKILL.md +62 -0
  39. package/skills/pagina-que-converte/SKILL.md +28 -0
  40. package/skills/performance-web/SKILL.md +28 -0
  41. package/skills/revisao-antes-de-entregar/SKILL.md +27 -0
  42. package/skills/slides-que-prendem/SKILL.md +40 -0
  43. package/studio/edge.py +16 -66
  44. package/studio/narrar.py +274 -0
  45. package/studio/pessoa.py +167 -0
  46. package/voz/primo_voz/__init__.py +0 -0
  47. package/voz/primo_voz/__main__.py +325 -0
  48. package/voz/primo_voz/escuta.py +235 -0
  49. package/voz/primo_voz/orbe.py +216 -0
  50. package/lib/audio-livre.js +0 -177
  51. package/lib/chaves.js +0 -149
  52. package/lib/claude-engine.js +0 -631
  53. package/lib/codex-engine.js +0 -208
  54. package/lib/remotion.js +0 -663
  55. package/lib/studio.js +0 -960
  56. package/studio/PRIMOCODE.md +0 -58
  57. package/studio/README.md +0 -154
  58. package/studio/__pycache__/edge.cpython-311.pyc +0 -0
  59. package/studio/__pycache__/elevenlabs.cpython-311.pyc +0 -0
  60. package/studio/__pycache__/voz.cpython-311.pyc +0 -0
  61. package/studio/arquivos/corte-teste-de-camera-fb823.json +0 -57
  62. package/studio/ferramentas/00-protocolo.md +0 -125
  63. package/studio/ferramentas/01-modelos.md +0 -270
  64. package/studio/ferramentas/README.md +0 -32
  65. package/studio/ferramentas/corte.md +0 -919
  66. package/studio/ferramentas/grade.md +0 -92
  67. package/studio/ferramentas/prisma.md +0 -107
  68. package/studio/ferramentas/prosa.md +0 -86
  69. package/studio/ferramentas/tela.md +0 -93
  70. package/studio/ferramentas/traco.md +0 -79
  71. package/studio/server.py +0 -2254
  72. package/studio/studio.log +0 -57
  73. package/studio/voz.py +0 -388
  74. package/studio/web/analise.html +0 -52
  75. package/studio/web/core.css +0 -1167
  76. package/studio/web/fontes/bebas.woff2 +0 -0
  77. package/studio/web/fontes/grotesk-700.woff2 +0 -0
  78. package/studio/web/fontes/inter-400.woff2 +0 -0
  79. package/studio/web/fontes/inter-700.woff2 +0 -0
  80. package/studio/web/fontes/jetbrains-400.woff2 +0 -0
  81. package/studio/web/fontes/playfair-700.woff2 +0 -0
  82. package/studio/web/fontes.css +0 -23
  83. package/studio/web/home.html +0 -217
  84. package/studio/web/js/analise.js +0 -510
  85. package/studio/web/js/audio.js +0 -607
  86. package/studio/web/js/editor.js +0 -1802
  87. package/studio/web/js/exportar.js +0 -222
  88. package/studio/web/js/ferramentas.js +0 -108
  89. package/studio/web/js/icones.js +0 -120
  90. package/studio/web/js/midia.js +0 -157
  91. package/studio/web/js/narracao.js +0 -325
  92. package/studio/web/js/player.js +0 -903
  93. package/studio/web/js/render-grade.js +0 -243
  94. package/studio/web/js/render-prosa.js +0 -104
  95. package/studio/web/js/render.js +0 -1852
  96. package/studio/web/js/sfx.js +0 -233
  97. package/studio/web/js/video.js +0 -464
  98. package/studio/web/js/voz-neural.js +0 -152
  99. package/studio/web/render.css +0 -1352
  100. package/studio/web/tf.html +0 -15
  101. package/studio/web/tool.html +0 -78
  102. package/studio/web/view.html +0 -27
@@ -1,48 +1,27 @@
1
1
  /**
2
- * boas-vindas.js — a abertura do PrimoCode.
2
+ * boas-vindas.js — a abertura do PrimoCode, na primeira vez e em toda vez.
3
3
  *
4
- * "O usuário vai rodar o comando de instalação do npm, vai instalar, vai
5
- * rodar `primo`, e aí, como acabou de instalar, vai aparecer pra fazer
6
- * login. Ele vai abrir automaticamente a janela lá no Conecta Primo AI com
7
- * o token, tudo pra fazer login na conta. Aí fez o login, o terminal checa
8
- * se sua conta é Premium ou Super. Se for, ele libera para a próxima etapa.
9
- * Se não, ele fala que você não tem acesso ao PrimoCode, porque tem que ter
10
- * o plano Premium ou Super.
4
+ * Duas coisas acontecem aqui, nesta ordem: a pessoa entra na conta do Conecta
5
+ * Primo AI, e o plano dela é conferido. Acabou. Não há terceira pergunta.
11
6
  *
12
- * A próxima etapa, se liberar o acesso, é selecionar: continuar com a API
13
- * padrão, ou usar conta Claude, GPT etc. Continuar com a API padrão, ele
14
- * libera e aparece aquela tela pronta pra enviar o primeiro pedido. Se
15
- * clicar em continuar com Claude/GPT, você seleciona, deu Enter, ele vai
16
- * pra próxima tela, e aí você seleciona Claude ou GPT — e quando seleciona,
17
- * ele dá aquele /gpt ou /claude, e vai funcionar como já funciona hoje."
7
+ * ── POR QUE NÃO SE ESCOLHE MAIS A IA ─────────────────────────────────────
8
+ * Existiam duas telas depois do login: "Com qual IA você quer trabalhar?" e
9
+ * "Qual conta?" (Claude ou GPT), que ligavam o `/claude` ou o `/gpt` e faziam
10
+ * o PrimoCode rodar em cima da assinatura da própria pessoa.
18
11
  *
19
- * ── O FLUXO, DO NPM AO PRIMEIRO PEDIDO ───────────────────────────────────
12
+ * Isso pedia que alguém que acabou de instalar um produto pago já tivesse
13
+ * OUTRO produto pago instalado e logado — e a resposta certa para quase todo
14
+ * mundo era a primeira opção. Uma pergunta cuja resposta certa é sempre a
15
+ * mesma não é escolha, é obstáculo.
20
16
  *
21
- * npm → `primocode` → login (a página /token do conecta-primo-ai, que é o
22
- * login normal do app com o token do PrimoCode; o site fala com o terminal
23
- * sozinho) → "✅ Autenticado, redirecionando…" → "Como quer prosseguir?",
24
- * com as SETINHAS: API padrão, API própria de outra IA, ou uma IA já
25
- * configurada. Escolheu, está pronto para usar.
17
+ * A API vem configurada no produto. Entrou na conta, pode pedir a primeira
18
+ * coisa.
26
19
  *
27
- * ── A ORDEM É O PRODUTO ──────────────────────────────────────────────────
28
- * Conectar → conferir o plano → escolher o motor. Nesta ordem, e não em
29
- * outra. A primeira versão disto oferecia o login como UMA OPÇÃO de menu, ao
30
- * lado de "decidir depois", e deixava quem não assina usar assim mesmo. Era
31
- * um produto diferente do que foi pedido.
32
- *
33
- * ── O BLOQUEIO É DELIBERADO ──────────────────────────────────────────────
34
- * Sem Premium ou Super, o PrimoCode não trabalha. Isso é uma decisão de
35
- * produto do dono, tomada com o custo na mesa: quem já instalou no gratuito
36
- * perde o acesso na atualização. Está escrito aqui para ninguém "consertar"
37
- * isso mais adiante achando que é bug.
38
- *
39
- * O bloqueio é de CORTESIA, não de segurança: este arquivo vai publicado no
40
- * npm com o código à vista e qualquer um apaga a checagem. Quem segura de
41
- * verdade é o primocode-server, que é quem tem a chave do provedor.
42
- *
43
- * ── POR QUE ISTO É UM MÓDULO ─────────────────────────────────────────────
44
- * Porque tem decisão dentro, e decisão precisa de teste — e teste não digita
45
- * em terminal. Aqui a entrada e a saída são funções que quem chama fornece.
20
+ * ── O BLOQUEIO É DE CORTESIA ─────────────────────────────────────────────
21
+ * Este arquivo vai publicado no npm com o código à vista: um `if (pro)` aqui
22
+ * é uma linha que qualquer um apaga. Quem segura de verdade é o
23
+ * primocode-server, que é quem tem a chave do provedor. O que se faz aqui é
24
+ * dizer o que houve e como resolver.
46
25
  */
47
26
 
48
27
  'use strict';
@@ -51,11 +30,9 @@ const fs = require('fs');
51
30
  const os = require('os');
52
31
  const path = require('path');
53
32
 
54
- const chavesModulo = require('./chaves.js');
55
-
56
33
  const MARCA = () => path.join(os.homedir(), '.primocode', 'ja-abriu.json');
57
34
 
58
- /** Já escolheu o motor alguma vez? A marca guarda a escolha, não o login. */
35
+ /** Já passou pela abertura alguma vez? A marca não guarda login nenhum. */
59
36
  function jaEscolheu() {
60
37
  try { return fs.existsSync(MARCA()); } catch { return false; }
61
38
  }
@@ -73,86 +50,6 @@ function esquecer() {
73
50
  try { fs.rmSync(MARCA(), { force: true }); return true; } catch { return false; }
74
51
  }
75
52
 
76
- // ── as duas telas de escolha ─────────────────────────────────────────────
77
-
78
- const MOTORES = [
79
- { id: 'padrao', titulo: 'Continuar com a API padrão',
80
- detalhe: 'A que já vem pronta, incluída no seu plano. É só começar.' },
81
- { id: 'propria', titulo: 'Usar a API própria de outra IA',
82
- detalhe: 'Você cola a sua chave do Claude ou do GPT, e ela é usada nas chamadas.' },
83
- { id: 'configurada', titulo: 'Usar uma IA já configurada',
84
- detalhe: 'O Claude Code ou o Codex que já está instalado e logado nesta máquina.' },
85
- ];
86
-
87
- const PROVEDORES = [
88
- { id: 'claude', titulo: 'Claude', comando: '/claude', chave: 'claude',
89
- detalhe: 'Usa o Claude Code da sua conta, no Opus.' },
90
- { id: 'gpt', titulo: 'GPT', comando: '/gpt', chave: 'codex',
91
- detalhe: 'Usa o Codex da sua conta, num modelo barato.' },
92
- ];
93
-
94
- /**
95
- * O que a pessoa digitou → qual item da lista.
96
- *
97
- * Aceita o número, o id, o começo do nome, e Enter (que é sempre o primeiro:
98
- * quem aperta Enter sem ler está dizendo "faz o que for melhor").
99
- */
100
- function escolher(texto, lista) {
101
- const t = String(texto || '').trim().toLowerCase();
102
- if (!t) return lista[0].id;
103
- const n = Number(t);
104
- if (Number.isInteger(n) && n >= 1 && n <= lista.length) return lista[n - 1].id;
105
- // Primeiro o que bate INTEIRO. O prefixo vem depois, senão "continuar com
106
- // a API padrão" cairia em "configurada" só por começar com "con".
107
- for (const e of lista) if (t === e.id || t === e.titulo.toLowerCase()) return e.id;
108
- for (const e of lista) if (e.id.length >= 3 && t.startsWith(e.id.slice(0, 3))) return e.id;
109
- // O jeito como as pessoas falam, e não como a lista se chama.
110
- if (lista === MOTORES) {
111
- if (/j[aá] config|j[aá] instal|j[aá] logad|configurad|instalad|logad/.test(t)) return 'configurada';
112
- if (/padr|inclu|mesmo|pronta|voc[eê]s|de voc/.test(t)) return 'padrao';
113
- if (/minha|meu|pr[oó]pri|conta|chave|assinatura|api de outra/.test(t)) return 'propria';
114
- } else {
115
- if (/claude|anthropic|opus|sonnet/.test(t)) return 'claude';
116
- if (/gpt|openai|codex|chatgpt/.test(t)) return 'gpt';
117
- }
118
- return null;
119
- }
120
-
121
- /**
122
- * A escolha na tela. Com terminal de verdade é a LISTA COM AS SETINHAS — ↑↓
123
- * anda, Enter confirma, como no Claude Code: ninguém precisa saber que o item
124
- * tem número. `selecionar` vem de quem chamou (o bin, que é dono do teclado);
125
- * onde ele não existe — teste, pipe, terminal sem raw mode — cai na lista
126
- * numerada de sempre, que responde ao número, ao nome e ao Enter.
127
- */
128
- async function perguntarDaLista({ perguntar, mostrar, cores: c, titulo, ajuda, lista, selecionar }) {
129
- if (selecionar) {
130
- const escolhido = await selecionar({ titulo, ajuda, lista });
131
- if (escolhido && lista.some((e) => e.id === escolhido)) return escolhido;
132
- if (escolhido === null) return lista[0].id; // Esc: fica com a primeira
133
- }
134
- mostrar('');
135
- mostrar(c.bold(c.brand(' ' + titulo)));
136
- if (ajuda) mostrar(c.muted(' ' + ajuda));
137
- mostrar('');
138
- lista.forEach((e, i) => {
139
- mostrar(' ' + c.brand(String(i + 1)) + c.muted('. ') + c.white(e.titulo));
140
- mostrar(' ' + c.dim(e.detalhe));
141
- });
142
- mostrar('');
143
-
144
- for (let tentativa = 0; tentativa < 3; tentativa++) {
145
- const dito = await perguntar(c.brand(' ❯ ')
146
- + c.muted(`1 a ${lista.length} (Enter = 1) `));
147
- const qual = escolher(dito, lista);
148
- if (qual) return qual;
149
- mostrar(c.muted(` Não entendi. Digite 1 a ${lista.length}.`));
150
- }
151
- // Três tentativas sem entender: fica com a primeira em vez de prender
152
- // alguém numa pergunta para usar o programa.
153
- return lista[0].id;
154
- }
155
-
156
53
  // ── a abertura ───────────────────────────────────────────────────────────
157
54
 
158
55
  /**
@@ -164,10 +61,9 @@ async function perguntarDaLista({ perguntar, mostrar, cores: c, titulo, ajuda, l
164
61
  * @param {()=>any} op.conectado a credencial local, ou null
165
62
  * @param {()=>Promise<any>} op.conectar roda o login (abre o navegador)
166
63
  * @param {()=>Promise<any>} op.sessao confirma o plano NO SERVIDOR
167
- * @param {(cmd:string)=>Promise<any>} op.aplicar executa /claude ou /gpt
168
64
  */
169
65
  async function rodar({ perguntar, mostrar, cores: c, interativo = true,
170
- conectado, conectar, sessao, aplicar, selecionar, chaves = chavesModulo } = {}) {
66
+ conectado, conectar, sessao } = {}) {
171
67
 
172
68
  /* Sem terminal de verdade (pipe, script, CI) não se pergunta nada — uma
173
69
  pergunta onde ninguém pode responder trava o processo para sempre, e
@@ -191,12 +87,6 @@ async function rodar({ perguntar, mostrar, cores: c, interativo = true,
191
87
  mostrar(c.muted(' Tente de novo com ') + c.brand('/connect') + c.muted('.'));
192
88
  return { liberado: false, motivo: 'sem-login' };
193
89
  }
194
- /* A MESMA FRASE DOS DOIS LADOS. A página mostra "✅ Autenticado,
195
- redirecionando…" e some; quem estava olhando o navegador volta ao
196
- terminal e precisa ver que o terminal soube — senão parece que a
197
- aprovação ficou no site. */
198
- mostrar('');
199
- mostrar(c.ok(' ✅ Autenticado, redirecionando…'));
200
90
  credencial = conectado ? conectado() : { plano: r.plano };
201
91
  }
202
92
 
@@ -235,70 +125,19 @@ async function rodar({ perguntar, mostrar, cores: c, interativo = true,
235
125
  mostrar('');
236
126
  mostrar(c.ok(' Conta liberada.') + c.muted(` Plano ${plano}.`));
237
127
 
238
- // ── 3. o motor ───────────────────────────────────────────────────────
239
- // Só na primeira vez: quem já escolheu não quer responder isso toda vez.
240
- if (jaEscolheu()) return { liberado: true, motivo: 'ja-escolheu', plano, perguntou: false };
241
-
242
- const motor = await perguntarDaLista({
243
- perguntar, mostrar, cores: c, selecionar,
244
- titulo: 'Como quer prosseguir?',
245
- ajuda: 'Use as setinhas ↑↓ e Enter para confirmar — ou digite o número.',
246
- lista: MOTORES,
247
- });
248
-
249
- if (motor === 'padrao') {
250
- marcar({ motor: 'padrao', plano });
251
- mostrar('');
252
- mostrar(c.muted(' Pronto. Pode pedir a primeira coisa.'));
253
- mostrar('');
254
- return { liberado: true, motor: 'padrao', plano, perguntou: true };
255
- }
256
-
257
- // ── 4. qual IA ───────────────────────────────────────────────────────
258
- const propria = motor === 'propria';
259
- const qual = await perguntarDaLista({
260
- perguntar, mostrar, cores: c, selecionar,
261
- titulo: propria ? 'De qual IA é a sua chave?' : 'Qual IA já configurada?',
262
- ajuda: propria
263
- ? 'A chave fica só nesta máquina, em ~/.primocode/chaves.json.'
264
- : 'Ele usa o programa que você já tem instalado e logado.',
265
- lista: PROVEDORES,
266
- });
267
- const escolhido = PROVEDORES.find((p) => p.id === qual) || PROVEDORES[0];
268
-
269
- /* ── A CHAVE PRÓPRIA ─────────────────────────────────────────────────
270
- * Só neste caminho se pede chave. Quem escolheu "já configurada" não tem
271
- * chave para colar — a dele está no programa em que já entrou, e pedir
272
- * uma ali seria inventar um passo que não existe.
273
- *
274
- * Chave recusada (colou a do outro provedor, veio truncada) NÃO trava a
275
- * abertura: diz o que houve, guarda a escolha do motor e segue. O motor
276
- * roda com o login que a pessoa já tem, e `/chave` conserta depois. */
277
- if (propria && perguntar) {
278
- const p = chaves.PROVEDORES[escolhido.chave] || {};
279
- mostrar('');
280
- mostrar(c.muted(` Cole a chave do ${escolhido.titulo}`)
281
- + c.dim(p.onde ? ` (pegue em ${p.onde})` : '') + c.muted('.'));
282
- mostrar(c.dim(' Enter em branco pula: dá para configurar depois com ')
283
- + c.brand('/chave ' + escolhido.chave) + c.dim('.'));
284
- const colada = String(await perguntar(c.brand(' ❯ ')) || '').trim();
285
- if (colada) {
286
- const g = chaves.guardar(escolhido.chave, colada);
287
- mostrar(g.ok
288
- ? c.ok(` Chave do ${g.nome} guardada.`) + c.muted(` ${g.disfarce}`)
289
- : c.err(` ${g.error}`));
290
- }
291
- }
128
+ /* Não há etapa 3. O que vinha aqui era a escolha do provedor de IA, e
129
+ ela morreu junto com o `/claude` e o `/gpt`: a API já vem com o
130
+ produto. A marca continua sendo gravada porque é ela que distingue a
131
+ primeira abertura das outras — quem já entrou não precisa ler o texto
132
+ de boas-vindas de novo. */
133
+ const primeira = !jaEscolheu();
134
+ if (primeira) marcar({ plano });
292
135
 
293
- marcar({ motor: escolhido.id, origem: motor, plano });
294
136
  mostrar('');
295
- if (aplicar) await aplicar(escolhido.comando);
137
+ mostrar(c.muted(primeira ? ' Tudo pronto. Pode pedir a primeira coisa.'
138
+ : ' Pode pedir.'));
296
139
  mostrar('');
297
- return { liberado: true, motor: escolhido.id, origem: motor,
298
- comando: escolhido.comando, plano, perguntou: true };
140
+ return { liberado: true, plano, primeira, perguntou: false };
299
141
  }
300
142
 
301
- module.exports = {
302
- rodar, jaEscolheu, marcar, esquecer, escolher,
303
- MOTORES, PROVEDORES, MARCA,
304
- };
143
+ module.exports = { rodar, jaEscolheu, marcar, esquecer, MARCA };
package/lib/catalogo.js CHANGED
@@ -74,52 +74,67 @@ const GRUPOS = [
74
74
  },
75
75
  {
76
76
  nome: 'Estúdio',
77
- resumo: 'a suíte de criação do PrimoCode — sobe sozinha, não precisa de app externo',
77
+ resumo: 'vídeo, motion, slides e arte — a IA faz, e no fim abre o resultado pronto',
78
78
  itens: [
79
- { tool: 'studio_manual', escreve: false, faz: 'lê o manual do estúdio e as seis ferramentas', args: null },
80
- { tool: 'studio_spec', escreve: false, faz: 'lê a spec de uma ferramenta (prisma, tela, prosa…)',
81
- args: 'ferramenta: prisma | tela | prosa | grade | corte | traco.' },
82
- { tool: 'studio_criar', escreve: false, faz: 'cria slides, arte, documento, planilha, vídeo ou diagrama',
83
- args: 'ferramenta: prisma | tela | prosa | grade | corte | traco. titulo. doc: objeto no formato que studio_spec daquela ferramenta descreve.' },
84
- { tool: 'studio_ler', escreve: false, faz: 'lê algo que já foi criado', args: 'id.' },
85
- { tool: 'studio_atualizar', escreve: false, faz: 'ajusta o que já existe, sem refazer do zero',
86
- args: 'id. titulo (opcional). doc (opcional): só as chaves que MUDAM — o resto do documento fica intacto.' },
87
- { tool: 'studio_versoes', escreve: false, faz: 'histórico de um arquivo, e volta a uma versão anterior',
88
- args: 'id. restaurar (opcional): "anterior" desfaz a última mudança, ou o nome de uma versão da lista.' },
89
- { tool: 'studio_listar', escreve: false, faz: 'lista o que já existe no estúdio', args: null },
90
- { tool: 'studio_midia', escreve: false, faz: 'manda imagem, música ou vídeo seu para usar nas peças',
91
- args: 'arquivo (opcional): caminho no projeto — sem ele, lista a galeria. nome (opcional).' },
92
- { tool: 'midia_procurar', escreve: false, faz: 'procura imagem, logo ou música na internet, com licença e crédito',
93
- args: 'consulta: o que procurar, específico ("painel solar em telhado residencial", não "imagem legal"). '
94
- + 'tipo (opcional): imagem | logo | musica (padrão imagem). quantos (opcional, padrão 6). '
95
- + 'Para MÚSICA e EFEITO prefira buscar_audio, que já baixa, manda para o estúdio e devolve o crédito; '
96
- + 'para a logo de uma EMPRESA prefira marca_da_empresa, que traz também cor, paleta e fontes do site dela. '
97
- + 'orientacao (opcional): paisagem | retrato | quadrado. '
98
- + 'Logo vem do Wikimedia Commons (SVG de verdade da marca); foto e música do Openverse (licença aberta); '
99
- + 'com PRIMOCODE_GOOGLE_KEY e PRIMOCODE_GOOGLE_CX configurados, entra também a busca de imagens do Google. '
100
- + 'Devolve a lista com url, autor e licença — escolha UMA e chame midia_baixar.' },
101
- { tool: 'midia_baixar', escreve: false, faz: 'baixa o que foi encontrado e já deixa disponível para as peças',
102
- args: 'url: a url do resultado de midia_procurar. titulo (opcional). semFundo (opcional): true recorta o fundo da imagem. '
103
- + 'paraEstudio (opcional, padrão true): também manda para a galeria do estúdio e devolve o "/midia/..." para usar em src e fundo. '
104
- + 'A licença fica gravada junto do arquivo; midia_creditos monta a lista para a cena final.' },
105
- { tool: 'midia_sem_fundo', escreve: false, faz: 'recorta o fundo de uma imagem que já está na máquina',
106
- args: 'arquivo: caminho da imagem. destino (opcional). Usa rembg quando existe; sem ele, só fundo de cor chapada (avisa na resposta).' },
107
- { tool: 'midia_creditos', escreve: false, faz: 'lista os créditos do que foi baixado, prontos para a cena final ou a descrição', args: null },
108
- { tool: 'remotion_projeto', escreve: true, faz: 'transforma uma peça do estúdio em projeto Remotion (React) na pasta do usuário',
109
- args: 'id da peça. pasta (opcional): nome da pasta. fps (opcional, padrão 30). renderizar: true só se o npm install já foi feito. '
110
- + 'Use SÓ quando o usuário pedir Remotion pelo nome, ou quando precisar de render determinístico, vídeo longo, codec/fps específico ou o projeto em React. '
111
- + 'O caminho normal continua sendo studio_criar + o botão de baixar, que sai em segundos sem instalar nada.' },
112
- { tool: 'studio_analisar', escreve: false, faz: 'ouve um vídeo já enviado: encurta as pausas e tira a fala que a pessoa errou e refez',
113
- args: 'arquivo: a URL "/midia/..." do vídeo. pausa (opcional): segundos a partir dos quais conta como pausa (padrão 0.6). '
114
- + 'manter (opcional): quantos segundos de cada pausa ficam — sem isso ele decide sozinho (respiração fica quase inteira, pausa longa fica 0,7s). manter:0 corta seco. '
115
- + 'repetida (opcional, padrão true): false desliga o corte de fala repetida. '
116
- + 'Devolve "trechos": vire cada um numa cena com {"tipo":"clipe","de":...,"ate":...}. '
117
- + 'Devolve também "repetidos": o que foi tirado por ter sido dito duas vezes — CONTE isso ao usuário, com os tempos, porque é fala dele que sumiu do vídeo.' },
118
- // Escreve no projeto (o .mp4 pronto), por isso `escreve: true`: é o
119
- // que faz a pasta do projeto nascer quando ainda não existe.
120
- { tool: 'studio_exportar', escreve: true, faz: 'gera o arquivo .mp4 de um vídeo do estúdio, sem ninguém clicar, e o deixa na pasta do projeto',
121
- args: 'id: o vídeo (corte). destino (opcional): caminho do arquivo de saída — sem ele, <titulo>.mp4 na pasta do projeto. fps (opcional, padrão 30). '
122
- + 'Leva o tempo do vídeo (um vídeo de 40s leva ~40s). Use quando o usuário pedir o ARQUIVO; para só mostrar, o link "ver" basta.' },
79
+ { tool: 'estudio_projeto', escreve: true, faz: 'abre, acha e lista os projetos do estúdio — cada peça é uma pasta que não some',
80
+ args: 'acao: criar | abrir | procurar | listar | anotar. '
81
+ + 'criar: titulo, pedido, formato (video|slides|arte). '
82
+ + 'abrir: id — devolve o plano, o CADERNO (o que foi decidido e por quê) e os arquivos. '
83
+ + 'procurar: texto — acha pelo que o usuário lembra ("aquele do PicPay"), inclusive por detalhe do meio do processo. '
84
+ + 'SEMPRE procure antes de criar quando o pedido falar de algo que já existe.' },
85
+
86
+ { tool: 'estudio_plano', escreve: true, faz: 'escreve ou ajusta o que o vídeo É — cenas, elementos, animação, câmera, áudio',
87
+ args: 'id. plano: {formato:{largura,altura,fps}, cenas:[…], audio:{…}} — ou cenas direto. '
88
+ + 'cena (opcional): o número de UMA cena para trocar só ela, sem reescrever o resto. '
89
+ + 'motivo (opcional): fica no caderno. voltarPara (opcional): nome de uma versão. '
90
+ + 'Sem plano nem cenas, LÊ o plano atual. '
91
+ + 'Cada elemento é {tipo: texto|forma|imagem|video|icone|legenda|componente} com x, y, cor, tamanho e '
92
+ + '"anima": {propriedade: [{t,v,e}]} — quadros-chave em segundos. A cena tem "camera": {zoom,x,y,rotacao} e '
93
+ + '"perspectiva" para 3D. NÃO existe layout pronto nem tema — cada propriedade do vídeo é escolha sua, a cada vídeo.' },
94
+
95
+ { tool: 'estudio_midia', escreve: true, faz: 'procura e baixa ícone, imagem, música e efeito sonoro da internet, e lista as fontes',
96
+ args: 'acao: imagem | icones | som | trilha | efeito | fontes. termo. id (para baixar). '
97
+ + 'baixar: o número do item da busca (1 é o primeiro). transparente: true para PNG recortado. '
98
+ + 'icones: sem "nome", procura; com "nome" ("lucide:zap"), baixa. '
99
+ + 'Tudo é BAIXADO para a pasta do projeto — o plano nunca aponta para a internet.' },
100
+
101
+ { tool: 'estudio_narrar', escreve: true, faz: 'gera a narração em voz neural e devolve o tempo MEDIDO de cada fala',
102
+ args: 'id. texto. voz (antonio|francisca|thalita). estilo (normal|anuncio|calmo|dramatico). '
103
+ + 'vozes: true lista as vozes. '
104
+ + 'Use os tempos que voltam para a duração das cenas — estimar por número de palavras erra em segundos.' },
105
+
106
+ { tool: 'estudio_video', escreve: true, faz: 'o que se faz com a gravação do usuário: importar, medir, cortar silêncio, transcrever, recortar a pessoa',
107
+ args: 'id. acao: importar | medir | silencio | cortar-silencio | quadro | legendas | recortar-pessoa. '
108
+ + 'arquivo: caminho (em "importar", de fora; nas outras, o relativo dentro do projeto). '
109
+ + 'cortar-silencio: minimoParaCortar (padrão 0.6s), respiro (padrão 0.18s) — os dois existem para o corte não soar robótico. '
110
+ + 'legendas: devolve os trechos com tempo, para virarem elemento "legenda". '
111
+ + 'recortar-pessoa: devolve um vídeo com fundo TRANSPARENTE, para pôr sobre qualquer fundo.' },
112
+
113
+ { tool: 'estudio_render', escreve: true, faz: 'vira arquivo pelo Remotion e abre para o usuário: .mp4 no vídeo, .pdf em slides e arte',
114
+ args: 'id. paginas: true força páginas paradas em vez de vídeo (projeto de formato "slides" ou "arte" já faz isso sozinho). '
115
+ + 'somenteProjeto: true escreve o projeto Remotion sem renderizar. minutos (padrão 30). '
116
+ + 'A primeira render de um projeto instala o Remotion na pasta dele (alguns minutos, uma vez). '
117
+ + 'Slide é uma CENA PARADA: o mesmo plano, as mesmas primitivas, e a página sai do quadro do MEIO da cena — '
118
+ + 'do primeiro sairia em branco, com a animação de entrada ainda fora do quadro.' },
119
+
120
+ { tool: 'estudio_site', escreve: false, faz: 'lê a identidade visual de uma marca: cores, fontes, logo e o que ela diz de si',
121
+ args: 'url — OU marca: o nome da empresa, quando não há link ("faça um vídeo para a Paris Group"). '
122
+ + 'id (opcional): anota no caderno do projeto. '
123
+ + 'Devolve FATOS medidos, não um roteiro — o roteiro é seu. '
124
+ + 'Use SEMPRE que o pedido citar uma empresa de verdade: inventar a cor de uma marca que existe '
125
+ + 'é erro que sai pronto no vídeo entregue. '
126
+ + 'Com id, BAIXA a logo para midia/ e devolve o src pronto — leia o "aviso" dela antes de usar: '
127
+ + 'a logo pode ser o símbolo monocromático, ou uma imagem que não é logo nenhuma.' },
128
+
129
+ { tool: 'planejar_producao', escreve: false, noLoop: true,
130
+ faz: 'ativa o DIRETOR: arco, narração cena a cena, voz, direção de arte, mídia a buscar e trilha — a estrutura ANTES de produzir',
131
+ args: 'pedido: o que produzir, como o usuário falou. segundos (opcional, padrão 30). '
132
+ + 'formato (opcional): video | apresentacao. contexto (opcional): o que você já apurou (identidade da marca, o que o site diz). '
133
+ + 'Devolve a PAUTA em prosa, mais voz, mídia a buscar e trilha. '
134
+ + 'Chame ANTES de estudio_plano em qualquer peça que tenha narração ou mais de três cenas: '
135
+ + 'quem monta o JSON das cenas gasta a atenção no JSON, e é ali que o roteiro vira legenda '
136
+ + '("Vantagens", "Nossos serviços") em vez de virar fala. '
137
+ + 'A pauta é para LER e compor com as primitivas — não copie os campos dela para dentro do plano.' },
123
138
  ],
124
139
  },
125
140
  {
@@ -159,13 +174,6 @@ const GRUPOS = [
159
174
  args: 'todos: lista de {content: a tarefa curta no imperativo, status: pending|in_progress|completed}. Reescreve a lista inteira a cada chamada; só uma "in_progress" por vez.' },
160
175
  { tool: 'pesquisar_referencia', escreve: false, faz: 'lê docs e exemplos antes de escrever — sobe a qualidade',
161
176
  args: 'consulta: o que pesquisar, de preferência em inglês. paginas (opcional, número, padrão 3, máximo 5).' },
162
- // Baixa o arquivo para <projeto>/midia/ — por isso `escreve: true`.
163
- { tool: 'buscar_audio', escreve: true, faz: 'acha música ou efeito sonoro livre na internet (Creative Commons), baixa e manda para o estúdio, com o crédito',
164
- args: 'consulta: o clima ou o som, em inglês ("upbeat corporate", "calm piano", "whoosh", "ding"). tipo (opcional): "musica" (padrão) ou "efeito". '
165
- + 'duracao (opcional, segundos): quanto o vídeo dura, para a faixa combinar. Devolve url para trilha.src ou som.arquivo, e o crédito a pôr na peça quando a licença pedir.' },
166
- { tool: 'marca_da_empresa', escreve: true, faz: 'puxa do site da empresa a logo, a cor principal, a paleta, as fontes e a frase — pronto para `marca` no estúdio',
167
- args: 'nome: a empresa como o usuário falou. site (opcional): o endereço, se souber — sem ele, o site é descoberto. '
168
- + 'Devolve marca:{cor, logo} para colar no documento, mais cores.paleta, fontes e frase. Chame ANTES de criar uma peça de marca.' },
169
177
  // Vai para memory-geral.md, FORA da pasta do projeto — por isso não
170
178
  // é write_file/edit_file (a sandbox de resolveInProject bloqueia
171
179
  // qualquer caminho fora do projeto, e é assim mesmo que tem que
@@ -180,14 +188,6 @@ const GRUPOS = [
180
188
  args: 'pedido: o que o usuário quer, com as palavras dele. pasta (opcional): onde construir — sem ela, uma pasta nova com o nome do pedido. rodadas (opcional, 1 a 4, padrão 2): quantas voltas de correção. '
181
189
  + 'USE quando pedirem um app, um site, uma página ou uma ferramenta INTEIRA, do zero. NÃO use para mexer no que já existe, para um arquivo só, nem para conserto: são seis a oito chamadas de agente, '
182
190
  + 'e gastá-las para trocar a cor de um botão come o dia do usuário. O revisor ABRE a página num navegador invisível e mede — página em branco e erro de JavaScript reprovam sozinhos, o que ele achar volta para o construtor.' },
183
- { tool: 'planejar_producao', escreve: false, noLoop: true,
184
- faz: 'ativa o DIRETOR: roteiro, narração cena a cena, voz, movimento, mídia a buscar e trilha — a estrutura ANTES de produzir',
185
- args: 'pedido: o que o usuário quer, com as palavras dele. segundos (opcional, padrão 30): duração alvo. '
186
- + 'formato (opcional): video | apresentacao. contexto (opcional): o que já se sabe (marca, público, dados).\n'
187
- + 'USE SEMPRE antes de criar vídeo, reels, teaser, animação ou apresentação com narração — inclusive quando o pedido parecer simples. '
188
- + 'Devolve uma PAUTA: produza exatamente o que ela diz (não invente cena, não troque a ordem, não reescreva a narração), '
189
- + 'buscando a mídia com midia_procurar + midia_baixar e montando com studio_criar (corte/prisma). '
190
- + 'NÃO use para ajustar peça existente nem quando o usuário pediu só o roteiro em texto.' },
191
191
  { tool: 'finish', escreve: false, faz: 'encerra dizendo o que fez', noLoop: true,
192
192
  args: 'summary (opcional): uma frase do que foi feito.' },
193
193
  ],
package/lib/config.js CHANGED
@@ -210,7 +210,7 @@ function appendProjectMemory(dir, entry) {
210
210
  * vezes numa tarefa só. */
211
211
  function lembrarCriacao({ ferramenta, titulo, id }) {
212
212
  if (!id) return;
213
- const linha = `- criou ${ferramenta || 'peça'} "${String(titulo || 'sem título').slice(0, 70)}" — id \`${id}\``;
213
+ const linha = `- criou ${ferramenta || 'peça'} "${String(titulo || 'sem título').slice(0, 70)}" — projeto \`${id}\``;
214
214
  try {
215
215
  ensureStructure();
216
216
  const texto = fs.existsSync(GENERAL_MEMORY) ? fs.readFileSync(GENERAL_MEMORY, 'utf-8') : '';
package/lib/conta.js CHANGED
@@ -38,26 +38,6 @@ const CONECTA = process.env.PRIMOCODE_CONTA_SERVER || 'https://conecta-primo-api
38
38
 
39
39
  const ARQUIVO = path.join(os.homedir(), '.primocode', 'conta.json');
40
40
 
41
- /* ── ONDE O LOGIN ABRE ───────────────────────────────────────────────────
42
- *
43
- * "Abre conecta-primo-ai.vercel.app/token do PrimoCode: abre o login normal
44
- * do app, mas com o token do PrimoCode. Aí o site se comunica com o
45
- * terminal."
46
- *
47
- * A página do token é do SITE, não da API. O servidor devolve a URL dele —
48
- * que num ambiente antigo pode apontar para outra origem ou para outro
49
- * caminho — e aqui ela é normalizada para o endereço acima, preservando o que
50
- * vier na query (é ali que vai o código do pareamento, quando vai).
51
- * `PRIMOCODE_SITE` troca a origem para rodar contra uma prévia local. */
52
- const SITE = (process.env.PRIMOCODE_SITE || 'https://conecta-primo-ai.vercel.app').replace(/\/+$/, '');
53
- const PAGINA_TOKEN = '/token';
54
-
55
- function paginaDeLogin(urlDoServidor) {
56
- let query = '';
57
- try { query = new URL(urlDoServidor).search; } catch { /* sem url utilizável: só a página */ }
58
- return SITE + PAGINA_TOKEN + query;
59
- }
60
-
61
41
  // Os planos que liberam tudo. A lista é a MESMA do servidor
62
42
  // (`PLANOS_COM_PRO`), e quem decide de verdade é ele: isto aqui só evita uma
63
43
  // ida à rede para mostrar o selo na abertura.
@@ -221,11 +201,11 @@ async function entrar({ abrir, contar = () => {}, minutos = 5 } = {}) {
221
201
  }
222
202
 
223
203
  let origemDoSite = null;
224
- try { origemDoSite = new URL(paginaDeLogin(pedido.url)).origin; } catch { /* url estranha: sem CORS para ela */ }
204
+ try { origemDoSite = new URL(pedido.url).origin; } catch { /* url estranha: sem CORS para ela */ }
225
205
  const local = await servirPareamento(pedido.codigo, origemDoSite);
226
206
  const copiado = await area.copiar(pedido.codigo);
227
207
 
228
- let url = paginaDeLogin(pedido.url);
208
+ let url = pedido.url;
229
209
  if (local.porta) url += (url.includes('?') ? '&' : '?') + 'porta=' + local.porta;
230
210
 
231
211
  contar({ url, codigo: pedido.codigo, copiado: copiado.ok, porta: local.porta });
@@ -307,5 +287,4 @@ function versao() {
307
287
  try { return require('../package.json').version; } catch { return '?'; }
308
288
  }
309
289
 
310
- module.exports = { entrar, sair, atual, sessao, dispositivos, servirPareamento, paginaDeLogin,
311
- ARQUIVO, CONECTA, SITE, PAGINA_TOKEN, PLANOS_PRO, PORTA_PAREAMENTO };
290
+ module.exports = { entrar, sair, atual, sessao, dispositivos, servirPareamento, ARQUIVO, CONECTA, PLANOS_PRO, PORTA_PAREAMENTO };