primocode 8.10.0 → 8.10.1
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/lib/studio.js +61 -7
- package/package.json +1 -1
- package/studio/server.py +27 -0
package/lib/studio.js
CHANGED
|
@@ -84,12 +84,12 @@ function copiar(de, para) {
|
|
|
84
84
|
for (const item of fs.readdirSync(de)) copiar(path.join(de, item), path.join(para, item));
|
|
85
85
|
return;
|
|
86
86
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
// Copia SEMPRE. A comparação esperta (tamanho + data) deixava passar
|
|
88
|
+
// arquivo alterado de mesmo tamanho: o npm instala com data antiga
|
|
89
|
+
// (1985, de propósito, por reprodutibilidade) e o teste de mtime nunca
|
|
90
|
+
// disparava. O que o usuário criou não corre risco — arquivos/ e midia/
|
|
91
|
+
// não estão em COPIAVEIS.
|
|
92
|
+
fs.copyFileSync(de, para);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
function prepararCasa() {
|
|
@@ -139,10 +139,64 @@ async function estaNoAr() {
|
|
|
139
139
|
// Uma subida por vez: sem esta trava, duas tools chamadas no mesmo turno
|
|
140
140
|
// disparariam dois servidores na mesma porta e o segundo morreria com
|
|
141
141
|
// "address in use" — barulho por nada.
|
|
142
|
+
/** A assinatura do server.py que está NO DISCO — a versão que deveria rodar. */
|
|
143
|
+
function assinaturaDoDisco() {
|
|
144
|
+
try {
|
|
145
|
+
const corpo = fs.readFileSync(path.join(CASA, 'server.py'));
|
|
146
|
+
return require('crypto').createHash('sha1').update(corpo).digest('hex').slice(0, 12);
|
|
147
|
+
} catch { return null; }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** A assinatura do processo que está NO AR — a versão que roda de fato. */
|
|
151
|
+
async function assinaturaNoAr() {
|
|
152
|
+
try { return (await pedir('GET', '/api/versao', null, 1500)).assinatura || null; }
|
|
153
|
+
catch { return null; } // servidor antigo não tem a rota — também é "desatualizado"
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Derruba o Studio que está no ar. Primeiro pelo caminho limpo (/api/sair);
|
|
158
|
+
* se for um servidor antigo, que não tem a rota, pelo sistema.
|
|
159
|
+
*/
|
|
160
|
+
async function derrubar() {
|
|
161
|
+
try { await pedir('POST', '/api/sair', {}, 1500); } catch {}
|
|
162
|
+
for (let i = 0; i < 10; i++) {
|
|
163
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
164
|
+
if (!(await estaNoAr())) return true;
|
|
165
|
+
}
|
|
166
|
+
// Servidor antigo: sem /api/sair. Mata pelo padrão da linha de comando.
|
|
167
|
+
try {
|
|
168
|
+
const { execSync } = require('child_process');
|
|
169
|
+
if (os.platform() === 'win32') {
|
|
170
|
+
execSync('powershell -NoProfile -Command "Get-CimInstance Win32_Process | ' +
|
|
171
|
+
"Where-Object {$_.CommandLine -like '*primocode*studio*server.py*'} | " +
|
|
172
|
+
'ForEach-Object { Stop-Process -Id $_.ProcessId -Force }"', { stdio: 'ignore' });
|
|
173
|
+
} else {
|
|
174
|
+
execSync("pkill -f '[.]primocode/studio/server.py'", { stdio: 'ignore' });
|
|
175
|
+
}
|
|
176
|
+
} catch {}
|
|
177
|
+
for (let i = 0; i < 10; i++) {
|
|
178
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
179
|
+
if (!(await estaNoAr())) return true;
|
|
180
|
+
}
|
|
181
|
+
return !(await estaNoAr());
|
|
182
|
+
}
|
|
183
|
+
|
|
142
184
|
let subindo = null;
|
|
143
185
|
|
|
144
186
|
async function garantirNoAr() {
|
|
145
|
-
if (await estaNoAr())
|
|
187
|
+
if (await estaNoAr()) {
|
|
188
|
+
// O npm pode ter trocado o código no disco DEPOIS de este processo
|
|
189
|
+
// subir — e aí o Studio no ar é o antigo, servindo o bug que o
|
|
190
|
+
// update corrigiu. Atualiza o disco e compara as assinaturas; se
|
|
191
|
+
// divergirem, reinicia. Na véspera de uma apresentação, "atualizei
|
|
192
|
+
// mas continua igual" é o pior modo de falha que existe.
|
|
193
|
+
prepararCasa();
|
|
194
|
+
const doDisco = assinaturaDoDisco();
|
|
195
|
+
const noAr = await assinaturaNoAr();
|
|
196
|
+
if (!doDisco || (noAr && noAr === doDisco)) return { ok: true, url: BASE, jaEstava: true };
|
|
197
|
+
await derrubar();
|
|
198
|
+
// cai para o caminho de subida normal, já com o código novo
|
|
199
|
+
}
|
|
146
200
|
if (subindo) return subindo;
|
|
147
201
|
|
|
148
202
|
subindo = (async () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "8.10.
|
|
3
|
+
"version": "8.10.1",
|
|
4
4
|
"description": "PrimoCode — agente de engenharia com IA e cursor próprio. Modelos grátis. 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/server.py
CHANGED
|
@@ -9,6 +9,7 @@ Só biblioteca padrão do Python. Nada para instalar.
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
import base64
|
|
12
|
+
import hashlib
|
|
12
13
|
import functools
|
|
13
14
|
import json
|
|
14
15
|
import mimetypes
|
|
@@ -30,6 +31,19 @@ FERRAMENTAS = os.path.join(RAIZ, "ferramentas")
|
|
|
30
31
|
MIDIA = os.path.join(RAIZ, "midia")
|
|
31
32
|
PORTA = int(sys.argv[1]) if len(sys.argv) > 1 else 7777
|
|
32
33
|
|
|
34
|
+
def _assinatura():
|
|
35
|
+
"""SHA-1 do próprio arquivo, lido NA SUBIDA. Se o npm trocar o código no
|
|
36
|
+
disco depois, o processo no ar continua respondendo a assinatura antiga —
|
|
37
|
+
e é exatamente essa diferença que o CLI usa para saber que precisa
|
|
38
|
+
reiniciar o Studio em vez de servir a versão velha para sempre."""
|
|
39
|
+
try:
|
|
40
|
+
with open(os.path.abspath(__file__), "rb") as fh:
|
|
41
|
+
return hashlib.sha1(fh.read()).hexdigest()[:12]
|
|
42
|
+
except Exception:
|
|
43
|
+
return "?"
|
|
44
|
+
|
|
45
|
+
ASSINATURA = _assinatura()
|
|
46
|
+
|
|
33
47
|
# 60 MB por arquivo: cobre faixa longa em mp3 e foto de câmera sem susto
|
|
34
48
|
LIMITE_MIDIA = 60 * 1024 * 1024
|
|
35
49
|
|
|
@@ -506,6 +520,9 @@ class Handler(SimpleHTTPRequestHandler):
|
|
|
506
520
|
texto = fh.read()
|
|
507
521
|
return self.txt(texto or json.dumps(info, ensure_ascii=False, indent=2))
|
|
508
522
|
|
|
523
|
+
if p == "/api/versao":
|
|
524
|
+
return self.js({"assinatura": ASSINATURA})
|
|
525
|
+
|
|
509
526
|
if p == "/api/voz":
|
|
510
527
|
return self.voz(urlparse(self.path).query)
|
|
511
528
|
if p == "/api/voz/status":
|
|
@@ -752,6 +769,16 @@ class Handler(SimpleHTTPRequestHandler):
|
|
|
752
769
|
corpo, status = guardar_midia(self.corpo())
|
|
753
770
|
return self.js(corpo, status)
|
|
754
771
|
|
|
772
|
+
if rota == "/api/sair":
|
|
773
|
+
# Só o CLI chama, e só quando o código no disco mudou (update do
|
|
774
|
+
# npm). Sem isto o processo antigo ficava no ar servindo o bug que
|
|
775
|
+
# o update tinha acabado de corrigir. Responde antes de morrer,
|
|
776
|
+
# para quem pediu não ver erro de conexão.
|
|
777
|
+
self.js({"ok": True, "tchau": ASSINATURA})
|
|
778
|
+
import threading
|
|
779
|
+
threading.Thread(target=lambda: (time.sleep(0.3), os._exit(0)), daemon=True).start()
|
|
780
|
+
return
|
|
781
|
+
|
|
755
782
|
if rota == "/api/arquivos":
|
|
756
783
|
try:
|
|
757
784
|
a = criar(self.corpo())
|