primocode 9.8.0-beta.7 → 9.8.0-beta.8

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/app/index.html CHANGED
@@ -8,138 +8,213 @@
8
8
  <link rel="icon" href="/icone.svg">
9
9
  </head>
10
10
  <!--
11
- A janela do PrimoCode (/rc).
11
+ A janela do PrimoCode — /rc, /ide e `primo ide` abrem esta mesma página.
12
12
 
13
- É a MESMA interface da página /code do Conecta Primo — de propósito. Quem
14
- usa o terminal, abre a janela e depois entra pelo navegador tem de
15
- reconhecer onde está no primeiro olhar: mesma barra lateral, mesmo botão de
16
- novo chat, mesmo prompt `❯`, mesmo mascote na tela vazia.
13
+ O layout é o de um app de mesa de agente: barra lateral com novo chat,
14
+ projetos e recentes; a pergunta no meio da tela vazia; o compositor embaixo
15
+ com projeto, anexo, permissão, esforço e voz; e um painel à direita com a
16
+ prévia do site e os arquivos (a IDE antiga, sem o editor).
17
17
 
18
- O que muda é de onde vem o conteúdo: aqui o histórico vem do servidor local
19
- (/api/conversas), que usa o lib/nuvem.js — a mesma cifra e a mesma coleção
20
- que a web lê direto do Firestore.
18
+ Os ícones são SVG em linha, de propósito: sem fonte de ícone, sem CDN, sem
19
+ pedir nada fora da máquina (regra do AGENTS.md). Cada um é um <symbol> aqui
20
+ em cima e um <use> onde aparece — o mesmo desenho não é copiado dez vezes.
21
21
  -->
22
22
  <body>
23
23
 
