sagadeck 1.2.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 (233) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +305 -0
  3. package/SKILL.md +148 -0
  4. package/bin/sagadeck.js +511 -0
  5. package/docs/ANDAMENTO.md +1255 -0
  6. package/docs/EXPERIENCIAS.md +39 -0
  7. package/docs/PLANO-EXPERIENCIAS.md +39 -0
  8. package/docs/PLANO-TESTE-AULAS-TECNICAS.md +44 -0
  9. package/docs/PLANO-TESTE-DESINFORMACAO.md +38 -0
  10. package/docs/REFERENCIA.md +1131 -0
  11. package/package.json +59 -0
  12. package/src/adaptive-layouts.js +18 -0
  13. package/src/ai/art-direction.js +8 -0
  14. package/src/ai/commands.js +130 -0
  15. package/src/ai/context.js +245 -0
  16. package/src/ai/deck-ai.js +1135 -0
  17. package/src/ai/directions.js +74 -0
  18. package/src/ai/document-materials.js +49 -0
  19. package/src/ai/document-visuals.js +215 -0
  20. package/src/ai/evaluation.js +31 -0
  21. package/src/ai/ground.js +55 -0
  22. package/src/ai/llm.js +292 -0
  23. package/src/ai/quality.js +51 -0
  24. package/src/ai/relay.js +45 -0
  25. package/src/ai/staged-generation.js +24 -0
  26. package/src/ai/transform.js +858 -0
  27. package/src/ai/usage.js +17 -0
  28. package/src/ai/variety.js +80 -0
  29. package/src/ai/video-frames.js +24 -0
  30. package/src/ai/video-generation.js +126 -0
  31. package/src/ai/web-operation.js +31 -0
  32. package/src/algo-catalog.js +153 -0
  33. package/src/algo-trace.js +139 -0
  34. package/src/api-client.js +514 -0
  35. package/src/api-collections.js +14 -0
  36. package/src/api-demo.js +222 -0
  37. package/src/aspect.js +40 -0
  38. package/src/build.js +323 -0
  39. package/src/carousel.js +56 -0
  40. package/src/chrome.js +65 -0
  41. package/src/code-highlight.js +68 -0
  42. package/src/code-lab-browser.js +22 -0
  43. package/src/code-lab.js +3 -0
  44. package/src/code-language.js +52 -0
  45. package/src/csv.js +74 -0
  46. package/src/decision-lab.js +20 -0
  47. package/src/deck-file.js +119 -0
  48. package/src/diagram/napkin-examples.js +13 -0
  49. package/src/diagram/napkin.js +539 -0
  50. package/src/docx.js +128 -0
  51. package/src/dynamics/git-sim.js +200 -0
  52. package/src/dynamics/layouts.js +112 -0
  53. package/src/elements.js +554 -0
  54. package/src/experiences.js +124 -0
  55. package/src/exploration.js +31 -0
  56. package/src/export/browser.js +53 -0
  57. package/src/export/estudo.js +67 -0
  58. package/src/export/pptx.js +475 -0
  59. package/src/export/roteiro.js +40 -0
  60. package/src/export/shots.js +182 -0
  61. package/src/figures/charts.js +383 -0
  62. package/src/figures/diagrams.js +117 -0
  63. package/src/figures/icons.js +166 -0
  64. package/src/figures/material-pictos.js +20 -0
  65. package/src/figures/pictos.js +255 -0
  66. package/src/figures/points.js +106 -0
  67. package/src/figures/qr.js +29 -0
  68. package/src/figures/ufmap.js +48 -0
  69. package/src/fiscal/autofix.js +377 -0
  70. package/src/fiscal/normalize.js +115 -0
  71. package/src/graph-lab.js +24 -0
  72. package/src/hologram.js +30 -0
  73. package/src/identity.js +86 -0
  74. package/src/import/crop.js +59 -0
  75. package/src/import/index.js +132 -0
  76. package/src/import/legacy-word.js +42 -0
  77. package/src/import/merge.js +45 -0
  78. package/src/import/office.js +123 -0
  79. package/src/import/omml.js +69 -0
  80. package/src/import/pdf-render.js +56 -0
  81. package/src/import/pptx.js +701 -0
  82. package/src/import/xml.js +42 -0
  83. package/src/infographic.js +401 -0
  84. package/src/layouts.js +723 -0
  85. package/src/lessons.js +171 -0
  86. package/src/library.js +399 -0
  87. package/src/markup.js +116 -0
  88. package/src/master.js +214 -0
  89. package/src/mcp/server.js +371 -0
  90. package/src/meeting.js +42 -0
  91. package/src/motion.js +56 -0
  92. package/src/package.js +116 -0
  93. package/src/playground.js +19 -0
  94. package/src/portal-scene.js +20 -0
  95. package/src/preferences.js +61 -0
  96. package/src/protect.js +51 -0
  97. package/src/purpose.js +24 -0
  98. package/src/pytrace.js +1092 -0
  99. package/src/research/duckduckgo.js +73 -0
  100. package/src/research/research.js +202 -0
  101. package/src/research/svg-image.js +17 -0
  102. package/src/research/visuals.js +52 -0
  103. package/src/runtime/api-collection-ui.js +5 -0
  104. package/src/runtime/api-collections.js +28 -0
  105. package/src/runtime/api-core.js +530 -0
  106. package/src/runtime/api-ui.js +966 -0
  107. package/src/runtime/base.css +1774 -0
  108. package/src/runtime/calc.js +139 -0
  109. package/src/runtime/code-lab.js +371 -0
  110. package/src/runtime/decision-lab.js +49 -0
  111. package/src/runtime/diagram.js +341 -0
  112. package/src/runtime/fit.js +268 -0
  113. package/src/runtime/fonts/caveat.css +2 -0
  114. package/src/runtime/fonts/comic-neue.css +3 -0
  115. package/src/runtime/fonts/fredoka.css +2 -0
  116. package/src/runtime/fonts/index.json +10 -0
  117. package/src/runtime/fonts/inter.css +2 -0
  118. package/src/runtime/fonts/patrick-hand.css +2 -0
  119. package/src/runtime/fonts/plus-jakarta-sans.css +2 -0
  120. package/src/runtime/fonts/press-start-2p.css +2 -0
  121. package/src/runtime/fonts/vt323.css +2 -0
  122. package/src/runtime/formula.js +148 -0
  123. package/src/runtime/graph-lab.js +72 -0
  124. package/src/runtime/meeting.css +7 -0
  125. package/src/runtime/meeting.js +10 -0
  126. package/src/runtime/motion.js +15 -0
  127. package/src/runtime/playground.js +11 -0
  128. package/src/runtime/points.js +19 -0
  129. package/src/runtime/portal-scene.css +6 -0
  130. package/src/runtime/portal-scene.js +22 -0
  131. package/src/runtime/runtime.js +827 -0
  132. package/src/runtime/science.js +89 -0
  133. package/src/runtime/skins/aluminio.css +31 -0
  134. package/src/runtime/skins/arcade.css +25 -0
  135. package/src/runtime/skins/aurora.css +17 -0
  136. package/src/runtime/skins/bauhaus.css +19 -0
  137. package/src/runtime/skins/editorial.css +19 -0
  138. package/src/runtime/skins/jornal.css +17 -0
  139. package/src/runtime/skins/manual-noite.css +14 -0
  140. package/src/runtime/skins/manual.css +14 -0
  141. package/src/runtime/skins/noite.css +21 -0
  142. package/src/runtime/skins/oceano.css +17 -0
  143. package/src/runtime/skins/pop.css +23 -0
  144. package/src/runtime/skins/prata.css +18 -0
  145. package/src/runtime/skins/prisma.css +28 -0
  146. package/src/runtime/skins/rabisco.css +18 -0
  147. package/src/runtime/skins/relevo.css +33 -0
  148. package/src/runtime/skins/sinal.css +16 -0
  149. package/src/runtime/skins/terminal.css +23 -0
  150. package/src/runtime/vendor/KATEX-LICENSE.txt +21 -0
  151. package/src/runtime/vendor/MERMAID-LICENSE.txt +21 -0
  152. package/src/runtime/vendor/PLOTLY-LICENSE.txt +21 -0
  153. package/src/runtime/vendor/katex.css +1 -0
  154. package/src/runtime/vendor/mermaid.min.js +3636 -0
  155. package/src/runtime/vendor/plotly.min.js +3620 -0
  156. package/src/runtime/video-player.js +35 -0
  157. package/src/science.js +162 -0
  158. package/src/studio/ai-response.js +36 -0
  159. package/src/studio/api-routes.js +152 -0
  160. package/src/studio/assets/contexto/leia-me.md +12 -0
  161. package/src/studio/assets/contexto/vendas.csv +7 -0
  162. package/src/studio/assets/imagens/colecao-arquitetura.png +0 -0
  163. package/src/studio/assets/imagens/colecao-cor.png +0 -0
  164. package/src/studio/assets/imagens/colecao-equipe.png +0 -0
  165. package/src/studio/assets/imagens/lavanda-cover.jpg +0 -0
  166. package/src/studio/code-routes.js +32 -0
  167. package/src/studio/command-approval.js +48 -0
  168. package/src/studio/demo-decks.js +98 -0
  169. package/src/studio/direction-routes.js +29 -0
  170. package/src/studio/errors.js +65 -0
  171. package/src/studio/exploration-demo.js +17 -0
  172. package/src/studio/exporting.js +105 -0
  173. package/src/studio/generation-report.js +10 -0
  174. package/src/studio/instance.js +48 -0
  175. package/src/studio/layout-samples.js +152 -0
  176. package/src/studio/project.js +371 -0
  177. package/src/studio/public/api-collections.js +28 -0
  178. package/src/studio/public/app.js +5265 -0
  179. package/src/studio/public/art-preview.js +10 -0
  180. package/src/studio/public/calc-fields.js +11 -0
  181. package/src/studio/public/chat-progress.js +47 -0
  182. package/src/studio/public/developer-fields.js +22 -0
  183. package/src/studio/public/explorer.js +443 -0
  184. package/src/studio/public/history.js +58 -0
  185. package/src/studio/public/index.html +867 -0
  186. package/src/studio/public/inspector.js +270 -0
  187. package/src/studio/public/lab-fields.js +5 -0
  188. package/src/studio/public/library.css +275 -0
  189. package/src/studio/public/library.html +55 -0
  190. package/src/studio/public/library.js +471 -0
  191. package/src/studio/public/merge-decks.js +155 -0
  192. package/src/studio/public/review-ui.js +100 -0
  193. package/src/studio/public/screenshot-editor.js +140 -0
  194. package/src/studio/public/share-ui.js +72 -0
  195. package/src/studio/public/slide-clipboard.js +23 -0
  196. package/src/studio/public/slide-form.js +1263 -0
  197. package/src/studio/public/studio-awareness.js +154 -0
  198. package/src/studio/public/studio-next.css +303 -0
  199. package/src/studio/public/style.css +1262 -0
  200. package/src/studio/public/ui-icons.js +2 -0
  201. package/src/studio/public/viewers.js +372 -0
  202. package/src/studio/public/visual-editor.js +326 -0
  203. package/src/studio/public/workspace.js +18 -0
  204. package/src/studio/public-files.js +1 -0
  205. package/src/studio/request-helpers.js +41 -0
  206. package/src/studio/server.js +1583 -0
  207. package/src/studio/share-routes.js +146 -0
  208. package/src/studio/slide-copy-routes.js +26 -0
  209. package/src/studio/slide-copy.js +30 -0
  210. package/src/studio/snapshot.js +160 -0
  211. package/src/studio/style-routes.js +103 -0
  212. package/src/studio/template-collections.js +97 -0
  213. package/src/studio/versions.js +95 -0
  214. package/src/studio/video-download.js +29 -0
  215. package/src/studio/visual-keys.js +52 -0
  216. package/src/studio/workspaces.js +39 -0
  217. package/src/table.js +93 -0
  218. package/src/templates/scaffold.js +372 -0
  219. package/src/themes.js +604 -0
  220. package/src/trace-view.js +450 -0
  221. package/src/uid.js +19 -0
  222. package/src/video-player.js +40 -0
  223. package/src/visual-edits.js +99 -0
  224. package/src/ws.js +134 -0
  225. package/templates/cenario/Texto no cen/303/241rio.yaml" +171 -0
  226. package/templates/cenario/imagens/cidade.svg +30 -0
  227. package/templates/cenario/imagens/pessoa.svg +15 -0
  228. package/templates/cenario-e-ciencia.yaml +47 -0
  229. package/templates/ensaio-api.yaml +160 -0
  230. package/templates/exemplo-alegre.yaml +75 -0
  231. package/templates/exemplo-keynote.yaml +90 -0
  232. package/templates/exemplo-napkin.yaml +139 -0
  233. package/templates/exemplo.yaml +228 -0
