auroq-os 2.5.1 → 2.5.4
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/.claude/hooks/ahi-capture.cjs +58 -0
- package/.claude/settings.local.json +13 -0
- package/CHANGELOG.md +21 -0
- package/README.md +38 -0
- package/bin/auroq-os.js +263 -23
- package/installer/README.md +20 -0
- package/installer/install.ps1 +127 -3
- package/installer/install.sh +90 -2
- package/lib/ahi/historico.mjs +38 -0
- package/lib/synapse-hashes.json +110 -0
- package/lib/telemetry.mjs +238 -0
- package/package.json +3 -3
- package/scripts/synapse-hashes.mjs +60 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Coletor AHI. Camada ao lado do Synapse: nunca substitui synapse-engine.cjs.
|
|
4
|
+
* Falha = exit 0. Sem stdout. Sem MCP Arcane neste negocio = no-op.
|
|
5
|
+
*/
|
|
6
|
+
process.on('uncaughtException', () => process.exit(0));
|
|
7
|
+
process.on('unhandledRejection', () => process.exit(0));
|
|
8
|
+
const kill = setTimeout(() => process.exit(0), 2500);
|
|
9
|
+
function done() {
|
|
10
|
+
try { clearTimeout(kill); } catch { /* ignore */ }
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let input = '';
|
|
15
|
+
process.stdin.setEncoding('utf8');
|
|
16
|
+
process.stdin.on('data', (chunk) => {
|
|
17
|
+
input += chunk;
|
|
18
|
+
if (input.length > 512 * 1024) input = input.slice(0, 512 * 1024);
|
|
19
|
+
});
|
|
20
|
+
process.stdin.on('end', () => {
|
|
21
|
+
try { capture(); } catch { /* never break Claude */ }
|
|
22
|
+
done();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
function redact(text) {
|
|
26
|
+
return String(text || '')
|
|
27
|
+
.replace(/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g, '[REDACTED]')
|
|
28
|
+
.replace(/\bBearer\s+[A-Za-z0-9\-._~+/]+=*/gi, '[REDACTED]')
|
|
29
|
+
.replace(/\bsk-(?:live|test|proj)?[-_]?[A-Za-z0-9]{16,}\b/g, '[REDACTED]')
|
|
30
|
+
.replace(/\bghp_[A-Za-z0-9]{20,}\b/g, '[REDACTED]')
|
|
31
|
+
.slice(0, 8000);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function capture() {
|
|
35
|
+
const fs = require('node:fs');
|
|
36
|
+
const path = require('node:path');
|
|
37
|
+
const cwd = process.cwd();
|
|
38
|
+
let mcp = false;
|
|
39
|
+
try {
|
|
40
|
+
const json = JSON.parse(fs.readFileSync(path.join(cwd, '.mcp.json'), 'utf8'));
|
|
41
|
+
mcp = Boolean(json && json.mcpServers && json.mcpServers.arcane);
|
|
42
|
+
} catch { /* sem MCP */ }
|
|
43
|
+
if (!mcp) return;
|
|
44
|
+
let event = {};
|
|
45
|
+
try { event = JSON.parse(input || '{}'); } catch { return; }
|
|
46
|
+
const name = event.hook_event_name || event.hookEventName;
|
|
47
|
+
if (!name) return;
|
|
48
|
+
const dir = path.join(cwd, '.auroq', 'ahi');
|
|
49
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
50
|
+
const row = {
|
|
51
|
+
at: new Date().toISOString(),
|
|
52
|
+
hook: String(name),
|
|
53
|
+
session_id: event.session_id || event.sessionId || null,
|
|
54
|
+
tool: event.tool_name || null,
|
|
55
|
+
text: redact(event.prompt || event.last_assistant_message || event.last_message || event.content || ''),
|
|
56
|
+
};
|
|
57
|
+
fs.appendFileSync(path.join(dir, 'events.jsonl'), `${JSON.stringify(row)}\n`, { mode: 0o600 });
|
|
58
|
+
}
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
"allow": [],
|
|
4
4
|
"deny": []
|
|
5
5
|
},
|
|
6
|
+
"env": {
|
|
7
|
+
"MCP_TIMEOUT": "120000"
|
|
8
|
+
},
|
|
6
9
|
"hooks": {
|
|
7
10
|
"UserPromptSubmit": [
|
|
8
11
|
{
|
|
@@ -14,6 +17,16 @@
|
|
|
14
17
|
"timeout": 10000
|
|
15
18
|
}
|
|
16
19
|
]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"matcher": "",
|
|
23
|
+
"hooks": [
|
|
24
|
+
{
|
|
25
|
+
"type": "command",
|
|
26
|
+
"command": "node .claude/hooks/ahi-capture.cjs",
|
|
27
|
+
"timeout": 3000
|
|
28
|
+
}
|
|
29
|
+
]
|
|
17
30
|
}
|
|
18
31
|
],
|
|
19
32
|
"PreCompact": [
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
|
+
## 2.5.4 — 2026-09-14
|
|
2
|
+
|
|
3
|
+
- Historico do trabalho Arcane: se o negocio tem MCP `arcane`, o hook `ahi-capture.cjs` registra a sessao. Sem clique, sem pause. Falha do coletor nao quebra o Claude (exit 0, sem stdout). Nao substitui o Synapse.
|
|
4
|
+
- `auroq-os historico` mostra ligado/desligado. Manutencao acrescenta o hook que faltar em `.claude/settings.local.json`.
|
|
5
|
+
|
|
6
|
+
## 2.5.2 — 2026-09-11
|
|
7
|
+
|
|
8
|
+
- **"UserPromptSubmit hook error ... hookEventName" a cada mensagem (EMB-040).** Quem instalou ate a 2.4.9 ficou com o Synapse antigo para sempre: a pasta era copiada sem sobrescrever e a correcao da 2.4.10 nunca chegava — o Claude Code recusava o pacote de regras em toda mensagem, enquanto a manutencao carimbava a versao nova. A manutencao agora troca todo arquivo do Synapse que seja uma versao oficial antiga (hashes das 90 versoes publicadas em `lib/synapse-hashes.json`) e guarda em `.auroq-core/preservado/synapse/` qualquer arquivo diferente de todas elas antes da troca. Integridade e `doctor` apontam o Synapse antigo em vez de dizer que esta tudo em dia.
|
|
9
|
+
- **MCP arcane com CONNECT_TIMEOUT no Claude Code (EMB-041).** O Claude Code desiste do conteudo ao vivo aos 30s; o npx a frio no Windows leva 44-77s. Instalar, manutencao, register-mcp e clone gravam `env.MCP_TIMEOUT = 120000` em `.claude/settings.local.json` (o Codex ja tinha 120s); o `doctor` cobra esse tempo.
|
|
10
|
+
- `.claude/settings.local.json` ilegivel deixa de virar `{}` (o aluno perdia os hooks): o original fica guardado ao lado e o arquivo volta a partir do modelo.
|
|
11
|
+
- Instaladores fixam 2.5.2. Correcao feita a partir da 2.5.1 publicada; o candidato de telemetria segue separado.
|
|
12
|
+
|
|
1
13
|
## 2.5.1 — 2026-09-04
|
|
2
14
|
|
|
15
|
+
## 2.5.3 — candidato local, nao publicado
|
|
16
|
+
|
|
17
|
+
- Bootstrap dependency-light e outbox privada compartilhada com o MCP; retries autenticados com IDs estaveis e eventos sem dono exclusivamente locais.
|
|
18
|
+
- Spool de etapas dos instaladores antes de Node, importado depois sem inferir identidade.
|
|
19
|
+
- Pin MCP 0.6.0 e orientacoes de doctor/manutencao; studentFiles e consentimento legado preservados.
|
|
20
|
+
- Instaladores oficiais fixam o motor publicado 2.5.2 (hotfix EMB-040/EMB-041); este candidato ainda nao foi publicado. Transporte/doctor novos dependem de publicacao/adocao.
|
|
21
|
+
- Locks e publicacao atomicos, claim do spool e TTL pre-Node; runtime Windows faz fail-closed por ACL/no-follow indisponiveis.
|
|
22
|
+
- Validacao Linux; Windows/macOS pendentes de execucao nativa.
|
|
23
|
+
|
|
3
24
|
- Motor registra arcane-mcp 0.5.0: diagnostico de tentativas, erros, ambiente e reenvio apos falha de rede. Conteudo de conversas e arquivos nao e coletado. Instaladores fixam esta versao validada.
|
|
4
25
|
|
|
5
26
|
## 2.5.0 — 2026-09-04
|
package/README.md
CHANGED
|
@@ -164,3 +164,41 @@ agents/ → Seu exercito (companion, workers, minds, squads)
|
|
|
164
164
|
---
|
|
165
165
|
|
|
166
166
|
*Auroq OS — by Euriler Jube / Arka*
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
## Observabilidade do motor (candidato 2.5.2)
|
|
170
|
+
|
|
171
|
+
Apos publicacao, `npx -y auroq-os@2.5.3 doctor` mostrara a fila local segura e os comandos pinados de
|
|
172
|
+
manutencao/diagnostico. `npx -y auroq-os@2.5.3 manutencao` preserva o caminho existente
|
|
173
|
+
de consentimento legado, studentFiles, customizacoes e dados do negocio. Nao ha
|
|
174
|
+
migracao forcada. O registro MCP passa a apontar para `arcane-mcp@0.6.0`.
|
|
175
|
+
Estes candidatos precisam ser publicados pelo coordenador apos review; consultar
|
|
176
|
+
novamente colisao no registry antes de publicar. A promocao esta bloqueada por
|
|
177
|
+
autenticacao npm (401 nas duas vias verificadas pelo coordenador). Os instaladores
|
|
178
|
+
oficiais continuam em `auroq-os@2.5.2`, publicado; o spool nativo local pode ser servido
|
|
179
|
+
com esse pin. Transporte/doctor novos dependem de publicacao e adocao dos candidatos.
|
|
180
|
+
|
|
181
|
+
O CLI inicia captura antes das dependencias pesadas e registra resultado ao sair.
|
|
182
|
+
Eventos com identidade ja existente podem seguir pela rota autenticada na proxima
|
|
183
|
+
inicializacao. Shell e PowerShell gravam somente etapas/codigos em spool privado
|
|
184
|
+
`~/.arcane/installer-outbox`, sem ler credenciais nem conteudo pessoal; 200 slots por
|
|
185
|
+
formato limitam o disco antes de haver Node. Saturado, deixa de capturar. Quando Node
|
|
186
|
+
executa nosso CLI, ele importa/expurga o spool com TTL de sete dias, preserva proveniencia
|
|
187
|
+
local e jamais atribui esses eventos ao login posterior. Cada execucao do instalador
|
|
188
|
+
expurga sete dias e slots interrompidos sob lock; nao precisa esperar Node. Nao existe daemon. O runtime Windows falha fechado (`OUTBOX_UNAVAILABLE`), sem
|
|
189
|
+
ler/enviar/importar fila enquanto nao houver garantia nativa de ACL/no-follow.
|
|
190
|
+
O PowerShell verifica DACL nativa e conserva seus eventos locais. O spool nao substitui o install.log legado,
|
|
191
|
+
que nao e lido nem enviado pela telemetria nova.
|
|
192
|
+
|
|
193
|
+
Captura cobre inicio e etapas Git/motor/acesso/pasta/montagem do instalador; no CLI,
|
|
194
|
+
instalacao/manutencao/doctor/login/register-mcp. Bootstrap conversacional e o trabalho
|
|
195
|
+
final do squad nao sao inferidos como sucesso por sair do CLI ou preparar arquivos.
|
|
196
|
+
Antes de o script chegar ou em computador que nunca executa nosso codigo, nao ha sinal.
|
|
197
|
+
A rota events continua autenticada, limitada e idempotente. A plataforma deve receber
|
|
198
|
+
a whitelist antes de distribuir/promover os clientes; os instaladores sincronizados
|
|
199
|
+
mantem o pin 2.5.1 publicado ate a promocao npm ser possivel. Nao precisa migration adicional.
|
|
200
|
+
|
|
201
|
+
O canal de adocao e o instalador oficial, o README/changelog, o doctor e o aviso de
|
|
202
|
+
comando aposentado quando a versao nova e executada. Isso nao atualiza silenciosamente
|
|
203
|
+
quem continua com o pacote antigo. Testado nesta VPS em Linux; sintaxe Bash e verificacoes
|
|
204
|
+
estaticas ASCII/compatibilidade PS passaram, mas Windows PS5.1 e macOS nao foram executados.
|
package/bin/auroq-os.js
CHANGED
|
@@ -12,6 +12,30 @@
|
|
|
12
12
|
|
|
13
13
|
'use strict';
|
|
14
14
|
|
|
15
|
+
async function observedMain() {
|
|
16
|
+
const telemetry = {...await import('../lib/telemetry.mjs')};
|
|
17
|
+
const credentials = require('../lib/credentials');
|
|
18
|
+
const owner = credentials.load()?.user?.id || null;
|
|
19
|
+
const stage = ({instalar:'install',manutencao:'update',doctor:'doctor',diagnostico:'doctor',login:'login','register-mcp':'bootstrap'})[process.argv[2]] || 'runtime';
|
|
20
|
+
const record = (outcome, error_code) => telemetry.enqueue(owner,{endpoint:'lifecycle',stage,outcome,error_code});
|
|
21
|
+
telemetry.setHost('auroq-os');telemetry.importInstallerSpool();record('started');
|
|
22
|
+
let importing = true;
|
|
23
|
+
process.once('exit',code=>record(code===0?'ok':'error',code===0?null:importing?'BOOTSTRAP_IMPORT':stage==='update'?'UPDATE_FAILED':'INSTALL_FAILED'));
|
|
24
|
+
process.once('uncaughtException',()=>{console.error('[auroq-os] RUNTIME_FAILURE');process.exit(1);});
|
|
25
|
+
process.once('unhandledRejection',()=>{console.error('[auroq-os] RUNTIME_FAILURE');process.exit(1);});
|
|
26
|
+
const cred = credentials.load();await telemetry.flush(cred?.user?.id,cred?.access_token);
|
|
27
|
+
try {
|
|
28
|
+
telemetry.runtimeReady=()=>{importing=false;};
|
|
29
|
+
runRuntime(telemetry);
|
|
30
|
+
} catch {
|
|
31
|
+
console.error('[auroq-os] '+(importing?'BOOTSTRAP_IMPORT':'RUNTIME_FAILURE')+'. Rode: npx -y auroq-os@2.5.3 doctor');
|
|
32
|
+
process.exitCode=1;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if(process.env.AUROQ_EXPORTA_INTERNOS==='1')runRuntime({runtimeReady(){}});
|
|
36
|
+
else observedMain().catch(()=>{console.error('[auroq-os] BOOTSTRAP_IMPORT');process.exitCode=1;});
|
|
37
|
+
|
|
38
|
+
function runRuntime(telemetry) {
|
|
15
39
|
const path = require('path');
|
|
16
40
|
const fs = require('fs-extra');
|
|
17
41
|
const { execSync, execFileSync } = require('child_process');
|
|
@@ -164,6 +188,7 @@ const HEALTH_CHECKS = [
|
|
|
164
188
|
['.claude/output-styles/direto.md', 'Output style Direto'],
|
|
165
189
|
['.claude/rules/operacao-autonoma.md', 'Rule de operacao autonoma'],
|
|
166
190
|
['.claude/hooks/synapse-engine.cjs', 'Synapse Hook'],
|
|
191
|
+
['.claude/hooks/ahi-capture.cjs', 'AHI Hook'],
|
|
167
192
|
['.claude/hooks/auroq-sync.cjs', 'Sync Hook'],
|
|
168
193
|
['.claude/rules/puxar-e-entregar.md', 'Rule puxar-e-entregar'],
|
|
169
194
|
['.claude/rules/mcp-usage.md', 'Rule MCP (carve-out Arcane)'],
|
|
@@ -246,6 +271,11 @@ async function runHealthChecks(targetDir) {
|
|
|
246
271
|
missing.push(`${label} (${file})`);
|
|
247
272
|
}
|
|
248
273
|
}
|
|
274
|
+
if (await synapseDesatualizado(targetDir)) {
|
|
275
|
+
// Aviso, nao trava: um clone de negocio antigo traz o arquivo velho pelo git e so a
|
|
276
|
+
// manutencao nesta maquina (ou na outra, depois entregue) troca.
|
|
277
|
+
warn('Synapse antigo nesta pasta (erro "hookEventName" a cada mensagem). Peca ao Ops: manutencao do motor.');
|
|
278
|
+
}
|
|
249
279
|
if (passed === HEALTH_CHECKS.length) {
|
|
250
280
|
success(`${passed}/${HEALTH_CHECKS.length} verificacoes OK`);
|
|
251
281
|
} else {
|
|
@@ -280,6 +310,14 @@ async function runDoctor(targetDir) {
|
|
|
280
310
|
error(`Arquivos do sistema: faltam ${faltando}`);
|
|
281
311
|
problemas.push('Peca manutencao do motor ao Ops dentro do Claude Code pra repor o que falta.');
|
|
282
312
|
}
|
|
313
|
+
if (!(await synapseRegistrado(targetDir))) {
|
|
314
|
+
error('O hook do Synapse nao esta registrado: as regras do sistema nao chegam ao modelo');
|
|
315
|
+
problemas.push('Rode: npx auroq-os@latest register-mcp na pasta (repoe o registro sem tirar nada seu). Depois feche e abra o Claude Code.');
|
|
316
|
+
}
|
|
317
|
+
if (await synapseDesatualizado(targetDir)) {
|
|
318
|
+
error('Synapse antigo: o Claude Code recusa as regras a cada mensagem ("hookEventName")');
|
|
319
|
+
problemas.push('Peca manutencao do motor ao Ops dentro do Claude Code (ou rode npx auroq-os@latest manutencao na pasta) pra trocar o Synapse antigo.');
|
|
320
|
+
}
|
|
283
321
|
|
|
284
322
|
// 2. O motor — e, principalmente, se quem abre pelo icone vai enxergar ele.
|
|
285
323
|
// Esta e a falha silenciosa do §5.4: no terminal funciona, no app nao, e
|
|
@@ -327,6 +365,15 @@ async function runDoctor(targetDir) {
|
|
|
327
365
|
}
|
|
328
366
|
if (pin) {
|
|
329
367
|
success(`MCP arcane registrado no Claude Code (${pin})`);
|
|
368
|
+
let espera = NaN;
|
|
369
|
+
try {
|
|
370
|
+
const local = JSON.parse((await fs.readFile(path.join(targetDir, '.claude', 'settings.local.json'), 'utf8')).replace(/^\uFEFF/, ''));
|
|
371
|
+
espera = Number(local?.env?.MCP_TIMEOUT);
|
|
372
|
+
} catch { /* sem arquivo ou ilegivel = sem tempo de espera */ }
|
|
373
|
+
if (!(espera >= CLAUDE_MCP_TIMEOUT_MS)) {
|
|
374
|
+
error('O Claude Code esta com a espera padrao de 30s pelo conteudo ao vivo — no Windows a abertura pode cair (CONNECT_TIMEOUT)');
|
|
375
|
+
problemas.push('Rode: npx auroq-os@latest register-mcp (da ao Claude Code 2 minutos de espera). Depois feche e abra o Claude Code.');
|
|
376
|
+
}
|
|
330
377
|
} else {
|
|
331
378
|
error('MCP arcane nao registrado em .mcp.json');
|
|
332
379
|
problemas.push('Rode: npx auroq-os register-mcp (reescreve o registro). Depois feche e abra o Claude Code.');
|
|
@@ -459,8 +506,12 @@ async function runDoctor(targetDir) {
|
|
|
459
506
|
// (rede domestica + antivirus) a aluna viu timeout em todas as que fez. Por isso:
|
|
460
507
|
// (1) o registro do Codex pede mais tempo, (2) instalacao/atualizacao ja baixam o
|
|
461
508
|
// pacote, (3) o doctor mede o arranque de verdade em vez de so olhar o registro.
|
|
462
|
-
const ARCANE_MCP_PIN = 'arcane-mcp@0.
|
|
509
|
+
const ARCANE_MCP_PIN = 'arcane-mcp@0.6.0';
|
|
463
510
|
const CODEX_MCP_STARTUP_SEC = 120;
|
|
511
|
+
// O Claude Code tambem desistia aos 30s (MCP_TIMEOUT) e o aluno via CONNECT_TIMEOUT com o
|
|
512
|
+
// servidor no ar e nenhum pedido chegando nele (11/09, EMB-041). Ele le o tempo do `env` das
|
|
513
|
+
// configuracoes da pasta: provado no 2.1.267, servidor de 40s cai aos 30s sem isto e conecta com isto.
|
|
514
|
+
const CLAUDE_MCP_TIMEOUT_MS = 120000;
|
|
464
515
|
const CODEX_ENV_MINIMO = ['PATH', 'PATHEXT', 'USERNAME', 'USERDOMAIN', 'USERPROFILE', 'TEMP', 'TMP',
|
|
465
516
|
'HOME', 'LOGNAME', 'SHELL', 'TERM', 'USER'];
|
|
466
517
|
|
|
@@ -508,7 +559,7 @@ function blocoCodexArcane(pin, opcoes) {
|
|
|
508
559
|
}
|
|
509
560
|
|
|
510
561
|
// Baixa o pacote do conteudo ao vivo com o MESMO nome que o registro usa (o cache do
|
|
511
|
-
// npx e por nome+versao: `arcane-mcp` e `arcane-mcp@0.
|
|
562
|
+
// npx e por nome+versao: `arcane-mcp` e `arcane-mcp@0.6.0` sao caches diferentes).
|
|
512
563
|
// `npx` no Windows e um .cmd: sem shell o Node recusa (EINVAL) e com `shell: true` o Node 24
|
|
513
564
|
// imprime um DeprecationWarning na tela do aluno (DEP0190). Chamar o cmd.exe direto, sem shell.
|
|
514
565
|
function comandoNpx(args) {
|
|
@@ -768,6 +819,16 @@ function rodarNoCodex(pasta) {
|
|
|
768
819
|
}
|
|
769
820
|
|
|
770
821
|
async function writeArcaneMcpConfig(targetDir) {
|
|
822
|
+
// Todo caminho que registra o MCP (instalar, manutencao, register-mcp, clone) passa aqui,
|
|
823
|
+
// entao e aqui que o Claude Code ganha o tempo de espera. Falhar em gravar isso (arquivo
|
|
824
|
+
// somente leitura, dono root) nao pode derrubar o registro nem o resto da manutencao.
|
|
825
|
+
let esperaOk = false;
|
|
826
|
+
try {
|
|
827
|
+
await patchEnableAllProjectMcpServers(targetDir);
|
|
828
|
+
esperaOk = true;
|
|
829
|
+
} catch (e) {
|
|
830
|
+
warn(`Nao consegui gravar .claude/settings.local.json (${e.code || e.message}): o Claude Code segue esperando so 30s pelo conteudo ao vivo. Confira a permissao desse arquivo e rode register-mcp de novo.`);
|
|
831
|
+
}
|
|
771
832
|
const pin = ARCANE_MCP_PIN;
|
|
772
833
|
const mcpPath = path.join(targetDir, '.mcp.json');
|
|
773
834
|
let json = { mcpServers: {} };
|
|
@@ -791,22 +852,181 @@ async function writeArcaneMcpConfig(targetDir) {
|
|
|
791
852
|
const next = (toml.trimEnd() ? `${toml.trimEnd()}\n\n` : '') + block;
|
|
792
853
|
await fs.writeFile(tomlPath, next);
|
|
793
854
|
}
|
|
855
|
+
return { esperaOk };
|
|
794
856
|
}
|
|
795
857
|
|
|
858
|
+
async function modeloSettingsLocal() {
|
|
859
|
+
try {
|
|
860
|
+
return await fs.readJSON(path.resolve(__dirname, '..', '.claude', 'settings.local.json'));
|
|
861
|
+
} catch {
|
|
862
|
+
return {};
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Copia dos bytes do aluno antes de reescrever o arquivo dele. Vai pra `.auroq/` (fora do
|
|
867
|
+
// git): um settings.local.json pode ter token no `env` e nao pode subir pro GitHub.
|
|
868
|
+
async function guardarSettingsLocal(targetDir, bruto, motivo) {
|
|
869
|
+
const quando = new Date().toISOString().slice(0, 19).replace(/[:T]/g, '-');
|
|
870
|
+
const guarda = path.join(targetDir, '.auroq', 'preservado', `settings.local.json.${motivo}-${quando}`);
|
|
871
|
+
await fs.ensureDir(path.dirname(guarda));
|
|
872
|
+
await fs.writeFile(guarda, bruto);
|
|
873
|
+
const giPath = path.join(targetDir, '.gitignore');
|
|
874
|
+
const gi = (await fs.pathExists(giPath)) ? await fs.readFile(giPath, 'utf8') : '';
|
|
875
|
+
if (!gi.split('\n').map(l => l.trim()).includes('.auroq/')) {
|
|
876
|
+
await fs.writeFile(giPath, `${gi.replace(/\s*$/, gi.trim() ? '\n' : '')}.auroq/\n`);
|
|
877
|
+
}
|
|
878
|
+
return path.relative(targetDir, guarda).split(path.sep).join('/');
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
const ehObjeto = v => Boolean(v) && typeof v === 'object' && !Array.isArray(v);
|
|
882
|
+
|
|
883
|
+
// `.claude/settings.local.json` e do aluno: so acrescenta o que falta, nunca tira nada.
|
|
884
|
+
// E o unico lugar que registra os hooks do Synapse e do PreCompact; se o arquivo nasce
|
|
885
|
+
// sem eles (clone numa maquina nova, ou criado pelo proprio Claude Code), as regras
|
|
886
|
+
// deixam de chegar ao modelo em silencio. Por isso os hooks do modelo que faltarem entram.
|
|
796
887
|
async function patchEnableAllProjectMcpServers(targetDir) {
|
|
797
888
|
const settingsPath = path.join(targetDir, '.claude', 'settings.local.json');
|
|
798
|
-
|
|
889
|
+
const modelo = await modeloSettingsLocal();
|
|
890
|
+
let json;
|
|
891
|
+
let mudou = false;
|
|
799
892
|
if (await fs.pathExists(settingsPath)) {
|
|
893
|
+
const bruto = await fs.readFile(settingsPath);
|
|
800
894
|
try {
|
|
801
|
-
json =
|
|
895
|
+
json = JSON.parse(bruto.toString('utf8').replace(/^\uFEFF/, ''));
|
|
896
|
+
if (!ehObjeto(json)) throw new Error('nao e um objeto');
|
|
802
897
|
} catch {
|
|
803
|
-
|
|
898
|
+
// Antes virava {} e o aluno perdia os hooks e o que mais tivesse ali.
|
|
899
|
+
const guarda = await guardarSettingsLocal(targetDir, bruto, 'ilegivel');
|
|
900
|
+
warn(`.claude/settings.local.json estava ilegivel: guardei o original em ${guarda} e refiz a partir do modelo`);
|
|
901
|
+
json = JSON.parse(JSON.stringify(modelo));
|
|
902
|
+
mudou = true;
|
|
903
|
+
}
|
|
904
|
+
if (json.env !== undefined && !ehObjeto(json.env)) {
|
|
905
|
+
const guarda = await guardarSettingsLocal(targetDir, bruto, 'env-invalido');
|
|
906
|
+
warn(`O "env" de .claude/settings.local.json nao era uma lista de variaveis: guardei o original em ${guarda}`);
|
|
907
|
+
delete json.env;
|
|
908
|
+
mudou = true;
|
|
804
909
|
}
|
|
910
|
+
} else {
|
|
911
|
+
json = JSON.parse(JSON.stringify(modelo));
|
|
912
|
+
mudou = true;
|
|
913
|
+
}
|
|
914
|
+
if (ehObjeto(modelo.hooks)) {
|
|
915
|
+
// Hook ja registrado (aqui ou no settings.json, com qualquer forma de caminho) nao
|
|
916
|
+
// entra de novo: rodar o Synapse duas vezes duplicaria as regras.
|
|
917
|
+
let doProjeto = {};
|
|
918
|
+
try {
|
|
919
|
+
doProjeto = JSON.parse((await fs.readFile(path.join(targetDir, '.claude', 'settings.json'), 'utf8')).replace(/^/, ''));
|
|
920
|
+
} catch { /* sem settings.json legivel */ }
|
|
921
|
+
const scripts = (grupos) => (Array.isArray(grupos) ? grupos : [])
|
|
922
|
+
.flatMap(g => (Array.isArray(g?.hooks) ? g.hooks : []))
|
|
923
|
+
.map(h => (String(h?.command || '').match(/[\w.-]+\.c?js\b/) || [''])[0]);
|
|
924
|
+
if (!ehObjeto(json.hooks)) json.hooks = {};
|
|
925
|
+
for (const [evento, grupos] of Object.entries(modelo.hooks)) {
|
|
926
|
+
const atuais = Array.isArray(json.hooks[evento]) ? json.hooks[evento] : [];
|
|
927
|
+
const presentes = new Set([...scripts(atuais), ...scripts(doProjeto?.hooks?.[evento])].filter(Boolean));
|
|
928
|
+
const faltando = (grupos || []).filter(g => scripts([g]).some(s => s && !presentes.has(s)));
|
|
929
|
+
if (faltando.length) {
|
|
930
|
+
json.hooks[evento] = [...atuais, ...faltando];
|
|
931
|
+
mudou = true;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
if (json.enableAllProjectMcpServers !== true) {
|
|
936
|
+
json.enableAllProjectMcpServers = true;
|
|
937
|
+
mudou = true;
|
|
805
938
|
}
|
|
806
|
-
|
|
807
|
-
|
|
939
|
+
const env = ehObjeto(json.env) ? json.env : {};
|
|
940
|
+
const atual = Number(env.MCP_TIMEOUT);
|
|
941
|
+
if (!(Number.isFinite(atual) && atual >= CLAUDE_MCP_TIMEOUT_MS)) {
|
|
942
|
+
json.env = { ...env, MCP_TIMEOUT: String(CLAUDE_MCP_TIMEOUT_MS) };
|
|
943
|
+
mudou = true;
|
|
944
|
+
}
|
|
945
|
+
if (!mudou) return false;
|
|
808
946
|
await fs.ensureDir(path.dirname(settingsPath));
|
|
809
947
|
await fs.writeJSON(settingsPath, json, { spaces: 2 });
|
|
948
|
+
return true;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// ─── Synapse: o motor que reforca as regras do sistema a cada mensagem ───
|
|
952
|
+
// Ate a 2.5.1 esta pasta era copiada com overwrite:false. Quem instalou ate a 2.4.9 ficou
|
|
953
|
+
// para sempre com o hook-runtime.js sem `hookEventName`: o Claude Code recusava o pacote
|
|
954
|
+
// inteiro ("UserPromptSubmit hook error" a cada mensagem) e nenhuma regra chegava ao modelo,
|
|
955
|
+
// enquanto a manutencao carimbava a versao nova (EMB-040). Agora: arquivo identico a uma
|
|
956
|
+
// versao que publicamos e trocado; arquivo que ninguem publicou (mexido a mao) e guardado
|
|
957
|
+
// em .auroq-core/preservado/synapse/ antes da troca.
|
|
958
|
+
function hashNormalizado(buf) {
|
|
959
|
+
return require('crypto').createHash('sha256').update(buf.toString('utf8').replace(/\r\n/g, '\n')).digest('hex');
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
async function listarArquivos(dir, base = dir) {
|
|
963
|
+
const saida = [];
|
|
964
|
+
for (const nome of await fs.readdir(dir)) {
|
|
965
|
+
const abs = path.join(dir, nome);
|
|
966
|
+
const st = await fs.lstat(abs);
|
|
967
|
+
if (st.isDirectory()) saida.push(...(await listarArquivos(abs, base)));
|
|
968
|
+
else if (st.isFile()) saida.push(path.relative(base, abs).split(path.sep).join('/'));
|
|
969
|
+
}
|
|
970
|
+
return saida;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
async function atualizarSynapse(sourceDir, targetDir) {
|
|
974
|
+
const origem = path.join(sourceDir, '.auroq-core', 'core', 'synapse');
|
|
975
|
+
const destino = path.join(targetDir, '.auroq-core', 'core', 'synapse');
|
|
976
|
+
if (!(await fs.pathExists(origem))) return null;
|
|
977
|
+
const oficiais = require('../lib/synapse-hashes.json');
|
|
978
|
+
const r = { novos: 0, atualizados: 0, iguais: 0, preservados: [], falhas: [] };
|
|
979
|
+
for (const rel of await listarArquivos(origem)) {
|
|
980
|
+
const src = path.join(origem, ...rel.split('/'));
|
|
981
|
+
const dst = path.join(destino, ...rel.split('/'));
|
|
982
|
+
try {
|
|
983
|
+
if (!(await fs.pathExists(dst))) {
|
|
984
|
+
await fs.copy(src, dst);
|
|
985
|
+
r.novos++;
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
const atual = await fs.readFile(dst);
|
|
989
|
+
const hAtual = hashNormalizado(atual);
|
|
990
|
+
if (hAtual === hashNormalizado(await fs.readFile(src))) {
|
|
991
|
+
r.iguais++;
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
994
|
+
if (!(oficiais[rel] || []).includes(hAtual)) {
|
|
995
|
+
const guarda = path.join(targetDir, '.auroq-core', 'preservado', 'synapse', `${rel}.${hAtual.slice(0, 12)}`);
|
|
996
|
+
if (!(await fs.pathExists(guarda))) {
|
|
997
|
+
await fs.ensureDir(path.dirname(guarda));
|
|
998
|
+
await fs.writeFile(guarda, atual);
|
|
999
|
+
}
|
|
1000
|
+
r.preservados.push(rel);
|
|
1001
|
+
}
|
|
1002
|
+
await fs.copy(src, dst, { overwrite: true });
|
|
1003
|
+
r.atualizados++;
|
|
1004
|
+
} catch (e) {
|
|
1005
|
+
r.falhas.push(`${rel} (${e.code || e.message})`);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
return r;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
async function synapseRegistrado(targetDir) {
|
|
1012
|
+
for (const nome of ['settings.local.json', 'settings.json']) {
|
|
1013
|
+
try {
|
|
1014
|
+
const json = JSON.parse((await fs.readFile(path.join(targetDir, '.claude', nome), 'utf8')).replace(/^/, ''));
|
|
1015
|
+
const grupos = Array.isArray(json?.hooks?.UserPromptSubmit) ? json.hooks.UserPromptSubmit : [];
|
|
1016
|
+
if (grupos.some(g => (g?.hooks || []).some(h => String(h?.command || '').includes('synapse-engine.cjs')))) return true;
|
|
1017
|
+
} catch { /* arquivo ausente ou ilegivel */ }
|
|
1018
|
+
}
|
|
1019
|
+
return false;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// O hook-runtime sem `hookEventName` e a assinatura do Synapse antigo (EMB-040).
|
|
1023
|
+
async function synapseDesatualizado(targetDir) {
|
|
1024
|
+
const arq = path.join(targetDir, '.auroq-core', 'core', 'synapse', 'runtime', 'hook-runtime.js');
|
|
1025
|
+
try {
|
|
1026
|
+
return !(await fs.readFile(arq, 'utf8')).includes('hookEventName');
|
|
1027
|
+
} catch {
|
|
1028
|
+
return false;
|
|
1029
|
+
}
|
|
810
1030
|
}
|
|
811
1031
|
|
|
812
1032
|
// ─── INSTALACAO ──────────────────────────────────────────
|
|
@@ -992,6 +1212,7 @@ async function init({ pasta = null, maintenance = false } = {}) {
|
|
|
992
1212
|
|
|
993
1213
|
// Hooks
|
|
994
1214
|
'.claude/hooks/synapse-engine.cjs',
|
|
1215
|
+
'.claude/hooks/ahi-capture.cjs',
|
|
995
1216
|
'.claude/hooks/precompact-session-digest.cjs',
|
|
996
1217
|
'.claude/hooks/auroq-sync.cjs',
|
|
997
1218
|
|
|
@@ -1196,25 +1417,26 @@ async function init({ pasta = null, maintenance = false } = {}) {
|
|
|
1196
1417
|
await fs.copy(seedRoot, targetDir, { overwrite: false });
|
|
1197
1418
|
success('Semente do aluno instalada (memoria, docs, cockpit — nunca sobrescreve)');
|
|
1198
1419
|
}
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
step('Preparando o conteudo ao vivo (baixa uma vez; no Windows prepara tambem pro Codex; pode levar 1-3 min)...');
|
|
1202
|
-
if (aquecerArcaneMcp()) success('Conteudo ao vivo pronto pra abrir rapido');
|
|
1203
|
-
else warn('Nao consegui baixar o conteudo ao vivo agora — a primeira abertura vai demorar mais');
|
|
1204
|
-
|
|
1205
|
-
// ─── Fase 4: Copiar Synapse Engine
|
|
1420
|
+
// ─── Fase 4: Synapse Engine (troca versao oficial antiga; guarda o que foi mexido a mao).
|
|
1421
|
+
// Antes do registro do MCP: se algo ali falhar, a correcao do hook (EMB-040) ja entrou.
|
|
1206
1422
|
step('Instalando Synapse Engine...');
|
|
1207
1423
|
|
|
1208
|
-
const
|
|
1209
|
-
|
|
1210
|
-
if (await fs.pathExists(synapseSrc)) {
|
|
1211
|
-
await fs.copy(synapseSrc, synapseDst, { overwrite: false });
|
|
1212
|
-
const synapseFiles = await fs.readdir(synapseSrc, { recursive: true });
|
|
1213
|
-
success(`Synapse Engine instalado (${synapseFiles.length} arquivos)`);
|
|
1214
|
-
} else {
|
|
1424
|
+
const sinapse = await atualizarSynapse(sourceDir, targetDir);
|
|
1425
|
+
if (!sinapse) {
|
|
1215
1426
|
warn('Synapse Engine nao encontrado na fonte — pulando');
|
|
1427
|
+
} else {
|
|
1428
|
+
success(`Synapse Engine em dia (${sinapse.novos} novo(s), ${sinapse.atualizados} atualizado(s))`);
|
|
1429
|
+
if (sinapse.preservados.length) {
|
|
1430
|
+
warn(`Arquivo(s) do Synapse diferente(s) de qualquer versao oficial guardado(s) em .auroq-core/preservado/synapse antes da troca: ${sinapse.preservados.join(', ')}`);
|
|
1431
|
+
}
|
|
1432
|
+
if (sinapse.falhas.length) warn(`Nao consegui atualizar: ${sinapse.falhas.join(', ')}`);
|
|
1216
1433
|
}
|
|
1217
1434
|
|
|
1435
|
+
await writeArcaneMcpConfig(targetDir);
|
|
1436
|
+
step('Preparando o conteudo ao vivo (baixa uma vez; no Windows prepara tambem pro Codex; pode levar 1-3 min)...');
|
|
1437
|
+
if (aquecerArcaneMcp()) success('Conteudo ao vivo pronto pra abrir rapido');
|
|
1438
|
+
else warn('Nao consegui baixar o conteudo ao vivo agora — a primeira abertura vai demorar mais');
|
|
1439
|
+
|
|
1218
1440
|
// ─── Fase 4b: Copiar Meta Squads e scripts (diretorios inteiros)
|
|
1219
1441
|
step('Instalando Meta Squads e scripts...');
|
|
1220
1442
|
|
|
@@ -1510,6 +1732,11 @@ ${DIM} Auroq OS v${PKG_VERSION} — Sistema Operacional de IA para Experts${RES
|
|
|
1510
1732
|
`);
|
|
1511
1733
|
}
|
|
1512
1734
|
|
|
1735
|
+
telemetry.runtimeReady();
|
|
1736
|
+
if (['doctor','diagnostico'].includes(process.argv[2])) {
|
|
1737
|
+
console.log('Observabilidade local:',JSON.stringify(telemetry.diagnostics()));
|
|
1738
|
+
console.log('Manutencao: npx -y auroq-os@2.5.3 manutencao; MCP: npx -y arcane-mcp@0.6.0 doctor');
|
|
1739
|
+
}
|
|
1513
1740
|
// ─── CLI ─────────────────────────────────────────────────
|
|
1514
1741
|
const command = process.argv[2];
|
|
1515
1742
|
|
|
@@ -1517,6 +1744,7 @@ if (['init', 'install', 'update'].includes(command)) {
|
|
|
1517
1744
|
error('Comando aposentado. Nada foi instalado ou alterado.');
|
|
1518
1745
|
error('Primeira instalacao: https://arcane.arka.education/auroq');
|
|
1519
1746
|
error('No negocio existente, peca ao Ops: manutencao do motor. Conteudo chega pelo MCP.');
|
|
1747
|
+
error('Diagnostico: npx -y auroq-os@2.5.3 doctor. Manutencao: npx -y auroq-os@2.5.3 manutencao.');
|
|
1520
1748
|
process.exitCode = 1;
|
|
1521
1749
|
} else if (command === 'instalar' || command === 'manutencao') {
|
|
1522
1750
|
// `--pasta <nome>`: o instalador manda o CLI criar e entrar na pasta do negocio,
|
|
@@ -1591,9 +1819,9 @@ if (['init', 'install', 'update'].includes(command)) {
|
|
|
1591
1819
|
error('Rode este comando na raiz do seu negocio.');
|
|
1592
1820
|
process.exit(1);
|
|
1593
1821
|
}
|
|
1594
|
-
await
|
|
1595
|
-
await writeArcaneMcpConfig(targetDir);
|
|
1822
|
+
const { esperaOk } = await writeArcaneMcpConfig(targetDir);
|
|
1596
1823
|
success('Conteudo ao vivo registrado (.mcp.json e .codex/config.toml).');
|
|
1824
|
+
if (esperaOk) success(`Claude Code espera o conteudo ao vivo por ate ${CLAUDE_MCP_TIMEOUT_MS / 1000}s (.claude/settings.local.json)`);
|
|
1597
1825
|
log(`${DIM}Baixando o conteudo ao vivo uma vez (pode levar 1-2 min)...${RESET}`);
|
|
1598
1826
|
if (aquecerArcaneMcp()) success('Conteudo ao vivo pronto pra abrir rapido');
|
|
1599
1827
|
else warn('Nao consegui baixar o conteudo ao vivo agora — a primeira abertura vai demorar mais');
|
|
@@ -1603,6 +1831,15 @@ if (['init', 'install', 'update'].includes(command)) {
|
|
|
1603
1831
|
error(`Falha ao registrar o MCP: ${err.message}`);
|
|
1604
1832
|
process.exit(1);
|
|
1605
1833
|
});
|
|
1834
|
+
} else if (command === 'historico') {
|
|
1835
|
+
import('../lib/ahi/historico.mjs').then((mod) => {
|
|
1836
|
+
const result = mod.run(process.argv.slice(3));
|
|
1837
|
+
console.log(JSON.stringify(result));
|
|
1838
|
+
process.exit(result.ok ? 0 : 1);
|
|
1839
|
+
}).catch((err) => {
|
|
1840
|
+
error(`Falha no historico: ${err.message}`);
|
|
1841
|
+
process.exit(1);
|
|
1842
|
+
});
|
|
1606
1843
|
} else if (command === 'sync-codex') {
|
|
1607
1844
|
// Regenera as skills do Codex CLI a partir dos comandos atuais do projeto.
|
|
1608
1845
|
try {
|
|
@@ -1627,6 +1864,7 @@ if (['init', 'install', 'update'].includes(command)) {
|
|
|
1627
1864
|
log(` ${CYAN}doctor${RESET} Diagnostico: arquivos, Node, MCP arcane, login e servidor`);
|
|
1628
1865
|
log(` ${CYAN}register-mcp${RESET} Reescrever o registro do conteudo ao vivo (MCP arcane) no projeto`);
|
|
1629
1866
|
log(` ${CYAN}sync-codex${RESET} Gerar e verificar as skills locais do Codex CLI ($nome)`);
|
|
1867
|
+
log(` ${CYAN}historico${RESET} Estado da captura do trabalho (ligada com o MCP Arcane)`);
|
|
1630
1868
|
log(` ${CYAN}conectar-1password${RESET} Conectar o 1Password CLI (token lido do Ctrl+C, nunca digitado)`);
|
|
1631
1869
|
log('');
|
|
1632
1870
|
log(`${DIM}Primeira instalacao: https://arcane.arka.education/auroq${RESET}`);
|
|
@@ -1634,3 +1872,5 @@ if (['init', 'install', 'update'].includes(command)) {
|
|
|
1634
1872
|
}
|
|
1635
1873
|
|
|
1636
1874
|
if (process.env.AUROQ_EXPORTA_INTERNOS === '1') module.exports = { blocoCodexArcane, envCodexArcane, lerBlocoArcane, lerEnvDoBloco, limparArcaneDoGlobal, envComoCodex };
|
|
1875
|
+
|
|
1876
|
+
}
|
package/installer/README.md
CHANGED
|
@@ -100,3 +100,23 @@ em vez do original, o teste quebra e diz qual é o certo.
|
|
|
100
100
|
Existe pra quem não pode depender de um `cd` ter funcionado. O instalador do Mac
|
|
101
101
|
entra na pasta antes de chamar o `init`, então não precisa da opção — mas ela é a
|
|
102
102
|
rede de segurança de qualquer outro chamador.
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
### Spool de observabilidade (npm 2.5.1 publicado; promocao pendente)
|
|
106
|
+
|
|
107
|
+
O script registra metadados fechados em `~/.arcane/installer-outbox` antes de Node.
|
|
108
|
+
Shell usa Perl nativo do Mac, umask 077 e lock com dono publicado atomicamente.
|
|
109
|
+
PowerShell verifica ACL privada e usa handle exclusivo liberado pelo kernel em crash.
|
|
110
|
+
Nao coleta identidade nem conteudo pessoal. O CLI importa como LOCAL, com TTL de sete
|
|
111
|
+
dias, e o doctor mostra os codigos; nao envia retroativamente ao primeiro login.
|
|
112
|
+
O proprio instalador expurga TTL e slots interrompidos a cada execucao, sem Node.
|
|
113
|
+
Limites: 200 slots por formato, sem captura antes
|
|
114
|
+
do download do proprio script, sem garantia de reconexao/adocao. Fonte oficial segue
|
|
115
|
+
neste diretorio; `plataforma-arcane/npm run sync:installer` sincroniza as duas copias.
|
|
116
|
+
|
|
117
|
+
Pins oficiais permanecem `auroq-os@2.5.2`, atualmente publicado. A publicacao do candidato
|
|
118
|
+
por 401 nas duas vias testadas pelo coordenador; nao alterar autenticacao aqui.
|
|
119
|
+
Candidatos motor 2.5.2/cliente 0.6.0 nao publicados. O spool local pode ser servido
|
|
120
|
+
com o pin antigo; novo transporte/doctor depende de npm e adocao. Runtime Windows
|
|
121
|
+
faz fail-closed ate dispor de ACL/no-follow seguros; spool PowerShell permanece local.
|
|
122
|
+
Execucao Linux de Bash/Perl nao valida macOS nem Windows/PowerShell 5.1.
|