24
- <div class="tela">
25
- <aside class="lado" id="lado" aria-label="Conversas e ajustes">
26
- <div class="lado-topo">
27
- <div class="marca-linha">
28
- <span id="marcaTopo"></span>
29
- <span class="nome">PrimoCode</span>
30
- <span class="versao" id="versao"></span>
31
- </div>
32
- <button class="novo" id="btnNovo">
33
- <svg width="15" height="15" viewBox="0 0 24 24" aria-hidden="true">
34
- <path d="M12 5v14M5 12h14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/>
35
- </svg>
36
- Novo chat
24
+ <svg width="0" height="0" style="position:absolute" aria-hidden="true">
25
+ <defs>
26
+ <symbol id="i-lado" viewBox="0 0 24 24"><rect x="3.5" y="4.5" width="17" height="15" rx="3"/><path d="M9.5 4.5v15"/></symbol>
27
+ <symbol id="i-painel" viewBox="0 0 24 24"><rect x="3.5" y="4.5" width="17" height="15" rx="3"/><path d="M14.5 4.5v15"/></symbol>
28
+ <symbol id="i-chev" viewBox="0 0 24 24"><path d="m7 10 5 5 5-5"/></symbol>
29
+ <symbol id="i-busca" viewBox="0 0 24 24"><circle cx="11" cy="11" r="6"/><path d="m20 20-4.6-4.6"/></symbol>
30
+ <symbol id="i-sino" viewBox="0 0 24 24"><path d="M6.5 16.5V11a5.5 5.5 0 0 1 11 0v5.5l1.5 1.5H5z"/><path d="M10 20.5a2 2 0 0 0 4 0"/></symbol>
31
+ <symbol id="i-editar" viewBox="0 0 24 24"><path d="M11 4.5H7a2.5 2.5 0 0 0-2.5 2.5v10A2.5 2.5 0 0 0 7 19.5h10a2.5 2.5 0 0 0 2.5-2.5v-4"/><path d="M17.8 3.9a1.8 1.8 0 0 1 2.5 2.5L12 14.7l-3.3.8.8-3.3z"/></symbol>
32
+ <symbol id="i-mais-circ" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8"/><path d="M12 8.5v7M8.5 12h7"/></symbol>
33
+ <symbol id="i-terminal" viewBox="0 0 24 24"><rect x="3.5" y="4.5" width="17" height="15" rx="3"/><path d="m7.5 10 2.5 2-2.5 2M12.5 14.5h4"/></symbol>
34
+ <symbol id="i-olho" viewBox="0 0 24 24"><path d="M2.8 12S6 5.8 12 5.8 21.2 12 21.2 12 18 18.2 12 18.2 2.8 12 2.8 12z"/><circle cx="12" cy="12" r="2.6"/></symbol>
35
+ <symbol id="i-barra" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="m14 7.5-4 9"/></symbol>
36
+ <symbol id="i-ajuda" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M9.7 9.6a2.4 2.4 0 1 1 3.3 2.2c-.6.3-1 .8-1 1.5v.4"/><circle cx="12" cy="16.6" r=".4"/></symbol>
37
+ <symbol id="i-pasta" viewBox="0 0 24 24"><path d="M3.5 7.5A2 2 0 0 1 5.5 5.5h3.8l2 2h7.2a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2h-13a2 2 0 0 1-2-2z"/></symbol>
38
+ <symbol id="i-mais" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></symbol>
39
+ <symbol id="i-perigo" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M12 7.8v5.2"/><circle cx="12" cy="16.3" r=".5"/></symbol>
40
+ <symbol id="i-escudo" viewBox="0 0 24 24"><path d="M12 3.5 5.5 6v5.2c0 4.3 2.8 7.6 6.5 9.3 3.7-1.7 6.5-5 6.5-9.3V6z"/><path d="m9.2 12 2 2 3.8-4"/></symbol>
41
+ <symbol id="i-mao" viewBox="0 0 24 24"><path d="M12 3.5 5.5 6v5.2c0 4.3 2.8 7.6 6.5 9.3 3.7-1.7 6.5-5 6.5-9.3V6z"/><path d="M12 8.5v4"/><circle cx="12" cy="15.6" r=".5"/></symbol>
42
+ <symbol id="i-mic" viewBox="0 0 24 24"><rect x="9" y="3.5" width="6" height="11" rx="3"/><path d="M5.8 11.5a6.2 6.2 0 0 0 12.4 0M12 17.7v2.8"/></symbol>
43
+ <symbol id="i-seta" viewBox="0 0 24 24"><path d="M12 19V5.5M6.5 11 12 5.5l5.5 5.5"/></symbol>
44
+ <symbol id="i-fechar" viewBox="0 0 24 24"><path d="m6.5 6.5 11 11M17.5 6.5l-11 11"/></symbol>
45
+ <symbol id="i-recarregar" viewBox="0 0 24 24"><path d="M19 12a7 7 0 1 1-2.1-5"/><path d="M19.2 4.5v4h-4"/></symbol>
46
+ <symbol id="i-arquivo" viewBox="0 0 24 24"><path d="M7 3.5h6.5l4.5 4.5v11a1.5 1.5 0 0 1-1.5 1.5h-9.5A1.5 1.5 0 0 1 5.5 19V5A1.5 1.5 0 0 1 7 3.5z"/><path d="M13.5 3.5V8H18"/></symbol>
47
+ <symbol id="i-ler" viewBox="0 0 24 24"><path d="M7 3.5h6.5l4.5 4.5v11a1.5 1.5 0 0 1-1.5 1.5h-9.5A1.5 1.5 0 0 1 5.5 19V5A1.5 1.5 0 0 1 7 3.5z"/><path d="M8.5 12.5h7M8.5 16h5"/></symbol>
48
+ <symbol id="i-rodar" viewBox="0 0 24 24"><path d="m6 8.5 4 3.5-4 3.5M12 16h6"/></symbol>
49
+ <symbol id="i-web" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M3.5 12h17M12 3.5c2.4 2.4 3.4 5.3 3.4 8.5s-1 6.1-3.4 8.5c-2.4-2.4-3.4-5.3-3.4-8.5s1-6.1 3.4-8.5z"/></symbol>
50
+ <symbol id="i-computador" viewBox="0 0 24 24"><rect x="3.5" y="4.5" width="17" height="11.5" rx="2"/><path d="M9 20h6M12 16v4"/></symbol>
51
+ <symbol id="i-plano" viewBox="0 0 24 24"><path d="M9.5 7h10M9.5 12h10M9.5 17h10"/><path d="m4 7 1.2 1.2L7 6.3M4 12l1.2 1.2L7 11.3"/><circle cx="5.5" cy="17" r=".8"/></symbol>
52
+ <symbol id="i-agente" viewBox="0 0 24 24"><rect x="5" y="7.5" width="14" height="11" rx="3"/><path d="M12 7.5V4.5"/><circle cx="9.5" cy="12.5" r=".9"/><circle cx="14.5" cy="12.5" r=".9"/></symbol>
53
+ <symbol id="i-pare" viewBox="0 0 24 24"><rect x="7.5" y="7.5" width="9" height="9" rx="1.6" fill="currentColor" stroke="none"/></symbol>
54
+ </defs>
55
+ </svg>
56
+
57
+ <div class="app" id="app">
58
+ <aside class="lado" id="lado" aria-label="Conversas e projetos">
59
+ <div class="lado-barra">
60
+ <button class="icone-btn" id="btnLado" type="button" aria-label="Recolher a barra lateral" title="Barra lateral (Ctrl+B)">
61
+ <svg class="ic"><use href="#i-lado"/></svg>
37
62
  </button>
38
63
  </div>
39
64
 
40
- <div class="rotulo">Conversas</div>
41
- <div class="recentes" id="recentes">
42
- <div class="vazio-lista" id="listaVazia">
43
- As conversas aparecem aqui quando você entra na sua conta com <code>/login</code>.
65
+ <div class="lado-marca">
66
+ <button class="marca-btn" id="btnMarca" type="button" aria-haspopup="menu">
67
+ <span>PrimoCode</span><svg class="ic ic-p"><use href="#i-chev"/></svg>
68
+ </button>
69
+ <div class="lado-acoes">
70
+ <button class="icone-btn" id="btnBusca" type="button" aria-label="Buscar nos recentes" title="Buscar"><svg class="ic"><use href="#i-busca"/></svg></button>
71
+ <button class="icone-btn" id="btnAvisos" type="button" aria-pressed="false" aria-label="Avisar quando terminar" title="Avisar quando o Primo terminar"><svg class="ic"><use href="#i-sino"/></svg></button>
44
72
  </div>
45
73
  </div>
46
74
 
