sagadeck 1.9.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/SKILL.md +1 -1
- package/docs/ANDAMENTO.md +115 -0
- package/docs/REFERENCIA.md +48 -2
- package/package.json +4 -4
- package/src/ai/context.js +7 -2
- package/src/ai/deck-ai.js +3 -1
- package/src/api-client.js +42 -10
- package/src/code-highlight.js +19 -2
- package/src/code-language.js +13 -0
- package/src/runtime/api-core.js +122 -11
- package/src/runtime/api-ui.js +2 -1
- package/src/runtime/skins/aurora-dev.css +39 -0
- package/src/runtime/skins/terminal-pro.css +33 -0
- package/src/studio/ai-chat-route.js +26 -7
- package/src/studio/developer-starters.js +46 -0
- package/src/studio/library-routes.js +7 -0
- package/src/studio/public/about.js +68 -0
- package/src/studio/public/app.js +2 -1
- package/src/studio/public/chat.js +5 -1
- package/src/studio/public/developer-authoring.css +12 -0
- package/src/studio/public/developer-authoring.js +306 -0
- package/src/studio/public/generation-guide.js +42 -0
- package/src/studio/public/index.html +18 -0
- package/src/studio/public/library.css +36 -0
- package/src/studio/public/library.html +4 -1
- package/src/studio/public/library.js +115 -28
- package/src/studio/public/slide-form.js +9 -14
- package/src/studio/public/style.css +2 -0
- package/src/studio/public/ui-icons.js +1 -1
- package/src/studio/server.js +2 -2
- package/src/themes.js +48 -0
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Ele vem com 11 temas (incluindo prata limpo e respirável, artesanal desenhado
|
|
|
16
16
|
```bash
|
|
17
17
|
npm install -g sagadeck
|
|
18
18
|
```
|
|
19
|
-
Requer **Node.js
|
|
19
|
+
Requer **Node.js 24+** e **Chrome ou Edge** instalados (o PDF, o PNG e as conferências visuais usam o navegador).
|
|
20
20
|
É tudo Node: não precisa de Python. (O pacote do PyPI parou na 1.5.0.)
|
|
21
21
|
|
|
22
22
|
## Comece aqui
|
|
@@ -285,6 +285,12 @@ navegador, mas não para editar.
|
|
|
285
285
|
|
|
286
286
|
## Slides para devs: API ao vivo
|
|
287
287
|
|
|
288
|
+
Na biblioteca, **Programação** e **APIs com cURL** abrem a criação com exemplos de pedidos, código colado,
|
|
289
|
+
anexos técnicos e prévia do briefing. Também há duas bases curtas para começar **sem IA**.
|
|
290
|
+
No editor, **Início ou Inserir → Código / cURL** transforma conteúdo colado em código, explicação guiada
|
|
291
|
+
ou requisição de API. Selecione linhas para criar as etapas; Bash/cURL, JSON e SQL têm realce offline.
|
|
292
|
+
Os temas **Terminal Pro** e **Aurora Dev**, em Design, trazem código legível e capas com rede animada.
|
|
293
|
+
|
|
288
294
|
O layout `api` é um slide tipo Postman: mostra o pedido (URL, corpo, cabeçalhos) e o código equivalente
|
|
289
295
|
(curl, Python e Python comentado), e o botão **Executar** roda o pedido de verdade na frente da plateia:
|
|
290
296
|
síncrono, polling com linha do tempo, streaming, upload, token (JWT decodificado), embeddings, TTS/STT e
|
package/SKILL.md
CHANGED
|
@@ -11,7 +11,7 @@ O sagadeck gera apresentações profissionais a partir de um arquivo YAML, ofere
|
|
|
11
11
|
|
|
12
12
|
Quando a pessoa pede para instalar o sagadeck e deixar pronto:
|
|
13
13
|
|
|
14
|
-
1. `npm install -g sagadeck` (Node
|
|
14
|
+
1. `npm install -g sagadeck` (Node 24+; não precisa de Python).
|
|
15
15
|
2. `sagadeck studio`: o navegador abre sozinho em **http://127.0.0.1:3517** (é também a primeira linha que o
|
|
16
16
|
comando escreve; `--sem-navegador` não abre). **O Studio é a interface do sagadeck**: biblioteca, editor e chat com
|
|
17
17
|
IA. O terminal fica aberto enquanto ela usa.
|
package/docs/ANDAMENTO.md
CHANGED
|
@@ -8,6 +8,121 @@ começar. Trabalho em curso vai para o GitHub (commit + push) a cada etapa, nunc
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
+
## UX para programação e APIs com cURL — 12/10/2026 (branch `codex/ux-dev-slides`, versão 1.10.0)
|
|
12
|
+
|
|
13
|
+
Pedido: reduzir dificuldade de uso, facilitar geração técnica, acrescentar recursos e dois temas bonitos com
|
|
14
|
+
animação; testar, PR, merge, publicar no npm e atualizar sagadeck-dev. Trabalho somente na pasta oficial,
|
|
15
|
+
sem worktrees, conforme AGENTS.md. Main começou limpa em `46e0afd`, com a CI dos quatro ambientes verde.
|
|
16
|
+
|
|
17
|
+
Diagnóstico: recursos técnicos existiam, mas começar dependia de descobrir layouts, escrever briefing do zero e
|
|
18
|
+
preencher campos estruturados. cURL era exibido sem realce de sintaxe; demos extensas não eram uma base curta.
|
|
19
|
+
Mudanças:
|
|
20
|
+
- Biblioteca com entradas Programação/APIs com cURL; Nova com IA oferece exemplos de pedidos, material técnico
|
|
21
|
+
colado, público/objetivo/roteiro opcionais, resumo, prévia do pedido e Ctrl+Enter. Intenção explícita orienta o
|
|
22
|
+
modelo pelo prompt, sem regras de decisão por regex; texto e estilo manual continuam prioritários.
|
|
23
|
+
- Bases sem IA (`src/studio/developer-starters.js`), cinco slides com exemplo consistente, guia, saída e terminal
|
|
24
|
+
de cenas. Criação pela biblioteca; a de APIs usa ENSAIO e funciona localmente sem credenciais externas.
|
|
25
|
+
- Início/Inserir › Código / cURL: conteúdo colado vira código, código guiado ou requisição de API. Parser de
|
|
26
|
+
argumentos sem shell e sem executar; opções não suportadas geram mensagem antes de inserir. Credenciais
|
|
27
|
+
reconhecidas viram referências a Segredos antes de gravar. Código de exibição é preservado literalmente.
|
|
28
|
+
- Etapas guiadas por seleção de linhas, no modal e em Formatar; linguagem inferida/selecionável; realce offline
|
|
29
|
+
novo de Bash/cURL, JSON e SQL. Anexos de código/configuração preservam espaços, indentação e linhas em branco.
|
|
30
|
+
- `terminal-pro` (grafite/menta, IDE) e `aurora-dev` (azul noite/lilás/ciano, painéis e luz suave): fontes embutidas,
|
|
31
|
+
contraste de texto/sintaxe, rede de capa que para fora dela e em movimento reduzido/exportação. Design usa o
|
|
32
|
+
mesmo caminho existente de tema salvo; nenhum deck pessoal modificado.
|
|
33
|
+
|
|
34
|
+
Regressões cobrem parser, segredos, código literal, etapas e arquivos gravados, prompts ao LLM, anexos técnicos,
|
|
35
|
+
criação sem IA, escolha dos temas por teclado, contraste, animação, movimento reduzido e exportação. Testes
|
|
36
|
+
direcionados de motor/fontes/guards: 85 aprovados; temas e geração passaram também no navegador.
|
|
37
|
+
Geração com IA real também aprovada: briefing técnico aplica Aurora Dev e mantém o cURL original copiável (62 s).
|
|
38
|
+
Revisão independente achou e reproduziu perdas de inteiros grandes ao proteger JSON e de variáveis na codificação
|
|
39
|
+
do formulário: proteção agora edita só spans sensíveis do texto. Interpolação contextual opt-in
|
|
40
|
+
(`request.bodyEncoding`/`urlEncoding`) mantém JSON/form/query corretos no envio; ensaio HTTP real confere `&`, aspas,
|
|
41
|
+
barras, Unicode, números grandes e máscara de respostas/gravações. URL com Segredo agora é enviada já resolvida.
|
|
42
|
+
Callbacks de upload/geração são invalidados ao trocar/cancelar modal; Tab fica no diálogo e Escape devolve foco.
|
|
43
|
+
Essas regressões falharam antes das correções e passaram depois. Suíte completa iniciada com as frentes estáveis.
|
|
44
|
+
Entrega final aguardando suíte completa e CI; publicação de `sagadeck@1.10.0` por release com trusted publishing.
|
|
45
|
+
Oracle: atualizar apenas `/opt/sagadeck-dev`/`sagadeck-dev.service` (3518), preservando produção. Reiniciar também
|
|
46
|
+
a instância local verificada na 3517 e conferir versão/recursos depois da atualização.
|
|
47
|
+
Retomada em 13/10 após limite da sessão. Suíte inicial: 1.084 testes, 1.054 aprovados, 27 condicionais pulados e
|
|
48
|
+
3 falhas (uma de Python ausente no PATH, uma de rótulo e a contagem do teste pai). Python passou isolado com o
|
|
49
|
+
runtime disponível no PATH; a guarda de palavras grudadas agora reconhece o nome técnico cURL sem enfraquecer
|
|
50
|
+
os demais rótulos. A revisão final também cobre execução pelo navegador preservando corpo JSON literal e comandos
|
|
51
|
+
equivalentes copiáveis com segredos em JSON/form/query. Suíte final concluída: `npm test`, 1.093 testes,
|
|
52
|
+
1.066 aprovados, 27 condicionais pulados, zero falhas (24 min). Geradores de cURL/JavaScript/Python também
|
|
53
|
+
enviaram pedidos reais ao ensaio HTTP. Preview e Formatar mantêm JSON literal; respostas/gravações não permitem
|
|
54
|
+
recuperar segredos mesmo em logs com JSON aninhado. Instância local reiniciada na 3517, versão 1.10.0, com os novos
|
|
55
|
+
módulos/CSS respondendo 200. PR #197 publicado; os quatro checks originais Ubuntu/Windows, Node 22/24, passaram.
|
|
56
|
+
Por orientação posterior do usuário, o suporte passa a Node 24+: engines, instruções de instalação e matriz da
|
|
57
|
+
CI atualizados, sem mudança no código já aprovado. YAML e metadados conferidos; a nova CI exige Ubuntu e Windows
|
|
58
|
+
com Node 24 antes de merge/release. DEV da Oracle recebeu `135cfd2`, com versão 1.10.0 e ambos os temas disponíveis;
|
|
59
|
+
produção preservada. A publicação npm ainda aguarda merge e CI do commit final.
|
|
60
|
+
PR #197 integrado em `03f9ff4`; local na 3517 e DEV na 3518 reiniciados com esse commit e versão 1.10.0.
|
|
61
|
+
Os dois checks do PR passaram, mas a nova rodada da main encontrou uma corrida no teste de Formatar: o helper
|
|
62
|
+
confirmava a gravação no servidor antes de o formulário do novo slide terminar de renderizar, lendo o corpo
|
|
63
|
+
do slide anterior. Publicação npm suspensa até corrigir e validar essa sincronização. Correção na branch
|
|
64
|
+
`codex/ux-dev-format-sync`; nenhuma mudança no conteúdo das apresentações nem no código da aplicação.
|
|
65
|
+
Atrasar `api/render-slide` em 400 ms reproduziu exatamente a falha da Ubuntu. O helper agora aguarda a
|
|
66
|
+
identidade do formulário pelo campo Título, além da gravação; a mesma regressão passou após o ajuste.
|
|
67
|
+
Seis testes dirigidos aprovados, zero erros JavaScript e corpos JSON/form conferidos também no arquivo salvo.
|
|
68
|
+
Suíte completa após o ajuste: `npm test`, 1.093 testes, 1.066 aprovados, 27 condicionais pulados e zero falhas
|
|
69
|
+
(30 min). A falha da main ficou restrita a essa sincronização; Windows passou. Correção pronta para commit/PR
|
|
70
|
+
e checks Ubuntu/Windows Node 24 antes de integrar e publicar a versão 1.10.0.
|
|
71
|
+
|
|
72
|
+
## Assistente em prévia de modelo na Oracle — 12/10/2026
|
|
73
|
+
|
|
74
|
+
O erro `APP_UNKNOWN · a4670ccb` da produção era `path.dirname(null)` no preparo dos anexos: modelos abertos
|
|
75
|
+
pela vitrine ainda não têm arquivo. O pedido parava antes de chegar ao modelo. Agora o chat só prepara documentos
|
|
76
|
+
em uma pasta existente; documento binário, imagem gerada e edição materializam a cópia com os helpers da biblioteca.
|
|
77
|
+
Conversa simples continua sem criar arquivo. A aba do servidor e o endereço do editor passam a usar essa mesma
|
|
78
|
+
cópia, evitando edição perdida ou segunda cópia no pedido seguinte. Modelo de fábrica e outras pessoas preservados.
|
|
79
|
+
Regressão em `test/studio-ai-preview.test.js`: falhou antes com o erro exato; cobre conversa, edição salva,
|
|
80
|
+
recarregamento no navegador, isolamento por usuário e documento inventariado na pasta pessoal.
|
|
81
|
+
Imagem gerada também é conferida no arquivo e na pasta pessoal. Texto e visão reais passaram nos dois serviços.
|
|
82
|
+
A CI da main apresentou timeout no teclado ao mudar o título logo após abrir o editor no Windows 24; o teste da
|
|
83
|
+
biblioteca agora espera a página terminar de carregar antes de editar, mantendo a conferência do arquivo salvo.
|
|
84
|
+
Validação: npm test com 1.034 testes, 1.008 aprovados, 26 condicionais pulados, zero falhas; quatro regressões novas
|
|
85
|
+
passaram na suíte. O teste da biblioteca com a espera de carregamento passou também isolado (13 testes).
|
|
86
|
+
Entrega: commit da correção para os dois serviços da Oracle após validação local; merge do PR somente com os quatro
|
|
87
|
+
checks verdes (Ubuntu/Windows, Node 22/24). Não há mudança de dependências nem publicação de versão no npm.
|
|
88
|
+
PR #196 publicado. Oracle dev (3518) e produção (3517) receberam o commit `e32a3c1` e reiniciaram; nos dois,
|
|
89
|
+
o pedido real em uma prévia voltou com `mode: llm`, conversa, sem diagnóstico de erro e sem criar cópia.
|
|
90
|
+
Bibliotecas pessoais não foram alteradas. A CI do PR continua sendo acompanhada para concluir o merge.
|
|
91
|
+
Revisão adicional reproduziu perda ao recarregar quando o patch só mudava campos do deck (tema, contexto…) ou
|
|
92
|
+
excluía slides: a lista de slides alterados era vazia e a cópia não nascia. A materialização agora vale para toda
|
|
93
|
+
ação do modelo, preservando conversa e alternativas em prévia. Regressão confere tema salvo, exclusão e demais
|
|
94
|
+
slides preservados; falhou antes da correção.
|
|
95
|
+
Suíte completa após esse ajuste: 1.035 testes, 1.009 aprovados, 26 condicionais pulados e zero falhas.
|
|
96
|
+
Os cinco testes de prévia passaram; nova CI será exigida para o commit final antes do merge do PR #196.
|
|
97
|
+
Oracle dev e produção receberam também `333857f`: conversa real e mudança de tema pelo modelo funcionaram;
|
|
98
|
+
o tema permaneceu ao reabrir o arquivo pelas APIs nativas. Apresentações sintéticas removidas pela biblioteca.
|
|
99
|
+
A CI aprovou Ubuntu 22/24 e Windows 22. Windows 24 teve timeout na galeria; o teste isolado passou (3/3) e a
|
|
100
|
+
reexecução aprovou o carrossel, mas encontrou `ERR_NO_BUFFER_SPACE` durante exportação. Isso indica pressão de
|
|
101
|
+
recursos de rede no runner; a faixa TCP dinâmica agora é ampliada somente nas máquinas descartáveis da CI,
|
|
102
|
+
com diagnóstico de estados TCP em falhas. Testes e exigência de zero erros de navegador preservados.
|
|
103
|
+
Código da aplicação permanece o mesmo da suíte local completa aprovada (1.009/26/0); ajuste de CI validado como
|
|
104
|
+
YAML, e os quatro ambientes serão novamente conferidos antes do merge.
|
|
105
|
+
|
|
106
|
+
## Sobre legível no tema escuro — 12/10/2026 (branch `sobre-tema-escuro`)
|
|
107
|
+
|
|
108
|
+
Narumi (com print): no Windows e no dev da Oracle o Sobre "não abre direito", com texto apagado. Causa: o CSS do
|
|
109
|
+
`about.js` pegava o fundo de `--panel`/`--bg`, que nenhuma página define (caía no branco), e o texto de `--text`,
|
|
110
|
+
que no tema escuro é claro, o que dava contraste 1,16. Agora fundo, borda e botão vêm de `--chrome`, `--ui-line` e
|
|
111
|
+
`--brand-fill`, que o editor (style.css/studio-next.css) e a biblioteca (library.css) definem nos dois temas. Teste
|
|
112
|
+
novo em `test/sobre.test.js`: contraste WCAG mínimo de 4,5 do texto contra o fundo da janela, nos temas claro e
|
|
113
|
+
escuro, no editor e na biblioteca. Ele falha com o CSS antigo.
|
|
114
|
+
|
|
115
|
+
## Ajuda › Sobre o SagaDeck — 12/10/2026 (branch `ajuda-sobre`)
|
|
116
|
+
|
|
117
|
+
Narumi: "insira um help com about no menu onde eu posso ver a versão". A aba **Ajuda** fecha a faixa do editor
|
|
118
|
+
(depois de Avançado, como no PowerPoint) com Sobre o SagaDeck, O que mudou (CHANGELOG) e Demos. O Sobre
|
|
119
|
+
(`src/studio/public/about.js`, `window.SagaAbout`; qualquer elemento com `data-about` abre) mostra a versão em
|
|
120
|
+
destaque, commit, Node e sistema (novos em `/api/instance`), desde quando está no ar, a biblioteca (no multiusuário,
|
|
121
|
+
só que é do servidor) e se a IA está configurada, com Copiar informações para suporte. Na biblioteca, um botão com o
|
|
122
|
+
ícone de informação no alto. Achados de passagem: o `dialog` nativo abria colado no canto (uma regra global zerava
|
|
123
|
+
a margem; o Sobre põe `margin:auto`) e botão da faixa que é link (`a.rbtn`) saía sublinhado. Teste:
|
|
124
|
+
`test/sobre.test.js`; a ordem da faixa em `studio.test.js`.
|
|
125
|
+
|
|
11
126
|
## `sagadeck ia-check`: o que de IA funciona, sem tela — 12/10/2026 (branch `ia-check`)
|
|
12
127
|
|
|
13
128
|
Narumi: "o sagadeck está preparado para ser testado por um agente de IA (tipo o Copilot) sem o front-end?
|
package/docs/REFERENCIA.md
CHANGED
|
@@ -15,7 +15,7 @@ Mídia paga não é proibida por padrão. Quando for apropriada, proponha seu us
|
|
|
15
15
|
```yaml
|
|
16
16
|
title: Nome da palestra # obrigatório (vira rodapé e título da janela)
|
|
17
17
|
author: Seu Nome · Cargo
|
|
18
|
-
theme: sinal # sinal | editorial | noite | bauhaus | terminal | jornal | rabisco | oceano | pop | aurora | prata | aluminio | arcade | relevo | musgo | wine | caderno | lousa | cartaz | infografico (ou customizado, ver fim)
|
|
18
|
+
theme: sinal # sinal | editorial | noite | bauhaus | terminal | terminal-pro | jornal | rabisco | oceano | pop | aurora | aurora-dev | prata | aluminio | arcade | relevo | musgo | wine | caderno | lousa | cartaz | infografico (ou customizado, ver fim)
|
|
19
19
|
palette: floresta # opcional: só as cores, vale em qualquer tema (ver "Temas e paletas"); sem ela, as cores do tema
|
|
20
20
|
duration: 50 # minutos — o modo apresentador mostra se você está adiantado/atrasado
|
|
21
21
|
event: Summit de Dados 2026 # opcional — para {evento} no rodapé/cabeçalho
|
|
@@ -132,7 +132,7 @@ Tabela em markdown no meio de um texto (`problem`, `body`, `text`, passo do `sol
|
|
|
132
132
|
|
|
133
133
|
### Cenas interativas para ensinar
|
|
134
134
|
|
|
135
|
-
`codewalk` mantém o código estável e revela a explicação, o destaque de linhas e uma saída esperada por etapa. O código e os comandos cURL são texto; não há execução. No Studio, a linguagem é inferida pela extensão do arquivo e pode ser escolhida no menu: Python
|
|
135
|
+
`codewalk` mantém o código estável e revela a explicação, o destaque de linhas e uma saída esperada por etapa. O código e os comandos cURL são texto; não há execução. No Studio, a linguagem é inferida pela extensão do arquivo e pode ser escolhida no menu: Python, Java, JavaScript, TypeScript, C#, Bash/cURL, JSON ou SQL. Node.js é um runtime de JavaScript, não uma linguagem separada; arquivos Node usam a opção JavaScript. A apresentação aplica realce sintático a essas oito linguagens sem depender de serviços externos. Use as setas da apresentação ou os botões numerados. Depois da última etapa, a seta avança ao slide seguinte. As etapas também aparecem corretamente na prévia do apresentador e são restauradas pelo endereço da apresentação.
|
|
136
136
|
|
|
137
137
|
```yaml
|
|
138
138
|
- layout: codewalk
|
|
@@ -172,6 +172,46 @@ O cabeçalho do deck aceita `fit: { minCodePt, minTextPt, wrapCode }`: até onde
|
|
|
172
172
|
|
|
173
173
|
O cabeçalho do deck aceita `motion: none | subtle | expressive` (padrão `subtle`; no Studio, menu **Apresentar ▾ › Animações**: Sem animação, Suaves, Expressivas). A preferência do sistema por movimento reduzido tem prioridade. A intensidade muda a animação; os controles e revelações continuam funcionando.
|
|
174
174
|
|
|
175
|
+
## Criar apresentações de programação e APIs no Studio
|
|
176
|
+
|
|
177
|
+
Na biblioteca, **Programação** e **APIs com cURL** abrem a criação com IA já orientada ao assunto. A pessoa pode
|
|
178
|
+
escrever livremente, usar um exemplo de pedido, colar código/comando/resposta ou anexar arquivos técnicos. Público,
|
|
179
|
+
objetivo e roteiro são opcionais. A prévia mostra o pedido completo antes de gerar; Ctrl+Enter inicia a geração.
|
|
180
|
+
O tema sugerido é `terminal-pro` para programação e `aurora-dev` para APIs; uma escolha manual de estilo prevalece.
|
|
181
|
+
Essas escolhas orientam o modelo pelo prompt, sem impor uma sequência fixa de layouts.
|
|
182
|
+
|
|
183
|
+
**Começar com um exemplo, sem IA** cria uma base editável de cinco slides na biblioteca, com exemplo consistente,
|
|
184
|
+
código guiado, saída e capa com terminal animado. Programação inclui um diagrama do fluxo. APIs inclui uma chamada
|
|
185
|
+
POST ao ambiente `ensaio`, que funciona no Studio local sem configurar credenciais. No servidor multiusuário,
|
|
186
|
+
chamadas de API seguem o modo de gravação existente. Cada uso cria uma apresentação independente.
|
|
187
|
+
|
|
188
|
+
**Inserir › Código / cURL** aceita conteúdo colado e oferece código para mostrar, código guiado por etapas ou
|
|
189
|
+
requisição de API a partir de cURL. O importador lê os argumentos sem abrir um shell e sem executar a chamada.
|
|
190
|
+
Nos guias, selecionar linhas e adicionar uma etapa cria o destaque, com explicação e saída ilustrativa editáveis;
|
|
191
|
+
o painel Formatar também oferece essa ação. Código de exibição e guia não são executados.
|
|
192
|
+
|
|
193
|
+
A importação de cURL aceita método/URL, cabeçalhos, corpo literal/JSON, consulta com `-G` e `--data-urlencode`,
|
|
194
|
+
aspas e continuação de linha. Credenciais reconhecidas em cabeçalhos, consulta ou corpo são substituídas por
|
|
195
|
+
`{{secret.nome}}`; a prévia informa os nomes para cadastrar em Ambientes. Os demais bytes do JSON, inclusive números
|
|
196
|
+
grandes e formatação, são preservados. Variáveis existentes `{{nome}}` continuam referenciadas. Opções sem equivalente
|
|
197
|
+
no executor, como `-L`, `-k`, multipart `-F`, corpo vindo de arquivo `@` e substituição de shell, geram um erro claro;
|
|
198
|
+
para apenas exibir o comando completo, escolha Código no slide.
|
|
199
|
+
|
|
200
|
+
O importador acrescenta `request.bodyEncoding: json | urlencoded` e `request.urlEncoding: query` quando apropriado.
|
|
201
|
+
Esses campos preservam o escape dos valores interpolados no corpo e nos parâmetros da URL, primeiro com variáveis
|
|
202
|
+
e depois com Segredos, inclusive aspas, Unicode e `&`. O restante do corpo literal permanece intacto. Não é preciso
|
|
203
|
+
configurá-los no formulário. Pedidos existentes sem esses campos mantêm a interpolação anterior.
|
|
204
|
+
Os exemplos copiáveis de cURL, JavaScript e Python respeitam esses mesmos escapes. O cURL simples continua curto;
|
|
205
|
+
quando um Segredo precisa ser escapado em JSON/form/query, o exemplo Bash inclui um pequeno helper Node.js e indica
|
|
206
|
+
essa dependência no próprio comando. Literais como `$HOME` continuam literais quando vieram assim no cURL original.
|
|
207
|
+
|
|
208
|
+
Além de Python, Java, JavaScript, TypeScript e C#, o realce offline aceita **Bash** (`.sh`, `.bash`; aliases `shell`,
|
|
209
|
+
`sh`, `curl`), **JSON** (`.json`, `.jsonc`) e **SQL** (`.sql`). Bash destaca comandos, opções e variáveis; JSON,
|
|
210
|
+
chaves e valores; SQL, palavras-chave sem exigir maiúsculas. O conteúdo continua copiável, com indentação e linhas
|
|
211
|
+
preservadas. Anexos de código (`js`, `ts`, `py`, `sh`, `http`, `sql`, `go`, `rs`, `java`, `cs` e outros) entram como
|
|
212
|
+
material de contexto sem remover espaços ou linhas em branco. Saídas simuladas devem ser identificadas como exemplos;
|
|
213
|
+
nunca afirme execução sem evidência nem invente endpoints ou resultados reais.
|
|
214
|
+
|
|
175
215
|
## Screenshots com destaques no Studio
|
|
176
216
|
|
|
177
217
|
Em **Início → Screenshot**, cole (Ctrl+V), arraste ou escolha uma imagem PNG, JPEG ou WebP de até 8 MB.
|
|
@@ -547,6 +587,12 @@ Linhas que começam com PALAVRAS EM MAIÚSCULAS seguidas de `:` viram etiquetas
|
|
|
547
587
|
|
|
548
588
|
## Temas e paletas
|
|
549
589
|
|
|
590
|
+
Temas de desenvolvimento: `terminal-pro` usa grafite, menta e azul, títulos alinhados à esquerda, rótulos
|
|
591
|
+
monoespaçados e janelas de código; `aurora-dev` usa azul noite, lilás e ciano, luz suave e painéis arredondados.
|
|
592
|
+
Ambos usam Space Grotesk e IBM Plex Mono embutidas, código sem ligaduras e cores de sintaxe com contraste de leitura.
|
|
593
|
+
A rede ambiente da capa anima enquanto ela está ativa; `ambient: false` remove o efeito. Movimento reduzido,
|
|
594
|
+
`motion: none` e exportação deixam a rede estática, com código e explicações legíveis.
|
|
595
|
+
|
|
550
596
|
Como no PowerPoint, são duas escolhas independentes:
|
|
551
597
|
|
|
552
598
|
- **Tema** (`theme`): fontes, raio das bordas, textura e uma **pele** própria que rearruma capa, seção e encerramento, muda o jeito dos slides de conteúdo (título, cartões, marcadores da lista, citação, número grande, frase) e põe ornamentos (`sinal`: faixa zebrada e selo no chapéu; `editorial`: fios e capa centralizada; `noite`: moldura fina; `bauhaus`: círculo, quadrado e triângulo; `terminal`: janela com barra e prompt; `jornal`: fios de jornal e manchete sublinhada; `rabisco`: fitas adesivas e títulos inclinados; `oceano`: ondas e chapéu em pílula; `pop`: adesivos e sombra chapada; `aurora`: brilhos e traço em gradiente; `prata`: limpo e centralizado; `relevo`: ficção científica sóbria (sala de controle de filme, sem néon): grafite profundo, curvas de nível ao fundo, cantoneiras de mira, título leve embaixo à esquerda, rótulos técnicos em monoespaçada; combina com ilustração 3D realista (terreno, cidade, água) e gráfico 3D (`science` com superfície); `arcade`: videogame e fliperama, tela de tubo com linhas de varredura, neon, título em pixel no meio e chão de tijolos de jogo de plataforma; `aluminio`: prata metálico de lançamento de aparelho premium, título cromado embaixo à esquerda e um disco de metal polido; `wine`: o `musgo` em vinho (mesmo arranjo, fontes, faixa e rede na capa): fundo vinho quase preto, vermelho-rosado e rosé, com o vermelho forte só no detalhe; `caderno`: visual thinking em papel claro — título em letra de mão alta (Amatic SC) com traço de lápis, chapéu coral em serifada (Gelasio), frase em lilás, rabiscos de ícones em pastel nos cantos (lupa, ampulheta, engrenagem, lápis, chave, lâmpada); `lousa`: quadro-negro verde com giz — título em giz amarelo (Cabin Sketch) com traço laranja, linha de giz sob o título, poeira e marcas de giz apagado, lâmpada e lápis de giz na capa; `cartaz`: cartaz de protesto em papel cinza com grão — condensada pesada (Bebas Neue) em faixa vermelha torta, chapéu como etiqueta amarela, moldura preta grossa na capa, número da seção num disco amarelo; `infografico`: dados chapados — capa azul com chão verde e nuvem, títulos em maiúsculas, chapéu numa tarja verde, números enormes em condensada; `musgo`: laboratório noturno de bom gosto (verde-musgo quase preto, âmbar e sálvia, serifada Fraunces com o destaque do título em âmbar itálico, rótulos em IBM Plex Mono com fio âmbar, texto em Space Grotesk): grade de pontos que some nas bordas e luz âmbar na capa, seção como separador com o número entre fios na linha do chapéu e o título grande embaixo, e a faixa que corre no pé da capa com os títulos das seções (`ticker`); combina com o terminal de cenas (`motion: {type: console}`) na figura da capa ou num `canvas`; `manual` e `manual-noite`: documentação técnica, sem ornamento, código e texto corrido legíveis — o par claro/escuro do mesmo arranjo, para material de consulta, tutorial e apostila: `manual` imprime bem, `manual-noite` descansa a vista). Nos slides de conteúdo: `sinal` barra de aviso e placas; `editorial` fios finos e citação centralizada; `noite` tudo centralizado e cartões vazados; `bauhaus` faixas de cor primária e círculos; `terminal` título como comando e cartões-janela; `jornal` fios duplos e colunas; `oceano` cartões flutuando e balões; `pop` contorno grosso e sombra dura; `prata` centralizado e sem caixa; `aluminio` cartões de alumínio com chanfro de luz; `relevo` painéis de vidro fosco com traço gelo; `arcade` cartões de 8 bits com borda de pixel e sombra dura; `rabisco` tracejados e números circulados; `aurora` vidro com brilho; `caderno` cartões brancos com moldura grossa pastel (menta, lilás, amarelo, coral) e marcador amarelo contornado; `lousa` cartões contornados a giz com borda torta e marcadores em pingo de giz; `cartaz` cartões brancos girados como fotos coladas, números de lista em etiqueta e destaque em vermelho; `infografico` cartões com a faixa da cor do grupo, números em cartões coloridos e números de lista em círculos coloridos; `musgo`/`wine` cartões vazados que sobem no mouse, números em monoespaçada âmbar, marcador em losango, código em janela com três bolinhas e botões âmbar. Trocar o tema já muda o arranjo: não reescreva slides só para "combinar" com o tema.
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sagadeck",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.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": {
|
|
7
7
|
"sagadeck": "bin/sagadeck.js"
|
|
8
8
|
},
|
|
9
9
|
"engines": {
|
|
10
|
-
"node": ">=
|
|
10
|
+
"node": ">=24.0.0"
|
|
11
11
|
},
|
|
12
12
|
"license": "MIT",
|
|
13
13
|
"homepage": "https://github.com/naruminho/sagadeck#readme",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"jszip": "^3.10.2",
|
|
32
|
-
"katex": "^0.
|
|
32
|
+
"katex": "^0.19.0",
|
|
33
33
|
"lucide-static": "^1.47.0",
|
|
34
34
|
"pdfjs-dist": "^6.3.289",
|
|
35
35
|
"playwright-core": "^1.63.0",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"jsqr": "^1.4.0",
|
|
66
66
|
"leaflet": "1.9.4",
|
|
67
67
|
"maplibre-gl": "6.13.0",
|
|
68
|
-
"mermaid": "^
|
|
68
|
+
"mermaid": "^12.1.0"
|
|
69
69
|
},
|
|
70
70
|
"scripts": {
|
|
71
71
|
"dev": "node bin/sagadeck.js studio",
|
package/src/ai/context.js
CHANGED
|
@@ -23,7 +23,12 @@ export async function extractDocText(name, buf) {
|
|
|
23
23
|
if (buf.length > UPLOAD_MAX_BYTES) throw new Error(`Arquivo grande demais (${(buf.length / 1048576).toFixed(1)} MB, limite 15 MB).`);
|
|
24
24
|
const ext = (String(name).match(/\.([a-z0-9]+)$/i)?.[1] || "").toLowerCase();
|
|
25
25
|
if(ext==='doc')return {...await extractDocText('converted.docx',await convertLegacyWord(buf)),detail:'doc (convertido para docx)'};
|
|
26
|
-
|
|
26
|
+
// Código é material da pessoa: espaços e linhas podem fazer parte de strings ou da sintaxe.
|
|
27
|
+
if (["js", "mjs", "cjs", "ts", "tsx", "jsx", "py", "sh", "bash", "ps1", "http", "sql", "go", "rs", "java", "c", "cpp", "h", "cs", "rb", "php", "html", "css"].includes(ext)) {
|
|
28
|
+
return { text: buf.toString("utf8"), detail: `código (.${ext})` };
|
|
29
|
+
}
|
|
30
|
+
if (["json", "jsonc", "yaml", "yml"].includes(ext)) return { text: buf.toString("utf8"), detail: "texto" };
|
|
31
|
+
if (["txt", "md", "markdown", "csv"].includes(ext)) {
|
|
27
32
|
return { text: clean(buf.toString("utf8")), detail: "texto" };
|
|
28
33
|
}
|
|
29
34
|
if (ext === "docx") {
|
|
@@ -74,7 +79,7 @@ export async function extractDocText(name, buf) {
|
|
|
74
79
|
return { text: clean(parts.join("\n\n")), detail: `xlsx (${sheets.length} planilha(s))` };
|
|
75
80
|
}
|
|
76
81
|
if (ext === "pdf") return pdfText(buf);
|
|
77
|
-
throw new Error(`Tipo não suportado${ext ? ` (.${ext})` : ""}:
|
|
82
|
+
throw new Error(`Tipo não suportado${ext ? ` (.${ext})` : ""}: anexe texto, código (js, ts, py, sh, http, sql e outros), docx, xlsx, pptx ou pdf.`);
|
|
78
83
|
}
|
|
79
84
|
|
|
80
85
|
async function pdfText(buf) {
|
package/src/ai/deck-ai.js
CHANGED
|
@@ -1063,7 +1063,7 @@ Antes de responder, verifique (e siga as Regras de edição):
|
|
|
1063
1063
|
const touched = { ...edited, slides: changed.map((i) => edited.slides[i]).filter(Boolean) }; // mesmos objetos: gera no lugar
|
|
1064
1064
|
const nImgs = images && !deferImages ? countImagePrompts(touched) : 0; // deferImages: quem chamou gera depois (Criar com IA)
|
|
1065
1065
|
const imgs = nImgs
|
|
1066
|
-
? await materializeImages(touched, { ...imageOptions, keepFailed: true,
|
|
1066
|
+
? await materializeImages(touched, { ...(typeof imageOptions === "function" ? imageOptions() : imageOptions), keepFailed: true,
|
|
1067
1067
|
onProgress: (m) => onProgress?.({ phase: "images", text: `${m[0].toUpperCase()}${m.slice(1)} (${nImgs} no total)…` }) })
|
|
1068
1068
|
: { done: [], failed: [] };
|
|
1069
1069
|
imgs.done.forEach((d) => actions.push(`Imagem gerada: ${path.basename(d.file)}`));
|
|
@@ -1148,6 +1148,8 @@ export function slidesForMinutes(min) {
|
|
|
1148
1148
|
// Estilo do modal (uma coleção) → { theme, direction } para a geração. Desconhecido: null.
|
|
1149
1149
|
// estilos que não são coleção: material técnico, o par claro/escuro do tema manual
|
|
1150
1150
|
const TECH_STYLES = {
|
|
1151
|
+
"terminal-pro": { theme: "terminal-pro", direction: "Desenvolvimento em Terminal Pro: fundo grafite, menta, código legível em janelas de IDE e transições discretas. Use os recursos nativos de código guiado, terminal de cenas e API quando ajudarem a explicar o conteúdo; preserve os exemplos da pessoa. Use o tema terminal-pro, sem trocar sua paleta escura por uma clara." },
|
|
1152
|
+
"aurora-dev": { theme: "aurora-dev", direction: "Desenvolvimento em Aurora Dev: azul noite com lilás e ciano, luz ambiente, diagramas e código em painéis legíveis, movimento suave sem competir com a leitura. Use recursos nativos de programação e APIs conforme o pedido; preserve os exemplos da pessoa. Use o tema aurora-dev, sem trocar sua paleta escura por uma clara." },
|
|
1151
1153
|
manual: { theme: "manual", direction: "Documentação técnica clara: texto corrido legível, código completo, diagramas e avisos; boa para imprimir. Use o tema manual." },
|
|
1152
1154
|
"manual-noite": { theme: "manual-noite", direction: "Documentação técnica escura: texto corrido legível, código em destaque, diagramas e avisos; descansa a vista. Use o tema manual-noite." },
|
|
1153
1155
|
};
|
package/src/api-client.js
CHANGED
|
@@ -362,12 +362,43 @@ export class ApiEnvironments {
|
|
|
362
362
|
return out.filter((s) => typeof s === "string" && s.length >= 6);
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
-
maskText(env, text) {
|
|
365
|
+
maskText(env, text, contextual = false) {
|
|
366
366
|
let s = String(text);
|
|
367
|
-
for (const secret of this.secrets(env)) s = s.split(secret).join(C.mask(secret));
|
|
367
|
+
if (!contextual) { for (const secret of this.secrets(env)) s = s.split(secret).join(C.mask(secret)); return s; }
|
|
368
|
+
// Pedidos importados codificam segredos em JSON/query/form. A resposta e o relatório podem
|
|
369
|
+
// devolver essas representações; nenhuma delas deve revelar o valor, mesmo quando curto.
|
|
370
|
+
const replacements = new Map();
|
|
371
|
+
for (const secret of [...this.secrets(env), ...Object.values(this.secretVars(env))].filter(Boolean)) {
|
|
372
|
+
const masked = C.mask(secret);
|
|
373
|
+
for (const [raw, safe] of [[secret, masked], [encodeURIComponent(secret), encodeURIComponent(masked)], [JSON.stringify(secret).slice(1, -1), JSON.stringify(masked).slice(1, -1)]]) if (!replacements.has(raw)) replacements.set(raw, safe);
|
|
374
|
+
}
|
|
375
|
+
for (const [raw, safe] of [...replacements].sort((a, b) => b[0].length - a[0].length)) s = s.split(raw).join(safe);
|
|
368
376
|
return s;
|
|
369
377
|
}
|
|
370
378
|
|
|
379
|
+
maskValue(env, value, depth = 0) {
|
|
380
|
+
if (typeof value === "string") {
|
|
381
|
+
const masked = this.maskText(env, value, true);
|
|
382
|
+
if (depth > 64 || !/^\s*[\[{\"]/.test(value) || Object.values(this.secretVars(env)).includes(value)) return masked;
|
|
383
|
+
try { JSON.parse(value); } catch { return masked; }
|
|
384
|
+
// APIs podem devolver JSON dentro de strings de logs. Decodifica cada string e mascara
|
|
385
|
+
// recursivamente; troca apenas esses spans para preservar números grandes e formatação.
|
|
386
|
+
let result = "", last = 0;
|
|
387
|
+
for (let at = 0; at < value.length; at++) {
|
|
388
|
+
if (value[at] !== '"') continue;
|
|
389
|
+
const from = at++;
|
|
390
|
+
while (at < value.length) { if (value[at] === "\\") at += 2; else if (value[at++] === '"') break; }
|
|
391
|
+
const decoded = JSON.parse(value.slice(from, at)), safe = this.maskValue(env, decoded, depth + 1);
|
|
392
|
+
if (safe !== decoded) { result += value.slice(last, from) + JSON.stringify(safe); last = at; }
|
|
393
|
+
at--;
|
|
394
|
+
}
|
|
395
|
+
return result + value.slice(last);
|
|
396
|
+
}
|
|
397
|
+
if (Array.isArray(value)) return value.map(item => this.maskValue(env, item, depth + 1));
|
|
398
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, this.maskValue(env, item, depth + 1)]));
|
|
399
|
+
return value;
|
|
400
|
+
}
|
|
401
|
+
|
|
371
402
|
async token(env, { renew = false } = {}) {
|
|
372
403
|
const t = env.token;
|
|
373
404
|
if (!t || !t.url) return null;
|
|
@@ -420,13 +451,13 @@ export class ApiEnvironments {
|
|
|
420
451
|
});
|
|
421
452
|
throw new ApiError(`Ambiente "${env.name}" não tem ${names.join(", ")} configurado em secrets:`, "config");
|
|
422
453
|
}
|
|
423
|
-
req = { ...req, url: C.
|
|
454
|
+
req = { ...req, url: C.renderURL(req.url, sv, req.urlEncoding), headers: C.render(req.headers || {}, sv), body: C.renderBody(req.body, sv, req.bodyEncoding) };
|
|
424
455
|
const headers = { Accept: "application/json, text/event-stream, */*", ...C.render(env.headers || {}, sv), ...(req.headers || {}) };
|
|
425
456
|
let body = req.body;
|
|
426
457
|
const file = req.file || null; // { name, type, data: Buffer } — o arquivo do slide (upload ou base64)
|
|
427
458
|
const needsFile = (req.form && Object.values(req.form).includes("@file")) || /\{\{\s*file\./.test(JSON.stringify(body ?? ""));
|
|
428
459
|
if (needsFile && !file) throw new ApiError("Este slide precisa de um arquivo: arraste um para o slide ou defina file: no YAML.", "config");
|
|
429
|
-
if (file && body != null) body = C.
|
|
460
|
+
if (file && body != null) body = C.renderBody(body, { "file.base64": file.data.toString("base64"), "file.name": file.name, "file.type": file.type || "application/octet-stream" }, req.bodyEncoding);
|
|
430
461
|
if (req.form) {
|
|
431
462
|
// multipart/form-data, montado à mão (sem dependências)
|
|
432
463
|
const boundary = "----sagadeck" + Math.random().toString(16).slice(2);
|
|
@@ -453,8 +484,9 @@ export class ApiEnvironments {
|
|
|
453
484
|
const tok = await this.token(env, { renew });
|
|
454
485
|
return tok ? { ...headers, [authHeader]: (t.prefix ?? "Bearer ") + tok } : headers;
|
|
455
486
|
};
|
|
456
|
-
const
|
|
457
|
-
|
|
487
|
+
const contextual = !!(req.bodyEncoding || req.urlEncoding);
|
|
488
|
+
const sent = (h) => ({ method, url: this.maskText(env, req.url, contextual), headers: contextual ? this.maskValue(env, h) : JSON.parse(this.maskText(env, JSON.stringify(h))), ...(file ? { file: { name: file.name, type: file.type, size: file.data.length } } : {}) });
|
|
489
|
+
return { env, method, url: req.url, body, withToken, sent, contextual, timeout: (Number(env.timeout) || 60) * 1000 };
|
|
458
490
|
}
|
|
459
491
|
|
|
460
492
|
// Pedido completo: a resposta volta inteira (JSON ou texto), com os segredos mascarados.
|
|
@@ -463,7 +495,7 @@ export class ApiEnvironments {
|
|
|
463
495
|
const run = async (renew) => {
|
|
464
496
|
const headers = await p.withToken(renew);
|
|
465
497
|
const t0 = Date.now();
|
|
466
|
-
const res = await open({ method: p.method, url:
|
|
498
|
+
const res = await open({ method: p.method, url: p.url, headers, body: p.body, ca: this.ca(p.env), insecure: !!p.env.insecure, timeout: p.timeout });
|
|
467
499
|
const buf = await readAll(res);
|
|
468
500
|
return { res, buf, headers, ms: Date.now() - t0 };
|
|
469
501
|
};
|
|
@@ -483,7 +515,7 @@ export class ApiEnvironments {
|
|
|
483
515
|
}
|
|
484
516
|
// áudio (TTS): vai inteiro, em base64, para o slide tocar
|
|
485
517
|
const body = /^audio\//i.test(type) ? { _audio: true, type: type.split(";")[0], base64: r.buf.toString("base64") }
|
|
486
|
-
: parseBody(Buffer.from(this.maskText(p.env, r.buf.toString("utf8"))), type);
|
|
518
|
+
: p.contextual ? this.maskValue(p.env, parseBody(r.buf, type)) : parseBody(Buffer.from(this.maskText(p.env, r.buf.toString("utf8"))), type);
|
|
487
519
|
return {
|
|
488
520
|
ok: r.res.statusCode < 400, status: r.res.statusCode, statusText: r.res.statusMessage || "", ms: r.ms, size: r.buf.length,
|
|
489
521
|
type, body, sent: p.sent(r.headers), env: p.env.name, token: this.tokenInfo(p.env.name), ...(jwt ? { jwt } : {}),
|
|
@@ -610,7 +642,7 @@ export class ApiEnvironments {
|
|
|
610
642
|
async open(req) {
|
|
611
643
|
const p = await this.prepare(req);
|
|
612
644
|
const headers = await p.withToken(false);
|
|
613
|
-
const res = await open({ method: p.method, url:
|
|
614
|
-
return { res, env: p.env, sent: p.sent(headers), mask: (s) => this.maskText(p.env, s) };
|
|
645
|
+
const res = await open({ method: p.method, url: p.url, headers, body: p.body, ca: this.ca(p.env), insecure: !!p.env.insecure, timeout: p.timeout });
|
|
646
|
+
return { res, env: p.env, sent: p.sent(headers), mask: (s) => this.maskText(p.env, s, p.contextual) };
|
|
615
647
|
}
|
|
616
648
|
}
|
package/src/code-highlight.js
CHANGED
|
@@ -6,6 +6,9 @@ const KEYWORDS = {
|
|
|
6
6
|
JavaScript: new Set("async await break case catch class const continue debugger default delete do else export extends finally for from function if import in instanceof let new of return static super switch this throw try typeof var void while with yield".split(" ")),
|
|
7
7
|
TypeScript: new Set("abstract any as asserts async await bigint boolean break case catch class const constructor continue declare default delete do else enum export extends finally for from function get if implements import in infer instanceof interface is keyof let namespace never new number object of package private protected public readonly return set static string super switch symbol this throw try type typeof undefined unique unknown var void while with yield".split(" ")),
|
|
8
8
|
"C#": new Set("abstract as async await base bool break byte case catch char checked class const continue decimal default delegate do double else enum event explicit extern false finally fixed float for foreach goto if implicit in int interface internal is lock long namespace new null object operator out override params private protected public readonly ref return sbyte sealed short sizeof stackalloc static string struct switch this throw try typeof uint ulong unchecked unsafe ushort using virtual void volatile while".split(" ")),
|
|
9
|
+
Bash: new Set("if then else elif fi for in do done while until case esac function select time export local readonly return exit source".split(" ")),
|
|
10
|
+
JSON: new Set(),
|
|
11
|
+
SQL: new Set("SELECT FROM WHERE JOIN INNER LEFT RIGHT FULL OUTER CROSS ON AS AND OR NOT IN IS NULL DISTINCT GROUP BY ORDER HAVING LIMIT OFFSET ASC DESC INSERT INTO VALUES UPDATE SET DELETE CREATE TABLE INDEX VIEW DROP ALTER WITH UNION ALL EXISTS CASE WHEN THEN ELSE END BETWEEN LIKE RETURNING BEGIN COMMIT ROLLBACK PRIMARY KEY REFERENCES DEFAULT".split(" ")),
|
|
9
12
|
};
|
|
10
13
|
|
|
11
14
|
const TYPES = {
|
|
@@ -14,11 +17,19 @@ const TYPES = {
|
|
|
14
17
|
JavaScript: new Set("Array Boolean Date Map Number Object Promise Set String".split(" ")),
|
|
15
18
|
TypeScript: new Set("Array any bigint boolean never number object string symbol unknown void".split(" ")),
|
|
16
19
|
"C#": new Set("bool byte char DateTime decimal double float int List long object short string Task uint ulong var void".split(" ")),
|
|
20
|
+
Bash: new Set(),
|
|
21
|
+
JSON: new Set(),
|
|
22
|
+
SQL: new Set("INT INTEGER BIGINT TEXT VARCHAR BOOLEAN FLOAT DOUBLE DECIMAL NUMERIC DATE TIMESTAMP JSON JSONB".split(" ")),
|
|
17
23
|
};
|
|
18
24
|
|
|
25
|
+
const SHELL_COMMANDS = new Set("curl echo printf git npm node python python3 pip pytest jq cat grep sed awk ls cd pwd mkdir rm cp mv sleep read test find sort head tail".split(" "));
|
|
26
|
+
|
|
19
27
|
const CONSTANTS = new Set(["False", "None", "True", "false", "null", "true", "undefined", "NaN", "Infinity"]);
|
|
20
28
|
|
|
21
29
|
function tokenPattern(language) {
|
|
30
|
+
if (language === "Bash") return /(?<comment>(?<!\S)#[^\n]*)|(?<string>'[^']*'|"(?:\\[\s\S]|[^"\\])*")|(?<variable>\$(?:\{[^}\n]+\}|[A-Za-z_][\w]*|[\d?@#*!$-]))|(?<flag>(?<!\S)--?[A-Za-z][\w-]*(?:=[^\s]+)?)|(?<number>\b\d+(?:\.\d+)?\b)|(?<identifier>[A-Za-z_][\w]*)|(?<operator>[|&;<>]+)|(?<punctuation>[{}()\[\]])/g;
|
|
31
|
+
if (language === "JSON") return /(?<comment>\/\/[^\n]*|\/\*[\s\S]*?\*\/)|(?<string>"(?:\\.|[^"\\])*")|(?<number>\b\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b)|(?<identifier>true|false|null)|(?<punctuation>[{}\[\],:])/g;
|
|
32
|
+
if (language === "SQL") return /(?<comment>--[^\n]*|\/\*[\s\S]*?\*\/)|(?<string>'(?:''|[^'])*'|"(?:""|[^"])*"|`[^`]*`)|(?<number>\b\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b)|(?<identifier>[A-Za-z_][\w$]*)|(?<operator>[-+*/%=&|^!<>~]+)|(?<punctuation>[{}()\[\].,;])/g;
|
|
22
33
|
const comments = language === "Python"
|
|
23
34
|
? "#[^\\n]*"
|
|
24
35
|
: "\\/\\/[^\\n]*|\\/\\*[\\s\\S]*?\\*\\/";
|
|
@@ -32,13 +43,18 @@ function tokenPattern(language) {
|
|
|
32
43
|
function classify(match, language, source, offset) {
|
|
33
44
|
const groups = match.groups;
|
|
34
45
|
if (groups.comment) return "comment";
|
|
46
|
+
if (groups.variable) return "constant";
|
|
47
|
+
if (groups.flag) return "keyword";
|
|
48
|
+
if (groups.string && language === "JSON" && /^\s*:/.test(source.slice(offset + match[0].length))) return "type";
|
|
35
49
|
if (groups.string) return "string";
|
|
36
50
|
if (groups.number) return "number";
|
|
37
51
|
if (groups.identifier) {
|
|
38
52
|
const word = groups.identifier;
|
|
39
|
-
|
|
53
|
+
const normalized = language === "SQL" ? word.toUpperCase() : word;
|
|
54
|
+
if (KEYWORDS[language].has(normalized)) return "keyword";
|
|
40
55
|
if (CONSTANTS.has(word)) return "constant";
|
|
41
|
-
if (TYPES[language].has(
|
|
56
|
+
if (TYPES[language].has(normalized)) return "type";
|
|
57
|
+
if (language === "Bash" && SHELL_COMMANDS.has(word)) return "function";
|
|
42
58
|
if (/^\s*\(/.test(source.slice(offset + match[0].length))) return "function";
|
|
43
59
|
}
|
|
44
60
|
if (groups.operator) return "operator";
|
|
@@ -47,6 +63,7 @@ function classify(match, language, source, offset) {
|
|
|
47
63
|
}
|
|
48
64
|
|
|
49
65
|
export function highlightCode(source, language) {
|
|
66
|
+
if (!KEYWORDS[language]) return source.split("\n").map(esc);
|
|
50
67
|
const pattern = tokenPattern(language);
|
|
51
68
|
const lines = [""];
|
|
52
69
|
const append = (value, kind = "") => {
|
package/src/code-language.js
CHANGED
|
@@ -11,6 +11,11 @@ const EXTENSIONS = new Map([
|
|
|
11
11
|
[".mts", "TypeScript"],
|
|
12
12
|
[".cts", "TypeScript"],
|
|
13
13
|
[".cs", "C#"],
|
|
14
|
+
[".sh", "Bash"],
|
|
15
|
+
[".bash", "Bash"],
|
|
16
|
+
[".json", "JSON"],
|
|
17
|
+
[".jsonc", "JSON"],
|
|
18
|
+
[".sql", "SQL"],
|
|
14
19
|
]);
|
|
15
20
|
|
|
16
21
|
const ALIASES = new Map([
|
|
@@ -26,6 +31,13 @@ const ALIASES = new Map([
|
|
|
26
31
|
["c#", "C#"],
|
|
27
32
|
["csharp", "C#"],
|
|
28
33
|
["cs", "C#"],
|
|
34
|
+
["bash", "Bash"],
|
|
35
|
+
["shell", "Bash"],
|
|
36
|
+
["sh", "Bash"],
|
|
37
|
+
["curl", "Bash"],
|
|
38
|
+
["json", "JSON"],
|
|
39
|
+
["jsonc", "JSON"],
|
|
40
|
+
["sql", "SQL"],
|
|
29
41
|
]);
|
|
30
42
|
|
|
31
43
|
export function inferCodeLanguage(filename) {
|
|
@@ -48,5 +60,6 @@ export function resolveCodeLanguage(language, filename) {
|
|
|
48
60
|
|
|
49
61
|
export function displayCodeLanguage(language, filename) {
|
|
50
62
|
const requested = String(language || "").trim();
|
|
63
|
+
if (requested.toLowerCase() === "curl") return "cURL";
|
|
51
64
|
return normalizeCodeLanguage(requested) || requested || inferCodeLanguage(filename);
|
|
52
65
|
}
|