primocode 9.8.0-beta.6 → 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
@@ -53,6 +53,7 @@ extra.instalarContador(api, contadorDaSessao, () => {
53
53
  const p = provedorLocal.atual();
54
54
  return p ? p.nome : 'Servidor PrimoCode';
55
55
  });
56
+ const tempos = require('../lib/tempos');
56
57
 
57
58
  // Rede de segurança de último recurso: sem isto, uma exceção fora de um
58
59
  // try/catch (ex.: dentro de um callback de socket, como o parser de SSE) não
@@ -107,10 +108,10 @@ const state = {
107
108
  // existe para a frente em vez de subir outro servidor ao lado.
108
109
  let janelaAberta = null;
109
110
 
110
- /* O que a janela do /rc, a IDE do /ide e o `primo --app` passam ao
111
- lib/app.js. Uma função só porque os três sobem o MESMO servidor: se cada um
112
- montasse o seu, o /ide aberto depois do /rc acharia um servidor sem a pasta
113
- 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. */
114
115
  function opcoesDaJanela(extra) {
115
116
  return Object.assign({
116
117
  versao: VERSION,
@@ -122,10 +123,37 @@ function opcoesDaJanela(extra) {
122
123
  projeto: state.currentProject,
123
124
  modo: state.mode,
124
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),
125
131
  }),
126
132
  // Lida na hora, não guardada: o /dir e o /project trocam a pasta
127
- // com a IDE aberta, e ela tem de acompanhar.
133
+ // com a janela aberta, e o painel tem de acompanhar.
128
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),
129
157
  }, extra || {});
130
158
  }
131
159
 
@@ -152,7 +180,7 @@ ${c.bold('Comandos')}
152
180
  ${c.brand('/copiar')} copia a última resposta
153
181
  ${c.brand('/historico')} [termo] procura nos seus pedidos antigos
154
182
  ${c.brand('/rc')} abre a janela do PrimoCode (chat, sem terminal)
