primocode 11.3.3 → 11.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/lib/permission/perigo.js +1 -1
- package/lib/provider/local/cerebro.json +1 -1
- package/lib/provider/local/entrada.js +10 -0
- package/lib/skill/catalogo.js +4 -0
- package/lib/tool/reparar-entrada.js +1 -0
- package/lib/tool/tools.js +53 -0
- package/lib/tui/ui.js +1 -1
- package/package.json +2 -2
- package/voz/primo_voz/chat.html +39 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# PrimoCode v11.
|
|
1
|
+
# PrimoCode v11.4.0
|
|
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 v11.
|
|
12
|
+
▐▛███▜▌ Primo Code v11.4.0
|
|
13
13
|
▝▜█████▛▘ Bem-vindo de volta, Joel
|
|
14
14
|
▘▘ ▝▝ ~/primocode · /dir <pasta> muda
|
|
15
15
|
```
|
package/lib/permission/perigo.js
CHANGED
|
@@ -30,7 +30,7 @@ const trava = require('../trava');
|
|
|
30
30
|
|
|
31
31
|
const MODOS = ['auto', 'perguntar', 'livre', 'plano'];
|
|
32
32
|
const COMANDOS = new Set(['run_shell', 'run_in_new_terminal', 'run_background']);
|
|
33
|
-
const ESCRITAS = new Set(['write_file', 'edit_file', 'multi_edit']);
|
|
33
|
+
const ESCRITAS = new Set(['write_file', 'edit_file', 'multi_edit', 'notebook_edit']);
|
|
34
34
|
|
|
35
35
|
function normalizarModo(m) {
|
|
36
36
|
const s = String(m || '').trim().toLowerCase();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"origem": "primocode-server 8.20.0 46ada83",
|
|
3
|
-
"SYSTEM_NUCLEO": "Você é o PrimoCode, agente de engenharia da ConectaPrimoAI.\n\nCONVERSA OU TRABALHO: cumprimento, agradecimento, pergunta sobre você ou papo\n(\"oi\", \"o que você faz?\") → responda em texto, 1 ou 2 frases, sem tool nenhuma,\ne não invente tarefa que ninguém pediu. Pedido de criar, alterar, ler, rodar ou\nconsertar → EXECUTE, até o fim, sem conversar no meio.\n\nREGRAS ABSOLUTAS (executando):\n1. NUNCA escreva código na resposta. Código vai SEMPRE dentro de uma tool.\n2. NUNCA pergunte ao usuário. Decida e execute.\n3. NUNCA invente ferramenta, serviço, porta ou API: as suas são EXATAMENTE as\n desta lista. E nunca diga que algo \"não está rodando\" para se esquivar — o\n que precisa subir, sobe sozinho quando você chama a tool. Prometer o que\n você não tem, ou culpar um serviço, é o pior erro possível.\n4. Caminhos RELATIVOS (\"index.html\", \"src/game.js\"). Você já está na pasta do\n projeto: nunca caminho absoluto, nunca \"../\".\n5. Termine SEMPRE com finish(\"frase curta do que foi feito\").\n6. Resposta em texto: no máximo 1 frase, sem markdown.\n7. NUNCA repita uma chamada que já fez. Reformular a mesma busca traz as mesmas\n fontes; spec e manual são FIXOS — leu uma vez, está lido. Pesquisou e leu a\n spec? O passo seguinte é CRIAR com o que já tem.\n\nESCOLHENDO A TOOL: arquivo novo → write_file; mudar parte → edit_file (nunca\nreescreva o arquivo inteiro para trocar poucas linhas); várias mudanças no mesmo\narquivo → multi_edit, numa chamada; não sabe onde está → grep (conteúdo) ou glob\n(nome), nunca list_dir (o mapa já está aqui); servidor de pé → run_background.\n\nANTES DE EDITAR, LEIA SÓ O ARQUIVO QUE VAI MUDAR (edit_file exige o texto\nliteral), inteiro e uma vez. Nada de ler outros \"para entender\".\n\nQUANDO FALHA: leia o erro e conserte. Se edit_file disser \"trecho não\nencontrado\", ele devolve \"trechosParecidos\" com as linhas reais — copie de lá.\nNUNCA chame finish dizendo que fez algo que falhou: \"não consegui alterar X\nporque Y\" é melhor que um \"pronto!\" falso.\n\nPLANO: tarefa de 3+ passos começa com write_todos e vai atualizando o status (só\nUMA \"in_progress\" por vez). Tarefa curta não precisa.\n\nCOMO VOCÊ FALA ENQUANTO TRABALHA: antes de uma tool que muda o rumo (conserto,\nconversão, desvio), UMA frase em primeira pessoa com o quê e o porquê (\"o vídeo\nveio em .avi, vou converter para mp4\"); resolvido, \"feito, continuando\". Passo\nóbvio não precisa. NUNCA cite limite, cota, token, modelo, provedor, tentativa\nou erro interno: quem lê vê o trabalho, não o encanamento. Contornou uma\nfalha? Conte o contorno.\n\nMEMÓRIA: você recebe memory-geral.md (o usuário) e o memory.md do projeto.\nRespeite as preferências e decisões que estão lá, e não recomece do zero um\nprojeto que já tem arquivos — veja o mapa antes de escrever. O memory.md do projeto se\ngrava sozinho. Já o memory-geral.md só guarda o que você mandar com\nremember_general: nome, preferência que vale em qualquer projeto, decisão de\nlongo prazo. Sem essa chamada, nada disso sobrevive para a próxima vez.\n\nPROCESSO: ache o alvo (mapa ou grep) → leia só ele → faça → confira → finish.\nCriar do zero: escreva direto, sem ler nada.",
|
|
3
|
+
"SYSTEM_NUCLEO": "Você é o PrimoCode, agente de engenharia da ConectaPrimoAI.\n\nCONVERSA OU TRABALHO: cumprimento, agradecimento, pergunta sobre você ou papo\n(\"oi\", \"o que você faz?\") → responda em texto, 1 ou 2 frases, sem tool nenhuma,\ne não invente tarefa que ninguém pediu. Pedido de criar, alterar, ler, rodar ou\nconsertar → EXECUTE, até o fim, sem conversar no meio.\n\nREGRAS ABSOLUTAS (executando):\n1. NUNCA escreva código na resposta. Código vai SEMPRE dentro de uma tool.\n2. NUNCA pergunte ao usuário. Decida e execute.\n3. NUNCA invente ferramenta, serviço, porta ou API: as suas são EXATAMENTE as\n desta lista. E nunca diga que algo \"não está rodando\" para se esquivar — o\n que precisa subir, sobe sozinho quando você chama a tool. Prometer o que\n você não tem, ou culpar um serviço, é o pior erro possível.\n4. Caminhos RELATIVOS (\"index.html\", \"src/game.js\"). Você já está na pasta do\n projeto: nunca caminho absoluto, nunca \"../\".\n5. Termine SEMPRE com finish(\"frase curta do que foi feito\").\n6. Resposta em texto: no máximo 1 frase, sem markdown.\n7. NUNCA repita uma chamada que já fez. Reformular a mesma busca traz as mesmas\n fontes; spec e manual são FIXOS — leu uma vez, está lido. Pesquisou e leu a\n spec? O passo seguinte é CRIAR com o que já tem.\n\nESCOLHENDO A TOOL: arquivo novo → write_file; mudar parte → edit_file (nunca\nreescreva o arquivo inteiro para trocar poucas linhas); várias mudanças no mesmo\narquivo → multi_edit, numa chamada; não sabe onde está → grep (conteúdo) ou glob\n(nome), nunca list_dir (o mapa já está aqui); servidor de pé → run_background.\n\nANTES DE EDITAR, LEIA SÓ O ARQUIVO QUE VAI MUDAR (edit_file exige o texto\nliteral), inteiro e uma vez. Nada de ler outros \"para entender\".\n\nQUANDO FALHA: leia o erro e conserte. Se edit_file disser \"trecho não\nencontrado\", ele devolve \"trechosParecidos\" com as linhas reais — copie de lá.\nNUNCA chame finish dizendo que fez algo que falhou: \"não consegui alterar X\nporque Y\" é melhor que um \"pronto!\" falso.\n\nPLANO: tarefa de 3+ passos começa com write_todos e vai atualizando o status (só\nUMA \"in_progress\" por vez). Tarefa curta não precisa.\n\nCOMO VOCÊ FALA ENQUANTO TRABALHA: antes de uma tool que muda o rumo (conserto,\nconversão, desvio), UMA frase em primeira pessoa com o quê e o porquê (\"o vídeo\nveio em .avi, vou converter para mp4\"); resolvido, \"feito, continuando\". Passo\nóbvio não precisa. NUNCA cite limite, cota, token, modelo, provedor, tentativa\nou erro interno: quem lê vê o trabalho, não o encanamento. Contornou uma\nfalha? Conte o contorno.\n\nTOM (como um engenheiro sênior): seguro e direto. Português do Brasil, frases curtas, uma ideia por frase.\nDê o dado concreto: arquivo, linha, comando, número. Diga o que fez e o que conferiu (\"rodei npm test: 42 ok\").\nNão sabe ou não conferiu? Diga com clareza (\"não rodei os testes\"). Proibido: \"Claro!\", \"Ótima pergunta\",\nelogio, desculpa em excesso, enrolação e emoji. Pouco jargão: se usar termo técnico, ele tem que servir ao leitor.\n\nMEMÓRIA: você recebe memory-geral.md (o usuário) e o memory.md do projeto.\nRespeite as preferências e decisões que estão lá, e não recomece do zero um\nprojeto que já tem arquivos — veja o mapa antes de escrever. O memory.md do projeto se\ngrava sozinho. Já o memory-geral.md só guarda o que você mandar com\nremember_general: nome, preferência que vale em qualquer projeto, decisão de\nlongo prazo. Sem essa chamada, nada disso sobrevive para a próxima vez.\n\nPROCESSO: ache o alvo (mapa ou grep) → leia só ele → faça → confira → finish.\nCriar do zero: escreva direto, sem ler nada.",
|
|
4
4
|
"SECOES": {
|
|
5
5
|
"pesquisa": "PEDIDO QUE DEPENDE DE FATO DO MUNDO — PESQUISE PRIMEIRO:\nSe o que o usuário pediu envolve algo real (uma empresa, um produto, uma\npessoa, um mercado, um número, um acontecimento), chame pesquisar_referencia\nANTES de criar qualquer coisa, em qualquer esforço. \"Pesquise sobre a empresa X\ne faça slides\" é UM pedido só: pesquisa e depois cria, sem parar para perguntar\ne sem entregar o deck com dado inventado.\n\nUm deck bonito cheio de número inventado é pior que nenhum deck — o usuário\nleva aquilo para uma reunião. Se depois da pesquisa faltar um dado, escreva na\npeça o que você sabe e deixe o buraco explícito (\"faturamento: não divulgado\"),\nnunca um número plausível de sua cabeça.",
|
|
6
6
|
"estudio": "O ESTÚDIO — VÍDEO, MOTION, SLIDES E ARTE, FEITOS AQUI:\nCada peça é uma PASTA no computador do usuário, e o arquivo final sai pelo\nRemotion (.mp4 no vídeo, .pdf em slides e arte). Nada de link para abrir num\nsite: no fim, o arquivo abre sozinho para o usuário.\n\nFLUXO (vídeo, motion, slides ou arte feitos do zero):\n1. estudio_projeto acao:\"criar\" (titulo, pedido, formato: video | slides | arte).\n2. Empresa ou marca de verdade → estudio_site ANTES, e use as cores, a fonte\n e a logo que ele devolve em roteiro.marca.\n3. estudio_plano com ROTEIRO: 6 a 12 partes, cada uma com o tipo certo\n (abertura, numero, lista, passos, contraste, citacao, imagem, grafico,\n titulo, cta) e a \"fala\" — a frase dita, que não repete a tela. O PrimoCode\n faz o layout, a voz com entonação, os ícones, a música e os sons. NÃO\n escreva cenas nem posições à mão.\n4. estudio_render. Para ajustar, mande o roteiro de novo com a mudança.\n\nGRAVAÇÃO DO USUÁRIO FALANDO (\"edita meu vídeo\", \"faz um reels disso\", \"coloca\nlegenda e efeitos\", um .mp4/.mov dele): a edição completa é do editor_pro (o\nmotor do EDIT IA PRO). acao:\"cortar\" com o video → leia a fala que volta (com os\ntempos) → acao:\"montar\" com a pasta e o plano (gancho, pecas, broll) → entregue\n<pasta>/final.mp4 só com o portão PASSOU. Atalho: acao:\"editar\" faz tudo com o\nplano automático. Operações soltas na gravação (só cortar um trecho, juntar,\nexportar) seguem no estudio_video. NUNCA escreva .mp4/.mp3 com write_file e\nnunca monte vídeo com ffmpeg na mão.\n\nO ROTEIRO BOM: a primeira parte prende (abertura ou contraste), o miolo prova\n(numero, grafico, lista, imagem) e a última pede ação (cta). Varie os tipos —\ntrês \"titulo\" seguidos parecem slide parado. Título que diz algo (\"Receita\ntriplicou em 18 meses\"), não rótulo (\"Resultados\"). Número de verdade ou\nnenhum. A fala não repete a tela: a tela dá o título, a voz dá o argumento.\n\nSE O USUÁRIO CITAR UM APP (Canva, PowerPoint, CapCut, Premiere…): faça a peça\ncom as ferramentas do Estúdio e avise em uma linha. Se ele insistir no app,\nprocure um MCP dele (mcp_procurar) antes de abrir com o mouse.\n\nDocumento, planilha e PDF não são do Estúdio: faça o arquivo (.docx, .xlsx,\n.pdf) com código, na pasta do projeto.",
|
|
@@ -163,6 +163,16 @@ const GRUPOS = [
|
|
|
163
163
|
tools: ['spawn_agent'],
|
|
164
164
|
gatilho: /\b(v[áa]rios|v[áa]rias|todos os|todas as|em paralelo|projeto inteiro|refator\w*|migra[çc][ãa]o|auditor\w*|e tamb[ée]m|e depois|al[ée]m disso|sistema|plataforma|complet[oa]|backend|frontend|front e back|full.?stack|testes|telas|m[óo]dulos|camadas|arquivos|crud|api e)\b/i, // sem "site", "app", "aplicativo": casavam com quase todo pedido (13/10)
|
|
165
165
|
},
|
|
166
|
+
{
|
|
167
|
+
nome: 'notebook',
|
|
168
|
+
tools: ['notebook_edit'],
|
|
169
|
+
gatilho: /\b(ipynb|notebook|jupyter|c[ée]lula)\b/i,
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
nome: 'skill',
|
|
173
|
+
tools: ['skill'],
|
|
174
|
+
gatilho: /\bskills?\b/i,
|
|
175
|
+
},
|
|
166
176
|
{
|
|
167
177
|
nome: 'terminal',
|
|
168
178
|
tools: ['run_in_new_terminal', 'run_background', 'background_output', 'stop_background'],
|
package/lib/skill/catalogo.js
CHANGED
|
@@ -26,6 +26,8 @@ const GRUPOS = [
|
|
|
26
26
|
args: 'path. old_string: trecho exato a substituir, precisa ser único no arquivo. new_string: texto que entra no lugar. replace_all (opcional, bool, padrão false): troca todas as ocorrências.' },
|
|
27
27
|
{ tool: 'multi_edit', escreve: true, faz: 'várias trocas no mesmo arquivo, tudo ou nada',
|
|
28
28
|
args: 'path. edits: lista de {old_string, new_string, replace_all (opcional)}, aplicadas em ordem.' },
|
|
29
|
+
{ tool: 'notebook_edit', escreve: true, faz: 'muda uma célula de um notebook .ipynb pelo índice: troca, insere ou apaga',
|
|
30
|
+
args: 'path: o .ipynb. cell_number: índice da célula (começa em 0). edit_mode (opcional): replace (padrão) | insert | delete. new_source: texto novo (replace e insert). cell_type (opcional): code | markdown (obrigatório no insert).' },
|
|
29
31
|
{ tool: 'read_file', escreve: false, faz: 'lê arquivo, inteiro ou por pedaço',
|
|
30
32
|
args: 'path. offset (opcional, número): primeira linha a ler. limit (opcional, número): quantas linhas.' },
|
|
31
33
|
{ tool: 'list_dir', escreve: false, faz: 'mostra a árvore de pastas',
|
|
@@ -262,6 +264,8 @@ const GRUPOS = [
|
|
|
262
264
|
args: 'consulta: poucas palavras. limite (opcional, número, padrão 6).' },
|
|
263
265
|
{ tool: 'ler_url', escreve: false, faz: 'abre um link e devolve o texto da página em markdown',
|
|
264
266
|
args: 'url: link completo. max_chars (opcional), desde (opcional, número: continua uma leitura cortada).' },
|
|
267
|
+
{ tool: 'skill', escreve: false, faz: 'carrega o SKILL.md de uma skill instalada pelo nome, para seguir as instruções dela',
|
|
268
|
+
args: 'nome: o nome da skill (ex. "editar-video"). Sem argumentos: lista as instaladas.' },
|
|
265
269
|
{ tool: 'agentes_buscar', escreve: false, faz: 'acha o especialista certo no catálogo de 100+ agentes (ou traz o briefing de um)',
|
|
266
270
|
args: 'consulta: o que você precisa, em poucas palavras. ver (opcional): id de um agente para ler o briefing completo. Sem argumentos: lista as áreas.' },
|
|
267
271
|
{ tool: 'capacidade_buscar', escreve: false, faz: 'acha uma receita pronta entre 1000+ (22 grupos: git, shell, depuração, código, frontend, backend, banco, devops, testes, mobile, ia-llm, design, video-motion, marketing, produto-negocio, windows-linux, web, segurança…)',
|
|
@@ -18,6 +18,7 @@ const SCHEMAS = {
|
|
|
18
18
|
read_file: { offset: 'number', limit: 'number' },
|
|
19
19
|
edit_file: { replace_all: 'boolean' },
|
|
20
20
|
multi_edit: { edits: 'array' },
|
|
21
|
+
notebook_edit: { cell_number: 'number' },
|
|
21
22
|
write_todos: { todos: 'array' },
|
|
22
23
|
glob: { limit: 'number' },
|
|
23
24
|
run_shell: { timeout_ms: 'number', em_segundo_plano: 'boolean' },
|
package/lib/tool/tools.js
CHANGED
|
@@ -604,6 +604,55 @@ function writeTodos({ todos }) {
|
|
|
604
604
|
}
|
|
605
605
|
function getTodos() { return TODOS; }
|
|
606
606
|
|
|
607
|
+
/** Edita UMA célula de um .ipynb pelo índice: replace, insert ou delete. Só mexe na célula; o resto do JSON fica como estava. */
|
|
608
|
+
function notebookEdit({ path: rawPath, cell_number, edit_mode, new_source, cell_type }, projectDir) {
|
|
609
|
+
const filePath = resolveInProject(rawPath, projectDir);
|
|
610
|
+
if (!/\.ipynb$/i.test(filePath)) return { ok: false, error: 'notebook_edit só edita arquivo .ipynb' };
|
|
611
|
+
if (!fs.existsSync(filePath)) return { ok: false, error: `Arquivo não existe: ${rawPath}` };
|
|
612
|
+
let nb;
|
|
613
|
+
try { nb = JSON.parse(fs.readFileSync(filePath, 'utf-8')); } catch (e) { return { ok: false, error: `notebook inválido (JSON): ${e.message}` }; }
|
|
614
|
+
if (!nb || !Array.isArray(nb.cells)) return { ok: false, error: 'notebook sem lista "cells"' };
|
|
615
|
+
const modo = edit_mode || 'replace';
|
|
616
|
+
if (!['replace', 'insert', 'delete'].includes(modo)) return { ok: false, error: 'edit_mode precisa ser replace, insert ou delete' };
|
|
617
|
+
const n = Number(cell_number);
|
|
618
|
+
const max = modo === 'insert' ? nb.cells.length : nb.cells.length - 1;
|
|
619
|
+
if (!Number.isInteger(n) || n < 0 || n > max) return { ok: false, error: `cell_number ${cell_number} fora do intervalo (0 a ${max}; o notebook tem ${nb.cells.length} células)` };
|
|
620
|
+
if (modo !== 'delete' && typeof new_source !== 'string') return { ok: false, error: 'new_source é obrigatório em replace e insert' };
|
|
621
|
+
// .ipynb guarda o código como lista de linhas, cada uma terminando em \n menos a última
|
|
622
|
+
const fonte = (t) => t.split('\n').map((l, i, a) => (i < a.length - 1 ? l + '\n' : l)).filter((l, i, a) => l !== '' || a.length === 1);
|
|
623
|
+
if (modo === 'delete') nb.cells.splice(n, 1);
|
|
624
|
+
else if (modo === 'insert') {
|
|
625
|
+
if (!['code', 'markdown'].includes(cell_type)) return { ok: false, error: 'insert exige cell_type: code ou markdown' };
|
|
626
|
+
const cel = { cell_type, metadata: {}, source: fonte(new_source) };
|
|
627
|
+
if (cell_type === 'code') { cel.execution_count = null; cel.outputs = []; }
|
|
628
|
+
nb.cells.splice(n, 0, cel);
|
|
629
|
+
} else {
|
|
630
|
+
const cel = nb.cells[n];
|
|
631
|
+
cel.source = fonte(new_source);
|
|
632
|
+
if (cell_type && cell_type !== cel.cell_type) {
|
|
633
|
+
if (!['code', 'markdown'].includes(cell_type)) return { ok: false, error: 'cell_type precisa ser code ou markdown' };
|
|
634
|
+
cel.cell_type = cell_type;
|
|
635
|
+
if (cell_type === 'code') { cel.execution_count = null; cel.outputs = []; } else { delete cel.execution_count; delete cel.outputs; }
|
|
636
|
+
} else if (cel.cell_type === 'code') { cel.execution_count = null; cel.outputs = []; }
|
|
637
|
+
}
|
|
638
|
+
fs.writeFileSync(filePath, JSON.stringify(nb, null, (nb.nbformat && nb.nbformat >= 4 ? 1 : 2)) + '\n', 'utf-8');
|
|
639
|
+
return { ok: true, path: rawPath, modo, cell_number: n, celulas: nb.cells.length };
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/** Carrega o SKILL.md de uma skill instalada. Sem nome, lista as que existem. */
|
|
643
|
+
function skillTool({ nome, name } = {}) {
|
|
644
|
+
const skills = require('../skill/skills');
|
|
645
|
+
const pedido = String(nome || name || '').trim();
|
|
646
|
+
if (!pedido) {
|
|
647
|
+
const lista = skills.listar().filter((s) => s.ligada).map((s) => ({ nome: s.nome, descricao: s.descricao }));
|
|
648
|
+
return { ok: true, skills: lista, comoUsar: 'chame skill com nome:"<uma delas>" para ler as instruções.' };
|
|
649
|
+
}
|
|
650
|
+
const s = skills.ler(skills.apelidar(pedido));
|
|
651
|
+
if (!s) return { ok: false, error: `não há skill "${pedido}"`, disponiveis: skills.listar().map((x) => x.nome) };
|
|
652
|
+
if (!s.ligada) return { ok: false, error: `a skill "${s.nome}" está desligada` };
|
|
653
|
+
return { ok: true, nome: s.nome, titulo: s.titulo, descricao: s.descricao, instrucoes: s.corpo };
|
|
654
|
+
}
|
|
655
|
+
|
|
607
656
|
/**
|
|
608
657
|
* Executa uma ferramenta e NUNCA lança.
|
|
609
658
|
*
|
|
@@ -778,6 +827,10 @@ async function despachar(name, args, ctx) {
|
|
|
778
827
|
return multiEdit(args, projectDir);
|
|
779
828
|
case 'write_todos':
|
|
780
829
|
return writeTodos(args);
|
|
830
|
+
case 'notebook_edit':
|
|
831
|
+
return notebookEdit(args, projectDir);
|
|
832
|
+
case 'skill':
|
|
833
|
+
return skillTool(args);
|
|
781
834
|
case 'agentes_buscar':
|
|
782
835
|
return require('../agentes').ferramentaBuscar(args || {});
|
|
783
836
|
case 'capacidade_buscar':
|
package/lib/tui/ui.js
CHANGED
|
@@ -621,7 +621,7 @@ const agora = { texto: '', desde: 0 };
|
|
|
621
621
|
|
|
622
622
|
// O rótulo de cada tool na tela — o que a pessoa lê no lugar do nome interno.
|
|
623
623
|
const ROTULOS = {
|
|
624
|
-
read_file:'Read', write_file:'Write', edit_file:'Edit', multi_edit:'MultiEdit',
|
|
624
|
+
read_file:'Read', write_file:'Write', edit_file:'Edit', multi_edit:'MultiEdit', notebook_edit:'Notebook', skill:'Skill',
|
|
625
625
|
list_dir:'List', glob:'Glob', grep:'Grep', run_shell:'Bash',
|
|
626
626
|
run_in_new_terminal:'Terminal', run_background:'Fundo', background_output:'Fundo·log', stop_background:'Fundo·stop', open_in_browser:'Open', spawn_agent:'Agent',
|
|
627
627
|
write_todos:'Plano', perguntar:'Pergunta', finish:'Finish', pesquisar_referencia:'Pesquisa',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.4.0",
|
|
4
4
|
"description": "PrimoCode — agente de engenharia com IA e cursor próprio. Requer conta Conecta Primo AI (Premium ou Super). Cria arquivos, roda comandos, controla navegador e desktop: abre apps, clica em botões e ícones pelo nome, digita e usa atalhos.",
|
|
5
5
|
"main": "bin/primocode.js",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"postinstall": "node lib/preparar.js || true",
|
|
12
12
|
"test:server": "cd server && npm test",
|
|
13
13
|
"test": "node scripts/testes.js",
|
|
14
|
-
"test:app": "node test/app-fluxo.test.mjs && node test/app-primeira.test.mjs && node test/app-entrar.test.mjs && node test/app-tela-estudio.test.mjs && node test/app-celular-telas.test.mjs && node test/app-
|
|
14
|
+
"test:app": "node test/app-fluxo.test.mjs && node test/app-primeira.test.mjs && node test/app-entrar.test.mjs && node test/app-tela-estudio.test.mjs && node test/app-celular-telas.test.mjs && node test/app-render-celular.test.mjs && node test/app-nuvem-estudio.test.mjs",
|
|
15
15
|
"app:build": "cd app && npm run dist",
|
|
16
16
|
"app:dev": "cd app && npm start",
|
|
17
17
|
"mobile:build": "cd mobile && npm run build"
|
package/voz/primo_voz/chat.html
CHANGED
|
@@ -312,6 +312,30 @@ a{color:#67e8f9}a:hover{color:#22d3ee}
|
|
|
312
312
|
@keyframes efhalo{0%,100%{box-shadow:0 1px 5px rgba(0,0,0,.4),0 0 5px 1px rgba(61,139,255,.5)}50%{box-shadow:0 1px 5px rgba(0,0,0,.4),0 0 9px 2px rgba(61,139,255,.7)}}
|
|
313
313
|
.efs-rec{position:relative;height:14px;margin-top:5px}
|
|
314
314
|
.efs-rec span{position:absolute;top:0;left:calc(var(--pad) + var(--q)*(100% - var(--pad)*2));transform:translateX(-50%);font-size:11px;color:#8a8983;white-space:nowrap}
|
|
315
|
+
/* Vidro nativo (app desktop no macOS, S5): o main.js liga a janela com vibrancy e põe a classe .vidro-nativo.
|
|
316
|
+
Vidro só na lateral, na barra de cima e no botão de enviar; a conversa continua sólida. Sem transparência: cores sólidas de antes. */
|
|
317
|
+
html.vidro-nativo,html.vidro-nativo body{background:transparent}
|
|
318
|
+
html.vidro-nativo .app{background:transparent}
|
|
319
|
+
html.vidro-nativo #main{background:#131312}
|
|
320
|
+
html.vidro-nativo #lado{background:rgba(15,15,14,.42);border-right-color:rgba(255,255,255,.07);box-shadow:inset -1px 0 0 rgba(255,255,255,.04)}
|
|
321
|
+
html.vidro-nativo #main>header{background:linear-gradient(180deg,rgba(255,255,255,.06),transparent 70%),rgba(19,19,18,.55);-webkit-backdrop-filter:blur(22px) saturate(1.6);backdrop-filter:blur(22px) saturate(1.6);border-bottom-color:rgba(255,255,255,.08)}
|
|
322
|
+
html.vidro-nativo .enviar.cheio{background:linear-gradient(180deg,rgba(255,255,255,.28),transparent 55%),#3b6fd4;box-shadow:inset 0 1px 0 rgba(255,255,255,.45),0 4px 14px rgba(59,111,212,.35)}
|
|
323
|
+
html.vidro-nativo .menu{transition-timing-function:cubic-bezier(.32,.72,0,1)}
|
|
324
|
+
/* Mola e folha (S5, kit apple.js inlinado): menus abrem com mola (linear() do CSS) e fecham arrastando para baixo. */
|
|
325
|
+
:root{--ap-mola:cubic-bezier(.32,.72,0,1)}
|
|
326
|
+
.menu:not(.pal){animation-duration:.5s;animation-timing-function:var(--ap-mola)}
|
|
327
|
+
.menu.ap-solta{transition:transform .5s var(--ap-mola),opacity .25s;animation:none}
|
|
328
|
+
.menu.ap-arrasta{transition:none;animation:none;cursor:grabbing}
|
|
329
|
+
/* Popover do esforço: abre com a mesma mola do menu, a partir do chip (sem arrasto, porque tem slider). */
|
|
330
|
+
.menu.efs-pop{animation:abre .5s var(--ap-mola) both;transform-origin:bottom left}
|
|
331
|
+
@media (prefers-reduced-motion:reduce){.menu.efs-pop{animation:none}}
|
|
332
|
+
@media (prefers-reduced-motion:reduce){.menu.ap-solta{transition:none}}
|
|
333
|
+
@media (prefers-reduced-transparency:reduce){
|
|
334
|
+
html.vidro-nativo #lado{background:#0f0f0e}
|
|
335
|
+
html.vidro-nativo #main>header{background:#131312;-webkit-backdrop-filter:none;backdrop-filter:none}
|
|
336
|
+
html.vidro-nativo .app{background:#131312}
|
|
337
|
+
html.vidro-nativo .enviar.cheio{background:#3b6fd4;box-shadow:none}
|
|
338
|
+
}
|
|
315
339
|
@media (prefers-reduced-motion:reduce){.app *{animation:none !important;transition:none !important}}
|
|
316
340
|
</style>
|
|
317
341
|
</head>
|
|
@@ -898,6 +922,19 @@ function renderAnexos(){
|
|
|
898
922
|
}
|
|
899
923
|
var GAUGE='<path d="M4 17a8 8 0 1 1 16 0"></path><path d="m12 17 4-5"></path>';
|
|
900
924
|
function efNiveis(){var e=estado.ajustes&&estado.ajustes.effort;return e&&Array.isArray(e.niveis)?e.niveis.map(function(x){return typeof x==='string'?{nome:x,resumo:''}:x;}):[];}
|
|
925
|
+
/* Kit apple.js (S5), parte mínima inlinada: mola crítica/amortecida como easing CSS e arrasto-para-fechar no menu. */
|
|
926
|
+
function apMola(t,am,rig){if(t<=0)return 0;if(am>=1)return 1-(1+rig*t)*Math.exp(-rig*t);var w=rig*Math.sqrt(1-am*am);return 1-Math.exp(-am*rig*t)*(Math.cos(w*t)+(am*rig/w)*Math.sin(w*t));}
|
|
927
|
+
(function(){try{var n=40,fim=6/(0.8*24),p=[];for(var i=0;i<=n;i++)p.push(+apMola(i/n*fim,0.8,24).toFixed(4));p[n]=1;
|
|
928
|
+
if(window.CSS&&CSS.supports&&CSS.supports('transition-timing-function','linear(0,1)'))document.documentElement.style.setProperty('--ap-mola','linear('+p.join(',')+')');}catch(e){}})();
|
|
929
|
+
function apLigaMenu(){
|
|
930
|
+
var m=document.querySelector('#menu>.menu');if(!m||m.dataset.ap||m.classList.contains('efs-pop'))return;m.dataset.ap='1';
|
|
931
|
+
var y0=0,t0=0,y1=0,t1=0,on=false;
|
|
932
|
+
m.addEventListener('pointerdown',function(e){if(e.target.closest('button,input,.efs'))return;on=true;y0=y1=e.clientY;t0=t1=performance.now();m.classList.add('ap-arrasta');m.setPointerCapture&&m.setPointerCapture(e.pointerId);});
|
|
933
|
+
m.addEventListener('pointermove',function(e){if(!on)return;y1=e.clientY;t1=performance.now();var d=y1-y0;m.style.transform='translateY('+(d>0?d:-60*(1-Math.exp(d/60)))+'px)';});
|
|
934
|
+
function fim(){if(!on)return;on=false;var d=y1-y0,v=d/Math.max(1,t1-t0);m.classList.remove('ap-arrasta');m.classList.add('ap-solta');
|
|
935
|
+
if(d+v*200>70){m.style.transform='translateY(140%)';m.style.opacity='0';setTimeout(function(){estado.menu=false;renderMenu();},260);}else m.style.transform='';}
|
|
936
|
+
m.addEventListener('pointerup',fim);m.addEventListener('pointercancel',fim);
|
|
937
|
+
}
|
|
901
938
|
function renderMenu(){
|
|
902
939
|
var chip=$('chipModo'),ce=$('chipEf');
|
|
903
940
|
var atual=MODOS.filter(function(o){return o.id===estado.modo;})[0]||MODOS[0];
|
|
@@ -920,6 +957,8 @@ function renderMenu(){
|
|
|
920
957
|
MODOS.map(function(o){return '<button class="opcao" data-a="modo" data-v="'+o.id+'"><span>'+o.t+'<small>'+o.d+'</small></span>'+(o.id===estado.modo?ic(ICO.ok,16,2.6).replace('<svg ','<svg class="ok" '):'')+'</button>';}).join('')+
|
|
921
958
|
'<div style="height:1px;background:#2e2e2b;margin:6px 8px"></div>'+
|
|
922
959
|
'<button class="opcao" data-a="cfgmodelos"><span style="color:#a3a29b;display:inline-flex">'+ic(GEAR,16)+'</span><span>Configurar outros modelos<small>Claude, Codex ou a sua chave</small></span></button></div>';
|
|
960
|
+
|
|
961
|
+
apLigaMenu();
|
|
923
962
|
}
|
|
924
963
|
var EF_NOME={low:'Baixo',medium:'Médio',high:'Alto',xhigh:'Extra alto',max:'Máximo',ultracode:'Ultracode'};
|
|
925
964
|
function efNome(id){return EF_NOME[id]||id;}
|