primocode 9.8.0-beta.5 → 9.8.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +15 -4
  2. package/app/ansi.js +55 -0
  3. package/app/ide.css +345 -0
  4. package/app/ide.html +183 -0
  5. package/app/ide.js +639 -0
  6. package/bin/primocode.js +315 -83
  7. package/kits/acolhedor/index.html +130 -0
  8. package/kits/acolhedor/script.js +25 -0
  9. package/kits/acolhedor/style.css +146 -0
  10. package/kits/editorial/index.html +115 -0
  11. package/kits/editorial/script.js +19 -0
  12. package/kits/editorial/style.css +112 -0
  13. package/kits/premium/index.html +83 -0
  14. package/kits/premium/script.js +11 -0
  15. package/kits/premium/style.css +87 -0
  16. package/kits/vitrine/index.html +108 -0
  17. package/kits/vitrine/script.js +20 -0
  18. package/kits/vitrine/style.css +117 -0
  19. package/lib/app.js +41 -6
  20. package/lib/browser.js +14 -1
  21. package/lib/checkpoint-git.js +218 -0
  22. package/lib/checkpoint.js +159 -44
  23. package/lib/comandos-extra.js +481 -0
  24. package/lib/config.js +5 -2
  25. package/lib/ide.js +337 -0
  26. package/lib/kits.js +165 -54
  27. package/lib/skills.js +6 -1
  28. package/lib/ui.js +171 -17
  29. package/package.json +2 -2
  30. package/skills/apresentacao-pptx/SKILL.md +52 -0
  31. package/skills/cartaz-de-arte/LICENSE.txt +202 -0
  32. package/skills/cartaz-de-arte/SKILL.md +47 -0
  33. package/skills/criar-skill/LICENSE.txt +202 -0
  34. package/skills/criar-skill/SKILL.md +49 -0
  35. package/skills/depurar-de-verdade/LICENSE.txt +21 -0
  36. package/skills/depurar-de-verdade/SKILL.md +14 -5
  37. package/skills/documento-word/SKILL.md +57 -0
  38. package/skills/interface-revisada/SKILL.md +47 -0
  39. package/skills/pdf-bonito/SKILL.md +48 -0
  40. package/skills/planilha-excel/SKILL.md +47 -0
  41. package/skills/react-sem-dor/SKILL.md +48 -0
  42. package/skills/revisao-antes-de-entregar/LICENSE.txt +21 -0
  43. package/skills/revisao-antes-de-entregar/SKILL.md +6 -1
  44. package/skills/temas-prontos/LICENSE.txt +202 -0
  45. package/skills/temas-prontos/SKILL.md +50 -0
  46. package/skills/testar-o-site/LICENSE.txt +202 -0
  47. package/skills/testar-o-site/SKILL.md +46 -0
  48. package/skills/video-no-remotion/SKILL.md +48 -0
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # PrimoCode v9.8.0-beta.5
1
+ # PrimoCode v9.8.0-beta.6
2
2
 
3
3
  Agente de engenharia brasileiro para o terminal, no estilo Claude Code.
4
4
  Cria arquivos de verdade, roda comandos, **controla o navegador e o desktop** —
@@ -9,7 +9,7 @@ OpenRouter, e cada apelido (`top`/`main`/`fast`) é uma **corrente**: se um mode
9
9
  está no limite de pedidos, o próximo atende. Você não vê o erro, e não paga nada.
10
10
 
11
11
  ```
12
- ▐▛███▜▌ Primo Code v9.8.0-beta.5
12
+ ▐▛███▜▌ Primo Code v9.8.0-beta.6
13
13
  ▝▜█████▛▘ Bem-vindo de volta, Joel
14
14
  ▘▘ ▝▝ ~/primocode · /dir <pasta> muda
15
15
  ```
@@ -284,6 +284,8 @@ primocode -c # continua a última conversa salva (--co
284
284
  |---|---|
285
285
  | `/dir <caminho>` | Trabalha numa pasta que já existe, onde você quiser |
286
286
  | `/project` | Lista projetos. `/project <nome>` cria em `~/primocode` |
287
+ | `/projetos [n]` | Os projetos de `~/primocode` do mais recente para o mais antigo, com quando mexeu e o tamanho. `/projetos 2` entra no segundo (o `/project` sem nome mostra a mesma lista) |
288
+ | `/novo <descrição>` | Começa um projeto NOVO do zero, em pasta própria com nome escolhido pelo modelo — sem cair num projeto parecido que já existe |
287
289
  | `/mode primocode\|chat\|auto` | Alterna o modo (fica guardado) |
288
290
  | `/voice` | Modo voz: uma orb, você fala, ele faz. O cérebro é o mesmo do terminal |
289
291
  | `/comandos` | Seus comandos: um `.md` em `~/.primocode/comandos/` (ou `<projeto>/.primocode/comandos/`) vira `/nome`, e `$ARGUMENTOS` recebe o que vem depois. Já vêm `/revisar`, `/explicar` e `/publicar`. `/comandos novo <nome>` cria um |
@@ -297,14 +299,23 @@ primocode -c # continua a última conversa salva (--co
297
299
  | `/attach <arquivo>` | Anexa arquivo ao próximo pedido |
298
300
  | `/detach` · `/files` | Remove / lista anexos |
299
301
  | `/all` | Lista as 46 ferramentas que ele sabe usar, por grupo |
