sagadeck 1.5.0 → 1.7.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.
package/README.md CHANGED
@@ -14,15 +14,25 @@ Ele vem com 11 temas (incluindo estilo Keynote Apple em prata clean e respiráve
14
14
  ## Instalação
15
15
 
16
16
  ```bash
17
- pip install sagadeck
17
+ npm install -g sagadeck
18
18
  ```
19
- Requer **Node.js 18+** e **Chrome ou Edge** instalados (o motor é JavaScript e vem empacotado no pacote Python).
19
+ Requer **Node.js 18+** e **Chrome ou Edge** instalados (o PDF, o PNG e as conferências visuais usam o navegador).
20
+ É tudo Node: não precisa de Python. (O pacote do PyPI parou na 1.5.0.)
21
+
22
+ ## Comece aqui
20
23
 
21
- Quem já vive no Node pode instalar direto, sem Python e sem bundle (vai a fonte + dependências):
22
24
  ```bash
23
- npm install -g sagadeck
25
+ sagadeck studio
24
26
  ```
25
- A IA funciona apontando para um modelrelay ou gateway compatível (`SAGADECK_LLM_URL`, veja abaixo); o `pip install "sagadeck[ia]"` continua sendo o caminho com o modelrelay junto.
27
+ O navegador abre sozinho em **http://127.0.0.1:3517** (se não abrir, abra esse endereço). **Essa é a página do
28
+ sagadeck**: a biblioteca das suas apresentações, o editor (estilo PowerPoint) e o chat com a IA. Não há outra interface
29
+ para abrir nem para criar. Deixe o terminal aberto enquanto usa; `--sem-navegador` não abre o navegador.
30
+
31
+ Para usar a IA (gerar apresentações, conversar com o chat), clique em **Configurar IA** (a biblioteca mostra uma faixa
32
+ enquanto ela não está configurada). **Usar a recomendação** preenche o OpenRouter com modelos que funcionam; cole a
33
+ chave, clique em **Testar** e **Salvar**. Se nem a recomendação responder, o problema é a rede ou a chave. Depois,
34
+ troque pelo provedor e pelos modelos que quiser. Detalhes em
35
+ [IA de verdade](#ia-de-verdade-llm).
26
36
 
27
37
  ## Como usar
28
38
 
@@ -69,64 +79,52 @@ O sagadeck foi projetado para ser usado por humanos e por agentes de IA:
69
79
  2. **Pelo Estúdio Visual**: Execute `sagadeck studio palestra.yaml` para abrir a interface web estilo PowerPoint, onde você pode editar visualmente no canvas 16:9 e conversar com a IA no chat lateral. A IA se auto-corrige e nunca deixa elementos sobrepostos ou fora das margens.
70
80
  3. **Pelo Protocolo MCP**: Execute `sagadeck mcp` para que IDEs agênticos (Cursor, Windsurf, Cline, Roo Code) descubram e invoquem diretamente as ferramentas de criação, leitura, fiscalização e auto-cura de apresentações.
71
81
 
72
- Se o código de uma ferramenta em Python quiser chamar o sagadeck diretamente (sem montar comandos de terminal), há uma API mínima:
73
-
74
- ```python
75
- import sagadeck
76
- sagadeck.autofix("palestra.yaml") # corrige sobreposições e margens automaticamente
77
- sagadeck.studio() # abre a biblioteca (Documentos/sagadeck no Windows)
78
- sagadeck.studio("palestra.yaml", port=3517) # abre direto o editor de um deck
79
- sagadeck.export("palestra.yaml", out="saida") # {'html': …, 'pptx': …, 'pdf': …, 'roteiro': …}
80
- print(sagadeck.check("palestra.yaml")) # relatório do fiscal em texto
81
- contexto = sagadeck.reference() # referência do YAML para colocar no prompt
82
- ```
83
-
84
82
  ## IA de verdade (LLM)
85
83
 
86
- O chat lateral do Studio, o Napkin (texto → slide), o **"✨ Deck com IA"** e a geração de imagens usam um LLM quando há um disponível; sem LLM, o chat e o Napkin continuam funcionando com as regras locais.
87
-
88
- O sagadeck fala com qualquer endpoint compatível com OpenAI (`/v1/chat/completions`). O caminho recomendado é o [modelrelay](https://github.com/naruminho/modelrelay), que decide pela configuração dele para onde as chamadas vão (OpenRouter, OpenAI, gateway corporativo), sem nada disso no sagadeck:
89
-
90
- ```bash
91
- pip install "sagadeck[ia]" # o sagadeck com o modelrelay na versão que ele exige
92
- # ou só o modelrelay: pip install -U modelrelay
93
- ```
94
-
95
- Com um modelrelay mais velho do que o exigido, o sagadeck avisa ao abrir e mostra o comando para atualizar.
96
- Quem desenvolve o modelrelay e o instalou direto do clone (`pip install -e .` na pasta dele, a "instalação
97
- editável": o Python lê o código da pasta, sem cópia) atualiza só com `git pull` ali, e o aviso diz isso, com a
98
- pasta. `pip install -e .` de novo só quando o modelrelay ganhar uma dependência nova.
84
+ O chat lateral do Studio, o Napkin (texto → slide), o **"Deck com IA"** e a geração de imagens usam um LLM quando há um configurado; sem ele, o chat e o Napkin continuam funcionando com as regras locais.
99
85
 
100
- O jeito mais fácil de configurar é pela tela: abra `sagadeck studio` e clique em **modelrelay** no alto da
101
- biblioteca (ou abra http://127.0.0.1:8765/ com um `modelrelay serve` rodando). Lá você escolhe o provedor
102
- (OpenRouter, OpenAI, Google, DeepSeek, o gateway da empresa…), cola a chave, testa e diz qual modelo faz
103
- **texto** e qual faz **imagem**. A tela grava o `~/.modelrelay/config.toml`, que também dá para editar à mão
104
- (`modelrelay init` cria um modelo comentado). O botão só aparece quando o modelrelay roda nesta máquina; no
105
- modo multiusuário, quem configura é o admin.
86
+ O sagadeck fala direto com qualquer provedor compatível com a API da OpenAI (`/v1/chat/completions`): OpenRouter,
87
+ OpenAI, o proxy de IA da empresa. Não há serviço à parte para instalar nem para manter rodando.
106
88
 
107
- À mão, no `~/.modelrelay/config.toml`, os apelidos que o sagadeck usa são:
89
+ **Pela tela** (o jeito mais fácil): **Configurar IA**, no alto da biblioteca, ou **IA desligada / IA ligada**, no
90
+ editor. Escolha o provedor (OpenRouter, OpenAI ou "outro compatível", com o endereço), cole a chave e diga o modelo
91
+ de cada papel:
108
92
 
109
- ```toml
110
- [models]
111
- "text" = "google/gemini-2.5-flash" # chat, Napkin e geração de deck
112
- "image" = "google/gemini-2.5-flash-image" # imagens
93
+ | papel | para quê | |
94
+ |---|---|---|
95
+ | texto | chat, Napkin, geração e revisão de decks | obrigatório |
96
+ | visão | ver o slide renderizado e as imagens coladas | opcional; vazio = o de texto |
97
+ | imagem | ilustrações (`image_prompt`) | opcional |
98
+ | busca | pesquisa na web da geração (no OpenRouter, um modelo `:online`) | opcional; sem ele, buscadores diretos |
99
+
100
+ **Testar** faz uma pergunta curta com o que está na tela antes de salvar. A configuração fica só nesta máquina, em
101
+ `~/.sagadeck/ia.json` (fora do código e fora da biblioteca, que pode sincronizar com a nuvem); a chave nunca volta
102
+ inteira para a página. No modo multiusuário, quem configura é quem administra o servidor.
103
+
104
+ **À mão**, o mesmo arquivo:
105
+
106
+ ```json
107
+ {
108
+ "provider": "openrouter",
109
+ "url": "https://openrouter.ai/api/v1",
110
+ "keyEnv": "OPENROUTER_API_KEY",
111
+ "models": { "text": "deepseek/deepseek-v4.1-flash", "image": "google/gemini-3.1-flash-image" }
112
+ }
113
113
  ```
114
+ A chave vai em `key` ou numa variável de ambiente indicada em `keyEnv`. `headers` acrescenta cabeçalhos fixos.
114
115
 
115
- O sagadeck se identifica para o modelrelay (cabeçalho `X-Modelrelay-App: sagadeck`), então dá para usar
116
- modelos diferentes só nele, sem afetar outros apps que usam o mesmo modelrelay:
116
+ **Provedor que não segue a API da OpenAI** (outro jeito de autenticar, outro endereço): em vez de mexer no sagadeck,
117
+ aponte `"adaptador"` para um arquivo `.mjs` seu cujo `export default` recebe `(url, init)` e devolve a resposta (como
118
+ o `fetch`). O resto do pedido segue no formato da OpenAI.
117
119
 
118
- ```toml
119
- [apps.sagadeck.models] # só o que muda para o sagadeck; o resto vem de [models]
120
- "text" = "deepseek/deepseek-v4-flash"
121
- ```
122
-
123
- Confira com `modelrelay show --app sagadeck`. Detalhes na seção *Per-app models* do README do modelrelay.
120
+ **Quem usava o modelrelay**: na primeira vez, o sagadeck traz a configuração dele (`~/.modelrelay/config.toml`:
121
+ provedor, chave e os modelos do sagadeck) para o `~/.sagadeck/ia.json`. Depois disso o modelrelay não é mais usado.
124
122
 
125
123
  **Modelo sem visão** (ex.: DeepSeek V4 Flash): o assistente manda uma foto do slide e as imagens que você cola.
126
124
  Se o modelo recusar imagem, o sagadeck refaz o pedido sem as imagens, avisa na resposta ("não enxerga imagens")
127
125
  e não insiste nesse modelo até reiniciar. Tudo funciona, só que a IA não vê o slide renderizado.
128
126
 
129
- Pronto: com o modelrelay instalado no mesmo Python, `sagadeck studio`, `new`, `napkin` e `imagens` sobem um `modelrelay serve` sozinhos enquanto rodam. Rodando o motor Node direto (`node bin/sagadeck.js`), deixe um `modelrelay serve` aberto em outro terminal.
127
+ Na linha de comando, `new`, `napkin` e `imagens` usam a mesma configuração:
130
128
 
131
129
  ```bash
132
130
  sagadeck new palestra --prompt "Palestra de 15 min para gerentes sobre IA com segurança. Ilustre onde fizer sentido." --slides=10
@@ -134,14 +132,15 @@ sagadeck napkin "1) cliente abre chamado 2) triagem por IA 3) analista revisa"
134
132
  sagadeck imagens palestra.yaml # gera as imagens pedidas com image_prompt: no YAML
135
133
  ```
136
134
 
137
- | variável | padrão | |
138
- |---|---|---|
139
- | `SAGADECK_LLM_URL` | `http://127.0.0.1:8765/v1` | qualquer API compatível com OpenAI (ex.: `https://openrouter.ai/api/v1`) |
140
- | `SAGADECK_LLM_KEY` | — | bearer token, se apontar direto para um provedor |
141
- | `SAGADECK_TEXT_MODEL` / `SAGADECK_IMAGE_MODEL` | `text` / `image` | nomes dos modelos |
142
- | `SAGADECK_LLM_TIMEOUT` | `180` | segundos por chamada |
143
- | `SAGADECK_NO_RELAY` | — | `1` impede o pacote Python de subir o modelrelay |
144
- | `SAGADECK_APP` | `sagadeck` | nome com que o sagadeck se identifica ao modelrelay (`[apps.<nome>.models]`) |
135
+ As variáveis abaixo valem por cima da configuração (útil em servidor e em testes):
136
+
137
+ | variável | |
138
+ |---|---|
139
+ | `SAGADECK_LLM_URL` | qualquer API compatível com OpenAI (ex.: `https://openrouter.ai/api/v1`) |
140
+ | `SAGADECK_LLM_KEY` | a chave (bearer token) |
141
+ | `SAGADECK_TEXT_MODEL` / `SAGADECK_VISION_MODEL` / `SAGADECK_IMAGE_MODEL` / `SAGADECK_SEARCH_MODEL` | os modelos de cada papel |
142
+ | `SAGADECK_LLM_TIMEOUT` | segundos por chamada (padrão `180`) |
143
+ | `SAGADECK_IA` | outro arquivo de configuração (padrão `~/.sagadeck/ia.json`) |
145
144
 
146
145
  **Imagens geradas: você pede no texto**, no chat, no briefing do "Deck com IA" ou no `--prompt`; não há caixa para marcar.
147
146
  "Com fotos em todos os slides" ilustra todos; "você decide onde ilustrar" deixa a IA escolher só os slides em que uma imagem
@@ -156,15 +155,13 @@ Todo YAML vindo do LLM é validado (renderiza cada slide); se falhar, o erro vol
156
155
  ```bash
157
156
  npm install
158
157
  node bin/sagadeck.js build templates/exemplo.yaml
159
- npm run bundle # empacota o motor em python/sagadeck/engine
160
- npm run build:py # gera dist/*.whl e dist/*.tar.gz
161
158
  npm test # suíte inteira: motor + runtime + Studio (clicando num Chrome headless)
162
159
  npm run test:unit # só o motor (rápido, sem navegador)
163
- SAGADECK_LIVE=1 npm test # inclui os testes que chamam o LLM de verdade (modelrelay)
160
+ SAGADECK_LIVE=1 npm test # inclui os testes que chamam o LLM de verdade (o configurado em Configurar IA)
164
161
  ```
165
162
  **Toda funcionalidade nova entra com teste** em `test/` — é o que garante que um refactor não apague o que já funciona. Veja [CLAUDE.md](CLAUDE.md).
166
163
 
167
- Publicação: crie uma release `vX.Y.Z` no GitHub (o workflow `.github/workflows/publish.yml` publica no PyPI via *trusted publishing*). A versão fica em `package.json` e `python/sagadeck/__init__.py` (o bundle confere se são iguais).
164
+ Publicação: com a CI verde, crie uma release `vX.Y.Z` no GitHub; o workflow `.github/workflows/publish.yml` publica no npm (*trusted publishing*). A versão fica em `package.json`.
168
165
 
169
166
  ## Arquivo `.sagadeck` (a apresentação inteira)
170
167
 
@@ -193,7 +190,7 @@ formato avisa para atualizar o sagadeck. Se um filtro de e-mail barrar a extens
193
190
  ## Biblioteca
194
191
 
195
192
  `sagadeck studio` sem arquivo abre a biblioteca: suas apresentações organizadas em tópicos, com capa, busca,
196
- lixeira (um dia) e o menu **⋯** de cada cartão (apresentar, renomear, duplicar, mover, baixar tudo/.sagadeck/PPTX/PDF).
193
+ lixeira (30 dias; o prazo se muda em Preferências) e o menu **⋯** de cada cartão (apresentar, renomear, duplicar, mover, baixar tudo/.sagadeck/PPTX/PDF).
197
194
 
198
195
  **Nova apresentação** tem três caminhos: *Descrever com IA* (o assunto, quanto tempo você tem, que vira o número de
199
196
  slides, o estilo e, se quiser, material de apoio: PDF, Word, PowerPoint, Excel ou um link), *A partir de um arquivo
@@ -258,8 +255,11 @@ Para ver funcionando sem configurar nada:
258
255
  O painel **Formatar** edita o endereço, o corpo em JSON, o modo (síncrono, polling, streaming, tempo real)
259
256
  e o que guardar para os próximos slides.
260
257
  - **Inserir → Ambientes:** onde ficam os endereços (`{{base}}`), tokens e segredos do *seu* serviço
261
- (dev, hom, prod). É gravado em `~/.sagadeck/ambientes.yaml`, na sua máquina, nunca no deck. O selo no
262
- slide (DEV, HOM, ENSAIO…) troca o ambiente na hora.
258
+ (dev, hom, prod). Preenche-se por campos: endereço base, variáveis, segredos (guardados cifrados) e a
259
+ autenticação (nenhuma, token fixo ou token que expira), com **Testar**; quem prefere edita o YAML na aba
260
+ "Como texto". É gravado em `~/.sagadeck/ambientes.yaml`, na sua máquina, nunca no deck. Sem ambiente seu, o
261
+ **ENSAIO** (API de mentira) já funciona. O selo no slide (DEV, HOM, ENSAIO…) troca o ambiente na hora. No
262
+ servidor multiusuário o botão não aparece (lá os slides de API não executam).
263
263
 
264
264
  Executar só funciona no Studio local; no HTML exportado e no modo multiusuário, o slide mostra a última
265
265
  resposta gravada. Todos os campos estão na [referência](docs/REFERENCIA.md#slide-api-requisição-ao-vivo).
@@ -301,8 +301,6 @@ src/export/ PowerPoint, PDF, imagens, roteiro e o fiscal
301
301
  templates/exemplo.yaml deck de exemplo com todos os layouts
302
302
  docs/REFERENCIA.md referência completa do YAML
303
303
  SKILL.md instruções para IAs gerarem decks com o sagadeck
304
- python/sagadeck/ pacote Python (CLI + API) que carrega o motor empacotado
305
- scripts/bundle.mjs empacota o motor para o pip
306
304
  tools/ppt-render.ps1 renderiza um .pptx pelo PowerPoint (para conferir fidelidade)
307
305
  tools/test-live.mjs teste automático do modo apresentação e dos widgets
308
306
  ```
package/SKILL.md CHANGED
@@ -7,15 +7,31 @@ description: Gera apresentações bonitas (HTML animado + PowerPoint editável +
7
7
 
8
8
  O sagadeck gera apresentações profissionais a partir de um arquivo YAML, oferecendo tanto linha de comando (CLI), estúdio web visual no estilo PowerPoint, quanto protocolo MCP para integração com IDEs agênticos (Claude Code, Cursor, Windsurf, Cline, Roo Code).
9
9
 
10
+ ## Para agentes: "deixar pronto para usar"
11
+
12
+ Quando a pessoa pede para instalar o sagadeck e deixar pronto:
13
+
14
+ 1. `npm install -g sagadeck` (Node 18+; não precisa de Python).
15
+ 2. `sagadeck studio`: o navegador abre sozinho em **http://127.0.0.1:3517** (é também a primeira linha que o
16
+ comando escreve; `--sem-navegador` não abre). **O Studio é a interface do sagadeck**: biblioteca, editor e chat com
17
+ IA. O terminal fica aberto enquanto ela usa.
18
+ 3. **Nunca crie uma página, um servidor ou uma interface própria** para o sagadeck, nem "suba as APIs" à mão: tudo
19
+ que a pessoa precisa está no Studio.
20
+ 4. Para a IA, mostre onde configurar: **Configurar IA** no alto da biblioteca (ou **IA desligada** no editor):
21
+ **Usar a recomendação** (OpenRouter com modelos que funcionam), a chave, Testar e Salvar. A configuração fica em `~/.sagadeck/ia.json`; nunca grave a chave no código nem
22
+ na pasta das apresentações.
23
+ 5. As apresentações moram na biblioteca (`SAGADECK_HOME`, senão Documentos\sagadeck no Windows e `~/sagadeck` fora
24
+ dele). Não crie decks em outra pasta.
25
+
10
26
  ## Comandos Disponíveis
11
27
 
12
28
  | comando | o que faz |
13
29
  |---|---|
14
- | `sagadeck studio [deck.yaml] [--port=3000]` | **Abre o estúdio web estilo PowerPoint** com canvas visual 16:9, edição WYSIWYG direta, detector de sobreposição em tempo real e **chat lateral com IA**. |
30
+ | `sagadeck studio [deck.yaml] [--port=3517]` | **Abre o estúdio web estilo PowerPoint** com canvas visual 16:9, edição WYSIWYG direta, detector de sobreposição em tempo real e **chat lateral com IA**. |
15
31
  | `sagadeck autofix <deck.yaml>` | **Auto-cura do layout**: detecta e repara automaticamente sobreposição entre elementos, elementos fora das margens seguras (120px) e excesso de texto. |
16
32
  | `sagadeck search "<termo>"` | **Pesquisa web sem bloqueio (DuckDuckGo)**: busca dados reais, números e referências confiáveis para enriquecer os slides. |
17
33
  | `sagadeck mcp` | **Servidor MCP**: expõe ferramentas padronizadas (JSON-RPC) para IDEs agênticos criarem, lerem, pesquisarem, fiscalizarem e editarem decks. |
18
- | `sagadeck new deck.yaml [--theme=prata]` | cria um deck de exemplo estruturado |
34
+ | `sagadeck new "<nome>" [--topic=T] [--theme=prata]` | cria um deck de exemplo na biblioteca |
19
35
  | `sagadeck build deck.yaml` | compila `deck.html` standalone (abre no navegador; tecla P = modo apresentador) |
20
36
  | `sagadeck check deck.yaml` | fiscal: texto estourado, sobreposição, contraste, fonte pequena, excesso de texto |
21
37
  | `sagadeck pptx deck.yaml` | gera `deck.pptx` **totalmente editável** no PowerPoint com formas e caixas nativas |
@@ -99,14 +115,6 @@ O agente no terminal deve seguir o fluxo:
99
115
  3. Rodar `sagadeck check deck.yaml` para confirmar ausência de avisos.
100
116
  4. Rodar `sagadeck all deck.yaml` para gerar todas as entregas.
101
117
 
102
- ### 3. Via API Python
103
- ```python
104
- import sagadeck
105
- sagadeck.autofix("palestra.yaml")
106
- sagadeck.build("palestra.yaml")
107
- sagadeck.studio("palestra.yaml", port=3000)
108
- ```
109
-
110
118
  ---
111
119
 
112
120
  ## Decisão de Arquitetura: Mesmo Repositório vs. Repositório Separado
@@ -114,7 +122,7 @@ sagadeck.studio("palestra.yaml", port=3000)
114
122
  ### Por que o Estúdio deve ficar no **mesmo repositório** (`sagadeck`):
115
123
  1. **Fonte Única da Verdade**: Os layouts (incluindo `stats` para KPIs e `steps` para processos), os 11 temas visuais (incluindo `prata` estilo Keynote Apple, `rabisco` artesanal pintado à mão, `oceano` azul elétrico vivo, `pop` alegre/chiclete e `aurora` neon), as regras de tipografia e as rotinas de verificação geométrica residem no mesmo código (`src/layouts.js`, `src/themes.js`, `src/fiscal/autofix.js`). Qualquer alteração ou novo layout adicionado ao compilador fica disponível **instantaneamente** no estúdio visual.
116
124
  2. **Zero Descompasso de Versões**: Em repositórios separados, quando o formato YAML ou os temas do SagaDeck evoluem, a interface visual quebra ou fica desatualizada até que alguém publique e atualize dependências externas.
117
- 3. **Experiência de Uso (DX) Imediata**: O desenvolvedor ou agente clona o repositório ou roda `pip install sagadeck` / `npm install` e tem **tudo num só lugar**: linha de comando (`sagadeck build`), estúdio visual (`sagadeck studio`), servidor de agentes (`sagadeck mcp`) e fiscal (`sagadeck check`).
125
+ 3. **Experiência de Uso (DX) Imediata**: O desenvolvedor ou agente clona o repositório ou roda `npm install -g sagadeck` e tem **tudo num só lugar**: linha de comando (`sagadeck build`), estúdio visual (`sagadeck studio`), servidor de agentes (`sagadeck mcp`) e fiscal (`sagadeck check`).
118
126
  4. **Leve e Modular**: A interface web foi construída com tecnologias nativas leves (Vanilla JS, CSS moderno e servidor HTTP embutido do Node.js sem frameworks pesados). Isso mantém o pacote pequeno (~1.1 MB empacotado) sem impactar o desempenho do compilador.
119
127
 
120
128
  ---
package/bin/sagadeck.js CHANGED
@@ -40,6 +40,12 @@ for (let i = 0; i < rawRest.length; i++) {
40
40
 
41
41
  const HELP = `sagadeck — YAML -> apresentação (HTML animado + PowerPoint editável + PDF + roteiro)
42
42
 
43
+ Para começar: sagadeck studio
44
+ abre o Studio no navegador (biblioteca, editor e chat com IA; http://127.0.0.1:3517).
45
+ Deixe o terminal aberto enquanto usa. A IA se configura lá, em Configurar IA.
46
+
47
+ Todos os comandos:
48
+
43
49
  sagadeck new <nome> --prompt "briefing" [--slides=10] [--duration=20] [--theme=x] deck inteiro escrito pelo LLM
44
50
  (pesquisa na web quando o pedido precisa de informação recente ou de um artigo; --sem-pesquisa desliga)
45
51
  (--duration dispensa --slides: ~1 slide a cada 1,5 min)
@@ -56,7 +62,7 @@ const HELP = `sagadeck — YAML -> apresentação (HTML animado + PowerPoint edi
56
62
  sagadeck roteiro <deck.yaml> gera <deck> - roteiro.pdf (miniaturas + notas + tempos)
57
63
  sagadeck estudo <deck.yaml> [--html] gera <deck> - material de estudo.pdf (cada slide + texto de consulta)
58
64
  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
65
+ sagadeck studio [deck.yaml|x.sagadeck] [--port=3517] [--library=PASTA] [--sem-navegador] sem arquivo: abre a biblioteca (no navegador); já aberto: usa o aberto
60
66
  (PASTA padrão: SAGADECK_HOME, senão Documentossagadeck no Windows, ~/sagadeck fora dele); com arquivo: abre o editor dele
61
67
  --host=0.0.0.0 abre para a rede (padrão: só esta máquina)
62
68
  --multiuser: uma biblioteca por usuário, atrás de um proxy que envia X-Sagadeck-User
@@ -387,6 +393,7 @@ async function main() {
387
393
  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); }
388
394
  }
389
395
  console.log(`✓ O SagaDeck Studio ${found.version} já está aberto (processo ${found.pid}): ${url}`);
396
+ { const B = await import("../src/studio/open-browser.js"); if (B.shouldOpenBrowser({ flags, multiuser })) B.openBrowser(url); }
390
397
  break;
391
398
  }
392
399
  if (decision.action === "conflict") {
@@ -394,22 +401,18 @@ async function main() {
394
401
  process.exit(1);
395
402
  }
396
403
  const agentUsers = typeof flags.agentes === "string" ? flags.agentes.split(",").map((x) => x.trim()).filter(Boolean) : undefined;
397
- // IA: sobe o `modelrelay serve` junto, se ele estiver instalado e ninguém escutar na porta dele (como o sagadeck do pip)
398
- const { startRelay } = await import("../src/ai/relay.js");
399
- const relay = multiuser ? null : await startRelay();
400
- if (relay) {
401
- console.log(`✓ IA: modelrelay em ${relay.url} (configuração: http://127.0.0.1:8765/)`);
402
- for (const sig of ["exit", "SIGINT", "SIGTERM"]) process.on(sig, () => { relay.stop(); if (sig !== "exit") process.exit(0); });
403
- }
404
404
  const server = createStudioServer(deckFile, { port, host, library, multiuser, userHeader: flags["user-header"], agentUsers });
405
405
  server.on("error", (e) => {
406
406
  if (e.code !== "EADDRINUSE") throw e;
407
407
  console.error(`A porta ${port} está ocupada por outro programa. Use --port=OUTRA.`);
408
408
  process.exit(1);
409
409
  });
410
- server.listen(port, host, () => {
411
- console.log(`✓ SagaDeck Studio ${VERSION} em http://${shownHost}:${port}${deckFile ? "" : " (biblioteca)"} (processo ${process.pid})`);
412
- console.log(multiuser ? ` multiusuário: bibliotecas em ${path.join(library, "usuarios")}` : ` biblioteca: ${library}`);
410
+ server.listen(port, host, async () => {
411
+ const { studioBanner } = await import("../src/studio/banner.js");
412
+ for (const line of studioBanner({ version: VERSION, url: `http://${shownHost}:${port}/`, library, ai: (await import("../src/ai/llm.js")).llmConfig(), multiuser, usersDir: path.join(library, "usuarios"), isLibrary: !deckFile })) console.log(line);
413
+ // depois do endereço (a primeira linha é sempre ele): abre o navegador na página
414
+ const B = await import("../src/studio/open-browser.js");
415
+ if (B.shouldOpenBrowser({ flags, multiuser }) && B.openBrowser(`http://${shownHost}:${port}/`)) console.log(" (abrindo no navegador; --sem-navegador desliga)");
413
416
  });
414
417
  break;
415
418
  }
package/docs/ANDAMENTO.md CHANGED
@@ -12,7 +12,7 @@ começar. Trabalho em curso vai para o GitHub (commit + push) a cada etapa, nunc
12
12
 
13
13
  **Para quem pega isto numa sessão nova, sem a conversa de origem.** O projeto é o **sagadeck**:
14
14
  - Repositório GitHub `naruminho/sagadeck`, clonado em `C:\Users\narum\src\sagadeck`, branch `main`.
15
- - Publicado no npm como `sagadeck` (1.5.0, com as mudanças de 08 e 09/10).
15
+ - Publicado no npm como `sagadeck` (1.7.0: primeiro uso guiado, Ambientes por campos; o PyPI parou na 1.5.0).
16
16
  - Ferramenta Node (ESM) que transforma apresentações escritas em YAML em HTML/PPTX.
17
17
  - Tem um editor web, o Studio (`sagadeck studio`, porta 3517).
18
18
  - Tem uma IA (via modelrelay em `http://127.0.0.1:8765/v1`) que cria e edita decks, inclusive a partir de PDFs de
@@ -35,13 +35,43 @@ datadas abaixo). O resto fica para depois e só entra se for pedido. Antes de co
35
35
 
36
36
  **OneDrive (a biblioteca agora mora em `C:\Users\narum\OneDrive\Documents\sagadeck` e sincroniza)**
37
37
 
38
- 2. **Cache fora da nuvem.** A lixeira passou a durar um dia (seção "Lixeira de um dia" abaixo). Sobram `.cache`
38
+ 2. **Cache fora da nuvem.** O prazo da lixeira virou preferência (seção "Lixeira com prazo nas Preferências"). Sobram `.cache`
39
39
  (5 MB) e `.historico` indo para o OneDrive; só pesa se a pasta Documentos estiver no OneDrive (comum no Windows 11
40
40
  e em empresa, mas não universal). O histórico de versões talvez deva continuar junto do deck (portável).
41
41
  3. **Arquivos só na nuvem (Files On-Demand).** Com espaço liberado, o OneDrive deixa só o marcador do arquivo, e
42
42
  ler o conteúdo força o download. Conferir se listar a biblioteca (`openLibrary`, `/api/library`) lê o YAML de
43
43
  cada deck; se lê, listar só pelo nome/metadado e abrir o conteúdo sob demanda.
44
44
 
45
+ ## CI verde de novo (e a regra: merge só com a CI verde) — 09/10/2026
46
+
47
+ A CI do GitHub estava vermelha havia dias (desde antes da 1.4.0) e 13 PRs foram mergeados e a 1.5.0 publicada assim.
48
+ Regra nova no CLAUDE.md: merge e release só com a CI verde nas duas máquinas. As falhas, uma por uma:
49
+
50
+ - **Python 3.12** (Ubuntu): escape inválido numa docstring; corrigido antes da 1.5.0 (seção abaixo).
51
+ - **Cópia de mídia entre apresentações** (Windows): defeito real. `copySlideAssets` comparava o caminho real do
52
+ arquivo com o caminho da pasta sem resolver; deck aberto por junção (o `C:\Users\narum\sagadeck` antigo é uma) ou
53
+ por nome curto do Windows (`RUNNER~1`, a pasta temporária da CI) recusava toda mídia como "fora da apresentação".
54
+ Compara os dois resolvidos. Teste com uma junção de verdade (`test/studio-chat-jobs.test.js`).
55
+ - **Animações paradas** (Windows): o Windows Server vem com "mostrar animações" desligado, o Chrome informa
56
+ `prefers-reduced-motion: reduce` e as cenas ficam paradas, de propósito. Os testes herdavam a preferência da máquina:
57
+ `newPage` (`test/helpers.js`) fixa "sem preferência", e quem testa o movimento reduzido pede `reducedMotion: "reduce"`.
58
+ - **Defeito real achado no caminho**: com o sistema pedindo movimento reduzido, a cena às vezes animava sem parar. O
59
+ iframe dizia "ready" antes de o script da página escutar; a página nunca respondia (nem "slide atual", nem "movimento
60
+ reduzido"). A página agora fala com cada cena assim que se liga a ela (`src/runtime/motion.js`) e a cena acompanha a
61
+ mudança da preferência (`src/motion.js`). Teste novo: sistema com movimento reduzido deixa o holograma parado
62
+ (falhava 3 em 4 sem a correção; 5 em 5 com).
63
+ - **"Parar a transformação"** (os dois): o teste esperava um texto do aviso de progresso para recarregar a página "no
64
+ meio". Agora espera o modelo (falso) ter recebido o pedido de escrever os slides.
65
+ - **Animações paradas no Windows da CI, a causa de verdade** (achada com o diagnóstico que os testes passaram a
66
+ imprimir, inclusive o estado interno da cena, `window.__sagaMotion`): na CI a cena ficava com "parada" ligado para
67
+ sempre, embora a preferência dissesse "sem redução": ouviu "movimento reduzido" no carregamento (do sistema, antes de
68
+ a preferência do teste valer) e o código só sabia ligar esse estado, nunca desligar. Defeito real: religar as
69
+ animações com a apresentação aberta não descongelava nada. Agora "parada" se recalcula (deck `motion: none`, página
70
+ pedindo, preferência do sistema) sempre que um deles muda, nos dois sentidos; a página sempre diz sim ou não e repete
71
+ quando a preferência muda. Teste: movimento reduzido que deixa de valer faz a cena voltar a animar (falhava antes).
72
+ - **Baixar PowerPoint sem as notas** (Windows): o clique esperava a "navegação" do download, e o PPTX numa máquina
73
+ lenta passa do limite do clique; o teste só precisa do pedido (`noWaitAfter`).
74
+
45
75
  ## Pacote Python: escape inválido numa docstring — 09/10/2026
46
76
 
47
77
  Antes da release 1.5.0. `python/sagadeck/api.py` tinha "Documentos\sagadeck" numa docstring: `\s` é escape inválido,
@@ -49,6 +79,18 @@ Antes da release 1.5.0. `python/sagadeck/api.py` tinha "Documentos\sagadeck" num
49
79
  Virou "Documentos/sagadeck". Teste novo em `test/python-relay.test.js`: compila todo o pacote com `-W error` (qualquer
50
80
  Python; falhava antes).
51
81
 
82
+ ## Lixeira com prazo nas Preferências (padrão 30 dias) — 09/10/2026
83
+
84
+ A "Lixeira de um dia" (abaixo) foi pedida pelos 407 MB de decks de teste numa máquina, mas saiu na 1.5.0 para todo
85
+ mundo: quem atualizasse perdia de vez, no primeiro uso, o que estava na lixeira havia mais de um dia. Agora o prazo é
86
+ uma preferência da máquina: **Preferências › Biblioteca › Quanto tempo a lixeira guarda** (1, 7, 30 ou 90 dias;
87
+ `biblioteca.lixeiraDias`, padrão 30). `openLibrary` lê o prazo a cada limpeza (`trashDays` só nos testes) e
88
+ `/api/library` devolve `trashDays`, que a tela da lixeira e o aviso ao excluir mostram.
89
+
90
+ - Testes: `test/library.test.js` (com 1 dia, anteontem some do disco e a de uma hora fica; sem escolha, 30 dias) e
91
+ `test/studio.test.js` (escolher "1 dia" nas Preferências grava no arquivo, a biblioteca passa a usar e a dizer "Fica
92
+ aqui por um dia"; falha sem o recurso).
93
+
52
94
  ## Crítica acionável: cada ponto vira um cartão com Aplicar e Ignorar — 09/10/2026
53
95
 
54
96
  Item 7 das próximas frentes. A leitura crítica (botão do chat) chegava como um texto único, com opções gerais ("leve
@@ -66,6 +108,79 @@ os achados para os slides").
66
108
  `missed`, elas entram no inventário com a caixa exata do texto e passam pela mesma releitura do recorte
67
109
  (`transcribeEquation`) que escreve o LaTeX das outras. Saiu da lista.
68
110
 
111
+ ## Primeiro uso: navegador abre sozinho, "Para começar" e a recomendação de IA — 09/10/2026
112
+
113
+ Pergunta de Narumi: quem instala pelo npm sabe pôr no ar, sabe a URL, precisa editar arquivo para a IA? Antes: um
114
+ comando (`sagadeck studio`), mas o navegador não abria, o `sagadeck` sozinho mostrava o `studio` como 19º item de uma
115
+ lista longa, e os campos de modelo do Configurar IA vinham vazios (ninguém sabe o nome exato de um modelo).
116
+
117
+ - `sagadeck studio` **abre o navegador** na página (`src/studio/open-browser.js`), inclusive quando já está aberto;
118
+ só para quem está no terminal: nunca em serviço/teste (sem TTY), CI, multiusuário, Linux sem tela ou
119
+ `--sem-navegador` / `SAGADECK_NO_BROWSER=1`. A primeira linha continua sendo o endereço.
120
+ - `sagadeck` sozinho começa com **"Para começar: sagadeck studio"** (o que faz, o endereço, deixar o terminal aberto).
121
+ - **Configurar IA** com a recomendação (`RECOMMENDED` em `src/ai/ia-config.js`: OpenRouter, DeepSeek para texto e
122
+ visão, Gemini para imagem, `:online` para busca) e **Usar a recomendação**, que preenche provedor e modelos. Ela é
123
+ também o teste da instalação: se nem com ela a IA responde, o problema é rede ou chave. Os campos trazem exemplos e
124
+ a tela diz que imagem e busca dependem do provedor. Sem listar os modelos do provedor (decisão: endereço próprio ou
125
+ de empresa pode não ter a lista, ou ser bloqueado).
126
+ - **Faixa "Configure a IA"** na biblioteca enquanto não está configurada (só onde se pode configurar; some ao salvar).
127
+ - Testes: `test/instance.test.js` (quando abre o navegador e o comando de cada sistema; `sagadeck` sozinho),
128
+ `test/library-ui.test.js` (faixa, recomendação preenche, salvar some a faixa).
129
+ - **Testes isolados de verdade**: a suíte completa falhou só nesta máquina num teste da lixeira, que leu as
130
+ Preferências reais (lixeira em 1 dia). 41 arquivos de teste não carregavam o isolamento (só os que importavam o
131
+ `helpers.js`). Agora `test/isolate.js` (biblioteca, ambientes, Preferências, IA e registro de comandos em pasta
132
+ temporária) é carregado antes de tudo pelo `npm test` (`--import`) e importado por todo arquivo de teste;
133
+ `guards.test.js` reprova arquivo de teste que não carregue.
134
+ - A avaliação ao vivo (`test/bench-live.test.js`) não fechava o navegador da revisão dos slides: o processo ficava
135
+ pendurado (dias). Fecha no fim; `guards.test.js` exige isso de todo teste que gera pelo caminho do Studio.
136
+ ## Ambientes por campos e sem o botão no servidor — 09/10/2026
137
+
138
+ Pergunta de Narumi: quem instala do zero sabe preencher o `ambientes.yaml` (slides de API)? O lugar já era automático
139
+ (o Studio grava em `~/.sagadeck/ambientes.yaml`), mas preencher era editar YAML numa caixa de texto. E no servidor
140
+ (multiusuário) o botão abria a janela com "O serviço recusou o acesso…", parecendo defeito.
141
+
142
+ - **Por campos** (`src/studio/public/api-envs-form.js`, aba padrão): um cartão por ambiente com endereço base,
143
+ variáveis, segredos (digitado vai cifrado; em branco mantém; ou o nome de uma variável de ambiente), autenticação
144
+ (nenhuma; token fixo, que vira `Authorization: Bearer {{secret.token}}`; token que expira, client credentials) e
145
+ **Testar** (salva, chama o endereço base e pede o token). Sem ambiente próprio, a tela explica que o ENSAIO já
146
+ funciona. A aba **Como texto** é o editor de YAML de antes.
147
+ - Motor (`src/api-client.js`): `formState`/`saveForm` (o mesmo arquivo, preservando comentários e o que o
148
+ formulário não conhece: `ca`, outros cabeçalhos), `check` (o Testar), cabeçalhos do ambiente com `{{secret.…}}` e
149
+ client secret do token cifrado. Rotas `/api/http/ambientes/form` e `/api/http/ambientes/testar`.
150
+ - **No servidor o botão some** (o slide de API não executa lá; `/api/http/state` diz `live: false`).
151
+ - Testes: `test/api-client.test.js` (salvar e ler de volta sem o valor dos segredos, comentários e `ca` preservados,
152
+ token fixo no pedido, segredo mantido em branco, Testar), `test/studio.test.js` (instalação do zero pela tela, até o
153
+ Testar confirmar com o token chegando na API), `test/studio-scripts.test.js` (botão some no multiusuário; falhava
154
+ sem o módulo).
155
+
156
+ ## IA embutida, só npm e o Studio como a página do sagadeck — 09/10/2026
157
+
158
+ Pedido de Narumi, depois de um agente (Copilot) instalar o sagadeck, abrir a página do modelrelay e, sem achar a
159
+ interface, criar uma do zero: deixar claro que a página é o Studio; tirar o pip (o pacote Python só chamava o Node);
160
+ e tirar o modelrelay (Python, outro repositório, versão para sincronizar), mantendo uma camada separada onde a pessoa
161
+ põe a chave e o modelo sem mexer no código.
162
+
163
+ - **IA embutida** (`src/ai/ia-config.js`): provedor, chave e modelos desta máquina em `~/.sagadeck/ia.json`
164
+ (`SAGADECK_IA`), fora do código e da biblioteca. Chave no arquivo ou numa variável (`keyEnv`); papéis `text`,
165
+ `vision`, `image`, `search` viram os modelos configurados (`resolveModel`); `headers` fixos; e `adaptador`: um `.mjs`
166
+ da pessoa cujo default `(url, init) => Response` faz o envio (provedor fora do padrão da OpenAI, sem código no
167
+ sagadeck). Variáveis `SAGADECK_LLM_*` valem por cima (servidor, testes). Sem nada configurado, a IA fica desligada.
168
+ - **Migração**: na primeira vez, o `~/.modelrelay/config.toml` vira o `ia.json` (provedor padrão, chave ou
169
+ `api_key_env`, `[models]` com `[apps.sagadeck.models]` por cima); não sobrescreve nada.
170
+ - **Configurar IA** (`src/studio/public/ai-settings.js`, `src/studio/ia-routes.js`): mesma tela na biblioteca (botão
171
+ Configurar IA) e no editor (IA desligada/ligada): provedor, endereço, chave (nunca volta inteira: `••••1234`),
172
+ modelos, **Testar** (pergunta curta com o que está na tela) e **Salvar**. No multiusuário não aparece e não grava.
173
+ - **Saiu**: `src/ai/relay.js` (subir o `modelrelay serve`), o cabeçalho `X-Modelrelay-App`, a rota `/api/ai/setup`,
174
+ a pasta `python/`, o `pyproject.toml`, `scripts/bundle.mjs` e os testes deles; a publicação vai só para o npm.
175
+ - **O Studio é a página** (`src/studio/banner.js`): a primeira linha do `sagadeck studio` é "Abra no navegador:
176
+ http://127.0.0.1:3517"; a IA aparece depois (provedor e modelo, ou onde configurar). README com "Comece aqui";
177
+ SKILL.md com "Para agentes: deixar pronto para usar" (Studio, nunca criar interface própria, Configurar IA) e a
178
+ porta certa (dizia 3000).
179
+ - Testes: `test/ia-config.test.js` (migração com o config.toml real, chave mascarada, llmConfig e papéis),
180
+ `test/ai.test.js` (chamada direta com a chave e o modelo configurados; adaptador faz o envio),
181
+ `test/library-ui.test.js` (Configurar IA de ponta a ponta: testa, salva, a chave não volta, multiusuário bloqueia),
182
+ `test/instance.test.js` (primeira linha do `sagadeck studio`; SKILL.md com porta, npm e a regra).
183
+
69
184
  ## Studio avisa quando o código mudou por baixo dele — 09/10/2026
70
185
 
71
186
  Continuação do incidente "nenhum slide abre" de 08/10: um Studio aberto antes de uma atualização segue com o servidor
@@ -282,7 +282,9 @@ No Studio, a aba **Variáveis** mostra o ambiente atual em uma tabela **Nome / V
282
282
  normais ali; nomes e valores são salvos no arquivo de ambientes, nunca no deck. Arraste um nome da tabela
283
283
  para um campo do slide para inserir `{{nome}}`. Segredos são listados pelo nome, mas seu valor nunca aparece.
284
284
  Os campos `save` do deck também aparecem com sua origem e disponibilidade.
285
- O editor de ambientes valida o YAML antes de habilitar **Salvar** e preserva a versão anterior em `ambientes.yaml.bak`.
285
+ O editor de ambientes abre **por campos** (endereço base, variáveis, segredos, autenticação: nenhuma, token fixo
286
+ `Authorization: Bearer {{secret.token}}` ou token que expira; **Testar** chama o endereço base e pede o token). A aba
287
+ **Como texto** valida o YAML antes de habilitar **Salvar**. Os dois preservam a versão anterior em `ambientes.yaml.bak`.
286
288
  O botão **Variáveis** (na barra do slide) é o inspect/watch da apresentação, tudo num lugar só:
287
289
  - **Ambiente**: as variáveis (criar, editar, apagar) e as **protegidas** (marque "protegida" ao criar): na tela só o nome
288
290
  e ••••; o olhinho mostra o valor por 15 segundos. Nos slides e no código gerado aparece o nome (`{{secret.chave}}`),
@@ -334,8 +336,9 @@ texto) para o modo sem Studio.
334
336
 
335
337
  Endereços, credenciais e segredos ficam **na máquina**, nunca no deck (o deck pode ir para o GitHub).
336
338
  Outro lugar: variável `SAGADECK_AMBIENTES`. O selo no slide (DEV, HOM…) troca o ambiente. No Studio,
337
- **Inserir → Ambientes** mostra o arquivo (ou um modelo comentado, se ele ainda não existe), confere o YAML
338
- antes de gravar e troca o ambiente em uso.
339
+ **Inserir → Ambientes** preenche o arquivo por campos (ou mostra o YAML na aba Como texto, com um modelo comentado
340
+ se ele ainda não existe), confere antes de gravar e troca o ambiente em uso. Os cabeçalhos do ambiente (`headers`)
341
+ aceitam `{{secret.nome}}`; o client secret do token pode ficar cifrado (`client_secret: "dpapi:…"` no Windows).
339
342
 
340
343
  ```yaml
341
344
  current: hom
@@ -926,14 +929,14 @@ específica; a pessoa pode completar a descrição e enviar. Também é possíve
926
929
  a IA gerar** e clicar em **Gerar imagem agora**, inclusive para substituir uma foto existente.
927
930
  O chat aceita pedidos como “gere uma foto com base no conteúdo deste slide” ou uma descrição visual própria.
928
931
  Ao trocar uma imagem, preserve `x`, `y`, `w`, `h`, `fit`, `radius` e os demais elementos; use `image_prompt`
929
- no elemento correto. A geração exige um provedor de imagens configurado no modelrelay. As imagens geradas
932
+ no elemento correto. A geração exige um modelo de imagem em Configurar IA. As imagens geradas
930
933
  ficam na pasta `imagens/` da apresentação.
931
934
 
932
935
  ## Ajustes diretos no Studio
933
936
 
934
937
  Em **Inserir → Neste slide**: Texto, Formas (menu com 12 desenhos: retângulo, arredondado, elipse, pílula, linha, triângulo, losango, hexágono, estrela, seta, chevron, balão), Imagem e Ícone adicionam elementos sem trocar o layout. Um clique em qualquer ponto de um objeto (texto, forma, imagem) o seleciona; clicar de novo no texto selecionado (ou duplo clique) escreve. Arrastar numa área vazia desenha um retângulo que seleciona vários; Shift+clique soma. Selecionados, dá para arrastar juntos, mover com as setas, redimensionar pela alça (um só) e usar a barra que flutua acima da seleção: tamanho e cor do texto, preenchimento da forma (`visualEdits.fill`), trazer para frente, enviar para trás (fica atrás dos outros objetos, nunca do fundo), excluir, alinhar (esquerda, centro, direita, em cima, meio, embaixo: vários entre si; um só, no slide), distribuir (3 ou mais, mesmo espaço), copiar e colar estilo (Ctrl+Alt+C / Ctrl+Alt+V), travar (o clique atravessa) e, numa imagem, trocar a imagem (`visualEdits.image`, com `fit: cover|contain` e `focus`: center, top, bottom, left, right, top left…). Ao arrastar, guias grudam nas bordas e no centro do slide e dos outros objetos (Alt solta). Na aba Propriedades, **Objetos do slide** lista tudo o que está no slide: seleciona até o travado, oculta/mostra e trava/destrava (`locked`; a decoração que veio do PowerPoint, `deco: true`, já vem travada). Tab/Shift+Tab passam de um objeto para outro; Escape solta. A aba **Propriedades** do painel lateral é o inspetor (como o Object Inspector): mostra só as categorias que fazem sentido para o que está selecionado (Posição e tamanho; Texto; Preenchimento e contorno; Aparência; Animação; Avançado) e, sempre, as do Slide e da Apresentação. Os ajustes de objeto ficam em `visualEdits` (`dx, dy, w, h, rotate, z, size, color, weight, italic, align, lineHeight, letterSpacing, uppercase, fill, stroke, strokeWidth, radius, opacity, shadow: suave|forte, step, anim, hidden, locked, image, fit, focus`); não os invente ao criar um deck. Delete/Backspace excluem o objeto selecionado (fica em `visualEdits` como `hidden`, recuperável); Ctrl+Z / Ctrl+Y desfazem e refazem qualquer mudança da apresentação (a da tela, a do formulário, a do assistente), com os botões ao lado do título. Duplo clique permite voltar a escrever. Os ajustes são persistidos em `visualEdits`, com chaves geradas pelo renderizador; não invente essas chaves ao criar um deck. Prefira alterar os campos semânticos. A exclusão visual usa `hidden` para preservar conteúdo recuperável. Mudanças estruturais grandes podem invalidar ajustes: remova `visualEdits` ao reconstruir um slide.
935
938
 
936
- `Arrumar layout` (aba Revisar) é local e baseado em regras: corrige sobreposição, margem, título comprido e texto que não coube, diz o que fez e se desfaz. `Revisar com IA` envia a imagem renderizada do slide ao modelo configurado e solicita melhorias; usa tokens. O status de IA abre a configuração do modelrelay. Um modelo sem visão é identificado na resposta; conectividade com `/models` não comprova que uma geração será aceita pelo provedor.
939
+ `Arrumar layout` (aba Revisar) é local e baseado em regras: corrige sobreposição, margem, título comprido e texto que não coube, diz o que fez e se desfaz. `Revisar com IA` envia a imagem renderizada do slide ao modelo configurado e solicita melhorias; usa tokens. O status de IA abre Configurar IA (provedor, chave e modelos desta máquina). Um modelo sem visão é identificado na resposta; conectividade com `/models` não comprova que uma geração será aceita pelo provedor.
937
940
  O fiscal local também sinaliza texto principal abaixo de 16 px como alerta de leitura; esse aviso é informativo e não reduz a fonte automaticamente.
938
941
 
939
942
  ## Densidade técnica e payloads extensos
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sagadeck",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "Gerador de apresentações: YAML -> HTML animado + PowerPoint editável, com temas e figuras geradas na hora.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -37,12 +37,11 @@
37
37
  },
38
38
  "scripts": {
39
39
  "dev": "node bin/sagadeck.js studio",
40
- "bundle": "node scripts/bundle.mjs",
41
- "build:py": "node scripts/bundle.mjs && python -m build",
40
+ "build:code-lab": "node scripts/bundle-code-lab.mjs",
42
41
  "test:live": "node tools/test-live.mjs",
43
- "test": "node --test --test-concurrency=1 \"test/*.test.js\"",
44
- "test:unit": "node --test test/engine.test.js",
45
- "test:ui": "node --test --test-concurrency=1 test/runtime.test.js test/studio.test.js"
42
+ "test": "node --import ./test/isolate.js --test --test-concurrency=1 \"test/*.test.js\"",
43
+ "test:unit": "node --import ./test/isolate.js --test test/engine.test.js",
44
+ "test:ui": "node --import ./test/isolate.js --test --test-concurrency=1 test/runtime.test.js test/studio.test.js"
46
45
  },
47
46
  "repository": {
48
47
  "type": "git",
package/src/ai/deck-ai.js CHANGED
@@ -1036,7 +1036,7 @@ Antes de responder, verifique (e siga as Regras de edição):
1036
1036
  commands.forEach((c, i) => actions.push(c.result?.denied ? `Comando ${i + 1} não autorizado: ${c.why || c.language}`
1037
1037
  : `Comando ${i + 1} (${c.language}): ${c.why || ""}${c.result?.timedOut ? " (tempo esgotado)" : c.result?.exitCode === 0 ? " (ok)" : ` (saída ${c.result?.exitCode ?? "erro"})`}`));
1038
1038
  if (visionRouted) actions.push(`O modelo de texto não enxerga imagens: para ver o slide, esta resposta veio do modelo de visão (${visionRouted}).`);
1039
- if (imagesDropped) actions.push("O modelo de texto atual não enxerga imagens: respondi sem ver o slide (e sem as imagens coladas). Para ele ver, use um modelo com visão em [apps.sagadeck.models] do modelrelay.");
1039
+ if (imagesDropped) actions.push("O modelo de texto atual não enxerga imagens: respondi sem ver o slide (e sem as imagens coladas). Para ele ver, escolha um modelo de visão em Configurar IA.");
1040
1040
  // conversa: nada muda (a resposta pode trazer opções clicáveis)
1041
1041
  if (talk) return { reply: prose, spec, actions, targetSlide, talk: true, options };
1042
1042
  // transformar a apresentação inteira: quem executa é o servidor (src/ai/transform.js), com o andamento no chat