47
- <div class="lado-pe">
48
- <button class="pe-btn" id="btnConfig">
49
- <svg width="15" height="15" viewBox="0 0 24 24" aria-hidden="true">
50
- <circle cx="12" cy="12" r="3.1" fill="none" stroke="currentColor" stroke-width="1.9"/>
51
- <path d="M12 2.8v2.4M12 18.8v2.4M21.2 12h-2.4M5.2 12H2.8M18.5 5.5l-1.7 1.7M7.2 16.8l-1.7 1.7M18.5 18.5l-1.7-1.7M7.2 7.2 5.5 5.5"
52
- fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/>
53
- </svg>
54
- Configurações
55
- </button>
56
- <div class="chips">
57
- <span class="chip" id="pasta" title="pasta de trabalho"></span>
58
- <span class="chip" id="modo"></span>
59
- <span class="chip" id="esforco"></span>
75
+ <div class="busca" id="busca" hidden>
76
+ <input id="campoBusca" type="search" placeholder="Buscar nos recentes" autocomplete="off">
77
+ </div>
78
+
79
+ <nav class="nav" aria-label="Principal">
80
+ <div class="nav-linha">
81
+ <button class="nav-item ativo" id="btnNovo" type="button"><svg class="ic"><use href="#i-editar"/></svg>Novo chat</button>
82
+ <button class="icone-btn" id="btnNovoProjeto" type="button" aria-label="Novo projeto" title="Novo projeto em ~/primocode"><svg class="ic"><use href="#i-mais-circ"/></svg></button>
60
83
  </div>
84
+ <button class="nav-item" id="navTerminal" type="button" aria-pressed="false"><svg class="ic"><use href="#i-terminal"/></svg>Terminal</button>
85
+ <button class="nav-item" id="navPrevia" type="button"><svg class="ic"><use href="#i-olho"/></svg>Prévia do site</button>
86
+ <button class="nav-item" id="navComandos" type="button"><svg class="ic"><use href="#i-barra"/></svg>Comandos</button>
87
+ </nav>
88
+
89
+ <div class="rolagem-lado">
90
+ <div class="secao" id="secaoProjetos">Projetos</div>
91
+ <div class="lista" id="listaProjetos"><div class="vazio-lista">Nenhum projeto</div></div>
92
+
93
+ <div class="secao" id="secaoRecentes">Recentes</div>
94
+ <div class="lista" id="listaRecentes"><div class="vazio-lista">Nenhuma conversa ainda</div></div>
95
+ </div>
96
+
97
+ <div class="lado-pe">
98
+ <div class="usuario"><span class="avatar" id="avatar">PC</span><span class="nome-usuario" id="nomeUsuario">Você</span></div>
99
+ <button class="icone-btn" id="btnAjuda" type="button" aria-label="Atalhos e comandos" title="Atalhos e comandos"><svg class="ic"><use href="#i-ajuda"/></svg></button>
61
100
  </div>
62
101
  </aside>
63
102
 
64
- <main class="conversa">
103
+ <main class="centro">
104
+ <header class="topo">
105
+ <button class="icone-btn" id="btnLadoAbrir" type="button" aria-label="Abrir a barra lateral" title="Barra lateral (Ctrl+B)"><svg class="ic"><use href="#i-lado"/></svg></button>
106
+ <div class="topo-titulo" id="topoTitulo"></div>
107
+ <button class="icone-btn" id="btnPainel" type="button" aria-pressed="false" aria-label="Abrir a prévia e os arquivos" title="Prévia e arquivos"><svg class="ic"><use href="#i-painel"/></svg></button>
108
+ </header>
109
+
65
110
  <div class="rolo" id="rolo">
66
- <div class="vazio" id="vazio">
67
- <span id="marcaGrande"></span>
68
- <h1>O que a gente vai construir?</h1>
69
- <p>Escreva do jeito que você falaria. Ele lê a pasta, escreve o código,
70
- roda o que precisar e conta o que fez.</p>
71
- <div class="sugestoes">
72
- <button class="sugestao" data-manda="/all">o que ele sabe fazer</button>
73
- <button class="sugestao" data-manda="/project">meus projetos</button>
74
- <button class="sugestao" data-manda="/effort">esforço</button>
75
- <button class="sugestao" data-manda="/help">comandos</button>
76
- </div>
77
- </div>
78
- <div id="conversa" class="tira" hidden></div>
111
+ <section class="vazio" id="vazio">
112
+ <span class="mascote" id="mascote"></span>
113
+ <h1>O que vamos criar?</h1>
114
+ </section>
115
+ <div class="chat" id="chat" hidden aria-live="polite"></div>
79
116
  </div>
80
117
 
81
- <div class="rodape">
82
- <div id="pensando" hidden><span class="pulso"></span><span id="pensando-txt">trabalhando</span></div>
83
- <form id="forma" class="caixa">
84
- <div class="campo-volta">
85
- <span class="chevron">❯</span>
86
- <textarea id="campo" rows="1" placeholder="Peça alguma coisa…"
87
- autocomplete="off" spellcheck="false"></textarea>
118
+ <section class="terminal" id="terminal" hidden aria-label="Terminal">
119
+ <header class="terminal-topo">
120
+ <span><b>Terminal</b> <small>o mesmo que aparece no CLI</small></span>
121
+ <button class="icone-btn" id="fecharTerminal" type="button" aria-label="Voltar para o chat"><svg class="ic"><use href="#i-fechar"/></svg></button>
122
+ </header>
123
+ <pre class="terminal-saida" id="terminalSaida"></pre>
124
+ </section>
125
+
126
+ <footer class="rodape">
127
+ <div class="compositor-zona" id="zona">
128
+ <div class="projeto-barra">
129
+ <button class="projeto-btn" id="btnProjeto" type="button" aria-haspopup="menu">
130
+ <svg class="ic"><use href="#i-pasta"/></svg><span id="projetoNome">Escolher projeto</span>
131
+ </button>
88
132
  </div>