300
- | `/desfazer` · `/undo` | Volta os arquivos que o último pedido mudou. `/desfazer lista` mostra os pedidos guardados · `/desfazer N` volta os N últimos |
302
+ | `/desfazer` · `/undo` | Volta o projeto para como estava antes do último pedido — inclusive o que um comando apagou, moveu ou gerou (com git instalado, cada pedido tira uma foto do projeto num git à parte, que não toca no seu `.git`; `node_modules`, `dist` e o que o `.gitignore` ignora ficam de fora). Sem git, volta o que o agente escreveu com as ferramentas de arquivo. `/desfazer lista` mostra os pedidos e quantos arquivos cada um mudou · `/desfazer N` volta os N últimos |
301
303
  | `/tree [n]` | Árvore do projeto atual |
302
304
  | `/read <arquivo>` | Mostra um arquivo do projeto |
303
305
  | `/run <comando>` | Executa no shell, dentro da pasta do projeto |
304
306
  | `/open <arquivo\|url>` | Abre no navegador |
307
+ | `/rc` | Abre a janela do PrimoCode: o chat, sem terminal |
308
+ | `/ide` | Abre a PrimoCode IDE: os arquivos do projeto à esquerda, o editor no meio (Ctrl+S salva), a prévia do site ao lado, o chat com o Primo à direita e o terminal embaixo. Quando ele mexe num arquivo, a lista acende e a prévia se atualiza sozinha. Usa o mesmo servidor do `/rc` |
309
+ | `primo ide` | Do shell: abre o PrimoCode já com a IDE aberta (`primo "pedido"` continua sendo o modo de um pedido só) |
310
+ | `/abrir` | Abre o projeto atual no navegador: o servidor dele, se estiver de pé, ou a `index.html` (o `/open` sem nada faz o mesmo) |
311
+ | `/copiar` | Copia a última resposta para a área de transferência (pbcopy, clip, wl-copy/xclip/xsel) |
312
+ | `/historico [termo]` | Procura nos seus pedidos guardados nesta máquina (`~/.primocode/history.jsonl`), do mais recente. As conversas inteiras da conta ficam no `/chats` |
305
313
  | `/status` | Health check do servidor |
314
+ | `/doctor` | Confere tudo com ✓/✗: Node, servidor, `/api`, Chrome, Python+PyObjC (cursor, macOS), disco e config. Cada ✗ diz como consertar |
315
+ | `/custo` | Tokens enviados e recebidos e pedidos feitos nesta sessão, por provedor (estimado em caracteres ÷ 4 quando o provedor não manda o número) |
316
+ | `/atalhos` | As teclas: Esc Esc para o agente, `!` roda no shell, Alt+Enter quebra linha… |
306
317
  | `/name <nome>` | Como o Primo te chama na saudação |
307
- | `/mascot primo\|fox\|cat` | Escolhe o mascote (mostra os três) |
318
+ | `/mascot [nome\|n]` | Galeria dos 10 mascotes lado a lado (primo, fox, cat, robo, coruja, dragao, fantasma, capivara, polvo, pinguim), cada um com nome e bordão na abertura. Escolha por nome ou número. Depois de cada tarefa ele reage numa linha (`(^‿^)` pronto, `(°_°)` não terminou); `/mascot reacoes off` desliga |
308
319
  | `/config` | Mostra suas preferências e onde ficam salvas |
309
320
  | `/clear` · `/cls` | Limpa conversa / tela |
310
321
  | `/uninstall` | Remove o PrimoCode (seus projetos ficam) |
