auroq-os 2.4.16 → 2.4.18
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 +23 -0
- package/bin/auroq-os.js +193 -7
- package/lib/diagnostico.js +5 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.4.18] — 2026-09-04
|
|
4
|
+
|
|
5
|
+
### Doctor faz o trabalho do suporte numa tela so (Codex)
|
|
6
|
+
Caso EMB-028 virou ping-pong com a aluna ("roda isso, agora aquilo, manda o print"). Agora o
|
|
7
|
+
`doctor`, quando o projeto tem registro no Codex:
|
|
8
|
+
- mostra a versao do Codex e o bloco `[mcp_servers.arcane]` da pasta;
|
|
9
|
+
- **tira um registro duplicado do arcane do config global** do Codex (`~/.codex/config.toml`),
|
|
10
|
+
com backup — e o que briga com o registro da pasta;
|
|
11
|
+
- **abre o conteudo ao vivo do jeito que o Codex abre** (ambiente minimo do Windows + env do
|
|
12
|
+
registro), lista as ferramentas, confere se `forja_bundle` esta la e chama `catalogo_listar`;
|
|
13
|
+
- imprime as ultimas linhas do log do Codex sobre o arcane e o stderr do MCP;
|
|
14
|
+
- se algo falhou, o unico pedido e "mande o print desta tela inteira".
|
|
15
|
+
|
|
16
|
+
## [2.4.17] — 2026-09-04
|
|
17
|
+
|
|
18
|
+
### Doctor no Windows: tela limpa
|
|
19
|
+
- O `doctor` (e o `init`/`register-mcp`) abriam o `npx` com `shell: true` no Windows, e o Node 24
|
|
20
|
+
imprimia um `DeprecationWarning` no meio do diagnostico do aluno. Agora chama o `cmd.exe`
|
|
21
|
+
direto — sem aviso.
|
|
22
|
+
- Aluno com Node proprio (sem o motor de bolso) recebia um X — "O motor esta valendo nesta
|
|
23
|
+
janela" — e a ordem de rodar o instalador de novo, com tudo funcionando. No Windows o caminho
|
|
24
|
+
e da conta, nao da janela: motor encontrado = OK.
|
|
25
|
+
|
|
3
26
|
## [2.4.16] — 2026-09-04
|
|
4
27
|
|
|
5
28
|
### Codex no Windows: conteudo ao vivo abria, mas "Sem conexao com o servidor da Arcane" (EMB-028)
|
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.');
|
|
@@ -449,12 +490,22 @@ function blocoCodexArcane(pin, opcoes) {
|
|
|
449
490
|
|
|
450
491
|
// Baixa o pacote do conteudo ao vivo com o MESMO nome que o registro usa (o cache do
|
|
451
492
|
// npx e por nome+versao: `arcane-mcp` e `arcane-mcp@0.4.1` sao caches diferentes).
|
|
493
|
+
// `npx` no Windows e um .cmd: sem shell o Node recusa (EINVAL) e com `shell: true` o Node 24
|
|
494
|
+
// imprime um DeprecationWarning na tela do aluno (DEP0190). Chamar o cmd.exe direto, sem shell.
|
|
495
|
+
function comandoNpx(args) {
|
|
496
|
+
if (process.platform !== 'win32') return { cmd: 'npx', args, opts: {} };
|
|
497
|
+
return {
|
|
498
|
+
cmd: process.env.ComSpec || 'cmd.exe',
|
|
499
|
+
args: ['/d', '/s', '/c', `npx ${args.join(' ')}`],
|
|
500
|
+
opts: { windowsVerbatimArguments: true },
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
452
504
|
function aquecerArcaneMcp() {
|
|
453
505
|
if (process.env.NODE_ENV === 'test' && process.env.AUROQ_SKIP_AUTH === '1') return true;
|
|
454
506
|
try {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
});
|
|
507
|
+
const c = comandoNpx(['-y', ARCANE_MCP_PIN, 'whoami']);
|
|
508
|
+
execFileSync(c.cmd, c.args, { stdio: 'pipe', timeout: 240000, ...c.opts });
|
|
458
509
|
return true;
|
|
459
510
|
} catch {
|
|
460
511
|
return false;
|
|
@@ -479,9 +530,8 @@ function medirArranqueArcaneMcp(pin, limiteMs = 180000) {
|
|
|
479
530
|
};
|
|
480
531
|
const timer = setTimeout(() => fecha(null), limiteMs);
|
|
481
532
|
try {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
});
|
|
533
|
+
const c = comandoNpx(['-y', pin]);
|
|
534
|
+
processo = spawn(c.cmd, c.args, { stdio: ['pipe', 'pipe', 'pipe'], ...c.opts });
|
|
485
535
|
} catch {
|
|
486
536
|
clearTimeout(timer);
|
|
487
537
|
resolve(null);
|
|
@@ -522,6 +572,142 @@ async function guardarBootstrapLocal(targetDir, file, dst, dstExists) {
|
|
|
522
572
|
return true;
|
|
523
573
|
}
|
|
524
574
|
|
|
575
|
+
|
|
576
|
+
// ── Codex: imitar EXATAMENTE como ele abre o conteudo ao vivo ────────────────
|
|
577
|
+
// O aluno nao pode virar o nosso laboratorio ("roda isso, agora aquilo"). O doctor
|
|
578
|
+
// abre o MCP com o mesmo ambiente minimo que o Codex usa no Windows (openai/codex#4180)
|
|
579
|
+
// mais o env do registro, lista as ferramentas e chama uma. Se aqui funciona e no
|
|
580
|
+
// Codex nao, o problema e do lado do Codex — e o log dele entra no relatorio.
|
|
581
|
+
const CODEX_ENV_MINIMO = ['PATH', 'PATHEXT', 'USERNAME', 'USERDOMAIN', 'USERPROFILE', 'TEMP', 'TMP',
|
|
582
|
+
'HOME', 'LOGNAME', 'SHELL', 'TERM', 'USER'];
|
|
583
|
+
|
|
584
|
+
function lerBlocoArcane(toml) {
|
|
585
|
+
const m = String(toml || '').match(/\[mcp_servers\.arcane\][\s\S]*?(?=\n\[|$(?![\s\S]))/);
|
|
586
|
+
return m ? m[0] : '';
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function lerEnvDoBloco(bloco) {
|
|
590
|
+
const env = {};
|
|
591
|
+
const m = String(bloco || '').match(/^env = \{ (.*) \}$/m);
|
|
592
|
+
if (!m) return env;
|
|
593
|
+
for (const par of m[1].split(', ')) {
|
|
594
|
+
const i = par.indexOf(' = ');
|
|
595
|
+
if (i < 0) continue;
|
|
596
|
+
env[par.slice(0, i).trim()] = par.slice(i + 3).replace(/^['"]|['"]$/g, '');
|
|
597
|
+
}
|
|
598
|
+
return env;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function abrirComoCodex(pin, envBloco, limiteMs = 120000) {
|
|
602
|
+
return new Promise((resolve) => {
|
|
603
|
+
const { spawn } = require('child_process');
|
|
604
|
+
const env = {};
|
|
605
|
+
for (const k of Object.keys(process.env)) {
|
|
606
|
+
if (CODEX_ENV_MINIMO.some(n => n.toLowerCase() === k.toLowerCase())) env[k] = process.env[k];
|
|
607
|
+
}
|
|
608
|
+
Object.assign(env, envBloco || {});
|
|
609
|
+
const c = comandoNpx(['-y', pin]);
|
|
610
|
+
const t0 = Date.now();
|
|
611
|
+
const r = { subiu: null, tools: null, temForja: false, chamada: null, stderr: '' };
|
|
612
|
+
let saida = '';
|
|
613
|
+
let erro = '';
|
|
614
|
+
let fim = false;
|
|
615
|
+
let p;
|
|
616
|
+
const fecha = () => {
|
|
617
|
+
if (fim) return;
|
|
618
|
+
fim = true;
|
|
619
|
+
clearTimeout(timer);
|
|
620
|
+
try { p.kill(); } catch { /* ja morreu */ }
|
|
621
|
+
r.stderr = erro.trim().slice(-600);
|
|
622
|
+
resolve(r);
|
|
623
|
+
};
|
|
624
|
+
const timer = setTimeout(fecha, limiteMs);
|
|
625
|
+
try {
|
|
626
|
+
p = spawn(c.cmd, c.args, { stdio: ['pipe', 'pipe', 'pipe'], env, ...c.opts });
|
|
627
|
+
} catch (e) {
|
|
628
|
+
clearTimeout(timer);
|
|
629
|
+
r.chamada = { erro: true, texto: `nao abriu: ${e.message}` };
|
|
630
|
+
resolve(r);
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
const envia = (o) => { try { p.stdin.write(`${JSON.stringify(o)}\n`); } catch { /* fechou */ } };
|
|
634
|
+
p.stderr.on('data', (d) => { erro += d.toString(); });
|
|
635
|
+
p.on('exit', fecha);
|
|
636
|
+
p.on('error', fecha);
|
|
637
|
+
p.stdout.on('data', (d) => {
|
|
638
|
+
saida += d.toString();
|
|
639
|
+
let idx;
|
|
640
|
+
while ((idx = saida.indexOf('\n')) >= 0) {
|
|
641
|
+
const linha = saida.slice(0, idx);
|
|
642
|
+
saida = saida.slice(idx + 1);
|
|
643
|
+
let msg;
|
|
644
|
+
try { msg = JSON.parse(linha); } catch { continue; }
|
|
645
|
+
if (msg.id === 1) {
|
|
646
|
+
r.subiu = Date.now() - t0;
|
|
647
|
+
envia({ jsonrpc: '2.0', method: 'notifications/initialized' });
|
|
648
|
+
envia({ jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} });
|
|
649
|
+
} else if (msg.id === 2) {
|
|
650
|
+
r.tools = (msg.result?.tools || []).map(t => t.name);
|
|
651
|
+
r.temForja = r.tools.includes('forja_bundle');
|
|
652
|
+
envia({ jsonrpc: '2.0', id: 3, method: 'tools/call', params: { name: 'catalogo_listar', arguments: {} } });
|
|
653
|
+
} else if (msg.id === 3) {
|
|
654
|
+
const texto = msg.result?.content?.[0]?.text || JSON.stringify(msg.error || msg.result || '');
|
|
655
|
+
r.chamada = {
|
|
656
|
+
erro: Boolean(msg.result?.isError || msg.error),
|
|
657
|
+
texto: String(texto).replace(/<!--.*?-->/g, '').split('\n').filter(Boolean).slice(0, 2).join(' ').slice(0, 220),
|
|
658
|
+
};
|
|
659
|
+
fecha();
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
envia({
|
|
664
|
+
jsonrpc: '2.0', id: 1, method: 'initialize',
|
|
665
|
+
params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'auroq-doctor-codex', version: '0' } },
|
|
666
|
+
});
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Registro do arcane no config GLOBAL do Codex (~/.codex/config.toml) briga com o da
|
|
671
|
+
// pasta. Sai de la, com backup; o da pasta e o que vale.
|
|
672
|
+
async function limparArcaneDoGlobal(caminho) {
|
|
673
|
+
if (!(await fs.pathExists(caminho))) return { existia: false, servidores: [], removido: false };
|
|
674
|
+
const toml = await fs.readFile(caminho, 'utf8');
|
|
675
|
+
const servidores = [...new Set([...toml.matchAll(/^\[mcp_servers\.([^\].]+)/gm)].map(m => m[1]))];
|
|
676
|
+
if (!/^\[mcp_servers\.arcane(\.|\])/m.test(toml)) return { existia: true, servidores, removido: false };
|
|
677
|
+
const backup = `${caminho}.bak-${new Date().toISOString().slice(0, 19).replace(/[:T]/g, '-')}`;
|
|
678
|
+
await fs.writeFile(backup, toml);
|
|
679
|
+
const novo = toml.replace(/\n*\[mcp_servers\.arcane(\.[^\]]+)?\][\s\S]*?(?=\n\[|$(?![\s\S]))/g, '');
|
|
680
|
+
await fs.writeFile(caminho, `${novo.trimEnd()}\n`);
|
|
681
|
+
return { existia: true, servidores, removido: true, backup };
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function versaoDoCodex() {
|
|
685
|
+
try {
|
|
686
|
+
const c = process.platform === 'win32'
|
|
687
|
+
? { cmd: process.env.ComSpec || 'cmd.exe', args: ['/d', '/s', '/c', 'codex --version'], opts: { windowsVerbatimArguments: true } }
|
|
688
|
+
: { cmd: 'codex', args: ['--version'], opts: {} };
|
|
689
|
+
return execFileSync(c.cmd, c.args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 20000, ...c.opts }).trim();
|
|
690
|
+
} catch {
|
|
691
|
+
return '';
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
async function rastroDoCodex(limite = 8) {
|
|
696
|
+
const arq = path.join(require('os').homedir(), '.codex', 'log', 'codex-tui.log');
|
|
697
|
+
try {
|
|
698
|
+
const st = await fs.stat(arq);
|
|
699
|
+
const fd = await fs.open(arq, 'r');
|
|
700
|
+
const tam = Math.min(st.size, 400000);
|
|
701
|
+
const buf = Buffer.alloc(tam);
|
|
702
|
+
await fs.read(fd, buf, 0, tam, st.size - tam);
|
|
703
|
+
await fs.close(fd);
|
|
704
|
+
const linhas = buf.toString('utf8').split('\n').filter(l => /arcane/i.test(l));
|
|
705
|
+
return linhas.slice(-limite).map(l => l.replace(/\u001b\[[0-9;]*m/g, '').slice(0, 200));
|
|
706
|
+
} catch {
|
|
707
|
+
return [];
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
525
711
|
async function writeArcaneMcpConfig(targetDir) {
|
|
526
712
|
const pin = ARCANE_MCP_PIN;
|
|
527
713
|
const mcpPath = path.join(targetDir, '.mcp.json');
|
|
@@ -1410,4 +1596,4 @@ if (command === 'init' || command === 'install') {
|
|
|
1410
1596
|
log('');
|
|
1411
1597
|
}
|
|
1412
1598
|
|
|
1413
|
-
if (process.env.AUROQ_EXPORTA_INTERNOS === '1') module.exports = { blocoCodexArcane, envCodexArcane };
|
|
1599
|
+
if (process.env.AUROQ_EXPORTA_INTERNOS === '1') module.exports = { blocoCodexArcane, envCodexArcane, lerBlocoArcane, lerEnvDoBloco, limparArcaneDoGlobal };
|
package/lib/diagnostico.js
CHANGED
|
@@ -38,11 +38,13 @@ function appEnxergaMotor({ homeDir, pathAtual = '', caminhoMotor = '', plataform
|
|
|
38
38
|
const partes = String(pathAtual).split(plataforma === 'win32' ? ';' : ':').filter(Boolean);
|
|
39
39
|
|
|
40
40
|
if (plataforma === 'win32') {
|
|
41
|
-
|
|
41
|
+
// No Windows o caminho e da conta do usuario, nao da janela: o que vale aqui vale
|
|
42
|
+
// pro icone. Aluno com Node proprio (sem o motor de bolso) nao e defeito — acusar
|
|
43
|
+
// isso mandava gente com tudo funcionando "rodar o instalador de novo" (04/09).
|
|
42
44
|
return {
|
|
43
|
-
visivel: partes.length > 0,
|
|
45
|
+
visivel: partes.length > 0 || Boolean(caminhoMotor),
|
|
44
46
|
perfisSemBloco: [],
|
|
45
|
-
motivo:
|
|
47
|
+
motivo: (partes.length > 0 || caminhoMotor) ? '' : 'Nenhum motor no caminho desta conta.',
|
|
46
48
|
};
|
|
47
49
|
}
|
|
48
50
|
|