89
- <button type="submit" id="mandar" class="enviar" title="Enviar (Enter)" aria-label="Enviar">
90
- <svg width="17" height="17" viewBox="0 0 24 24" aria-hidden="true">
91
- <path d="M5 12h13M12.5 6l6 6-6 6" fill="none" stroke="currentColor"
92
- stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>
93
- </svg>
94
- </button>
95
- <button type="button" id="parar" class="enviar parar" title="Parar (Esc)" hidden aria-label="Parar">
96
- <svg width="15" height="15" viewBox="0 0 24 24" aria-hidden="true"><rect x="7" y="7" width="10" height="10" rx="2" fill="currentColor"/></svg>
97
- </button>
98
- </form>
99
- <div class="dica" id="dica">Enter manda · Shift+Enter quebra linha · arraste um arquivo aqui</div>
100
- </div>
133
+ <form class="compositor" id="forma">
134
+ <div class="anexos" id="anexos" hidden></div>
135
+ <textarea id="campo" rows="1" placeholder="Peça qualquer coisa" autocomplete="off" spellcheck="false" aria-label="Pedido"></textarea>
136
+ <div class="controles">
137
+ <button class="icone-btn grande" id="btnAnexar" type="button" aria-label="Anexar arquivo" title="Anexar arquivo"><svg class="ic"><use href="#i-mais"/></svg></button>
138
+ <input id="arquivo" type="file" multiple hidden>
139
+ <button class="chip-perm" id="btnPermissao" type="button" aria-haspopup="menu" data-modo="auto">
140
+ <svg class="ic" id="permIcone"><use href="#i-escudo"/></svg><span id="permRotulo">Pergunta antes do perigoso</span>
141
+ </button>
142
+ <span class="espaco"></span>
143
+ <button class="seletor" id="btnEsforco" type="button" aria-haspopup="menu" title="Esforço do agente (/effort)">
144
+ <b id="esforcoRotulo">Equilíbrio</b><span id="esforcoNome">medium</span><svg class="ic ic-p"><use href="#i-chev"/></svg>
145
+ </button>
146
+ <button class="icone-btn" id="btnMic" type="button" aria-pressed="false" aria-label="Ligar o modo voz" title="Modo voz (/voice)"><svg class="ic"><use href="#i-mic"/></svg></button>
147
+ <button class="enviar" id="btnEnviar" type="submit" disabled aria-label="Enviar" title="Enviar (Enter)"><svg class="ic"><use href="#i-seta"/></svg></button>
148
+ </div>
149
+ </form>
150
+ </div>
151
+ </footer>
101
152
  </main>
102
- </div>
103
153
 
104
- <dialog id="dlgConfig">
105
- <div class="dlg-topo">
106
- <h2>Configurações</h2>
107
- <button class="copiar" id="fecharConfig">Fechar</button>
108
- </div>
109
- <div class="dlg-corpo">
110
- <div class="grupo">
111
- <h3>Tema</h3>
112
- <div class="opcoes" id="temas">
113
- <button class="opcao" data-tema="escuro">Escuro</button>
114
- <button class="opcao" data-tema="claro">Claro</button>
115
- <button class="opcao" data-tema="sistema">Do sistema</button>
154
+ <aside class="painel" id="painel" hidden aria-label="Prévia e arquivos">
155
+ <header class="painel-topo">
156
+ <div class="abas" role="tablist">
157
+ <button class="aba" id="abaPrevia" type="button" role="tab" aria-selected="true">Prévia</button>
158
+ <button class="aba" id="abaArquivos" type="button" role="tab" aria-selected="false">Arquivos</button>
159
+ </div>
160
+ <span class="espaco"></span>
161
+ <button class="icone-btn" id="btnRecarregar" type="button" aria-label="Recarregar" title="Recarregar"><svg class="ic"><use href="#i-recarregar"/></svg></button>
162
+ <button class="icone-btn" id="fecharPainel" type="button" aria-label="Fechar o painel"><svg class="ic"><use href="#i-fechar"/></svg></button>
163
+ </header>
164
+ <div class="painel-corpo" id="corpoPrevia">
165
+ <iframe class="previa" id="previa" title="Prévia do site"
166
+ sandbox="allow-scripts allow-forms allow-modals allow-popups allow-pointer-lock"></iframe>
167
+ <div class="painel-vazio" id="previaVazia" hidden>
168
+ <span class="mascote pequeno" id="mascotePainel"></span>
169
+ <p>Ainda não tem site nesta pasta.</p>
170
+ <p class="fraco">Peça "crie uma página de…" e ele aparece aqui, atualizando sozinho.</p>
116
171
  </div>
117
172
  </div>