155
- ${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
156
184
  ${c.brand('/login')} entra na sua conta · ${c.brand('/account')} mostra o plano
157
185
  ${c.brand('/chats')} [n] suas conversas — as mesmas do app e do celular
158
186
  ${c.brand('/logout')} desconecta este computador da conta
@@ -209,7 +237,7 @@ const SLASH_COMANDOS = [
209
237
  ['/copiar', 'copia a última resposta'],
210
238
  ['/historico', 'procura nos seus pedidos antigos: /historico login'],
211
239
  ['/rc', 'abre a janela do PrimoCode'],
212
- ['/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'],
213
241
  ['/login', 'entra na sua conta Conecta Primo'],
214
242
  ['/account', 'sua conta, plano e computadores conectados'],
215
243
  ['/chats', 'suas conversas salvas, as mesmas do app e do celular'],
@@ -1055,6 +1083,16 @@ async function sendPrompt(userText) {
1055
1083
  return;
1056
1084
  }
1057
1085
 
1086
+ // PRIMOCODE_TEMPOS=1: uma linha no fim com o custo de cada fase.
1087
+ tempos.comecar();
1088
+ try { await enviarPedido(userText); }
1089
+ finally {
1090
+ const linha = tempos.relatorio();
1091
+ if (linha) console.log(c.dim(` ⏱ ${linha}`));
1092
+ }
1093
+ }
1094
+
1095
+ async function enviarPedido(userText) {
1058
1096
  let prompt = userText;
1059
1097
  // "@index.html" no pedido é um /attach só deste pedido: vai junto dos
1060
1098
  // anexos, mas não fica em state.attachedFiles para o próximo.
@@ -1091,15 +1129,22 @@ async function sendPrompt(userText) {
1091
1129
 
1092
1130
  // O resumo da rodada anterior é feito em segundo plano logo depois
1093
1131
  // dela; aqui só se espera ele terminar, para não sair com memória velha.
1094
- await esperarResumo();
1095
-
1132
+ //
1096
1133
  // O pedido passa por uma cabeça antes de virar trabalho: conserta o
1097
1134
  // que está torto, diz qual é o entregável e o que conta como pronto.
1098
1135
  // UMA chamada faz as duas coisas: arruma o pedido torto e, se fizer
1099
1136
  // sentido, divide em passos. Eram duas idas ao modelo por pedido, e
1100
1137
  // num plano limitado por REQUISIÇÕES (o grátis da Groq são 30 por
1101
1138
  // minuto) cada chamada extra custa tanto quanto token.
1102
- const passos = await dividirTarefa(prompt);
1139
+ //
1140
+ // As duas esperas correm JUNTAS. Uma não lê o que a outra escreve: o
1141
+ // plano só olha o texto do pedido, e o resumo só a conversa de antes.
1142
+ // Em fila, o pedido de duas fases pagava o resumo e DEPOIS o plano —
1143
+ // duas idas ao modelo em série antes da primeira rodada.
1144
+ const [, passos] = await Promise.all([
1145
+ tempos.medir('espera do resumo', () => esperarResumo()),
1146
+ tempos.medir('plano', () => dividirTarefa(prompt)),
1147
+ ]);
1103
1148
  const instrucaoKit = await prepararKit(plan, userText);
1104
1149
 
1105
1150
  let r = null;
@@ -1214,8 +1259,8 @@ async function sendPrompt(userText) {
1214
1259
  state.gravador.turno(userText, r.assistantMessage.content,
1215
1260
  { modo: 'primocode', projeto: state.currentProject || null });
1216
1261
  } else {
1217
- await esperarResumo();
1218
- await runChat(prompt, userText);
1262
+ await tempos.medir('espera do resumo', () => esperarResumo());
1263
+ await tempos.medir('chat', () => runChat(prompt, userText));
1219
1264
  }
1220
1265
 
1221
1266
  state.attachedFiles = [];
@@ -2456,26 +2501,26 @@ async function handleSlash(text) {
2456
2501
  break;
2457
2502
  }
2458
2503
 
2459
- /* A IDE: "um app ao invés de CLI, aí fica uma IDE e uma CLI".
2460
- Mora no MESMO servidor do /rc (lib/app.js + lib/ide.js): com a
2461
- janela do chat já aberta, o /ide só abre outra tela nele — mesma
2462
- 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. */
2463
2509
  case 'ide': {
2464
- if (janelaAberta && janelaAberta.urlIde) {
2465
- await appLib.abrirJanela(janelaAberta.urlIde);
2466
- console.log(c.ok(' IDE aberta.') + c.muted(' (no mesmo servidor da janela que já estava aberta)'));
2467
- 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é)'));
2468
2513
  break;
2469
2514
  }
2470
- const sp = new Spinner('abrindo a IDE').start();
2515
+ const sp = new Spinner('abrindo a janela').start();
2471
2516
  try {
2472
2517
  janelaAberta = await appLib.abrir(opcoesDaJanela({ abrirEm: 'ide' }));
2473
2518
  sp.stop();
2474
2519
  const j = janelaAberta.janela || {};
2475
- console.log(c.ok(' IDE aberta.')
2520
+ console.log(c.ok(' Janela aberta, com a prévia do site ao lado.')
2476
2521
  + c.muted(j.modo === 'janela' ? ` (${j.navegador})`
2477
2522
  : j.modo === 'aba' ? ' (aba do navegador padrão)' : ''));
2478
- console.log(c.dim(` ${janelaAberta.urlIde}`));
2523
+ console.log(c.dim(` ${janelaAberta.urlPainel}`));
2479
2524
  if (j.modo === 'nenhum') {
2480
2525
  console.log(c.warn(' Não consegui abrir sozinho — copie o endereço acima no navegador.'));
2481
2526
  }
@@ -2801,7 +2846,7 @@ async function main() {
2801
2846
  return;
2802
2847
  }
2803
2848
 
2804
- /* `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.
2805
2850
  Só a palavra SOZINHA: `primo ide para gatos` continua sendo um pedido
2806
2851
  ao agente (modo um-tiro), como sempre foi. */
2807
2852
  const abrirIde = args.length === 1 && args[0].toLowerCase() === 'ide';
@@ -2921,7 +2966,13 @@ async function main() {
2921
2966
  try {
2922
2967
  if (ehBash(text)) await rodarBash(text);
2923
2968
  else if (isSlash(text)) await handleSlash(text);
2924
- 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
+ }
2925
2976
  } catch (e) {
2926
2977
  fala.problema(e);
2927
2978
  }
@@ -2986,7 +3037,7 @@ async function main() {
2986
3037
  // O que sobrou de adiantado não era resposta de nada: vira comando,
2987
3038
  // na ordem em que foi digitado. Nada se perde, só esperou a vez.
2988
3039
  segurando = false;
2989
- // 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
2990
3041
  // mandando pedidos que o servidor recusa.
2991
3042
  if (abrirIde) queue.push('/ide');
2992
3043
  while (adiantadas.length) queue.push(adiantadas.shift());