primocode 9.2.0 → 9.2.2
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/act.js +58 -6
- package/lib/repeticao.js +6 -1
- package/lib/tools.js +35 -10
- package/lib/ui.js +26 -4
- package/lib/voz.js +60 -9
- package/package.json +1 -1
- package/studio/edge.py +13 -3
- package/studio/pessoa.py +24 -7
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# PrimoCode v9.2.
|
|
1
|
+
# PrimoCode v9.2.2
|
|
2
2
|
|
|
3
3
|
Agente de engenharia brasileiro para o terminal, no estilo Claude Code.
|
|
4
4
|
Cria arquivos de verdade, roda comandos, **controla o navegador e o desktop** —
|
|
@@ -9,7 +9,7 @@ OpenRouter, e cada apelido (`top`/`main`/`fast`) é uma **corrente**: se um mode
|
|
|
9
9
|
está no limite de pedidos, o próximo atende. Você não vê o erro, e não paga nada.
|
|
10
10
|
|
|
11
11
|
```
|
|
12
|
-
▐▛███▜▌ Primo Code v9.2.
|
|
12
|
+
▐▛███▜▌ Primo Code v9.2.2
|
|
13
13
|
▝▜█████▛▘ Bem-vindo de volta, Joel
|
|
14
14
|
▘▘ ▝▝ ~/primocode · /dir <pasta> muda
|
|
15
15
|
```
|
package/lib/act.js
CHANGED
|
@@ -541,13 +541,29 @@ async function runAct(userPrompt, opts) {
|
|
|
541
541
|
if (!subagent && profundidade === 0) {
|
|
542
542
|
const producao = require('./estudio/diretor.js').precisaDeProducao(pedido);
|
|
543
543
|
if (producao.producao) {
|
|
544
|
+
/* "SE ESTIVER NA SUA LISTA" não é hesitação — é a diferença entre
|
|
545
|
+
a tarefa seguir e a tarefa morrer.
|
|
546
|
+
|
|
547
|
+
A primeira versão mandava chamar `planejar_producao` sem
|
|
548
|
+
ressalva. Só que quem declara as ferramentas ao modelo é o
|
|
549
|
+
SERVIDOR, e este texto é do cliente: com o servidor
|
|
550
|
+
desatualizado, o modelo obedecia a uma ordem para chamar algo
|
|
551
|
+
que não estava na lista dele, e o provedor recusava com
|
|
552
|
+
"attempted to call tool 'planejar_producao' which was not in
|
|
553
|
+
request.tools" — erro FATAL, em inglês, na cara do usuário, com
|
|
554
|
+
a tarefa perdida antes da primeira ação.
|
|
555
|
+
|
|
556
|
+
Um modelo não chama tool fora da lista por conta própria. Só
|
|
557
|
+
chamou porque este parágrafo mandou. */
|
|
544
558
|
messages.push({ role: 'system', content:
|
|
545
|
-
'ESTE PEDIDO É DE PRODUÇÃO AUDIOVISUAL.
|
|
546
|
-
+ `
|
|
547
|
-
+ '
|
|
548
|
-
+ 'e é ela que você produz, cena a cena, compondo com as primitivas.
|
|
549
|
-
+ '
|
|
550
|
-
+ 'que
|
|
559
|
+
'ESTE PEDIDO É DE PRODUÇÃO AUDIOVISUAL. Se `planejar_producao` estiver na sua lista de '
|
|
560
|
+
+ `ferramentas, chame-o ANTES de criar qualquer peça (formato "${producao.formato}"): `
|
|
561
|
+
+ 'ele devolve a pauta — arco, narração falada, voz, direção de arte e a mídia a buscar — '
|
|
562
|
+
+ 'e é ela que você produz, cena a cena, compondo com as primitivas. '
|
|
563
|
+
+ 'Se ele NÃO estiver na sua lista, não o chame e não procure equivalente: siga com as '
|
|
564
|
+
+ 'ferramentas que você tem e escreva o roteiro antes das cenas, não dentro do JSON delas. '
|
|
565
|
+
+ 'Nunca chame uma ferramenta que não está na sua lista — a chamada é recusada e a tarefa '
|
|
566
|
+
+ 'se perde.' });
|
|
551
567
|
}
|
|
552
568
|
}
|
|
553
569
|
|
|
@@ -569,6 +585,11 @@ async function runAct(userPrompt, opts) {
|
|
|
569
585
|
// usuário — que não tem prompt nenhum para ajustar. É o modelo tropeçando
|
|
570
586
|
// ao escrever o JSON da chamada, e quase sempre acerta na segunda.
|
|
571
587
|
let funcoesQuebradas = 0;
|
|
588
|
+
/* As ferramentas que o provedor recusou por não estarem na lista do
|
|
589
|
+
pedido. Guardadas para não entrar em laço: tira a instrução, tenta de
|
|
590
|
+
novo UMA vez, e se voltar a acontecer com a mesma ferramenta é porque a
|
|
591
|
+
causa é outra. */
|
|
592
|
+
const toolsRecusadas = new Set();
|
|
572
593
|
let respostasVazias = 0;
|
|
573
594
|
// O teto de tokens é POR MINUTO, somando TODAS as chamadas — o plano, o
|
|
574
595
|
// resumo e cada rodada do loop dividem a mesma cota. Um pedido perfeito
|
|
@@ -674,6 +695,37 @@ async function runAct(userPrompt, opts) {
|
|
|
674
695
|
continue;
|
|
675
696
|
}
|
|
676
697
|
|
|
698
|
+
/* FERRAMENTA FORA DA LISTA DO PEDIDO — recuperável, e antes não era.
|
|
699
|
+
O provedor recusa com "attempted to call tool 'X' which was not in
|
|
700
|
+
request.tools" e a rodada morria ali: erro cru em inglês, tarefa
|
|
701
|
+
perdida antes da primeira ação. Visto em produção num "crie um
|
|
702
|
+
vídeo para a Paris Group".
|
|
703
|
+
|
|
704
|
+
Quem mandou chamar aquilo foi uma instrução NOSSA, de sistema (o
|
|
705
|
+
gatilho do diretor). Então a cura é tirar a instrução que cita a
|
|
706
|
+
ferramenta recusada e repetir a rodada: o modelo segue com o que
|
|
707
|
+
ele realmente tem, e a pessoa recebe o trabalho em vez de um erro.
|
|
708
|
+
Uma vez por ferramenta — insistindo, o problema é outro. */
|
|
709
|
+
const foraDaLista = streamError && String(streamError)
|
|
710
|
+
.match(/attempted to call tool '([^']+)' which was not in request\.tools/i);
|
|
711
|
+
if (foraDaLista && !PARAR && !toolsRecusadas.has(foraDaLista[1])) {
|
|
712
|
+
toolsRecusadas.add(foraDaLista[1]);
|
|
713
|
+
const nome = foraDaLista[1];
|
|
714
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
715
|
+
if (messages[i].role === 'system' && String(messages[i].content || '').includes(nome)) {
|
|
716
|
+
messages.splice(i, 1);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
messages.push({ role: 'system', content:
|
|
720
|
+
`A ferramenta "${nome}" NÃO está disponível nesta sessão. Não a chame e não procure `
|
|
721
|
+
+ 'equivalente com outro nome: siga a tarefa com as ferramentas da sua lista. '
|
|
722
|
+
+ 'Se nenhuma delas servir para o que foi pedido, diga isso ao usuário em uma frase, '
|
|
723
|
+
+ 'em vez de desistir sem explicar.' });
|
|
724
|
+
if (fluxo) fluxo.fechar();
|
|
725
|
+
iteration--;
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
|
|
677
729
|
if (streamError && !PARAR && (ehTetoDeToken(streamError) || ehTransitorio(streamError))) {
|
|
678
730
|
if (fluxo) fluxo.fechar();
|
|
679
731
|
// Conversa longa: encolher resolve, porque o peso é o histórico.
|
package/lib/repeticao.js
CHANGED
|
@@ -33,7 +33,12 @@
|
|
|
33
33
|
'use strict';
|
|
34
34
|
|
|
35
35
|
/** Tools cuja resposta é documento fixo — repetir é sempre desperdício. */
|
|
36
|
-
|
|
36
|
+
/* O documento fixo que NÃO pode encolher na conversa. Eram o manual e a spec
|
|
37
|
+
do Estúdio antigo; o Estúdio novo não tem manual porque não tem formato a
|
|
38
|
+
decorar. Quem ocupa esse lugar é a PAUTA do diretor, escrita uma vez, que o
|
|
39
|
+
produtor relê enquanto monta as cenas — encolhê-la faz o modelo chamar o
|
|
40
|
+
diretor de novo, que é o laço que isto existe para evitar. */
|
|
41
|
+
const REFERENCIAS = new Set(['planejar_producao', 'estudio_site']);
|
|
37
42
|
|
|
38
43
|
/** Acima disto, duas buscas são a mesma busca. */
|
|
39
44
|
const LIMITE_SEMELHANCA = 0.6;
|
package/lib/tools.js
CHANGED
|
@@ -429,8 +429,9 @@ async function despachar(name, args, ctx) {
|
|
|
429
429
|
return {
|
|
430
430
|
ok: false,
|
|
431
431
|
error: `"${args.path}" é arquivo de vídeo/áudio, e write_file só escreve TEXTO — isso criaria um arquivo falso que não abre. `
|
|
432
|
-
+ 'Vídeo se faz no
|
|
433
|
-
+ 'O
|
|
432
|
+
+ 'Vídeo se faz no Estúdio: estudio_projeto (criar) → planejar_producao → estudio_plano → '
|
|
433
|
+
+ 'estudio_render. O render devolve o CAMINHO do arquivo — é esse o vídeo. '
|
|
434
|
+
+ 'Não monte vídeo com ffmpeg à mão.',
|
|
434
435
|
};
|
|
435
436
|
}
|
|
436
437
|
const filePath = resolveInProject(args.path, projectDir);
|
|
@@ -610,14 +611,38 @@ async function despachar(name, args, ctx) {
|
|
|
610
611
|
divergir do catálogo daqui, e ela se conserta lá. Mas a rede
|
|
611
612
|
é o que separa uma tarefa que se recupera de uma que morre
|
|
612
613
|
com o usuário esperando. */
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
614
|
+
{
|
|
615
|
+
/* O CASO QUE ACONTECE DE VERDADE tem nome: o servidor
|
|
616
|
+
está desatualizado e continua oferecendo o Estúdio
|
|
617
|
+
antigo (studio_criar, studio_spec…). Visto ao vivo, e a
|
|
618
|
+
reação do modelo foi a pior possível: chutou nomes de
|
|
619
|
+
outro produto (`repo_browser.read_file`), tentou
|
|
620
|
+
escrever um .mp4 com write_file, e desistiu com "parei
|
|
621
|
+
sem concluir" — sem nunca dizer ao usuário qual era o
|
|
622
|
+
problema.
|
|
623
|
+
|
|
624
|
+
Então a resposta separa os dois casos. Para o Estúdio
|
|
625
|
+
velho ela EXPLICA o que houve e manda diagnosticar em
|
|
626
|
+
voz alta se as novas não estiverem na lista dele: um
|
|
627
|
+
"o servidor está desatualizado" é acionável; um "parei
|
|
628
|
+
sem concluir" não é. */
|
|
629
|
+
const velhoEstudio = /^studio_/.test(String(name));
|
|
630
|
+
return {
|
|
631
|
+
ok: false,
|
|
632
|
+
error: `A ferramenta "${name}" não existe.`,
|
|
633
|
+
parecidas: catalogo.parecidasCom(name),
|
|
634
|
+
comoResolver: velhoEstudio
|
|
635
|
+
? 'O Estúdio foi TROCADO: studio_* não existe mais. As ferramentas de hoje são '
|
|
636
|
+
+ 'planejar_producao, estudio_projeto, estudio_plano, estudio_midia, estudio_narrar, '
|
|
637
|
+
+ 'estudio_video, estudio_render e estudio_site. Use-as. '
|
|
638
|
+
+ 'Se ELAS não estiverem na sua lista de ferramentas, o servidor está desatualizado: '
|
|
639
|
+
+ 'PARE de tentar outros nomes e diga isso ao usuário em uma frase — é o que ele '
|
|
640
|
+
+ 'precisa saber para resolver.'
|
|
641
|
+
: 'Chame uma das ferramentas que existem de verdade; as "parecidas" acima são as de '
|
|
642
|
+
+ 'nome mais próximo. NÃO invente nome de ferramenta e não tente variações — se '
|
|
643
|
+
+ 'nenhuma servir, diga ao usuário o que faltou, em vez de desistir calado.',
|
|
644
|
+
};
|
|
645
|
+
}
|
|
621
646
|
}
|
|
622
647
|
}
|
|
623
648
|
}
|
package/lib/ui.js
CHANGED
|
@@ -295,12 +295,18 @@ const ROTULOS = {
|
|
|
295
295
|
desktop_drag:'Desktop·drag', desktop_type:'Desktop·type', desktop_key:'Desktop·key',
|
|
296
296
|
desktop_scroll:'Desktop·scroll', desktop_size:'Desktop·size',
|
|
297
297
|
desktop_double_click:'Desktop·2click', desktop_open_app:'Desktop·app',
|
|
298
|
-
studio_manual
|
|
299
|
-
|
|
300
|
-
|
|
298
|
+
/* Os nomes do Estúdio ATUAL. Os antigos (studio_manual, studio_spec,
|
|
299
|
+
studio_criar…) saíram daqui de propósito: enquanto estavam, uma
|
|
300
|
+
chamada a ferramenta que não existe mais aparecia na tela com
|
|
301
|
+
rótulo bonito ("Estúdio·spec corte"), como se fosse trabalho
|
|
302
|
+
legítimo — e só a linha de baixo revelava que tinha falhado. */
|
|
303
|
+
planejar_producao:'Diretor', estudio_projeto:'Estúdio·projeto',
|
|
304
|
+
estudio_plano:'Estúdio·plano', estudio_narrar:'Estúdio·narração',
|
|
305
|
+
estudio_video:'Estúdio·vídeo', estudio_render:'Estúdio·render',
|
|
306
|
+
estudio_site:'Estúdio·marca',
|
|
301
307
|
desktop_windows:'Desktop·janelas', desktop_focus:'Desktop·foco',
|
|
302
308
|
desktop_elements:'Desktop·elementos', desktop_click_element:'Desktop·click',
|
|
303
|
-
|
|
309
|
+
estudio_midia:'Estúdio·mídia', construir_app:'Construir',
|
|
304
310
|
remember_general:'Lembrar',
|
|
305
311
|
};
|
|
306
312
|
|
|
@@ -343,6 +349,22 @@ function friendlyError(raw) {
|
|
|
343
349
|
}
|
|
344
350
|
} catch {}
|
|
345
351
|
|
|
352
|
+
/* FERRAMENTA QUE NÃO ESTÁ NA LISTA DO PEDIDO.
|
|
353
|
+
O provedor recusa a chamada e devolve, em inglês:
|
|
354
|
+
"tool call validation failed: attempted to call tool 'X' which was not
|
|
355
|
+
in request.tools". Isso saía cru na tela e a tarefa morria ali.
|
|
356
|
+
|
|
357
|
+
A causa é sempre a mesma e não é do usuário: o servidor declara um
|
|
358
|
+
conjunto de ferramentas e o cliente esperava outro. Então a frase diz o
|
|
359
|
+
que É — em português — e aponta para o que resolve. */
|
|
360
|
+
const foraDaLista = s.match(/attempted to call tool '([^']+)' which was not in request\.tools/i);
|
|
361
|
+
if (foraDaLista) {
|
|
362
|
+
return `O servidor não ofereceu a ferramenta "${foraDaLista[1]}" nesta chamada, `
|
|
363
|
+
+ 'então ela foi recusada. Isso acontece quando o servidor está numa versão '
|
|
364
|
+
+ 'anterior à do aplicativo. Confira em /status; se persistir, o servidor precisa '
|
|
365
|
+
+ 'ser atualizado.';
|
|
366
|
+
}
|
|
367
|
+
|
|
346
368
|
// Falta de saldo: nem bug, nem espera. O provedor diz quanto a conta paga,
|
|
347
369
|
// e o servidor já refaz o pedido com esse teto — se ainda assim chegou aqui,
|
|
348
370
|
// é porque nem o mínimo cabe.
|
package/lib/voz.js
CHANGED
|
@@ -125,7 +125,13 @@ function conferir() {
|
|
|
125
125
|
|
|
126
126
|
const pip = falta.filter((f) => f.pacote).map((f) => f.pacote);
|
|
127
127
|
const linhas = [];
|
|
128
|
-
|
|
128
|
+
/* O comando que a pessoa vai COPIAR precisa funcionar na máquina dela.
|
|
129
|
+
Mandar `pip install` seco é mandar ela bater na PEP 668 de novo,
|
|
130
|
+
exatamente no mesmo lugar onde o botão "Instalar agora?" já bateu. */
|
|
131
|
+
if (pip.length) {
|
|
132
|
+
linhas.push(`${py} -m pip install --user ${pip.join(' ')}`);
|
|
133
|
+
linhas.push(`(se recusar com "externally-managed-environment", acrescente --break-system-packages)`);
|
|
134
|
+
}
|
|
129
135
|
if (falta.some((f) => f.modulo === 'sounddevice' && f.sistema)) {
|
|
130
136
|
linhas.push(COMO_SISTEMA.portaudio[process.platform] || COMO_SISTEMA.portaudio.linux);
|
|
131
137
|
}
|
|
@@ -146,16 +152,61 @@ function conferir() {
|
|
|
146
152
|
}
|
|
147
153
|
|
|
148
154
|
/** Instala o que dá para instalar com pip. O tkinter não passa por aqui. */
|
|
155
|
+
/* AS TENTATIVAS, EM ORDEM — e por que uma só não serve.
|
|
156
|
+
*
|
|
157
|
+
* O Python do Homebrew e o do Debian/Ubuntu RECUSAM `pip install` no ambiente
|
|
158
|
+
* do sistema: é a PEP 668, "externally-managed-environment". Uma tentativa
|
|
159
|
+
* só, sem `--break-system-packages`, falha sempre nessas máquinas — e foi o
|
|
160
|
+
* que aconteceu com o dono, no Mac dele, ao aceitar "Instalar agora?".
|
|
161
|
+
*
|
|
162
|
+
* A ordem vai da menos invasiva para a mais: `--user` primeiro, que não toca
|
|
163
|
+
* no Python do sistema; a quebra do bloqueio só quando o resto falhou.
|
|
164
|
+
*/
|
|
165
|
+
const TENTATIVAS_PIP = [
|
|
166
|
+
(pacotes) => ['-m', 'pip', 'install', '--user', '--quiet', ...pacotes],
|
|
167
|
+
(pacotes) => ['-m', 'pip', 'install', '--user', '--quiet', '--break-system-packages', ...pacotes],
|
|
168
|
+
(pacotes) => ['-m', 'pip', 'install', '--quiet', ...pacotes],
|
|
169
|
+
(pacotes) => ['-m', 'pip', 'install', '--quiet', '--break-system-packages', ...pacotes],
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
/** A linha que interessa de um despejo do pip. */
|
|
173
|
+
function porqueOPipFalhou(saida) {
|
|
174
|
+
const texto = String(saida || '').trim();
|
|
175
|
+
if (!texto) return '';
|
|
176
|
+
if (/externally-managed-environment/i.test(texto)) {
|
|
177
|
+
return 'o Python desta máquina não deixa instalar no ambiente do sistema (PEP 668)';
|
|
178
|
+
}
|
|
179
|
+
/* A linha que começa com "error:" é a que o pip escreveu para ser lida.
|
|
180
|
+
Cortar os últimos 400 caracteres — o que se fazia antes — pega o MEIO
|
|
181
|
+
de uma frase: o dono viu "não deu: n result in a broken Homebrew
|
|
182
|
+
installation", que não é português nem inglês. */
|
|
183
|
+
const erro = texto.split('\n').reverse().find((l) => /^\s*error:/i.test(l));
|
|
184
|
+
if (erro) return erro.replace(/^\s*error:\s*/i, '').trim().slice(0, 200);
|
|
185
|
+
const ultima = texto.split('\n').filter((l) => l.trim()).pop() || '';
|
|
186
|
+
return ultima.trim().slice(0, 200);
|
|
187
|
+
}
|
|
188
|
+
|
|
149
189
|
function instalar(py, pacotes, aoFalar = () => {}) {
|
|
150
190
|
return new Promise((resolve) => {
|
|
151
191
|
aoFalar(`instalando ${pacotes.join(', ')} (uma vez, alguns minutos)`);
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
192
|
+
let ultimoErro = '';
|
|
193
|
+
|
|
194
|
+
const tentar = (i) => {
|
|
195
|
+
if (i >= TENTATIVAS_PIP.length) {
|
|
196
|
+
return resolve({ ok: false, error: ultimoErro || 'o pip não conseguiu instalar' });
|
|
197
|
+
}
|
|
198
|
+
const p = spawn(py, TENTATIVAS_PIP[i](pacotes));
|
|
199
|
+
let saida = '';
|
|
200
|
+
p.stderr.on('data', (d) => { saida += d.toString(); });
|
|
201
|
+
p.stdout.on('data', (d) => { saida += d.toString(); });
|
|
202
|
+
p.on('close', (code) => {
|
|
203
|
+
if (code === 0) return resolve({ ok: true });
|
|
204
|
+
ultimoErro = porqueOPipFalhou(saida) || `pip saiu com ${code}`;
|
|
205
|
+
tentar(i + 1);
|
|
206
|
+
});
|
|
207
|
+
p.on('error', (e) => { ultimoErro = e.message; tentar(i + 1); });
|
|
208
|
+
};
|
|
209
|
+
tentar(0);
|
|
159
210
|
});
|
|
160
211
|
}
|
|
161
212
|
|
|
@@ -336,4 +387,4 @@ print(json.dumps(desperta.baixar_modelo()))
|
|
|
336
387
|
});
|
|
337
388
|
}
|
|
338
389
|
|
|
339
|
-
module.exports = { conferir, instalar, abrir, conferirEspera, baixarEspera, python, COMO_FALAR, PASTA };
|
|
390
|
+
module.exports = { conferir, instalar, porqueOPipFalhou, abrir, conferirEspera, baixarEspera, python, COMO_FALAR, PASTA };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "9.2.
|
|
3
|
+
"version": "9.2.2",
|
|
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": {
|
package/studio/edge.py
CHANGED
|
@@ -120,9 +120,19 @@ def disponivel():
|
|
|
120
120
|
def _rotina_instalar():
|
|
121
121
|
global _instalando, _verificado
|
|
122
122
|
try:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
# As mesmas tentativas do narrar.py: `--user` sozinho é recusado pelo
|
|
124
|
+
# Python do Homebrew e do Debian/Ubuntu (PEP 668), e uma tentativa só
|
|
125
|
+
# falha sempre nessas máquinas — calada, porque a saída vai para o
|
|
126
|
+
# DEVNULL. A narração então cai na voz de reserva e o vídeo sai com
|
|
127
|
+
# outra voz, sem ninguém saber por quê.
|
|
128
|
+
for args in (["-m", "pip", "install", "--user", "--quiet", "edge-tts"],
|
|
129
|
+
["-m", "pip", "install", "--user", "--quiet", "--break-system-packages", "edge-tts"],
|
|
130
|
+
["-m", "pip", "install", "--quiet", "edge-tts"],
|
|
131
|
+
["-m", "pip", "install", "--quiet", "--break-system-packages", "edge-tts"]):
|
|
132
|
+
r = subprocess.run([sys.executable] + args,
|
|
133
|
+
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=180)
|
|
134
|
+
if r.returncode == 0:
|
|
135
|
+
break
|
|
126
136
|
except Exception as e:
|
|
127
137
|
sys.stderr.write("[edge] pip install falhou: %s\n" % e)
|
|
128
138
|
finally:
|
package/studio/pessoa.py
CHANGED
|
@@ -72,12 +72,27 @@ def faltando():
|
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
def instalar(pacotes):
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
"""Traz os pacotes, tentando na ordem da menos invasiva para a mais.
|
|
76
|
+
|
|
77
|
+
Uma tentativa só não serve: o Python do Homebrew e o do Debian/Ubuntu
|
|
78
|
+
recusam instalar no ambiente do sistema (PEP 668,
|
|
79
|
+
"externally-managed-environment"), e nessas máquinas a instalação falharia
|
|
80
|
+
sempre — aqui, calada, porque a saída é capturada e o except engole.
|
|
81
|
+
"""
|
|
82
|
+
tentativas = (
|
|
83
|
+
["-m", "pip", "install", "--user", "--quiet", *pacotes],
|
|
84
|
+
["-m", "pip", "install", "--user", "--quiet", "--break-system-packages", *pacotes],
|
|
85
|
+
["-m", "pip", "install", "--quiet", *pacotes],
|
|
86
|
+
["-m", "pip", "install", "--quiet", "--break-system-packages", *pacotes],
|
|
87
|
+
)
|
|
88
|
+
for args in tentativas:
|
|
89
|
+
try:
|
|
90
|
+
r = subprocess.run([sys.executable] + args, capture_output=True, timeout=900)
|
|
91
|
+
if r.returncode == 0 and not faltando():
|
|
92
|
+
return True
|
|
93
|
+
except Exception:
|
|
94
|
+
continue
|
|
95
|
+
return False
|
|
81
96
|
|
|
82
97
|
|
|
83
98
|
def recortar(entrada, saida, suavizar=7, encolher=2, limiar=0.6):
|
|
@@ -176,7 +191,9 @@ def main():
|
|
|
176
191
|
|
|
177
192
|
if args.comando == "conferir":
|
|
178
193
|
saida = {"ok": not falta, "falta": falta,
|
|
179
|
-
"comoResolver": f"{sys.executable} -m pip install " + " ".join(falta)
|
|
194
|
+
"comoResolver": (f"{sys.executable} -m pip install --user " + " ".join(falta)
|
|
195
|
+
+ ' (se recusar com "externally-managed-environment",'
|
|
196
|
+
" acrescente --break-system-packages)") if falta else None}
|
|
180
197
|
else:
|
|
181
198
|
if falta:
|
|
182
199
|
# A instalação é pesada (o MediaPipe passa de 100 MB) e demora.
|