118
- <div class="grupo">
119
- <h3>Cor</h3>
120
- <div class="cores" id="cores">
121
- <button class="cor" data-cor="ciano" style="background:#22d3ee" aria-label="Ciano"></button>
122
- <button class="cor" data-cor="verde" style="background:#34d399" aria-label="Verde"></button>
123
- <button class="cor" data-cor="roxo" style="background:#a78bfa" aria-label="Roxo"></button>
124
- <button class="cor" data-cor="ambar" style="background:#fbbf24" aria-label="Âmbar"></button>
125
- <button class="cor" data-cor="rosa" style="background:#fb7185" aria-label="Rosa"></button>
173
+ <div class="painel-corpo arquivos" id="corpoArquivos" hidden>
174
+ <div class="arvore" id="arvore" role="tree"></div>
175
+ <div class="visor">
176
+ <div class="visor-topo" id="visorNome">Escolha um arquivo</div>
177
+ <pre class="visor-texto" id="visor"></pre>
126
178
  </div>
127
179
  </div>
128
- <div class="grupo">
129
- <h3>Esta janela</h3>
130
- <div class="linha-info"><span>Pasta</span><b id="pastaConfig">—</b></div>
131
- <div class="linha-info"><span>Modo</span><b id="modoConfig">—</b></div>
132
- <div class="linha-info"><span>Esforço</span><b id="esforcoConfig">—</b></div>
133
- <p style="margin:10px 0 0;font-size:13px;color:var(--fraco)">
134
- Estes três se mudam no terminal: <code>/dir</code>, <code>/mode</code> e <code>/effort</code>.
135
- </p>
180
+ </aside>
181
+ </div>
182
+
183
+ <div class="menu" id="menu" role="menu" hidden></div>
184
+
185
+ <dialog class="dialogo" id="dlgAjuda" aria-labelledby="tituloAjuda">
186
+ <header class="dlg-topo">
187
+ <h2 id="tituloAjuda">Atalhos e comandos</h2>
188
+ <button class="icone-btn" id="fecharAjuda" type="button" aria-label="Fechar"><svg class="ic"><use href="#i-fechar"/></svg></button>
189
+ </header>
190
+ <div class="dlg-corpo">
191
+ <h3>Teclado</h3>
192
+ <dl class="atalhos">
193
+ <dt><kbd>Enter</kbd></dt><dd>manda o pedido</dd>
194
+ <dt><kbd>Shift</kbd> <kbd>Enter</kbd></dt><dd>quebra a linha</dd>
195
+ <dt><kbd>Esc</kbd> <kbd>Esc</kbd></dt><dd>para o que o Primo está fazendo</dd>
196
+ <dt><kbd>Ctrl</kbd> <kbd>B</kbd></dt><dd>mostra ou esconde a barra lateral</dd>
197
+ <dt><kbd>Ctrl</kbd> <kbd>J</kbd></dt><dd>abre o terminal</dd>
198
+ </dl>
199
+ <h3>Comandos</h3>
200
+ <p class="fraco">Digite no campo, como no terminal. Clique para usar.</p>
201
+ <div class="comandos" id="listaComandos">
202
+ <button type="button" data-cmd="/help">/help <span>todos os comandos</span></button>
203
+ <button type="button" data-cmd="/novo ">/novo <span>projeto novo, do zero</span></button>
204
+ <button type="button" data-cmd="/abrir">/abrir <span>abre o site do projeto</span></button>
205
+ <button type="button" data-cmd="/desfazer">/desfazer <span>volta o que ele mexeu</span></button>
206
+ <button type="button" data-cmd="/status">/status <span>conta e servidor</span></button>
207
+ <button type="button" data-cmd="/mascot">/mascot <span>troca o mascote</span></button>
208
+ <button type="button" data-cmd="/login">/login <span>entra na sua conta</span></button>
209
+ <button type="button" data-cmd="/doctor">/doctor <span>confere o que costuma dar problema</span></button>
136
210
  </div>
137
211
  </div>
138
212
  </dialog>
139
213
 
140
- <div id="alvo-solto"><div><strong>Solte aqui</strong><span>o caminho entra no pedido</span></div></div>
214
+ <div class="alvo-solto" id="alvoSolto"><div><strong>Solte aqui</strong><span>o arquivo entra como anexo do pedido</span></div></div>
141
215
 
142
216
  <script src="/primo-marca.js"></script>
217
+ <script src="/ansi.js"></script>
143
218
  <script src="/app.js"></script>
144
219
  </body>
145
220
  </html>
@@ -131,5 +131,62 @@
131
131
  return "data:image/svg+xml," + encodeURIComponent(logo({ tamanho: 32 }));
132
132
  }
133
133
 