package/app/ansi.js ADDED
@@ -0,0 +1,55 @@
1
+ /* ansi.js — a saída do terminal virando HTML com as cores do PrimoCode.
2
+ *
3
+ * A mesma tradução do app.js (a janela do /rc), agora num arquivo que a IDE
4
+ * também carrega. Só o que o PrimoCode realmente emite: cor 256 (38;5;N),
5
+ * negrito e reset. O resto dos códigos (mover cursor, apagar linha) é desenho
6
+ * de terminal e é descartado — na página quem rola é o navegador.
7
+ *
8
+ * O `estado` atravessa os pedaços: o Node corta a saída onde der, e um
9
+ * "\x1b[38;5;45m" pode chegar num pedaço e o texto colorido no seguinte.
10
+ */
11
+ (function (raiz) {
12
+ "use strict";
13
+ var ANSI = /\x1b\[([0-9;?]*)([a-zA-Z])/g;
14
+
15
+ function fugir(s) {
16
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
17
+ }
18
+
19
+ function paraHtml(texto, estado) {
20
+ var fora = "";
21
+ var ultimo = 0;
22
+ var m;
23
+ ANSI.lastIndex = 0;
24
+ function abrir() {
25
+ var classes = [];
26
+ if (estado.cor) classes.push("c" + estado.cor);
27
+ if (estado.forte) classes.push("forte");
28
+ return classes.length ? '<span class="' + classes.join(" ") + '">' : "";
29
+ }
30
+ function fechar() { return (estado.cor || estado.forte) ? "</span>" : ""; }
31
+
32
+ while ((m = ANSI.exec(texto)) !== null) {
33
+ var pedaco = texto.slice(ultimo, m.index);
34
+ if (pedaco) fora += abrir() + fugir(pedaco) + fechar();
35
+ ultimo = m.index + m[0].length;
36
+ if (m[2] !== "m") continue;
37
+ var partes = m[1].split(";");
38
+ for (var i = 0; i < partes.length; i++) {
39
+ var n = partes[i];
40
+ if (n === "" || n === "0") { estado.cor = null; estado.forte = false; }
41
+ else if (n === "1") estado.forte = true;
42
+ else if (n === "38" && partes[i + 1] === "5") { estado.cor = partes[i + 2]; i += 2; }
43
+ else if (n === "39") estado.cor = null;
44
+ else if (n === "22") estado.forte = false;
45
+ }
46
+ }
47
+ var resto = texto.slice(ultimo);
48
+ if (resto) fora += abrir() + fugir(resto) + fechar();
49
+ // \r sozinho é o terminal voltando ao começo da linha para redesenhar;
50
+ // na página ele só deixaria um caractere invisível no meio do texto.
51
+ return fora.replace(/\r(?!\n)/g, "");
52
+ }
53
+
54
+ raiz.PrimoAnsi = { paraHtml: paraHtml, fugir: fugir };
55
+ })(window);
package/app/ide.css ADDED
@@ -0,0 +1,345 @@
1
+ /* A PrimoCode IDE.
2
+ *
3
+ * "Um app ao invés de CLI, aí fica uma IDE e uma CLI."
4
+ *
5
+ * Esta folha vem DEPOIS do app.css e não define cor nenhuma: tokens, o
6
+ * `.saida` com as cores ANSI, a bolha `.voce`, o campo com o `❯` e o botão
7
+ * de enviar são os mesmos da janela do /rc. Aqui só mora a planta da IDE —
8
+ * árvore à esquerda, editor e prévia no meio, chat à direita, terminal
9
+ * embaixo — e o que só ela tem (abas, calha de linhas, o acender da árvore).
10
+ */
11
+
12
+ body.ide {
13
+ --w-arvore: 236px;
14
+ --w-chat: 368px;
15
+ --h-term: 188px;
16
+ --linha: 20px; /* altura de uma linha do editor — a calha depende dela */
17
+ --codigo-tam: 13px;
18
+ --editor-fundo: #0b121b;
19
+ --realce: rgba(251, 191, 36, .16); /* amber: "o agente mexeu aqui" */
20
+
21
+ display: grid;
22
+ grid-template-columns: var(--w-arvore) minmax(0, 1fr) var(--w-chat);
23
+ grid-template-rows: 44px minmax(0, 1fr) var(--h-term) 26px;
24
+ grid-template-areas:
25
+ "topo topo topo"
26
+ "arvore centro chat"
27
+ "arvore terminal chat"
28
+ "barra barra barra";
29
+ background-image: none;
30
+ }
31
+ html[data-tema="claro"] body.ide { --editor-fundo: #fbfdfe; --realce: rgba(217, 119, 6, .14); }
32
+ body.ide.sem-terminal { grid-template-rows: 44px minmax(0, 1fr) 0 26px; }
33
+ body.ide.sem-terminal .terminal { display: none; }
34
+
35
+ /* ── cabeçalhos de painel ─────────────────────────────────────────────── */
36
+ .painel-cabeca {
37
+ display: flex; align-items: center; gap: 8px; flex: 0 0 auto;
38
+ height: 34px; padding: 0 10px 0 14px;
39
+ font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--apagado);
40
+ border-bottom: 1px solid var(--borda);
41
+ }
42
+ .painel-cabeca > span:first-child { font-weight: 600; }
43
+ .icone-btn {
44
+ margin-left: auto; display: grid; place-items: center; width: 26px; height: 26px;
45
+ border: 0; border-radius: 7px; background: none; color: var(--apagado); cursor: pointer;
46
+ }
47
+ .icone-btn:hover { background: var(--alto); color: var(--texto); }
48
+ :focus-visible { outline: 2px solid var(--azul); outline-offset: 1px; }
49
+
50
+ /* ── topo ─────────────────────────────────────────────────────────────── */
51
+ .topo {
52
+ grid-area: topo; display: flex; align-items: center; gap: 18px; padding: 0 12px 0 14px;
53
+ background: var(--barra); border-bottom: 1px solid var(--borda); min-width: 0;
54
+ -webkit-app-region: drag;
55
+ }
56
+ .topo button, .topo a { -webkit-app-region: no-drag; }
57
+ .topo-marca { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
58
+ .topo-marca .nome { font-weight: 620; letter-spacing: -.01em; }
59
+ .selo {
60
+ font-family: var(--mono); font-size: 10.5px; font-weight: 600; letter-spacing: .06em;
61
+ color: var(--azul); border: 1px solid color-mix(in srgb, var(--azul) 45%, transparent);
62
+ border-radius: 5px; padding: 1px 6px;
63
+ }
64
+ .topo-projeto {
65
+ display: flex; align-items: center; gap: 7px; min-width: 0; color: var(--fraco); font-size: 13px;
66
+ }
67
+ .topo-projeto b { color: var(--texto); font-weight: 560; white-space: nowrap; }
68
+ .topo-projeto .caminho {
69
+ font-family: var(--mono); font-size: 11.5px; color: var(--apagado);
70
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
71
+ }
72
+ .topo-dir { margin-left: auto; display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
73
+ .botao-topo {
74
+ display: flex; align-items: center; gap: 6px; height: 28px; padding: 0 10px;
75
+ font: inherit; font-size: 12.5px; color: var(--fraco); text-decoration: none; cursor: pointer;
76
+ background: none; border: 1px solid var(--borda-viva); border-radius: 8px;
77
+ }
78
+ .botao-topo:hover { color: var(--texto); border-color: var(--azul); }
79
+ .botao-topo[aria-pressed="true"] { color: var(--texto); background: var(--alto); }
80
+
81
+ /* ── árvore ───────────────────────────────────────────────────────────── */
82
+ .arvore {
83
+ grid-area: arvore; display: flex; flex-direction: column; min-height: 0;
84
+ background: var(--barra); border-right: 1px solid var(--borda);
85
+ }
86
+ .lista { flex: 1 1 0; overflow: auto; padding: 6px 6px 12px; font-size: 13px; }
87
+ .lista::-webkit-scrollbar, .chat-rolo::-webkit-scrollbar, .terminal-saida::-webkit-scrollbar,
88
+ #codigo::-webkit-scrollbar { width: 10px; height: 10px; }
89
+ .lista::-webkit-scrollbar-thumb, .chat-rolo::-webkit-scrollbar-thumb, .terminal-saida::-webkit-scrollbar-thumb,
90
+ #codigo::-webkit-scrollbar-thumb { background: var(--borda-viva); border-radius: 8px; border: 3px solid transparent; background-clip: padding-box; }
91
+ #codigo::-webkit-scrollbar-corner { background: transparent; }
92
+
93
+ .no {
94
+ position: relative; display: flex; align-items: center; gap: 6px; width: 100%;
95
+ height: 26px; padding: 0 8px 0 calc(8px + var(--nivel, 0) * 14px);
96
+ border: 0; border-radius: 6px; background: none; color: var(--fraco);
97
+ font: inherit; font-size: 13px; text-align: left; cursor: pointer; white-space: nowrap;
98
+ }
99
+ .no:hover { background: var(--alto); color: var(--texto); }
100
+ .no[aria-selected="true"] { background: color-mix(in srgb, var(--azul) 13%, transparent); color: var(--texto); }
101
+ .no .nome-no { overflow: hidden; text-overflow: ellipsis; }
102
+ .seta {
103
+ flex: 0 0 12px; display: grid; place-items: center; color: var(--apagado);
104
+ transition: transform .14s ease;
105
+ }
106
+ .no[aria-expanded="true"] .seta { transform: rotate(90deg); }
107
+ .no.pasta { color: var(--texto); }
108
+
109
+ /* O tipo de arquivo numa etiqueta de duas letras, e não num ícone: quem está
110
+ começando aprende a extensão lendo "js", "css", "md" — um desenhinho não
111
+ ensina nada. */
112
+ .tipo {
113
+ flex: 0 0 22px; font-family: var(--mono); font-size: 9.5px; font-weight: 700;
114
+ text-transform: uppercase; letter-spacing: .02em; text-align: center; color: var(--apagado);
115
+ }
116
+ .tipo.t-html { color: var(--alerta); }
117
+ .tipo.t-css { color: var(--azul); }
118
+ .tipo.t-js, .tipo.t-ts, .tipo.t-mjs { color: #fde68a; }
119
+ .tipo.t-json { color: var(--fraco); }
120
+ .tipo.t-img { color: #fda4af; }
121
+ .tipo.t-py { color: #7dd3fc; }
122
+ html[data-tema="claro"] .tipo.t-js, html[data-tema="claro"] .tipo.t-ts { color: #a16207; }
123
+ html[data-tema="claro"] .tipo.t-img { color: #be123c; }
124
+ html[data-tema="claro"] .tipo.t-py { color: #0369a1; }
125
+
126
+ /* O acender de "o agente mexeu aqui". Âmbar porque é aviso — o cyan já é a
127
+ cor do que está selecionado. A etiqueta "agora" garante que o estado não
128
+ depende só de cor. */
129
+ .no.mexido { background: var(--realce); color: var(--texto); }
130
+ .no.mexido::after {
131
+ content: "agora"; margin-left: auto; padding-left: 8px;
132
+ font-family: var(--mono); font-size: 10px; color: var(--alerta);
133
+ }
134
+ @media (prefers-reduced-motion: no-preference) {
135
+ .no.mexido { animation: acender 3.6s ease-out forwards; }
136
+ @keyframes acender {
137
+ 0% { background: color-mix(in srgb, var(--alerta) 34%, transparent); }
138
+ 100% { background: var(--realce); }
139
+ }
140
+ }
141
+ .arvore-pe {
142
+ flex: 0 0 auto; padding: 8px 14px 10px; border-top: 1px solid var(--borda);
143
+ font-size: 11.5px; color: var(--apagado); line-height: 1.45;
144
+ }
145
+ .arvore-pe:empty { display: none; }
146
+
147
+ /* ── centro: abas ─────────────────────────────────────────────────────── */
148
+ .centro { grid-area: centro; display: flex; flex-direction: column; min-width: 0; min-height: 0; background: var(--editor-fundo); }
149
+ .abas {
150
+ flex: 0 0 auto; display: flex; align-items: stretch; height: 36px;
151
+ background: var(--barra); border-bottom: 1px solid var(--borda);
152
+ }
153
+ .abas-rolo { flex: 1 1 auto; display: flex; overflow-x: auto; scrollbar-width: none; }
154
+ .abas-rolo::-webkit-scrollbar { display: none; }
155
+ .aba {
156
+ position: relative; flex: 0 0 auto; display: flex; align-items: center; gap: 7px;
157
+ max-width: 220px; padding: 0 8px 0 12px; cursor: pointer;
158
+ border: 0; border-right: 1px solid var(--borda); background: none;
159
+ color: var(--apagado); font: inherit; font-size: 12.5px;
160
+ }
161
+ .aba:hover { color: var(--texto); }
162
+ .aba[aria-selected="true"] { background: var(--editor-fundo); color: var(--texto); }
163
+ .aba[aria-selected="true"]::before {
164
+ content: ""; position: absolute; left: 0; right: 0; top: 0; height: 2px; background: var(--azul);
165
+ }
166
+ .aba .nome-aba { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
167
+ .aba .fechar {
168
+ display: grid; place-items: center; width: 18px; height: 18px; border-radius: 5px;
169
+ color: var(--apagado); font-size: 14px; line-height: 1;
170
+ }
171
+ .aba .fechar:hover { background: var(--alto); color: var(--texto); }
172
+ /* Não salvo: a bolinha no lugar do ×, como em todo editor — e o título da
173
+ aba diz "não salvo" para quem não conhece a convenção. */
174
+ .aba.sujo .fechar::before { content: "●"; font-size: 10px; color: var(--alerta); }
175
+ .aba.sujo .fechar > span { display: none; }
176
+ .aba.sujo:hover .fechar::before { display: none; }
177
+ .aba.sujo:hover .fechar > span { display: inline; }
178
+
179
+ .aba-previa {
180
+ flex: 0 0 auto; display: flex; align-items: center; gap: 7px; margin: 5px 6px; padding: 0 12px;
181
+ border: 1px solid var(--borda-viva); border-radius: 8px; background: none; cursor: pointer;
182
+ color: var(--fraco); font: inherit; font-size: 12.5px;
183
+ }
184
+ .aba-previa:hover { color: var(--texto); border-color: var(--azul); }
185
+ .aba-previa[aria-pressed="true"] {
186
+ color: var(--azul); border-color: color-mix(in srgb, var(--azul) 55%, transparent);
187
+ background: color-mix(in srgb, var(--azul) 9%, transparent);
188
+ }
189
+
190
+ /* ── centro: editor + prévia lado a lado ──────────────────────────────── */
191
+ .palco { flex: 1 1 0; display: flex; min-height: 0; }
192
+ .area-editor { position: relative; flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; }
193
+ .area-previa {
194
+ flex: 1 1 0; min-width: 0; display: flex; flex-direction: column;
195
+ border-left: 1px solid var(--borda); background: var(--painel);
196
+ }
197
+ /* Janela estreita: editor e prévia não cabem juntos com a árvore e o chat.
198
+ A prévia passa a ocupar o palco inteiro e o botão dela vira a troca. */
199
+ @media (max-width: 1240px) {
200
+ .centro.com-previa .area-editor { display: none; }
201
+ .area-previa { border-left: 0; }
202
+ }
203
+
204
+ .editor { flex: 1 1 0; display: flex; min-height: 0; font-family: var(--mono); font-size: var(--codigo-tam); }
205
+ .calha {
206
+ flex: 0 0 auto; min-width: 48px; padding: 10px 12px 10px 10px; overflow: hidden;
207
+ text-align: right; color: var(--apagado); opacity: .7; line-height: var(--linha);
208
+ font-variant-numeric: tabular-nums; user-select: none; white-space: pre;
209
+ border-right: 1px solid var(--borda);
210
+ }
211
+ .calha .viva { color: var(--texto); opacity: 1; }
212
+ .texto-volta { position: relative; flex: 1 1 0; min-width: 0; }
213
+ .linha-atual {
214
+ position: absolute; left: 0; right: 0; top: 10px; height: var(--linha);
215
+ background: color-mix(in srgb, var(--azul) 6%, transparent); pointer-events: none;
216
+ }
217
+ #codigo {
218
+ position: absolute; inset: 0; width: 100%; height: 100%;
219
+ padding: 10px 16px; margin: 0; border: 0; outline: none; resize: none;
220
+ background: transparent; color: var(--texto); caret-color: var(--azul);
221
+ font: inherit; line-height: var(--linha); tab-size: 2; white-space: pre; overflow: auto;
222
+ }
223
+ #codigo::selection { background: color-mix(in srgb, var(--azul) 26%, transparent); }
224
+
225
+ .faixa {
226
+ flex: 0 0 auto; display: flex; align-items: center; gap: 10px; padding: 8px 14px;
227
+ font-size: 12.5px; color: var(--texto);
228
+ background: color-mix(in srgb, var(--alerta) 12%, transparent);
229
+ border-bottom: 1px solid color-mix(in srgb, var(--alerta) 32%, transparent);
230
+ }
231
+ .faixa > span { flex: 1 1 auto; }
232
+ .faixa > span::before { content: "⚠ "; color: var(--alerta); }
233
+ .faixa-btn {
234
+ font: inherit; font-size: 12px; padding: 4px 10px; border-radius: 7px; cursor: pointer;
235
+ border: 1px solid var(--borda-viva); background: var(--painel); color: var(--texto);
236
+ }
237
+ .faixa-btn:hover { border-color: var(--alerta); }
238
+
239
+ .aviso-arquivo {
240
+ flex: 1 1 0; display: flex; flex-direction: column; align-items: center; justify-content: center;
241
+ gap: 14px; padding: 24px; color: var(--fraco); text-align: center;
242
+ }
243
+ .aviso-arquivo img {
244
+ max-width: min(100%, 560px); max-height: 60%; border-radius: 8px; border: 1px solid var(--borda);
245
+ background: repeating-conic-gradient(var(--alto) 0 25%, transparent 0 50%) 0 0 / 16px 16px;
246
+ }
247
+ .aviso-arquivo p { margin: 0; max-width: 46ch; line-height: 1.55; }
248
+
249
+ .boas-vindas {
250
+ flex: 1 1 0; display: flex; flex-direction: column; align-items: center; justify-content: center;
251
+ gap: 14px; padding: 28px; text-align: center;
252
+ }
253
+ .boas-vindas h1 { margin: 6px 0 0; font-size: 22px; font-weight: 620; letter-spacing: -.02em; text-wrap: balance; }
254
+ .boas-vindas p { margin: 0; max-width: 50ch; color: var(--fraco); line-height: 1.6; }
255
+ .atalhos { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px 22px; margin: 10px 0 0; }
256
+ .atalhos > div { display: flex; align-items: center; gap: 8px; }
257
+ .atalhos dt { display: flex; gap: 3px; }
258
+ .atalhos dd { margin: 0; font-size: 12.5px; color: var(--apagado); }
259
+ kbd {
260
+ font-family: var(--mono); font-size: 11px; color: var(--texto);
261
+ padding: 1px 6px; border-radius: 5px; border: 1px solid var(--borda-viva);
262
+ border-bottom-width: 2px; background: var(--alto);
263
+ }
264
+
265
+ .previa-barra {
266
+ flex: 0 0 auto; display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 8px 0 12px;
267
+ border-bottom: 1px solid var(--borda); background: var(--barra); font-size: 12px;
268
+ }
269
+ .bolinha { width: 8px; height: 8px; border-radius: 50%; background: var(--azul); flex: 0 0 auto; }
270
+ .previa-endereco {
271
+ font-family: var(--mono); color: var(--fraco); padding: 3px 10px; border-radius: 6px;
272
+ background: var(--painel); border: 1px solid var(--borda);
273
+ min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
274
+ }
275
+ .previa-estado { font-family: var(--mono); font-size: 11px; color: var(--apagado); white-space: nowrap; }
276
+ .previa-estado.fresco { color: var(--azul); }
277
+ #previa { flex: 1 1 0; width: 100%; border: 0; background: #fff; }
278
+ .previa-vazia { flex: 1 1 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; color: var(--fraco); padding: 24px; text-align: center; }
279
+ .previa-vazia p { margin: 0; line-height: 1.8; }
280
+
281
+ /* ── terminal ─────────────────────────────────────────────────────────── */
282
+ .terminal {
283
+ grid-area: terminal; display: flex; flex-direction: column; min-height: 0; min-width: 0;
284
+ border-top: 1px solid var(--borda); background: var(--fundo);
285
+ }
286
+ .terminal-dica { text-transform: none; letter-spacing: 0; font-size: 11.5px; color: var(--apagado); opacity: .8; }
287
+ .terminal-saida {
288
+ flex: 1 1 0; overflow: auto; padding: 8px 14px 12px; font-size: 12px; line-height: 1.55;
289
+ }
290
+
291
+ /* ── chat ─────────────────────────────────────────────────────────────── */
292
+ .chat {
293
+ grid-area: chat; display: flex; flex-direction: column; min-height: 0; min-width: 0;
294
+ border-left: 1px solid var(--borda); background: var(--painel);
295
+ }
296
+ .chat-estado {
297
+ margin-left: auto; display: none; align-items: center; gap: 7px;
298
+ text-transform: none; letter-spacing: 0; font-family: var(--mono); font-size: 11.5px; color: var(--azul);
299
+ }
300
+ body.ocupado .chat-estado { display: flex; }
301
+ .chat-rolo { flex: 1 1 0; overflow-y: auto; }
302
+ .chat-vazio { padding: 28px 18px 8px; text-align: center; color: var(--fraco); }
303
+ .chat-vazio p { margin: 0 0 12px; }
304
+ .chat-vazio .sugestoes { display: flex; flex-direction: column; align-items: stretch; gap: 7px; }
305
+ .chat-tira { padding: 4px 14px 18px; }
306
+ .chat-tira .voce { margin: 16px 0 10px; }
307
+ .chat-tira .voce > div { max-width: 92%; font-size: 13.5px; }
308
+ .chat-tira .saida { font-size: 12.2px; }
309
+ .chat-tira .aviso-erro { font-size: 13px; }
310
+
311
+ .chat-caixa { flex: 0 0 auto; display: flex; flex-direction: column; gap: 7px; padding: 10px 12px 12px; border-top: 1px solid var(--borda); }
312
+ .chat-caixa .campo-volta { align-items: flex-end; padding: 7px 7px 7px 11px; }
313
+ .chat-caixa .enviar { width: 30px; height: 30px; border-radius: 8px; }
314
+ .anexo {
315
+ display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--fraco); cursor: pointer;
316
+ min-width: 0;
317
+ }
318
+ .anexo input { accent-color: var(--azul); margin: 0; }
319
+ .anexo span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
320
+ .anexo b { font-family: var(--mono); font-weight: 500; color: var(--texto); }
321
+
322
+ /* ── barra de status ──────────────────────────────────────────────────── */
323
+ .barra {
324
+ grid-area: barra; display: flex; align-items: center; gap: 16px; padding: 0 12px;
325
+ font-family: var(--mono); font-size: 11.5px; color: var(--apagado);
326
+ background: var(--barra); border-top: 1px solid var(--borda); white-space: nowrap; min-width: 0;
327
+ }
328
+ .barra-espaco { flex: 1 1 auto; }
329
+ .barra-item { display: flex; align-items: center; gap: 6px; font-variant-numeric: tabular-nums; }
330
+ .barra-item:empty { display: none; }
331
+ .ponto { width: 7px; height: 7px; border-radius: 50%; background: var(--apagado); }
332
+ body.ocupado .ponto { background: var(--azul); }
333
+ .barra-aviso { color: var(--alerta); overflow: hidden; text-overflow: ellipsis; min-width: 0; }
334
+ .barra-salvo.sujo { color: var(--alerta); }
335
+
336
+ /* ── janelas menores ──────────────────────────────────────────────────── */
337
+ @media (max-width: 1100px) {
338
+ body.ide { --w-arvore: 200px; --w-chat: 316px; --h-term: 160px; }
339
+ .topo-projeto .caminho, .terminal-dica, #esforco { display: none; }
340
+ }
341
+ @media (max-width: 760px) {
342
+ /* Abaixo disso não é mais IDE: é o chat. A janela do /rc faz isso melhor. */
343
+ body.ide { grid-template-columns: 0 minmax(0, 1fr) 0; }
344
+ .arvore, .chat { display: none; }
345
+ }
package/app/ide.html ADDED
@@ -0,0 +1,183 @@
1
+ <!doctype html>
2
+ <html lang="pt-BR" data-tema="escuro">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>PrimoCode IDE</title>
7
+ <link rel="stylesheet" href="/app.css">
8
+ <link rel="stylesheet" href="/ide.css">
9
+ <link rel="icon" href="/icone.svg">
10
+ </head>
11
+ <!--
12
+ A PrimoCode IDE (/ide, ou `primo ide` no terminal).
13
+
14
+ "Um app ao invés de CLI, aí fica uma IDE e uma CLI."
15
+
16
+ Mesmo servidor, mesma chave e mesmo fluxo da janela do /rc (lib/app.js);
17
+ o que ela tem a mais mora em lib/ide.js. As cores vêm do app.css — a IDE
18
+ não define paleta própria, para as duas janelas serem o mesmo produto.
19
+
20
+ Sem CDN de propósito: nada de CodeMirror nem Monaco. A regra da casa é "sem
21
+ dependência externa", e um editor que some quando a internet cai é o pior
22
+ editor para quem está começando. O editor abaixo é um <textarea> com número
23
+ de linha, Tab e Ctrl+S — leve, e sempre funciona.
24
+ -->
25
+ <body class="ide">
26
+
27
+ <header class="topo">
28
+ <div class="topo-marca">
29
+ <span id="marcaTopo"></span>
30
+ <span class="nome">PrimoCode</span>
31
+ <span class="selo">IDE</span>
32
+ </div>
33
+ <div class="topo-projeto" title="pasta do projeto">
34
+ <svg width="14" height="14" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/></svg>
35
+ <b id="nomeProjeto">—</b>
36
+ <span id="pastaProjeto" class="caminho"></span>
37
+ </div>
38
+ <div class="topo-dir">
39
+ <span class="chip" id="modo"></span>
40
+ <span class="chip" id="esforco"></span>
41
+ <button class="botao-topo" id="btnTerminal" aria-pressed="true" title="Mostrar ou esconder o terminal (Ctrl+J)">
42
+ <svg width="14" height="14" viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="4" width="18" height="16" rx="2.5" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="m7 9 3 3-3 3M12.5 15H17" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
43
+ Terminal
44
+ </button>
45
+ <a class="botao-topo" id="linkChat" href="/" title="Abrir a janela só de chat">Só o chat</a>
46
+ </div>
47
+ </header>
48
+
49
+ <aside class="arvore" aria-label="Arquivos do projeto">
50
+ <div class="painel-cabeca">
51
+ <span>Arquivos</span>
52
+ <button class="icone-btn" id="btnRecarregar" title="Recarregar a lista" aria-label="Recarregar a lista">
53
+ <svg width="14" height="14" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 11a8 8 0 1 0-2.3 5.7M20 4v7h-7" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"/></svg>
54
+ </button>
55
+ </div>
56
+ <div class="lista" id="lista" role="tree"></div>
57
+ <div class="arvore-pe" id="arvorePe"></div>
58
+ </aside>
59
+
60
+ <main class="centro" id="centro">
61
+ <div class="abas" role="tablist" id="abas">
62
+ <div class="abas-rolo" id="abasRolo"></div>
63
+ <button class="aba-previa" id="btnPrevia" aria-pressed="false" title="Mostrar a prévia do site (atualiza sozinha)">
64
+ <svg width="14" height="14" viewBox="0 0 24 24" aria-hidden="true"><path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12Z" fill="none" stroke="currentColor" stroke-width="1.8"/><circle cx="12" cy="12" r="2.8" fill="none" stroke="currentColor" stroke-width="1.8"/></svg>
65
+ Prévia
66
+ </button>
67
+ </div>
68
+
69
+ <div class="palco">
70
+ <section class="area-editor" id="areaEditor">
71
+ <div class="faixa" id="faixa" hidden>
72
+ <span id="faixaTexto"></span>
73
+ <button class="faixa-btn" id="faixaRecarregar">Ver a versão do disco</button>
74
+ <button class="faixa-btn" id="faixaManter">Manter a minha</button>
75
+ </div>
76
+
77
+ <div class="editor" id="editor" hidden>
78
+ <div class="calha" id="calha" aria-hidden="true"></div>
79
+ <div class="texto-volta">
80
+ <div class="linha-atual" id="linhaAtual" aria-hidden="true"></div>
81
+ <textarea id="codigo" spellcheck="false" autocomplete="off" autocapitalize="off"
82
+ wrap="off" aria-label="Editor de código"></textarea>
83
+ </div>
84
+ </div>
85
+
86
+ <div class="aviso-arquivo" id="avisoArquivo" hidden>
87
+ <img id="avisoImagem" alt="" hidden>
88
+ <p id="avisoTexto"></p>
89
+ </div>
90
+
91
+ <div class="boas-vindas" id="boasVindas">
92
+ <span id="marcaGrande"></span>
93
+ <h1>Seu projeto, o Primo e o site, lado a lado.</h1>
94
+ <p>Abra um arquivo na lista à esquerda, ou peça ao Primo no chat. Quando ele
95
+ mexer num arquivo, a lista acende e a prévia se atualiza sozinha.</p>
96
+ <dl class="atalhos">
97
+ <div><dt><kbd>Ctrl</kbd> <kbd>S</kbd></dt><dd>salva o arquivo</dd></div>
98
+ <div><dt><kbd>Tab</kbd></dt><dd>indenta a linha</dd></div>
99
+ <div><dt><kbd>Ctrl</kbd> <kbd>J</kbd></dt><dd>mostra o terminal</dd></div>
100
+ </dl>
101
+ </div>
102
+ </section>
103
+
104
+ <section class="area-previa" id="areaPrevia" hidden>
105
+ <div class="previa-barra">
106
+ <span class="bolinha" aria-hidden="true"></span>
107
+ <span class="previa-endereco" id="previaEndereco">index.html</span>
108
+ <span class="previa-estado" id="previaEstado"></span>
109
+ <button class="icone-btn" id="btnRecarregarPrevia" title="Recarregar a prévia" aria-label="Recarregar a prévia">
110
+ <svg width="14" height="14" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 11a8 8 0 1 0-2.3 5.7M20 4v7h-7" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"/></svg>
111
+ </button>
112
+ </div>
113
+ <!-- Sandbox SEM a mesma origem da IDE: ver lib/ide.js. O código da
114
+ prévia não pode herdar a chave e dar ordens ao agente. -->
115
+ <iframe id="previa" title="Prévia do site"
116
+ sandbox="allow-scripts allow-forms allow-modals allow-popups allow-pointer-lock"></iframe>
117
+ <div class="previa-vazia" id="previaVazia" hidden>
118
+ <p><b>Ainda não há um index.html neste projeto.</b></p>
119
+ <p>Peça ao Primo: <button class="sugestao" data-manda="crie uma página inicial simples em index.html">crie uma página inicial</button></p>
120
+ </div>
121
+ </section>
122
+ </div>
123
+ </main>
124
+
125
+ <section class="terminal" id="terminal" aria-label="Terminal">
126
+ <div class="painel-cabeca">
127
+ <span>Terminal</span>
128
+ <span class="terminal-dica">a mesma saída do PrimoCode no terminal</span>
129
+ <button class="icone-btn" id="btnFecharTerminal" title="Esconder o terminal" aria-label="Esconder o terminal">
130
+ <svg width="14" height="14" viewBox="0 0 24 24" aria-hidden="true"><path d="m6 9 6 6 6-6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
131
+ </button>
132
+ </div>
133
+ <pre class="saida terminal-saida" id="terminalSaida"></pre>
134
+ </section>
135
+
136
+ <aside class="chat" aria-label="Chat com o Primo">
137
+ <div class="painel-cabeca">
138
+ <span>Primo</span>
139
+ <span class="chat-estado" id="chatEstado"><span class="pulso"></span> trabalhando</span>
140
+ </div>
141
+ <div class="chat-rolo" id="chatRolo">
142
+ <div class="chat-vazio" id="chatVazio">
143
+ <p>Peça do jeito que você falaria.</p>
144
+ <div class="sugestoes">
145
+ <button class="sugestao" data-manda="explica o que este projeto faz">explica este projeto</button>
146
+ <button class="sugestao" data-manda="crie um site de uma página sobre mim, em index.html">crie um site sobre mim</button>
147
+ <button class="sugestao" data-manda="/help">comandos</button>
148
+ </div>
149
+ </div>
150
+ <div class="chat-tira" id="chatTira"></div>
151
+ </div>
152
+ <form class="chat-caixa" id="forma">
153
+ <label class="anexo" id="anexo" hidden>
154
+ <input type="checkbox" id="anexar" checked>
155
+ <span>incluir <b id="anexoNome"></b></span>
156
+ </label>
157
+ <div class="campo-volta">
158
+ <span class="chevron">❯</span>
159
+ <textarea id="campo" rows="1" placeholder="Peça alguma coisa…" autocomplete="off" spellcheck="false"></textarea>
160
+ <button type="submit" id="mandar" class="enviar" title="Enviar (Enter)" aria-label="Enviar">
161
+ <svg width="16" height="16" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h13M12.5 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
162
+ </button>
163
+ <button type="button" id="parar" class="enviar parar" title="Parar (Esc)" aria-label="Parar" hidden>
164
+ <svg width="14" height="14" viewBox="0 0 24 24" aria-hidden="true"><rect x="7" y="7" width="10" height="10" rx="2" fill="currentColor"/></svg>
165
+ </button>
166
+ </div>
167
+ </form>
168
+ </aside>
169
+
170
+ <footer class="barra">
171
+ <span class="barra-item" id="barraAgente"><span class="ponto"></span> <span id="barraAgenteTxt">Primo pronto</span></span>
172
+ <span class="barra-item barra-aviso" id="barraAviso"></span>
173
+ <span class="barra-espaco"></span>
174
+ <span class="barra-item" id="barraPosicao"></span>
175
+ <span class="barra-item" id="barraLingua"></span>
176
+ <span class="barra-item" id="barraSalvo"></span>
177
+ </footer>
178
+
179
+ <script src="/primo-marca.js"></script>
180
+ <script src="/ansi.js"></script>
181
+ <script src="/ide.js"></script>
182
+ </body>
183
+ </html>