auroq-os 2.4.17 → 2.4.19
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/.auroq-core/core-config.yaml +1 -1
- package/CHANGELOG.md +24 -0
- package/bin/auroq-os.js +204 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.4.19] — 2026-09-04
|
|
4
|
+
|
|
5
|
+
### O npx guarda cache por ambiente — o Codex abria "a frio" mesmo depois de aquecido
|
|
6
|
+
Medido em Windows limpo: com o pacote ja baixado no PowerShell (2s), a primeira abertura com o
|
|
7
|
+
ambiente que o Codex da ao MCP ainda levava 45-53s. Cada ambiente tem o seu cache. E o que
|
|
8
|
+
explica a aluna ver timeout (30s) ontem e "ferramenta indisponivel" hoje, com o `doctor` limpo.
|
|
9
|
+
- `init`, `update` e `register-mcp` aquecem os DOIS ambientes no Windows: o do terminal e o
|
|
10
|
+
do Codex (minimo + variaveis do registro).
|
|
11
|
+
- O `doctor` ja abria "como o Codex abre" (2.4.18); agora esse passo usa exatamente o mesmo
|
|
12
|
+
ambiente do aquecimento, entao ele tambem prepara a maquina.
|
|
13
|
+
|
|
14
|
+
## [2.4.18] — 2026-09-04
|
|
15
|
+
|
|
16
|
+
### Doctor faz o trabalho do suporte numa tela so (Codex)
|
|
17
|
+
Caso EMB-028 virou ping-pong com a aluna ("roda isso, agora aquilo, manda o print"). Agora o
|
|
18
|
+
`doctor`, quando o projeto tem registro no Codex:
|
|
19
|
+
- mostra a versao do Codex e o bloco `[mcp_servers.arcane]` da pasta;
|
|
20
|
+
- **tira um registro duplicado do arcane do config global** do Codex (`~/.codex/config.toml`),
|
|
21
|
+
com backup — e o que briga com o registro da pasta;
|
|
22
|
+
- **abre o conteudo ao vivo do jeito que o Codex abre** (ambiente minimo do Windows + env do
|
|
23
|
+
registro), lista as ferramentas, confere se `forja_bundle` esta la e chama `catalogo_listar`;
|
|
24
|
+
- imprime as ultimas linhas do log do Codex sobre o arcane e o stderr do MCP;
|
|
25
|
+
- se algo falhou, o unico pedido e "mande o print desta tela inteira".
|
|
26
|
+
|
|
3
27
|
## [2.4.17] — 2026-09-04
|
|
4
28
|
|
|
5
29
|
### Doctor no Windows: tela limpa
|
package/bin/auroq-os.js
CHANGED
|
@@ -390,6 +390,47 @@ async function runDoctor(targetDir) {
|
|
|
390
390
|
}
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
+
// 7. Codex — relatorio completo numa tela so (EMB-028). Nada de pedir comando por comando.
|
|
394
|
+
if (pin && !semArranque && temToml) {
|
|
395
|
+
log('');
|
|
396
|
+
log(` ${CYAN}Codex — relatorio pro suporte${RESET}`);
|
|
397
|
+
const vCodex = versaoDoCodex();
|
|
398
|
+
if (vCodex) success(`Codex: ${vCodex}`);
|
|
399
|
+
else warn('Codex nao encontrado nesta janela (so importa se voce usa o Codex)');
|
|
400
|
+
|
|
401
|
+
const globalPath = path.join(require('os').homedir(), '.codex', 'config.toml');
|
|
402
|
+
const g = await limparArcaneDoGlobal(globalPath);
|
|
403
|
+
if (g.existia) {
|
|
404
|
+
log(` ${DIM}Config global do Codex: ${g.servidores.length} servidor(es)${g.servidores.length ? ` (${g.servidores.join(', ')})` : ''}${RESET}`);
|
|
405
|
+
if (g.removido) success(`Registro duplicado do arcane no global: removido (backup em ${path.basename(g.backup)})`);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const bloco = lerBlocoArcane(tomlTexto);
|
|
409
|
+
for (const l of bloco.split('\n').filter(l => l && !l.startsWith('#'))) log(` ${DIM} ${l.length > 160 ? `${l.slice(0, 157)}...` : l}${RESET}`);
|
|
410
|
+
|
|
411
|
+
log(` ${DIM}Abrindo o conteudo ao vivo do jeito que o Codex abre (ambiente minimo + registro)...${RESET}`);
|
|
412
|
+
const r = await abrirComoCodex(pin, lerEnvDoBloco(bloco));
|
|
413
|
+
if (r.subiu === null) {
|
|
414
|
+
error('Aberto como o Codex abre: NAO respondeu');
|
|
415
|
+
} else {
|
|
416
|
+
const ferramentas = r.tools ? `${r.tools.length} ferramentas (forja_bundle: ${r.temForja ? 'sim' : 'NAO'})` : 'ferramentas: sem resposta';
|
|
417
|
+
const chamada = r.chamada
|
|
418
|
+
? (r.chamada.erro ? `catalogo_listar: ERRO — ${r.chamada.texto}` : `catalogo_listar: OK — ${r.chamada.texto}`)
|
|
419
|
+
: 'catalogo_listar: sem resposta';
|
|
420
|
+
(r.temForja && r.chamada && !r.chamada.erro ? success : error)(`Aberto como o Codex abre: ${(r.subiu / 1000).toFixed(1)}s · ${ferramentas} · ${chamada}`);
|
|
421
|
+
}
|
|
422
|
+
if (r.stderr) for (const l of r.stderr.split('\n').slice(-4)) log(` ${DIM} stderr: ${l.slice(0, 160)}${RESET}`);
|
|
423
|
+
|
|
424
|
+
const rastro = await rastroDoCodex();
|
|
425
|
+
if (rastro.length) {
|
|
426
|
+
log(` ${DIM}Ultimas linhas do log do Codex sobre o arcane:${RESET}`);
|
|
427
|
+
for (const l of rastro) log(` ${DIM} ${l}${RESET}`);
|
|
428
|
+
}
|
|
429
|
+
if (r.subiu === null || !r.temForja || !r.chamada || r.chamada.erro) {
|
|
430
|
+
problemas.push('Mande o print desta tela INTEIRA pro suporte (11 93621-4486) — ela ja traz tudo que precisamos.');
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
393
434
|
log('');
|
|
394
435
|
if (problemas.length === 0) {
|
|
395
436
|
success('Tudo certo. Pode chamar o Companion.');
|
|
@@ -411,6 +452,9 @@ async function runDoctor(targetDir) {
|
|
|
411
452
|
// pacote, (3) o doctor mede o arranque de verdade em vez de so olhar o registro.
|
|
412
453
|
const ARCANE_MCP_PIN = 'arcane-mcp@0.4.1';
|
|
413
454
|
const CODEX_MCP_STARTUP_SEC = 120;
|
|
455
|
+
const CODEX_ENV_MINIMO = ['PATH', 'PATHEXT', 'USERNAME', 'USERDOMAIN', 'USERPROFILE', 'TEMP', 'TMP',
|
|
456
|
+
'HOME', 'LOGNAME', 'SHELL', 'TERM', 'USER'];
|
|
457
|
+
|
|
414
458
|
const MCP_ARRANQUE_LIMITE_MS = 9000; // abaixo do que o Codex tolera (10s)
|
|
415
459
|
|
|
416
460
|
// No Windows o Codex abre o MCP com um ambiente minimo (PATH, USERNAME, USERPROFILE,
|
|
@@ -460,15 +504,39 @@ function comandoNpx(args) {
|
|
|
460
504
|
};
|
|
461
505
|
}
|
|
462
506
|
|
|
507
|
+
// O ambiente que o Codex da ao MCP no Windows (minimo + o env do registro). Medido em
|
|
508
|
+
// 04/09/2026 (windows-latest): o npx guarda cache POR AMBIENTE — aquecido no PowerShell
|
|
509
|
+
// (2s), a primeira abertura pelo Codex ainda levava 45-53s. Por isso aquecemos os dois.
|
|
510
|
+
function envComoCodex({ plataforma = process.platform, env = process.env } = {}) {
|
|
511
|
+
const base = {};
|
|
512
|
+
for (const k of Object.keys(env)) {
|
|
513
|
+
if (CODEX_ENV_MINIMO.some(n => n.toLowerCase() === k.toLowerCase())) base[k] = env[k];
|
|
514
|
+
}
|
|
515
|
+
if (plataforma === 'win32') {
|
|
516
|
+
for (const nome of CODEX_ENV_WINDOWS) {
|
|
517
|
+
const chave = Object.keys(env).find(k => k.toLowerCase() === nome.toLowerCase());
|
|
518
|
+
if (chave && env[chave]) base[nome] = env[chave];
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return base;
|
|
522
|
+
}
|
|
523
|
+
|
|
463
524
|
function aquecerArcaneMcp() {
|
|
464
525
|
if (process.env.NODE_ENV === 'test' && process.env.AUROQ_SKIP_AUTH === '1') return true;
|
|
526
|
+
const c = comandoNpx(['-y', ARCANE_MCP_PIN, 'whoami']);
|
|
465
527
|
try {
|
|
466
|
-
const c = comandoNpx(['-y', ARCANE_MCP_PIN, 'whoami']);
|
|
467
528
|
execFileSync(c.cmd, c.args, { stdio: 'pipe', timeout: 240000, ...c.opts });
|
|
468
|
-
return true;
|
|
469
529
|
} catch {
|
|
470
530
|
return false;
|
|
471
531
|
}
|
|
532
|
+
if (process.platform === 'win32') {
|
|
533
|
+
try {
|
|
534
|
+
execFileSync(c.cmd, c.args, { stdio: 'pipe', timeout: 240000, env: envComoCodex(), ...c.opts });
|
|
535
|
+
} catch {
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return true;
|
|
472
540
|
}
|
|
473
541
|
|
|
474
542
|
// Abre o conteudo ao vivo do jeito que o Codex/Claude Code abrem e mede quanto tempo
|
|
@@ -531,6 +599,137 @@ async function guardarBootstrapLocal(targetDir, file, dst, dstExists) {
|
|
|
531
599
|
return true;
|
|
532
600
|
}
|
|
533
601
|
|
|
602
|
+
|
|
603
|
+
// ── Codex: imitar EXATAMENTE como ele abre o conteudo ao vivo ────────────────
|
|
604
|
+
// O aluno nao pode virar o nosso laboratorio ("roda isso, agora aquilo"). O doctor
|
|
605
|
+
// abre o MCP com o mesmo ambiente minimo que o Codex usa no Windows (openai/codex#4180)
|
|
606
|
+
// mais o env do registro, lista as ferramentas e chama uma. Se aqui funciona e no
|
|
607
|
+
// Codex nao, o problema e do lado do Codex — e o log dele entra no relatorio.
|
|
608
|
+
|
|
609
|
+
function lerBlocoArcane(toml) {
|
|
610
|
+
const m = String(toml || '').match(/\[mcp_servers\.arcane\][\s\S]*?(?=\n\[|$(?![\s\S]))/);
|
|
611
|
+
return m ? m[0] : '';
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function lerEnvDoBloco(bloco) {
|
|
615
|
+
const env = {};
|
|
616
|
+
const m = String(bloco || '').match(/^env = \{ (.*) \}$/m);
|
|
617
|
+
if (!m) return env;
|
|
618
|
+
for (const par of m[1].split(', ')) {
|
|
619
|
+
const i = par.indexOf(' = ');
|
|
620
|
+
if (i < 0) continue;
|
|
621
|
+
env[par.slice(0, i).trim()] = par.slice(i + 3).replace(/^['"]|['"]$/g, '');
|
|
622
|
+
}
|
|
623
|
+
return env;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function abrirComoCodex(pin, envBloco, limiteMs = 120000) {
|
|
627
|
+
return new Promise((resolve) => {
|
|
628
|
+
const { spawn } = require('child_process');
|
|
629
|
+
const env = envComoCodex();
|
|
630
|
+
Object.assign(env, envBloco || {});
|
|
631
|
+
const c = comandoNpx(['-y', pin]);
|
|
632
|
+
const t0 = Date.now();
|
|
633
|
+
const r = { subiu: null, tools: null, temForja: false, chamada: null, stderr: '' };
|
|
634
|
+
let saida = '';
|
|
635
|
+
let erro = '';
|
|
636
|
+
let fim = false;
|
|
637
|
+
let p;
|
|
638
|
+
const fecha = () => {
|
|
639
|
+
if (fim) return;
|
|
640
|
+
fim = true;
|
|
641
|
+
clearTimeout(timer);
|
|
642
|
+
try { p.kill(); } catch { /* ja morreu */ }
|
|
643
|
+
r.stderr = erro.trim().slice(-600);
|
|
644
|
+
resolve(r);
|
|
645
|
+
};
|
|
646
|
+
const timer = setTimeout(fecha, limiteMs);
|
|
647
|
+
try {
|
|
648
|
+
p = spawn(c.cmd, c.args, { stdio: ['pipe', 'pipe', 'pipe'], env, ...c.opts });
|
|
649
|
+
} catch (e) {
|
|
650
|
+
clearTimeout(timer);
|
|
651
|
+
r.chamada = { erro: true, texto: `nao abriu: ${e.message}` };
|
|
652
|
+
resolve(r);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
const envia = (o) => { try { p.stdin.write(`${JSON.stringify(o)}\n`); } catch { /* fechou */ } };
|
|
656
|
+
p.stderr.on('data', (d) => { erro += d.toString(); });
|
|
657
|
+
p.on('exit', fecha);
|
|
658
|
+
p.on('error', fecha);
|
|
659
|
+
p.stdout.on('data', (d) => {
|
|
660
|
+
saida += d.toString();
|
|
661
|
+
let idx;
|
|
662
|
+
while ((idx = saida.indexOf('\n')) >= 0) {
|
|
663
|
+
const linha = saida.slice(0, idx);
|
|
664
|
+
saida = saida.slice(idx + 1);
|
|
665
|
+
let msg;
|
|
666
|
+
try { msg = JSON.parse(linha); } catch { continue; }
|
|
667
|
+
if (msg.id === 1) {
|
|
668
|
+
r.subiu = Date.now() - t0;
|
|
669
|
+
envia({ jsonrpc: '2.0', method: 'notifications/initialized' });
|
|
670
|
+
envia({ jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} });
|
|
671
|
+
} else if (msg.id === 2) {
|
|
672
|
+
r.tools = (msg.result?.tools || []).map(t => t.name);
|
|
673
|
+
r.temForja = r.tools.includes('forja_bundle');
|
|
674
|
+
envia({ jsonrpc: '2.0', id: 3, method: 'tools/call', params: { name: 'catalogo_listar', arguments: {} } });
|
|
675
|
+
} else if (msg.id === 3) {
|
|
676
|
+
const texto = msg.result?.content?.[0]?.text || JSON.stringify(msg.error || msg.result || '');
|
|
677
|
+
r.chamada = {
|
|
678
|
+
erro: Boolean(msg.result?.isError || msg.error),
|
|
679
|
+
texto: String(texto).replace(/<!--.*?-->/g, '').split('\n').filter(Boolean).slice(0, 2).join(' ').slice(0, 220),
|
|
680
|
+
};
|
|
681
|
+
fecha();
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
envia({
|
|
686
|
+
jsonrpc: '2.0', id: 1, method: 'initialize',
|
|
687
|
+
params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'auroq-doctor-codex', version: '0' } },
|
|
688
|
+
});
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Registro do arcane no config GLOBAL do Codex (~/.codex/config.toml) briga com o da
|
|
693
|
+
// pasta. Sai de la, com backup; o da pasta e o que vale.
|
|
694
|
+
async function limparArcaneDoGlobal(caminho) {
|
|
695
|
+
if (!(await fs.pathExists(caminho))) return { existia: false, servidores: [], removido: false };
|
|
696
|
+
const toml = await fs.readFile(caminho, 'utf8');
|
|
697
|
+
const servidores = [...new Set([...toml.matchAll(/^\[mcp_servers\.([^\].]+)/gm)].map(m => m[1]))];
|
|
698
|
+
if (!/^\[mcp_servers\.arcane(\.|\])/m.test(toml)) return { existia: true, servidores, removido: false };
|
|
699
|
+
const backup = `${caminho}.bak-${new Date().toISOString().slice(0, 19).replace(/[:T]/g, '-')}`;
|
|
700
|
+
await fs.writeFile(backup, toml);
|
|
701
|
+
const novo = toml.replace(/\n*\[mcp_servers\.arcane(\.[^\]]+)?\][\s\S]*?(?=\n\[|$(?![\s\S]))/g, '');
|
|
702
|
+
await fs.writeFile(caminho, `${novo.trimEnd()}\n`);
|
|
703
|
+
return { existia: true, servidores, removido: true, backup };
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function versaoDoCodex() {
|
|
707
|
+
try {
|
|
708
|
+
const c = process.platform === 'win32'
|
|
709
|
+
? { cmd: process.env.ComSpec || 'cmd.exe', args: ['/d', '/s', '/c', 'codex --version'], opts: { windowsVerbatimArguments: true } }
|
|
710
|
+
: { cmd: 'codex', args: ['--version'], opts: {} };
|
|
711
|
+
return execFileSync(c.cmd, c.args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 20000, ...c.opts }).trim();
|
|
712
|
+
} catch {
|
|
713
|
+
return '';
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
async function rastroDoCodex(limite = 8) {
|
|
718
|
+
const arq = path.join(require('os').homedir(), '.codex', 'log', 'codex-tui.log');
|
|
719
|
+
try {
|
|
720
|
+
const st = await fs.stat(arq);
|
|
721
|
+
const fd = await fs.open(arq, 'r');
|
|
722
|
+
const tam = Math.min(st.size, 400000);
|
|
723
|
+
const buf = Buffer.alloc(tam);
|
|
724
|
+
await fs.read(fd, buf, 0, tam, st.size - tam);
|
|
725
|
+
await fs.close(fd);
|
|
726
|
+
const linhas = buf.toString('utf8').split('\n').filter(l => /arcane/i.test(l));
|
|
727
|
+
return linhas.slice(-limite).map(l => l.replace(/\u001b\[[0-9;]*m/g, '').slice(0, 200));
|
|
728
|
+
} catch {
|
|
729
|
+
return [];
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
534
733
|
async function writeArcaneMcpConfig(targetDir) {
|
|
535
734
|
const pin = ARCANE_MCP_PIN;
|
|
536
735
|
const mcpPath = path.join(targetDir, '.mcp.json');
|
|
@@ -980,7 +1179,7 @@ async function init({ pasta = null } = {}) {
|
|
|
980
1179
|
}
|
|
981
1180
|
await patchEnableAllProjectMcpServers(targetDir);
|
|
982
1181
|
await writeArcaneMcpConfig(targetDir);
|
|
983
|
-
step('Preparando o conteudo ao vivo (baixa uma vez; pode levar 1-
|
|
1182
|
+
step('Preparando o conteudo ao vivo (baixa uma vez; no Windows prepara tambem pro Codex; pode levar 1-3 min)...');
|
|
984
1183
|
if (aquecerArcaneMcp()) success('Conteudo ao vivo pronto pra abrir rapido');
|
|
985
1184
|
else warn('Nao consegui baixar o conteudo ao vivo agora — a primeira abertura vai demorar mais');
|
|
986
1185
|
|
|
@@ -1258,7 +1457,7 @@ async function clone(repoArg) {
|
|
|
1258
1457
|
}
|
|
1259
1458
|
|
|
1260
1459
|
await writeArcaneMcpConfig(destDir);
|
|
1261
|
-
step('Preparando o conteudo ao vivo (baixa uma vez; pode levar 1-
|
|
1460
|
+
step('Preparando o conteudo ao vivo (baixa uma vez; no Windows prepara tambem pro Codex; pode levar 1-3 min)...');
|
|
1262
1461
|
if (aquecerArcaneMcp()) success('Conteudo ao vivo pronto pra abrir rapido');
|
|
1263
1462
|
else warn('Nao consegui baixar o conteudo ao vivo agora — a primeira abertura vai demorar mais');
|
|
1264
1463
|
|
|
@@ -1419,4 +1618,4 @@ if (command === 'init' || command === 'install') {
|
|
|
1419
1618
|
log('');
|
|
1420
1619
|
}
|
|
1421
1620
|
|
|
1422
|
-
if (process.env.AUROQ_EXPORTA_INTERNOS === '1') module.exports = { blocoCodexArcane, envCodexArcane };
|
|
1621
|
+
if (process.env.AUROQ_EXPORTA_INTERNOS === '1') module.exports = { blocoCodexArcane, envCodexArcane, lerBlocoArcane, lerEnvDoBloco, limparArcaneDoGlobal, envComoCodex };
|