134
- raiz.PrimoMarca = { svg: svg, logo: logo, favicon: favicon, MATRIZ: MATRIZ, CORPO: CORPO, DETALHE: DETALHE };
134
+ /* ── O MASCOTE EM TRAÇO ────────────────────────────────────────────────
135
+ A tela vazia da janela pede um ícone de LINHA, fino e neutro, no lugar
136
+ do bicho em pixel colorido: a janela é cinza e calma, e um mascote de
137
+ blocos cianos no meio dela gritava mais alto que a pergunta. Um traço
138
+ por mascote de lib/ui.js (MASCOTS), mesmo nome de id — quem escolheu a
139
+ coruja no /mascot vê a coruja aqui também. Os três de orelha (primo,
140
+ raposa, gato) têm o ❯_ do prompt no rosto: é a marca do PrimoCode.
141
+
142
+ Tudo em `currentColor`, então a cor vem do CSS e segue o tema. */
143
+ var CORPO_LINHA = "M14 16h20a5 5 0 0 1 5 5v12a5 5 0 0 1-5 5H14a5 5 0 0 1-5-5V21a5 5 0 0 1 5-5z";
144
+ var ROSTO_PROMPT = "M17 23.5l4 3.5-4 3.5M24 31h7";
145
+ var PES = "M17 38v4M31 38v4";
146
+ var LINHAS = {
147
+ primo: [CORPO_LINHA, ROSTO_PROMPT, PES, "M16 16v-6h4v6M28 16v-6h4v6", "M9 26H5M39 26h4"],
148
+ fox: [CORPO_LINHA, ROSTO_PROMPT, PES, "M12 17 9 6l9 10M36 17l3-11-9 10"],
149
+ cat: [CORPO_LINHA, ROSTO_PROMPT, PES, "M14 16l1-7 6 7M34 16l-1-7-6 7"],
150
+ robo: ["M13 14h22a4 4 0 0 1 4 4v14a4 4 0 0 1-4 4H13a4 4 0 0 1-4-4V18a4 4 0 0 1 4-4z",
151
+ "M24 14V9", "c24 6.5 2", "c18 24 2.5", "c30 24 2.5", "M19 31h10", "M16 36v5M32 36v5"],
152
+ coruja: ["M12 11l6 5h12l6-5v19a12 12 0 0 1-24 0z", "c19 24 4", "c29 24 4", "M24 29l-2 3h4z"],
153
+ dragao: [CORPO_LINHA, PES, "M13 16 9 8M35 16l4-8", "M9 22 3 18v10l6-2M39 22l6-4v10l-6-2",
154
+ "c18.5 25 1.5", "c29.5 25 1.5", "M21 31h6"],
155
+ fantasma: ["M12 40V22a12 12 0 0 1 24 0v18l-4-3-4 3-4-3-4 3-4-3z", "c20 23 2", "c28 23 2"],
156
+ capivara: ["M8 24c0-6 5-9 11-9h13c6 0 9 5 9 11v5a6 6 0 0 1-6 6H14a6 6 0 0 1-6-6z",
157
+ "M14 16l1-4 4 3", "c14.5 22.5 1.5", "M8 27h3", "M15 37v4M33 37v4"],
158
+ polvo: ["M14 27a10 10 0 1 1 20 0v1H14z", "c20 22 1.5", "c28 22 1.5",
159
+ "M15 28c-4 4-6 8-3 12M20 28c-1 5-1 9 1 12M28 28c1 5 1 9-1 12M33 28c4 4 6 8 3 12"],
160
+ pinguim: ["M24 7c-8 0-12 8-12 18s4 15 12 15 12-5 12-15S32 7 24 7z",
161
+ "M24 18c-4 0-6 5-6 11s2 8 6 8 6-2 6-8-2-11-6-11z", "c20.5 14 1.2", "c27.5 14 1.2",
162
+ "M22 17l2 2 2-2", "M18 41h5M25 41h5"],
163
+ };
164
+
165
+ /**
166
+ * O mascote em traço, para a tela vazia.
167
+ * @param {object} op
168
+ * @param {string} [op.mascote] id de lib/ui.js (primo, fox, cat, robo…); desconhecido = primo
169
+ * @param {number} [op.tamanho] lado em px
170
+ * @param {string} [op.rotulo] texto alternativo; vazio = decorativo
171
+ */
172
+ function linha(op) {
173
+ op = op || {};
174
+ var lado = op.tamanho || 48;
175
+ var partes = LINHAS[op.mascote] || LINHAS.primo;
176
+ var acessivel = op.rotulo
177
+ ? ' role="img" aria-label="' + String(op.rotulo).replace(/"/g, "&quot;") + '"'
178
+ : ' aria-hidden="true"';
179
+ var corpo = partes.map(function (d) {
180
+ if (d.charAt(0) === "c") { // "cX Y R" = círculo
181
+ var n = d.slice(1).split(" ");
182
+ return '<circle cx="' + n[0] + '" cy="' + n[1] + '" r="' + n[2] + '"/>';
183
+ }
184
+ return '<path d="' + d + '"/>';
185
+ }).join("");
186
+ return '<svg viewBox="0 0 48 48" width="' + lado + '" height="' + lado + '"' + acessivel
187
+ + ' fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">'
188
+ + corpo + "</svg>";
189
+ }
190
+
191
+ raiz.PrimoMarca = { svg: svg, logo: logo, linha: linha, LINHAS: LINHAS, favicon: favicon, MATRIZ: MATRIZ, CORPO: CORPO, DETALHE: DETALHE };
135
192
  })(typeof window !== "undefined" ? window : globalThis);