@@ -0,0 +1,511 @@
1
+ #!/usr/bin/env node
2
+ // sagadeck — gerador de apresentações. Uso: sagadeck <comando> <deck.yaml> [opções]
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import os from "node:os";
7
+ import { buildFile, loadSpec, buildHTML } from "../src/build.js";
8
+ import { THEMES } from "../src/themes.js";
9
+ import { listIcons } from "../src/figures/icons.js";
10
+
11
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
12
+ // templates: ../templates (repositório) ou ./templates (motor empacotado para o pip)
13
+ const TEMPLATES = [path.join(HERE, "..", "templates"), path.join(HERE, "templates")].find((d) => fs.existsSync(d));
14
+ const DOCS = [path.join(HERE, "..", "docs"), path.join(HERE, "docs")].find((d) => fs.existsSync(d));
15
+ const SKILL = [path.join(HERE, "..", "SKILL.md"), path.join(HERE, "docs", "SKILL.md")].find((f) => fs.existsSync(f));
16
+ const VERSION = (() => { for (const f of [path.join(HERE, "..", "package.json"), path.join(HERE, "package.json")]) { try { return JSON.parse(fs.readFileSync(f, "utf8")).version; } catch {} } return "?"; })();
17
+ const [, , cmd, ...rawRest] = process.argv;
18
+ const flags = {};
19
+ const args = [];
20
+ for (let i = 0; i < rawRest.length; i++) {
21
+ const item = rawRest[i];
22
+ if (item.startsWith("--")) {
23
+ const eqIdx = item.indexOf("=");
24
+ if (eqIdx !== -1) {
25
+ flags[item.slice(2, eqIdx)] = item.slice(eqIdx + 1);
26
+ } else {
27
+ const key = item.slice(2);
28
+ const next = rawRest[i + 1];
29
+ if (next && !next.startsWith("--")) {
30
+ flags[key] = next;
31
+ i++;
32
+ } else {
33
+ flags[key] = true;
34
+ }
35
+ }
36
+ } else {
37
+ args.push(item);
38
+ }
39
+ }
40
+
41
+ const HELP = `sagadeck — YAML -> apresentação (HTML animado + PowerPoint editável + PDF + roteiro)
42
+
43
+ sagadeck new <nome> --prompt "briefing" [--slides=10] [--duration=20] [--theme=x] deck inteiro escrito pelo LLM
44
+ (pesquisa na web quando o pedido precisa de informação recente ou de um artigo; --sem-pesquisa desliga)
45
+ (--duration dispensa --slides: ~1 slide a cada 1,5 min)
46
+ (imagens: peça no briefing; --images = "você decide onde ilustrar", --no-images = nenhuma)
47
+ sagadeck napkin <texto|arquivo> [-o deck.yaml] [--rules] texto bruto -> slide visual (LLM se houver; --rules força as regras)
48
+ sagadeck imagens <deck.yaml> gera as imagens pedidas com image_prompt: no YAML (modelo de imagem)
49
+ sagadeck scaffold <nome> [--theme=prata] [--type=pitch|keynote|palestra] gera esqueleto narrativo pronto (economiza 80% de tokens)
50
+ sagadeck new <nome> [--topic=T] [--theme=x] cria um deck de exemplo NA BIBLIOTECA (<biblioteca>/<tópico>/<nome>/)
51
+ sagadeck build <deck.yaml> gera <deck>.html (abre no navegador; P = modo apresentador)
52
+ sagadeck check <deck.yaml> procura texto estourado, sobreposição, contraste, excesso de texto
53
+ sagadeck shots <deck.yaml> [--steps] [--only=3,5] PNG de cada slide + folhas de contato (para revisar)
54
+ sagadeck pptx <deck.yaml> [--native-charts] [--sem-notas] gera <deck>.pptx editável (com animações dos cliques e notas; --sem-notas tira as notas, para mandar a alguém)
55
+ sagadeck pdf <deck.yaml> gera <deck>.pdf (um slide por página)
56
+ sagadeck roteiro <deck.yaml> gera <deck> - roteiro.pdf (miniaturas + notas + tempos)
57
+ sagadeck estudo <deck.yaml> [--html] gera <deck> - material de estudo.pdf (cada slide + texto de consulta)
58
+ sagadeck all <deck.yaml> build + check + pptx + pdf + roteiro
59
+ sagadeck studio [deck.yaml|x.sagadeck] [--port=3517] [--library=PASTA] sem arquivo: abre a biblioteca; já aberto: usa o aberto
60
+ (PASTA padrão: SAGADECK_HOME ou ~/sagadeck); com arquivo: abre o editor dele
61
+ --host=0.0.0.0 abre para a rede (padrão: só esta máquina)
62
+ --multiuser: uma biblioteca por usuário, atrás de um proxy que envia X-Sagadeck-User
63
+ --agentes=naru,ana: no multiusuário, quem pode deixar a IA rodar comandos (padrão: ninguém)
64
+ sagadeck ensaio-api [--port=3000] ensaio do slide "api": deck de exemplo contra uma API de mentira (sem VPN)
65
+ o deck fica na biblioteca (Sem tópico/Ensaio APIs de IA ao vivo); se já existir, reabre
66
+ sagadeck pack <deck.yaml> [saida.sagadeck] a apresentação inteira num arquivo (YAML + imagens, CSS, widgets)
67
+ sagadeck unpack <x.sagadeck> [pasta] extrai um .sagadeck (ou .zip) numa pasta
68
+ sagadeck importar <arquivo.pptx> [--topico=X] [--titulo=Y] [--sem-fotos]
69
+ traz um PowerPoint para a biblioteca, fiel ao original
70
+ sagadeck autofix <deck.yaml> [--out=pasta] auto-corrige sobreposições, margens e excesso de texto no YAML
71
+ sagadeck mcp inicia o servidor MCP para IDEs agênticos (Cursor, Claude Code, Cline)
72
+ sagadeck watch <deck.yaml> recompila o HTML sempre que o YAML mudar
73
+ sagadeck themes [--out=pasta] gera uma vitrine com todos os temas
74
+ sagadeck icons [filtro] lista ícones disponíveis (2.100+)
75
+ sagadeck search <termo> [--limit=5] pesquisa na web via DuckDuckGo (sem bloqueio) para enriquecer dados
76
+ sagadeck ref imprime a referência completa do YAML (ótimo para dar a um LLM)
77
+ sagadeck skill imprime as instruções para agentes de IA
78
+ sagadeck --version
79
+
80
+ Saídas vão para a pasta do YAML (ou --out=pasta).`;
81
+
82
+ function paths(yamlFile) {
83
+ if (!yamlFile) { console.error("Informe o arquivo .yaml"); process.exit(1); }
84
+ const abs = path.resolve(yamlFile);
85
+ const out = flags.out ? path.resolve(flags.out) : path.dirname(abs);
86
+ const base = path.basename(abs).replace(/\.(ya?ml)$/i, "");
87
+ return { abs, out, base, html: path.join(out, `${base}.html`), pptx: path.join(out, `${base}.pptx`), pdf: path.join(out, `${base}.pdf`), roteiro: path.join(out, `${base} - roteiro.pdf`), shots: path.join(out, `${base}-revisao`), tmpShots: path.join(os.tmpdir(), `sagadeck-${base.replace(/[^\w-]+/g, "_")}`) };
88
+ }
89
+
90
+ function doBuild(p, quiet) {
91
+ const r = buildFile(p.abs, p.html);
92
+ if (!quiet) {
93
+ console.log(`✓ HTML: ${p.html}`);
94
+ console.log(` ${r.meta.slides.length} slides · tema ${r.theme.name} · tempo planejado ${r.planned} min${r.spec.duration ? ` de ${r.spec.duration}` : ""}`);
95
+ if (r.warnings.length) { console.log("⚠ Avisos:"); r.warnings.forEach((w) => console.log(" - " + w)); }
96
+ }
97
+ return r;
98
+ }
99
+
100
+ async function doCheck(p) {
101
+ const { check } = await import("../src/export/shots.js");
102
+ const { report, errors } = await check(p.html);
103
+ if (errors.length) { console.log("✗ Erros de JavaScript:"); errors.forEach((e) => console.log(" - " + e)); }
104
+ if (!report.length) console.log("✓ check: nenhum problema de layout encontrado");
105
+ else {
106
+ console.log(`⚠ check: ${report.length} slide(s) com possíveis problemas`);
107
+ for (const r of report) for (const i of r.issues) console.log(` slide ${r.slide}: ${i.kind}${i.px ? ` (${i.px}px)` : ""}${i.ratio ? ` (${i.ratio}:1)` : ""} — "${i.text}"`);
108
+ }
109
+ return report;
110
+ }
111
+
112
+ async function doShots(p, dir = p.shots) {
113
+ const { shots, contactSheet } = await import("../src/export/shots.js");
114
+ const only = flags.only ? String(flags.only).split(",").map(Number) : null;
115
+ const { files, errors } = await shots(p.html, dir, { steps: !!flags.steps, only });
116
+ const sheets = await contactSheet(files, dir);
117
+ console.log(`✓ ${files.length} imagens em ${dir}`);
118
+ sheets.forEach((s) => console.log(` folha: ${s}`));
119
+ if (errors.length) errors.forEach((e) => console.log(" ✗ " + e));
120
+ return files;
121
+ }
122
+
123
+ async function main() {
124
+ switch (cmd) {
125
+ case "napkin":
126
+ case "visual": {
127
+ const input = args.join(" ").trim();
128
+ if (!input) {
129
+ console.error("Uso: sagadeck napkin <texto bruto ou arquivo.txt> [-o deck.yaml] [--theme=sinal]");
130
+ process.exit(1);
131
+ }
132
+ let content = input;
133
+ if (fs.existsSync(input)) {
134
+ content = fs.readFileSync(input, "utf8");
135
+ }
136
+ const { textToVisualSlide, napkinToYaml } = await import("../src/diagram/napkin.js");
137
+ const theme = flags.theme || "sinal";
138
+ const tone = flags.tone;
139
+ const title = flags.title;
140
+
141
+ // Com um LLM disponível (e sem --rules), o slide é escrito pela IA.
142
+ const { llmAvailable } = await import("../src/ai/llm.js");
143
+ if (!flags.rules && await llmAvailable()) {
144
+ const { textToSlide } = await import("../src/ai/deck-ai.js");
145
+ const YAML = (await import("yaml")).default;
146
+ const outPath = flags.o || flags.out;
147
+ const dir = outPath ? path.dirname(path.resolve(outPath)) : process.cwd();
148
+ const res = await textToSlide(content, { theme, tone, title, images: !!flags.images, imageOptions: { baseDir: dir } });
149
+ console.log(`✨ [Napkin · LLM] layout: ${res.detectedType.toUpperCase()}\n💡 ${res.rationale}\n`);
150
+ if (outPath) {
151
+ const target = path.resolve(outPath);
152
+ fs.mkdirSync(path.dirname(target), { recursive: true });
153
+ fs.writeFileSync(target, YAML.stringify({ title: title || "Diagrama", theme, slides: [res.slide] }, { indent: 2 }), "utf8");
154
+ console.log(`✓ Diagrama visual gerado em: ${target}`);
155
+ } else {
156
+ console.log(YAML.stringify(res.slide, { indent: 2 }));
157
+ }
158
+ break;
159
+ }
160
+
161
+ const outPath = flags.o || flags.out;
162
+ if (outPath) {
163
+ const target = path.resolve(outPath);
164
+ const yamlStr = napkinToYaml(content, { theme, tone, title });
165
+ fs.mkdirSync(path.dirname(target), { recursive: true });
166
+ fs.writeFileSync(target, yamlStr, "utf8");
167
+ console.log(`✓ Diagrama visual gerado com sucesso em: ${target}`);
168
+ } else {
169
+ const res = textToVisualSlide(content, { theme, tone, title });
170
+ console.log(`✨ [Napkin AI] Padrão detectado: ${res.detectedType.toUpperCase()} (confiança: ${Math.round(res.confidence * 100)}%)`);
171
+ console.log(`💡 Raciocínio: ${res.rationale}\n`);
172
+ const YAML = (await import("yaml")).default;
173
+ console.log(YAML.stringify(res.slide, { indent: 2 }));
174
+ }
175
+ break;
176
+ }
177
+ case "scaffold": {
178
+ // sempre na biblioteca (ver newDeckPath)
179
+ const { newDeckPath } = await import("../src/library.js");
180
+ const target = newDeckPath(args[0], { topic: typeof flags.topic === "string" ? flags.topic : undefined, title: typeof flags.title === "string" ? flags.title : undefined });
181
+ if (fs.existsSync(target) && !flags.force) {
182
+ console.error(`Erro: o arquivo ${target} já existe. Use --force para sobrescrever.`);
183
+ process.exit(1);
184
+ }
185
+ const { generateScaffold } = await import("../src/templates/scaffold.js");
186
+ const YAML = (await import("yaml")).default;
187
+ const theme = flags.theme || "prata";
188
+ const type = flags.type || "keynote";
189
+ const title = flags.title || (args[0] ? path.basename(args[0], path.extname(args[0])) : "Nova Apresentação");
190
+ const author = flags.author || "Seu Nome";
191
+ const spec = generateScaffold({ title, theme, type, author });
192
+ fs.mkdirSync(path.dirname(target), { recursive: true });
193
+ fs.writeFileSync(target, YAML.stringify(spec, { indent: 2 }), "utf8");
194
+ console.log(`✓ Esqueleto narrativo gerado em: ${target}`);
195
+ console.log(` Tipo: ${type} · Tema: ${theme} · Slides: ${spec.slides.length}`);
196
+ console.log(` Estrutura ideal (cover -> statement -> stats -> steps -> cards -> end) pronta para preenchimento.`);
197
+ break;
198
+ }
199
+ case "new": {
200
+ // sempre na biblioteca (ver newDeckPath): "sagadeck new Minha palestra" -> <biblioteca>/Sem tópico/Minha palestra/
201
+ const { newDeckPath } = await import("../src/library.js");
202
+ const target = newDeckPath(args[0], { topic: typeof flags.topic === "string" ? flags.topic : undefined, title: typeof flags.title === "string" ? flags.title : undefined });
203
+ if (fs.existsSync(target)) { console.error(`${target} já existe`); process.exit(1); }
204
+ if (flags.prompt || flags.briefing) {
205
+ // Deck inteiro escrito pelo LLM a partir de um briefing
206
+ let briefing = String(flags.prompt || flags.briefing);
207
+ if (fs.existsSync(briefing)) briefing = fs.readFileSync(briefing, "utf8");
208
+ if (flags.images === true) briefing += "\n\nImagens: você decide onde vale ilustrar com imagens geradas (não precisa ser em todos os slides).";
209
+ if (flags["no-images"]) briefing += "\n\nNão gere imagens.";
210
+ const { generateDeck, toYaml } = await import("../src/ai/deck-ai.js");
211
+ const { diagramCheck, closeSnapshots } = await import("../src/studio/snapshot.js");
212
+ const dir = path.dirname(target);
213
+ const { spec, images } = await generateDeck(briefing, {
214
+ drawCheck: diagramCheck, // diagramas: confere desenhando (se houver Chrome/Edge)
215
+ theme: typeof flags.theme === "string" ? flags.theme : undefined,
216
+ slides: Number(flags.slides) || undefined,
217
+ duration: Number(flags.duration) || undefined,
218
+ images: !flags["no-images"],
219
+ imageOptions: { baseDir: dir, assetsDir: path.join(dir, "imagens") },
220
+ research: flags["sem-pesquisa"] ? false : "auto", // a IA decide se pesquisa; as fontes ficam em contexto/pesquisa/
221
+ researchDir: dir,
222
+ onProgress: (m) => console.log(` … ${m}`),
223
+ }).finally(closeSnapshots);
224
+ fs.mkdirSync(dir, { recursive: true });
225
+ fs.writeFileSync(target, toYaml(spec));
226
+ console.log(`✓ criado ${target} com IA: ${spec.slides.length} slides · tema ${spec.theme || "sinal"}`);
227
+ images.done.forEach((d) => console.log(` 🖼 ${path.relative(process.cwd(), d.file)}`));
228
+ images.failed.forEach((f) => console.log(` ✗ imagem não gerada ("${f.prompt.slice(0, 50)}"): ${f.error}`));
229
+ console.log(` próximo passo: sagadeck studio "${target}" ou sagadeck all "${target}"`);
230
+ break;
231
+ }
232
+ let t = fs.readFileSync(path.join(TEMPLATES, "exemplo.yaml"), "utf8");
233
+ if (flags.theme) t = t.replace(/^theme: .*/m, `theme: ${flags.theme}`);
234
+ fs.mkdirSync(path.dirname(target), { recursive: true });
235
+ fs.writeFileSync(target, t);
236
+ console.log(`✓ criado ${target}\n próximo passo: sagadeck build "${target}"`);
237
+ break;
238
+ }
239
+ case "imagens": case "images": {
240
+ // Gera as imagens pedidas com `image_prompt:` no YAML e troca por `image: imagens/…`
241
+ const p = paths(args[0]);
242
+ const { loadSpec } = await import("../src/build.js");
243
+ const { materializeImages, countImagePrompts, toYaml } = await import("../src/ai/deck-ai.js");
244
+ const spec = loadSpec(p.abs);
245
+ const n = countImagePrompts(spec);
246
+ if (!n) { console.log("Nenhum `image_prompt:` no deck; nada a gerar."); break; }
247
+ console.log(`Gerando ${n} imagem(ns)…`);
248
+ const { done, failed } = await materializeImages(spec, { baseDir: spec._dir, keepFailed: true, onProgress: (m) => console.log(` … ${m}`) });
249
+ (await import("../src/deck-file.js")).writeDeckFile(p.abs, spec);
250
+ done.forEach((d) => console.log(` 🖼 ${path.relative(process.cwd(), d.file)}`));
251
+ failed.forEach((f) => console.log(` ✗ "${f.prompt.slice(0, 50)}": ${f.error}`));
252
+ console.log(`✓ ${done.length} gerada(s), YAML atualizado: ${p.abs}`);
253
+ if (failed.length) process.exitCode = 1;
254
+ break;
255
+ }
256
+ case "build": doBuild(paths(args[0])); break;
257
+ case "check": { const p = paths(args[0]); doBuild(p); await doCheck(p); break; }
258
+ case "shots": { const p = paths(args[0]); doBuild(p, true); await doShots(p); break; }
259
+ case "pptx": {
260
+ const p = paths(args[0]); const r = doBuild(p, true);
261
+ const { exportPptx } = await import("../src/export/pptx.js");
262
+ console.log("… exportando PowerPoint");
263
+ const { errors } = await exportPptx(p.html, p.pptx, { theme: r.theme, meta: { ...r.meta, slides: r.slidesMeta }, nativeCharts: !!flags["native-charts"], notes: !flags["sem-notas"], log: flags.verbose ? console.log : () => {} });
264
+ errors.forEach((e) => console.log(" ✗ " + e));
265
+ console.log(`✓ PPTX: ${p.pptx}`);
266
+ break;
267
+ }
268
+ case "pdf": {
269
+ const p = paths(args[0]); doBuild(p, true);
270
+ const { pdf } = await import("../src/export/shots.js");
271
+ await pdf(p.html, p.pdf); console.log(`✓ PDF: ${p.pdf}`); break;
272
+ }
273
+ case "roteiro": {
274
+ const p = paths(args[0]); const r = doBuild(p, true);
275
+ const { shots } = await import("../src/export/shots.js");
276
+ const { roteiroPDF } = await import("../src/export/roteiro.js");
277
+ const { files } = await shots(p.html, p.tmpShots + "-mini", { scale: 0.5, jpeg: true });
278
+ await roteiroPDF({ slidesMeta: r.slidesMeta, shotFiles: files, outFile: p.roteiro, title: r.meta.title, author: r.meta.author, duration: r.spec.duration });
279
+ console.log(`✓ Roteiro: ${p.roteiro}`); break;
280
+ }
281
+ // material de estudo: cada slide inteiro + o texto de consulta (sem as notas); --html para um arquivo HTML só
282
+ case "estudo": {
283
+ const p = paths(args[0]); const r = doBuild(p, true);
284
+ const { shots } = await import("../src/export/shots.js");
285
+ const { estudoHTML, estudoPDF } = await import("../src/export/estudo.js");
286
+ const { files } = await shots(p.html, p.tmpShots + "-estudo", { scale: 0.7, jpeg: true });
287
+ const html = estudoHTML({ title: r.meta.title, author: r.meta.author, date: r.spec.date, slidesMeta: r.slidesMeta, shotFiles: files });
288
+ const out = p.roteiro.replace(/ - roteiro\.pdf$/, ` - material de estudo.${flags.html ? "html" : "pdf"}`);
289
+ if (flags.html) fs.writeFileSync(out, html); else await estudoPDF(html, out);
290
+ console.log(`✓ Material de estudo: ${out}`); break;
291
+ }
292
+ case "all": {
293
+ const p = paths(args[0]); const r = doBuild(p);
294
+ await doCheck(p);
295
+ const files = await doShots(p, flags.revisao ? p.shots : p.tmpShots);
296
+ const { exportPptx } = await import("../src/export/pptx.js");
297
+ await exportPptx(p.html, p.pptx, { theme: r.theme, meta: { ...r.meta, slides: r.slidesMeta }, nativeCharts: !!flags["native-charts"], notes: !flags["sem-notas"] });
298
+ console.log(`✓ PPTX: ${p.pptx}`);
299
+ const { pdf } = await import("../src/export/shots.js");
300
+ await pdf(p.html, p.pdf); console.log(`✓ PDF: ${p.pdf}`);
301
+ const { roteiroPDF } = await import("../src/export/roteiro.js");
302
+ const { shots: mini } = await import("../src/export/shots.js");
303
+ const small = (await mini(p.html, p.tmpShots + "-mini", { scale: 0.5, jpeg: true })).files;
304
+ await roteiroPDF({ slidesMeta: r.slidesMeta, shotFiles: small, outFile: p.roteiro, title: r.meta.title, author: r.meta.author, duration: r.spec.duration });
305
+ console.log(`✓ Roteiro: ${p.roteiro}`);
306
+ break;
307
+ }
308
+ case "watch": {
309
+ const p = paths(args[0]); doBuild(p);
310
+ console.log("observando mudanças… (Ctrl+C para sair)");
311
+ let t; fs.watch(path.dirname(p.abs), () => { clearTimeout(t); t = setTimeout(() => { try { doBuild(p); } catch (e) { console.error("✗ " + e.message); } }, 150); });
312
+ break;
313
+ }
314
+ case "pack": {
315
+ const p = paths(args[0]);
316
+ const { packDeck, EXTENSION } = await import("../src/package.js");
317
+ const name = path.basename(p.abs).replace(/\.ya?ml$/i, "");
318
+ const out = path.resolve(args[1] || path.join(path.dirname(p.abs), name + EXTENSION));
319
+ const { zip, files, missing } = await packDeck(loadSpec(p.abs), { baseDir: path.dirname(p.abs), name, generator: "sagadeck cli" });
320
+ fs.writeFileSync(out, zip);
321
+ console.log(`✓ ${out} (${(zip.length / 1024).toFixed(0)} KB · ${files.length} arquivo(s) junto)`);
322
+ missing.forEach((m) => console.log(` ✗ não encontrado (listado em FALTANDO.txt): ${m}`));
323
+ break;
324
+ }
325
+ case "importar": case "import": {
326
+ const src = path.resolve(args[0] || "");
327
+ if (!fs.existsSync(src)) { console.error(`não achei ${src}`); process.exit(1); }
328
+ const { openLibrary, defaultLibraryRoot } = await import("../src/library.js");
329
+ const lib = openLibrary(flags.library || defaultLibraryRoot());
330
+ const r = await lib.importOffice(fs.readFileSync(src), flags.topico || flags.topic, path.basename(src), { title: flags.titulo || flags.title, snapshots: !flags["sem-fotos"], log: (m) => console.log(` ${m}`) });
331
+ console.log(`importado: ${r.slides} slides${r.snapshots ? ` · fotos do original pelo ${r.snapBy}` : ""}${r.converted ? ` · ${r.converted} figura(s) WMF/EMF convertidas` : ""}
332
+ ${r.file}`);
333
+ break;
334
+ }
335
+ case "unpack": {
336
+ const src = path.resolve(args[0] || "");
337
+ if (!fs.existsSync(src)) { console.error(`não achei ${src}`); process.exit(1); }
338
+ const { unpackDeck } = await import("../src/package.js");
339
+ let dest = path.resolve(args[1] || src.replace(/\.(sagadeck|zip)$/i, ""));
340
+ for (let n = 2; fs.existsSync(dest) && !args[1]; n++) dest = `${src.replace(/\.(sagadeck|zip)$/i, "")}-${n}`;
341
+ const { file } = await unpackDeck(fs.readFileSync(src), dest);
342
+ console.log(`✓ extraído em ${dest}\n apresentação: ${file}`);
343
+ break;
344
+ }
345
+ case "studio": case "web": {
346
+ const { createStudioServer } = await import("../src/studio/server.js");
347
+ let deckFile = args[0] ? path.resolve(args[0]) : null;
348
+ if (deckFile && /\.(sagadeck|zip)$/i.test(deckFile)) {
349
+ const { unpackDeck } = await import("../src/package.js");
350
+ let dest = deckFile.replace(/\.(sagadeck|zip)$/i, "");
351
+ for (let n = 2; fs.existsSync(dest); n++) dest = `${deckFile.replace(/\.(sagadeck|zip)$/i, "")}-${n}`;
352
+ deckFile = (await unpackDeck(fs.readFileSync(deckFile), dest)).file;
353
+ console.log(` extraído em ${dest}`);
354
+ }
355
+ const { DEFAULT_PORT, VERSION, probeInstance, instanceDecision } = await import("../src/studio/instance.js");
356
+ const port = Number(flags.port || process.env.PORT || DEFAULT_PORT);
357
+ // só esta máquina por padrão (no banco, 0.0.0.0 abriria a biblioteca para a rede); --host para mudar
358
+ const host = typeof flags.host === "string" ? flags.host : "127.0.0.1";
359
+ const multiuser = !!flags.multiuser;
360
+ if (multiuser && !["127.0.0.1", "localhost", "::1"].includes(host)) {
361
+ console.error("--multiuser confia no cabeçalho de usuário do proxy (nginx): só funciona escutando em 127.0.0.1.");
362
+ process.exit(1);
363
+ }
364
+ const { defaultLibraryRoot } = await import("../src/library.js");
365
+ const library = typeof flags.library === "string" ? path.resolve(flags.library) : defaultLibraryRoot();
366
+ // --agentes=naru,ana: no multiusuário, só esses usuários podem deixar a IA rodar comandos (na máquina do servidor)
367
+ // já tem um Studio nesta porta? mesma biblioteca e versão: usa o aberto (nada de dois servidores nos mesmos arquivos)
368
+ const shownHost = host === "0.0.0.0" ? "localhost" : host;
369
+ const found = await probeInstance(port, host);
370
+ const decision = instanceDecision(found, { library, version: VERSION });
371
+ if (decision.action === "reuse") {
372
+ let url = `http://${shownHost}:${port}/`;
373
+ if (deckFile) {
374
+ const rel = path.relative(library, deckFile);
375
+ if (!rel.startsWith("..") && !path.isAbsolute(rel)) url += `editor?deck=${encodeURIComponent(rel.split(path.sep).join("/"))}`;
376
+ else { console.error(`O Studio já está aberto em http://${shownHost}:${port} com a biblioteca ${library}, e este deck está fora dela. Use --port=OUTRA para abri-lo à parte.`); process.exit(1); }
377
+ }
378
+ console.log(`✓ O SagaDeck Studio ${found.version} já está aberto (processo ${found.pid}): ${url}`);
379
+ break;
380
+ }
381
+ if (decision.action === "conflict") {
382
+ console.error(`A porta ${port} já tem um SagaDeck Studio aberto com ${decision.why}. Use --port=OUTRA para abrir à parte.`);
383
+ process.exit(1);
384
+ }
385
+ const agentUsers = typeof flags.agentes === "string" ? flags.agentes.split(",").map((x) => x.trim()).filter(Boolean) : undefined;
386
+ // IA: sobe o `modelrelay serve` junto, se ele estiver instalado e ninguém escutar na porta dele (como o sagadeck do pip)
387
+ const { startRelay } = await import("../src/ai/relay.js");
388
+ const relay = multiuser ? null : await startRelay();
389
+ if (relay) {
390
+ console.log(`✓ IA: modelrelay em ${relay.url} (configuração: http://127.0.0.1:8765/)`);
391
+ for (const sig of ["exit", "SIGINT", "SIGTERM"]) process.on(sig, () => { relay.stop(); if (sig !== "exit") process.exit(0); });
392
+ }
393
+ const server = createStudioServer(deckFile, { port, host, library, multiuser, userHeader: flags["user-header"], agentUsers });
394
+ server.on("error", (e) => {
395
+ if (e.code !== "EADDRINUSE") throw e;
396
+ console.error(`A porta ${port} está ocupada por outro programa. Use --port=OUTRA.`);
397
+ process.exit(1);
398
+ });
399
+ server.listen(port, host, () => {
400
+ console.log(`✓ SagaDeck Studio ${VERSION} em http://${shownHost}:${port}${deckFile ? "" : " (biblioteca)"} (processo ${process.pid})`);
401
+ console.log(multiuser ? ` multiusuário: bibliotecas em ${path.join(library, "usuarios")}` : ` biblioteca: ${library}`);
402
+ });
403
+ break;
404
+ }
405
+ // Ensaio do slide "api": API de mentira + ambientes temporários + o deck de exemplo, no Studio.
406
+ // Nada disso toca no seu ~/.sagadeck/ambientes.yaml.
407
+ case "ensaio-api": {
408
+ const { startMockApi, demoEnvFile, DEMO_FILES } = await import("../src/api-demo.js");
409
+ const { createStudioServer } = await import("../src/studio/server.js");
410
+ const { defaultLibraryRoot } = await import("../src/library.js");
411
+ const { newDeckPath } = await import("../src/library.js");
412
+ const YAML = (await import("yaml")).default;
413
+ const mock = await startMockApi({ statuses: ["STARTED", "RUNNING", "RUNNING", "FINISHED"] });
414
+ // só os ambientes (apontam para a porta desta API de mentira) ficam no temporário; o deck mora na biblioteca
415
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "sagadeck-ensaio-"));
416
+ const envFile = path.join(tmp, "ambientes.yaml");
417
+ fs.writeFileSync(envFile, demoEnvFile(mock));
418
+ const tpl = path.join(TEMPLATES, "ensaio-api.yaml");
419
+ const deckFile = newDeckPath(null, { title: YAML.parse(fs.readFileSync(tpl, "utf8")).title });
420
+ const dir = path.dirname(deckFile);
421
+ fs.mkdirSync(dir, { recursive: true });
422
+ // já existe (de outra vez, talvez editado): reabre como está; nada é sobrescrito
423
+ if (!fs.existsSync(deckFile)) fs.copyFileSync(tpl, deckFile);
424
+ for (const [name, text] of Object.entries(DEMO_FILES)) if (!fs.existsSync(path.join(dir, name))) fs.writeFileSync(path.join(dir, name), text);
425
+ const port = Number(flags.port || process.env.PORT || 3000);
426
+ // ensaio: false — os ambientes do ensaio já estão no arquivo (dev e hom), sem o embutido repetido
427
+ const server = createStudioServer(deckFile, { port, host: "127.0.0.1", library: defaultLibraryRoot(), apiEnvFile: envFile, ensaio: false });
428
+ server.listen(port, "127.0.0.1", () => {
429
+ console.log(`✓ Ensaio no Studio: http://127.0.0.1:${port}/editor`);
430
+ console.log(` deck: ${deckFile}`);
431
+ console.log(` API de mentira em ${mock.url} · ambientes do ensaio em ${envFile}`);
432
+ console.log(" Apresente (F5 / Apresentar) e clique em Executar em cada slide. Ctrl+C encerra.");
433
+ });
434
+ break;
435
+ }
436
+ case "autofix": {
437
+ const p = paths(args[0]);
438
+ const { autofixDeck } = await import("../src/fiscal/autofix.js");
439
+ const YAML = (await import("yaml")).default;
440
+ const spec = loadSpec(p.abs);
441
+ console.log(`… analisando e auto-corrigindo ${p.abs}`);
442
+ const res = autofixDeck(spec);
443
+ const outYaml = flags.out ? path.resolve(flags.out) : p.abs;
444
+ (await import("../src/deck-file.js")).writeDeckFile(outYaml, res.spec);
445
+ console.log(`✓ Auto-correção concluída! Salvo em: ${outYaml}`);
446
+ if (res.actions.length === 0) {
447
+ console.log(" Nenhum problema de sobreposição ou margem encontrado.");
448
+ } else {
449
+ console.log(` ${res.modifiedSlidesCount} slide(s) corrigido(s):`);
450
+ for (const a of res.actions) {
451
+ console.log(` • Slide ${a.slide} (${a.layout}):`);
452
+ for (const item of a.actions) console.log(` - ${item}`);
453
+ }
454
+ }
455
+ break;
456
+ }
457
+ case "mcp": {
458
+ const { runMCPServer } = await import("../src/mcp/server.js");
459
+ runMCPServer();
460
+ break;
461
+ }
462
+ case "themes": {
463
+ const out = path.resolve(flags.out || path.join(process.cwd(), "sagadeck-temas"));
464
+ fs.mkdirSync(out, { recursive: true });
465
+ const { shots, contactSheet } = await import("../src/export/shots.js");
466
+ const src = path.join(TEMPLATES, "exemplo.yaml");
467
+ for (const name of Object.keys(THEMES)) {
468
+ const spec = loadSpec(src); spec.theme = name; spec.id = "tema-" + name;
469
+ const html = path.join(out, `tema-${name}.html`);
470
+ fs.writeFileSync(html, buildHTML(spec).html);
471
+ const { files } = await shots(html, path.join(out, `tema-${name}`), {});
472
+ const sheets = await contactSheet(files, path.join(out, `tema-${name}`), { perSheet: 12 });
473
+ console.log(`✓ ${name}: ${html}\n ${sheets[0]}`);
474
+ }
475
+ break;
476
+ }
477
+ case "icons": { const l = listIcons(args[0]); console.log(l.join(" ")); console.log(`\n${l.length} ícones`); break; }
478
+ case "search": {
479
+ const query = args.join(" ");
480
+ if (!query) {
481
+ console.error("Uso: sagadeck search <termo de busca>");
482
+ process.exit(1);
483
+ }
484
+ const { searchDuckDuckGo } = await import("../src/research/duckduckgo.js");
485
+ const limit = Number(flags.limit || 5);
486
+ console.log(`… pesquisando no DuckDuckGo: "${query}"`);
487
+ try {
488
+ const results = await searchDuckDuckGo(query, { limit });
489
+ if (flags.json) {
490
+ console.log(JSON.stringify(results, null, 2));
491
+ } else {
492
+ console.log(`✓ ${results.length} resultado(s) encontrado(s):\n`);
493
+ results.forEach((r, i) => {
494
+ console.log(`${i + 1}. \x1b[1m${r.title}\x1b[0m`);
495
+ console.log(` \x1b[36m${r.url}\x1b[0m`);
496
+ if (r.snippet) console.log(` ${r.snippet}\n`);
497
+ });
498
+ }
499
+ } catch (err) {
500
+ console.error(`✗ Erro na pesquisa: ${err.message}`);
501
+ }
502
+ break;
503
+ }
504
+ case "ref": case "referencia": process.stdout.write(fs.readFileSync(path.join(DOCS, "REFERENCIA.md"), "utf8")); break;
505
+ case "skill": process.stdout.write(fs.readFileSync(SKILL, "utf8")); break;
506
+ case "--version": case "-v": case "version": console.log(`sagadeck ${VERSION}`); break;
507
+ default: console.log(HELP);
508
+ }
509
+ }
510
+
511
+ main().catch((e) => { console.error("✗ " + (e.stack || e.message)); process.exit(1); });