package/bin/primocode.js CHANGED
@@ -108,10 +108,10 @@ const state = {
108
108
  // existe para a frente em vez de subir outro servidor ao lado.
109
109
  let janelaAberta = null;
110
110
 
111
- /* O que a janela do /rc, a IDE do /ide e o `primo --app` passam ao
112
- lib/app.js. Uma função só porque os três sobem o MESMO servidor: se cada um
113
- montasse o seu, o /ide aberto depois do /rc acharia um servidor sem a pasta
114
- do projeto — e a IDE nasceria sem árvore. */
111
+ /* O que o /rc, o /ide e o `primo --app` passam ao lib/app.js. Uma função
112
+ só porque os três sobem a MESMA janela: se cada um montasse a sua, o /ide
113
+ aberto depois do /rc acharia um servidor sem a pasta do projeto — e o
114
+ painel da prévia nasceria vazio. */
115
115
  function opcoesDaJanela(extra) {
116
116
  return Object.assign({
117
117
  versao: VERSION,
@@ -123,10 +123,37 @@ function opcoesDaJanela(extra) {
123
123
  projeto: state.currentProject,
124
124
  modo: state.mode,
125
125
  esforco: state.config.effort,
126
+ permissoes: perigo.normalizarModo(state.config.permissoes),
127
+ voz: !!vozEmEspera,
128
+ usuario: state.config.userName || null,
129
+ mascote: state.config.mascot || 'primo',
130
+ anexos: state.attachedFiles.map((f) => f.path),
126
131
  }),
127
132
  // Lida na hora, não guardada: o /dir e o /project trocam a pasta
128
- // com a IDE aberta, e ela tem de acompanhar.
133
+ // com a janela aberta, e o painel tem de acompanhar.
129
134
  pastaProjeto: () => projectDir(),
135
+ /* As ações da janela passam pelos MESMOS caminhos dos comandos — o
136
+ /projetos, o /clear, o /permissoes, o /effort, o /attach — para a
137
+ janela e o terminal nunca discordarem sobre onde se está. */
138
+ entrarProjeto: (dir) => {
139
+ entrarNoProjeto(dir, 'projeto');
140
+ console.log(c.ok(` Trabalhando em ${state.currentProject}`) + c.muted(` (${shortPath(dir)})`));
141
+ refreshPrompt();
142
+ },
143
+ novoChat: () => handleSlash('/clear'),
144
+ mudarPermissoes: (m) => handleSlash('/permissoes ' + m),
145
+ mudarEsforco: (n) => aplicarEffort(n),
146
+ anexar: (f) => {
147
+ state.attachedFiles.push(f);
148
+ console.log(c.ok(`Anexado: ${f.path} (${tools.fmtBytes(f.content.length)})`) + c.muted(' pela janela'));
149
+ refreshPrompt();
150
+ },
151
+ desanexar: (nome) => {
152
+ state.attachedFiles = nome ? state.attachedFiles.filter((f) => f.path !== nome) : [];
153
+ refreshPrompt();
154
+ },
155
+ voz: (ligar) => handleSlash(ligar ? '/voice' : '/voice parar'),
156
+ continuarConversa: (alvo) => abrirConversa(alvo),
130
157
  }, extra || {});
131
158
  }
132
159
 
@@ -153,7 +180,7 @@ ${c.bold('Comandos')}
153
180
  ${c.brand('/copiar')} copia a última resposta
154
181
  ${c.brand('/historico')} [termo] procura nos seus pedidos antigos
155
182
  ${c.brand('/rc')} abre a janela do PrimoCode (chat, sem terminal)
156
- ${c.brand('/ide')} abre a IDE: arquivos, editor, prévia do site e o chat
183
+ ${c.brand('/ide')} abre a janela com a prévia do site e os arquivos ao lado
157
184
  ${c.brand('/login')} entra na sua conta · ${c.brand('/account')} mostra o plano
158
185
  ${c.brand('/chats')} [n] suas conversas — as mesmas do app e do celular
159
186
  ${c.brand('/logout')} desconecta este computador da conta
@@ -210,7 +237,7 @@ const SLASH_COMANDOS = [
210
237
  ['/copiar', 'copia a última resposta'],
211
238
  ['/historico', 'procura nos seus pedidos antigos: /historico login'],
212
239
  ['/rc', 'abre a janela do PrimoCode'],
213
- ['/ide', 'abre a IDE: arquivos, editor, prévia e o chat, lado a lado'],
240
+ ['/ide', 'abre a janela com a prévia do site e os arquivos ao lado'],
214
241
  ['/login', 'entra na sua conta Conecta Primo'],
215
242
  ['/account', 'sua conta, plano e computadores conectados'],
216
243
  ['/chats', 'suas conversas salvas, as mesmas do app e do celular'],
@@ -2474,26 +2501,26 @@ async function handleSlash(text) {
2474
2501
  break;
2475
2502
  }
2476
2503
 
2477
- /* A IDE: "um app ao invés de CLI, aí fica uma IDE e uma CLI".
2478
- Mora no MESMO servidor do /rc (lib/app.js + lib/ide.js): com a
2479
- janela do chat já aberta, o /ide só abre outra tela nele — mesma
2480
- chave, mesmo agente, a mesma conversa nas duas. */
2504
+ /* O /ide virou a MESMA janela do /rc, abrindo com a prévia do site
2505
+ à direita. O editor de código saiu ("remove a IDE ou deixa
2506
+ idêntico ao Codex"): quem não programa precisa ver o site
2507
+ nascendo, não de um editor para quebrar o que o agente fez. O
2508
+ comando continua existindo para o dedo viciado não bater em nada. */
2481
2509
  case 'ide': {
2482
- if (janelaAberta && janelaAberta.urlIde) {
2483
- await appLib.abrirJanela(janelaAberta.urlIde);
2484
- console.log(c.ok(' IDE aberta.') + c.muted(' (no mesmo servidor da janela que já estava aberta)'));
2485
- console.log(c.dim(` ${janelaAberta.urlIde}`));
2510
+ if (janelaAberta) {
2511
+ await appLib.abrirJanela(janelaAberta.urlPainel || janelaAberta.url);
2512
+ console.log(c.ok(' Janela com a prévia aberta.') + c.muted(' (no mesmo servidor que já estava de pé)'));
2486
2513
  break;
2487
2514
  }
2488
- const sp = new Spinner('abrindo a IDE').start();
2515
+ const sp = new Spinner('abrindo a janela').start();
2489
2516
  try {
2490
2517
  janelaAberta = await appLib.abrir(opcoesDaJanela({ abrirEm: 'ide' }));
2491
2518
  sp.stop();
2492
2519
  const j = janelaAberta.janela || {};
2493
- console.log(c.ok(' IDE aberta.')
2520
+ console.log(c.ok(' Janela aberta, com a prévia do site ao lado.')
2494
2521
  + c.muted(j.modo === 'janela' ? ` (${j.navegador})`
2495
2522
  : j.modo === 'aba' ? ' (aba do navegador padrão)' : ''));
2496
- console.log(c.dim(` ${janelaAberta.urlIde}`));
2523
+ console.log(c.dim(` ${janelaAberta.urlPainel}`));
2497
2524
  if (j.modo === 'nenhum') {
2498
2525
  console.log(c.warn(' Não consegui abrir sozinho — copie o endereço acima no navegador.'));
2499
2526
  }
@@ -2819,7 +2846,7 @@ async function main() {
2819
2846
  return;
2820
2847
  }
2821
2848
 
2822
- /* `primo ide`: o terminal de sempre, com a IDE já aberta ao lado.
2849
+ /* `primo ide`: o terminal de sempre, com a janela (e a prévia) já aberta.
2823
2850
  Só a palavra SOZINHA: `primo ide para gatos` continua sendo um pedido
2824
2851
  ao agente (modo um-tiro), como sempre foi. */
2825
2852
  const abrirIde = args.length === 1 && args[0].toLowerCase() === 'ide';
@@ -2939,7 +2966,13 @@ async function main() {
2939
2966
  try {
2940
2967
  if (ehBash(text)) await rodarBash(text);
2941
2968
  else if (isSlash(text)) await handleSlash(text);
2942
- else await sendPrompt(text);
2969
+ else {
2970
+ // Com a janela aberta, o pedido digitado AQUI também vira
2971
+ // balão lá — senão a janela mostraria a resposta de uma
2972
+ // pergunta que ninguém fez.
2973
+ if (janelaAberta && janelaAberta.pedidoDoTerminal) janelaAberta.pedidoDoTerminal(text);
2974
+ await sendPrompt(text);
2975
+ }
2943
2976
  } catch (e) {
2944
2977
  fala.problema(e);
2945
2978
  }
@@ -3004,7 +3037,7 @@ async function main() {
3004
3037
  // O que sobrou de adiantado não era resposta de nada: vira comando,
3005
3038
  // na ordem em que foi digitado. Nada se perde, só esperou a vez.
3006
3039
  segurando = false;
3007
- // Depois da abertura (e do login dela): a IDE sem conta abriria
3040
+ // Depois da abertura (e do login dela): a janela sem conta abriria
3008
3041
  // mandando pedidos que o servidor recusa.
3009
3042
  if (abrirIde) queue.push('/ide');
3010
3043
  while (adiantadas.length) queue.push(adiantadas.shift());
package/lib/act.js CHANGED
@@ -138,6 +138,9 @@ function limparParaOModelo(result) {
138
138
 
139
139
  function logResult(kind, result, profundidade) {
140
140
  const p = fala.resultado(profundidade);
141
+ // A linha da janela (lib/app.js) fecha aqui: o mesmo resultado que o
142
+ // terminal desenha embaixo, resumido. Ver o canal em lib/fala.js.
143
+ fala.avisar('resultado', fala.resumoDoResultado(kind, result));
141
144
  if (!result) return;
142
145
  // Uma tool que falhou é informação para o AGENTE, que vai consertar na
143
146
  // volta seguinte — não é o erro do usuário. Âmbar, uma linha, sem o
@@ -1131,6 +1134,7 @@ async function runAct(userPrompt, opts) {
1131
1134
  const porta = await permissao.pedir(portaoControle, tc.name, tc.arguments, autorizar);
1132
1135
  if (!porta.ok) {
1133
1136
  fala.acao(tc.name, tc.arguments, profundidade);
1137
+ fala.avisar('resultado', { nome: tc.name, ok: false, resumo: 'não autorizado' });
1134
1138
  console.log(fala.resultado(profundidade) + c.warn('não autorizado'));
1135
1139
  registro.anotar(livro, tc.name, tc.arguments, { ok: false, error: porta.error });
1136
1140
  messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify({ ok: false, error: porta.error }) });
@@ -1148,6 +1152,7 @@ async function runAct(userPrompt, opts) {
1148
1152
  });
1149
1153
  if (!risco.ok) {
1150
1154
  fala.acao(tc.name, tc.arguments, profundidade);
1155
+ fala.avisar('resultado', { nome: tc.name, ok: false, resumo: `não autorizado — ${risco.motivo}` });
1151
1156
  console.log(fala.resultado(profundidade) + c.warn(`não autorizado — ${risco.motivo}`));
1152
1157
  registro.anotar(livro, tc.name, tc.arguments, { ok: false, error: risco.error });
1153
1158
  messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify({ ok: false, error: risco.error }) });