auroq-os 2.6.9 → 2.7.0

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.
Files changed (43) hide show
  1. package/.auroq-core/como-funciona.md +162 -0
  2. package/.auroq-core/core/synapse/output/formatter.js +22 -2
  3. package/.auroq-core/core-config.yaml +1 -1
  4. package/.claude/CLAUDE.md +12 -4
  5. package/.claude/commands/auroq-companion.md +1 -1
  6. package/.claude/hooks/precompact-session-digest.cjs +165 -29
  7. package/.claude/output-styles/direto.md +8 -3
  8. package/.claude/rules/agent-authority.md +5 -5
  9. package/.claude/rules/dna-operacional.md +1 -1
  10. package/.claude/rules/memoria-inteligente.md +29 -7
  11. package/.claude/rules/natural-language-first.md +3 -3
  12. package/.claude/rules/operacao-autonoma.md +20 -23
  13. package/.claude/rules/puxar-e-entregar.md +7 -7
  14. package/.claude/rules/titulo-da-barra.md +3 -3
  15. package/.claude/settings.local.json +11 -1
  16. package/.synapse/agent-companion +2 -1
  17. package/.synapse/agent-ops +4 -4
  18. package/.synapse/agent-organizer +2 -2
  19. package/.synapse/context +3 -3
  20. package/.synapse/global +1 -0
  21. package/.synapse/squad-clone-forge +1 -1
  22. package/.synapse/squad-etlmaker +2 -2
  23. package/.synapse/squad-squad-forge +1 -1
  24. package/AGENTS.md +138 -12
  25. package/CHANGELOG.md +31 -0
  26. package/README.md +5 -5
  27. package/agents/organizer/agents/organizer.md +8 -8
  28. package/agents/organizer/tasks/start.md +1 -1
  29. package/bin/auroq-os.js +70 -16
  30. package/installer/install.ps1 +2 -2
  31. package/installer/install.sh +2 -2
  32. package/lib/barra/barra.cjs +52 -18
  33. package/lib/barra/ccstatusline-grok.json +98 -0
  34. package/lib/barra/index.js +158 -32
  35. package/lib/legacy-shims.json +17 -0
  36. package/lib/runtime-integrity.json +17 -13
  37. package/lib/synapse-hashes.json +2 -1
  38. package/package.json +5 -4
  39. package/scripts/runtime-integrity.mjs +1 -1
  40. package/seeds/agents/companion/data/contexto-dinamico.md +1 -1
  41. package/seeds/agents/companion/data/padroes-observados.md +1 -1
  42. package/seeds/business/cockpit.md +2 -2
  43. package/seeds/business/templates/tracker-template.md +1 -1
package/bin/auroq-os.js CHANGED
@@ -70,7 +70,7 @@ ${PURPLE} ━━━━━━━━━━━━━━━━━━━━━━━
70
70
  ${PURPLE}${BOLD} ▄▀█ █ █ █▀█ █▀█ █▀█${RESET} ${DIM}OS v${PKG_VERSION}${RESET}
71
71
  ${PURPLE}${BOLD} █▀█ █▄▄█ █▀▄ █▄█ ▀▀█${RESET}
72
72
 
73
- ${DIM} Sistema Operacional de IA para Experts${RESET}
73
+ ${DIM} Sistema Operacional de IA para o seu negocio${RESET}
74
74
  ${DIM} Pensar. Fazer. Lembrar. Com IA.${RESET}
75
75
 
76
76
  ${PURPLE} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}
@@ -1433,6 +1433,20 @@ async function patchEnableAllProjectMcpServers(targetDir) {
1433
1433
  mudou = true;
1434
1434
  }
1435
1435
  }
1436
+ // 2.7.0: o gancho da compactação nasceu com "timeout": 15000 (o Claude Code conta em segundos:
1437
+ // ~4 h). O modelo agora diz 10; quem já tinha a pasta recebe o valor do modelo.
1438
+ const tempoDoModelo = (modelo.hooks.PreCompact || []).flatMap(g => g?.hooks || [])
1439
+ .find(h => /precompact-session-digest\.cjs/.test(String(h?.command || '')))?.timeout;
1440
+ if (Number.isFinite(tempoDoModelo)) {
1441
+ for (const g of Array.isArray(json.hooks.PreCompact) ? json.hooks.PreCompact : []) {
1442
+ for (const h of Array.isArray(g?.hooks) ? g.hooks : []) {
1443
+ if (/precompact-session-digest\.cjs/.test(String(h?.command || '')) && Number(h.timeout) > 60) {
1444
+ h.timeout = tempoDoModelo;
1445
+ mudou = true;
1446
+ }
1447
+ }
1448
+ }
1449
+ }
1436
1450
  }
1437
1451
  if (require('../lib/ahi/install.cjs').mergeHooks(json, targetDir, 'claude-code')) mudou = true;
1438
1452
  if (json.enableAllProjectMcpServers !== true) {
@@ -1473,6 +1487,33 @@ async function listarArquivos(dir, base = dir) {
1473
1487
  return saida;
1474
1488
  }
1475
1489
 
1490
+ // 2.7.0 — As regras de base moram no AGENTS.md (a manutencao sempre troca) e o CLAUDE.md as
1491
+ // importa. Ate a 2.6.10 o CLAUDE.md nunca era tocado depois da primeira instalacao: quem instalou
1492
+ // cedo ficou com a doutrina da v1.1.0 para sempre. Agora: identico a uma versao que publicamos ->
1493
+ // trocado pelo novo; mexido pelo aluno (ex.: nome do Companion na FASE 10) -> fica como esta e so
1494
+ // ganha, no fim, a linha que importa o AGENTS.md. Nada do aluno e apagado.
1495
+ const IMPORTA_AGENTS = '@../AGENTS.md';
1496
+ async function atualizarClaudeMd(src, dst) {
1497
+ if (!(await fs.pathExists(dst))) {
1498
+ await fs.copy(src, dst);
1499
+ return 'novo';
1500
+ }
1501
+ const atual = await fs.readFile(dst);
1502
+ const hAtual = hashNormalizado(atual);
1503
+ if (hAtual === hashNormalizado(await fs.readFile(src))) return 'igual';
1504
+ const oficiais = require('../lib/legacy-shims.json')['.claude/CLAUDE.md'] || [];
1505
+ if (oficiais.includes(hAtual)) {
1506
+ await fs.copy(src, dst, { overwrite: true });
1507
+ return 'atualizado';
1508
+ }
1509
+ const texto = atual.toString('utf8');
1510
+ if (texto.split(/\r?\n/).some(linha => linha.trim() === IMPORTA_AGENTS)) return 'do-aluno';
1511
+ const nl = texto.includes('\r\n') ? '\r\n' : '\n';
1512
+ const bloco = ['', '', '## Regras de base do Auroq OS (a atualizacao mantem em dia)', '', IMPORTA_AGENTS, ''].join(nl);
1513
+ await fs.writeFile(dst, texto.replace(/\s+$/, '') + bloco);
1514
+ return 'importou';
1515
+ }
1516
+
1476
1517
  async function atualizarSynapse(sourceDir, targetDir) {
1477
1518
  const origem = path.join(sourceDir, '.auroq-core', 'core', 'synapse');
1478
1519
  const destino = path.join(targetDir, '.auroq-core', 'core', 'synapse');
@@ -1549,14 +1590,14 @@ function opcoesDeLoginDeTeste() {
1549
1590
  }
1550
1591
 
1551
1592
  // ─── INSTALACAO ──────────────────────────────────────────
1552
- // Barra de status do Claude Code (2.6.8): a mesma do Euriler, no HOME do aluno. Nunca derruba a
1593
+ // Barra de status (2.6.8; Codex e Grok desde 2.6.10): a mesma do Euriler, no HOME do aluno. Nunca derruba a
1553
1594
  // instalacao: sem a barra o sistema funciona igual, entao qualquer falha vira aviso.
1554
1595
  function ligarBarraDeStatus() {
1555
- step('Ligando a barra de status do Claude Code...');
1596
+ step('Ligando a barra de status (Claude Code, Codex e Grok)...');
1556
1597
  try {
1557
1598
  const r = require('../lib/barra').instalarBarra();
1558
1599
  const textos = {
1559
- 'ligada': 'Barra de status ligada (vale em todo terminal do Claude Code)',
1600
+ 'ligada': 'Barra de status ligada no Claude Code (vale em todo terminal)',
1560
1601
  'atualizada': 'Barra de status em dia',
1561
1602
  'em-dia': 'Barra de status em dia',
1562
1603
  'barra-do-aluno': 'Voce ja tem uma barra de status propria no Claude Code; ficou como estava',
@@ -1565,6 +1606,13 @@ function ligarBarraDeStatus() {
1565
1606
  };
1566
1607
  if (textos[r.estado]) success(textos[r.estado]);
1567
1608
  else warn(`Barra de status nao ligada (${r.estado}${r.detalhe ? `: ${r.detalhe}` : ''})`);
1609
+ // 2.6.10: a mesma barra no Grok (se instalado) e a barra nativa do Codex com os mesmos itens.
1610
+ const outras = { ligada: 'ligada', atualizada: 'em dia', 'em-dia': 'em dia', 'barra-do-aluno': 'a sua ficou como estava', 'tirada-pelo-aluno': 'desligada por voce' };
1611
+ for (const [nome, estado] of [['Codex', r.codex], ['Grok', r.grok]]) {
1612
+ if (!estado || estado === 'sem-grok') continue;
1613
+ if (outras[estado]) success(`Barra de status no ${nome}: ${outras[estado]}`);
1614
+ else warn(`Barra de status no ${nome} nao ligada (${estado})`);
1615
+ }
1568
1616
  return r;
1569
1617
  } catch (e) {
1570
1618
  warn(`Barra de status nao ligada (${String(e && e.message || e).split(/\r?\n/)[0].slice(0, 200)}). O resto funciona normal.`);
@@ -1738,6 +1786,7 @@ async function init({ pasta = null, maintenance = false, modoAgente = false, mig
1738
1786
  '.auroq-core/constitution.md',
1739
1787
  '.auroq-core/core-config.yaml',
1740
1788
  '.auroq-core/dna-operacional.md',
1789
+ '.auroq-core/como-funciona.md', // 2.7.0 — doutrina que saiu do CLAUDE.md (consulta sob demanda)
1741
1790
  '.auroq-core/development/sistema-gestao-projetos.md',
1742
1791
  '.auroq-core/development/sistema-memoria.md',
1743
1792
 
@@ -1930,6 +1979,7 @@ async function init({ pasta = null, maintenance = false, modoAgente = false, mig
1930
1979
  }
1931
1980
 
1932
1981
  let copied = 0;
1982
+ let claudeMd = null;
1933
1983
  for (const file of frameworkFiles) {
1934
1984
  // Pular companion.md generico se ja tem personalizado
1935
1985
  if (file === '.claude/commands/companion.md' && hasCustomCompanion) {
@@ -1942,7 +1992,9 @@ async function init({ pasta = null, maintenance = false, modoAgente = false, mig
1942
1992
  const dstExists = await fs.pathExists(dst);
1943
1993
  // Framework files: SEMPRE sobrescrever (atualizar)
1944
1994
  // Student files: so copiar se NAO existem (preservar dados)
1945
- if ((isStudentFile || file.startsWith('business/') || file.startsWith('docs/knowledge/') || file === '.claude/CLAUDE.md') && dstExists) {
1995
+ if (file === '.claude/CLAUDE.md') {
1996
+ claudeMd = await atualizarClaudeMd(src, dst);
1997
+ } else if ((isStudentFile || file.startsWith('business/') || file.startsWith('docs/knowledge/')) && dstExists) {
1946
1998
  // Nao sobrescrever dados do aluno
1947
1999
  } else if (file === '.claude/settings.json' && dstExists) {
1948
2000
  // Mescla: o que e do framework vem do modelo; permissoes, statusLine, env e hooks do aluno ficam.
@@ -1957,6 +2009,8 @@ async function init({ pasta = null, maintenance = false, modoAgente = false, mig
1957
2009
  }
1958
2010
  }
1959
2011
  success(`${copied} arquivos instalados`);
2012
+ if (claudeMd === 'atualizado') success('CLAUDE.md atualizado para as regras de trabalho novas');
2013
+ if (claudeMd === 'importou') success('CLAUDE.md seu mantido, agora com as regras de base do AGENTS.md');
1960
2014
  if (removidos > 0) {
1961
2015
  success(`${removidos} regra(s) antiga(s) aposentada(s)`);
1962
2016
  }
@@ -2148,21 +2202,21 @@ ${DIM} Suas customizacoes (sobrevivem a todo update):${RESET}
2148
2202
  ${CYAN}.claude/settings.local.json${RESET} ${DIM}— suas configuracoes locais${RESET}
2149
2203
 
2150
2204
  ${DIM} Agentes core disponiveis:${RESET}
2151
- ${PURPLE}/companion${RESET} ${DIM}— Parceiro cognitivo${RESET}
2152
- ${PURPLE}/AuroqOS:agents:ops${RESET} ${DIM}— Git, deploy, ambiente${RESET}
2153
- ${PURPLE}/organizer${RESET} ${DIM}— Organizacao e backup${RESET}
2205
+ ${PURPLE}/auroq-companion${RESET} ${DIM}— Chefe da sua equipe: fale com ele, ele aciona o resto${RESET}
2206
+ ${PURPLE}/AuroqOS:agents:ops${RESET} ${DIM}— O tecnico: instala, atualiza, conserta e conecta${RESET}
2207
+ ${PURPLE}/auroq-organizer${RESET} ${DIM}— Organizacao e backup${RESET}
2154
2208
 
2155
- ${DIM} Meta Squads (criadores de agentes):${RESET}
2156
- ${PURPLE}/squad-forge${RESET} ${DIM}— Cria squads multi-agente${RESET}
2157
- ${PURPLE}/mind-forge${RESET} ${DIM}— Cria mentes sinteticas${RESET}
2158
- ${PURPLE}/worker-forge${RESET} ${DIM}— Cria workers${RESET}
2159
- ${PURPLE}/clone-forge${RESET} ${DIM}— Clona mentes reais${RESET}
2160
- ${PURPLE}/etlmaker${RESET} ${DIM}— Extrai e estrutura conhecimento${RESET}
2209
+ ${DIM} Meta Squads (criadores de agentes; o Companion chama quando precisa):${RESET}
2210
+ ${PURPLE}/auroq-squad-forge${RESET} ${DIM}— Cria squads multi-agente${RESET}
2211
+ ${PURPLE}/auroq-mind-forge${RESET} ${DIM}— Cria mentes sinteticas${RESET}
2212
+ ${PURPLE}/auroq-worker-forge${RESET} ${DIM}— Cria workers${RESET}
2213
+ ${PURPLE}/auroq-clone-forge${RESET} ${DIM}— Clona mentes reais${RESET}
2214
+ ${PURPLE}/auroq-etlmaker${RESET} ${DIM}— Extrai e estrutura conhecimento${RESET}
2161
2215
 
2162
2216
  ${DIM} Claude Code e Codex sao runtimes oficiais. Escolha um por sessao; ambos usam os mesmos agentes e arquivos.${RESET}
2163
2217
 
2164
2218
  ${PURPLE}${BOLD} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}
2165
- ${DIM} Auroq OS v${PKG_VERSION} — Sistema Operacional de IA para Experts${RESET}
2219
+ ${DIM} Auroq OS v${PKG_VERSION} — Sistema Operacional de IA para o seu negocio${RESET}
2166
2220
  ${DIM} "Tu aumentaste o meu poder como do boi selvagem." — Sl 92:10${RESET}
2167
2221
  `);
2168
2222
  }
@@ -2332,7 +2386,7 @@ ${DIM} Ritual de todo dia, em toda maquina:${RESET}
2332
2386
  ${DIM} Nunca levante da maquina com trabalho nao entregue — o sistema lembra, mas so voce entrega.${RESET}
2333
2387
 
2334
2388
  ${PURPLE}${BOLD} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}
2335
- ${DIM} Auroq OS v${PKG_VERSION} — Sistema Operacional de IA para Experts${RESET}
2389
+ ${DIM} Auroq OS v${PKG_VERSION} — Sistema Operacional de IA para o seu negocio${RESET}
2336
2390
  `);
2337
2391
  }
2338
2392
 
@@ -589,7 +589,7 @@ function Invoke-AuroqLogin {
589
589
  # sucesso. Usado como pergunta de sim/nao, ele dizia "voce ja estava logado"
590
590
  # pra quem nunca logou, pulava o portao e deixava a conta sem acesso chegar ate
591
591
  # a criacao da pasta.
592
- & (Get-AuroqNpx) -y auroq-os@2.6.9 check-access
592
+ & (Get-AuroqNpx) -y auroq-os@2.7.0 check-access
593
593
  if ($LASTEXITCODE -ne 0) {
594
594
  Write-AuroqLog 'login' 'recusado'
595
595
  Write-Host ''
@@ -677,7 +677,7 @@ function Invoke-AuroqInit {
677
677
  if ($env:AUROQ_INSTALL_DRY_RUN) { Write-AuroqOk 'Sistema - seria montado aqui'; return }
678
678
 
679
679
  Set-Location $script:AuroqPastaAlvo
680
- & (Get-AuroqNpx) -y auroq-os@2.6.9 instalar
680
+ & (Get-AuroqNpx) -y auroq-os@2.7.0 instalar
681
681
  if ($LASTEXITCODE -ne 0) {
682
682
  Write-AuroqLog 'init' 'falhou'
683
683
  Write-AuroqErro 'A montagem do sistema parou no meio.' 'Rode o mesmo comando de novo. Se repetir, mande pro suporte o arquivo install.log da pasta .auroq.'
@@ -219,9 +219,9 @@ auroq_tem_terminal() {
219
219
  # recebem pedaco do script no lugar da resposta dele.
220
220
  auroq_npx_aluno() {
221
221
  if [ -t 0 ]; then
222
- npx -y auroq-os@2.6.9 "$@"
222
+ npx -y auroq-os@2.7.0 "$@"
223
223
  else
224
- npx -y auroq-os@2.6.9 "$@" < /dev/tty
224
+ npx -y auroq-os@2.7.0 "$@" < /dev/tty
225
225
  fi
226
226
  }
227
227
 
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
- // Barra de status do Claude Code do aluno (2.6.8). Mora em ~/.auroq/barra/barra.cjs e e o
4
- // `statusLine.command` do ~/.claude/settings.json: vale em qualquer terminal e no app Auroq OS.
3
+ // Barra de status do aluno (2.6.8; Grok desde 2.6.10). Mora em ~/.auroq/barra/barra.cjs e e o
4
+ // `statusLine.command` do ~/.claude/settings.json e o `[ui.status_line]` do ~/.grok/config.toml:
5
+ // vale em qualquer terminal e no app Auroq OS. O Codex usa a barra nativa dele (ver index.js).
5
6
  //
6
7
  // node barra.cjs desenha a barra (o Claude Code manda o JSON da sessao na entrada)
8
+ // node barra.cjs grok desenha a barra no Grok (mesmo JSON, com os nomes do Grok)
7
9
  // node barra.cjs titulo "Texto" grava o titulo da conversa (uma vez por sessao; a IA chama)
8
10
  // node barra.cjs topico pedaco da barra: o titulo desta sessao
9
11
  // node barra.cjs data pedaco da barra: "sex 16/10 11:39"
@@ -19,6 +21,7 @@ const PASTA = __dirname;
19
21
  const TITULOS = path.join(PASTA, 'titulos');
20
22
  const MOTOR = path.join(PASTA, 'ccstatusline', 'dist', 'ccstatusline.js');
21
23
  const LAYOUT = path.join(PASTA, 'ccstatusline.json');
24
+ const LAYOUT_GROK = path.join(PASTA, 'ccstatusline-grok.json');
22
25
  const SESSAO_VALIDA = /^[A-Za-z0-9._-]{1,128}$/;
23
26
  const SETE_DIAS_MS = 7 * 24 * 60 * 60 * 1000;
24
27
 
@@ -46,16 +49,19 @@ function arquivoDoTitulo(sessao) {
46
49
  }
47
50
 
48
51
  function gravarTitulo(texto) {
49
- const sessao = process.env.CLAUDE_CODE_SESSION_ID;
52
+ // Claude Code e Grok poem o id da sessao no terminal da IA. Um aberto dentro do outro herda os
53
+ // dois, entao grava nos dois: cada barra le o seu.
54
+ const sessoes = [process.env.CLAUDE_CODE_SESSION_ID, process.env.GROK_SESSION_ID].filter(s => s && SESSAO_VALIDA.test(s));
50
55
  const titulo = String(texto || '').replace(/[\r\n\t]+/g, ' ').replace(/[\u0000-\u001f\u007f]/g, '').trim().slice(0, 60);
51
- if (!titulo || !sessao || !SESSAO_VALIDA.test(sessao)) return;
56
+ if (!titulo || !sessoes.length) return;
52
57
  fs.mkdirSync(TITULOS, { recursive: true });
53
- const alvo = arquivoDoTitulo(sessao);
54
- // Uma vez por sessao: o primeiro titulo fica, como o do Euriler.
55
- try {
56
- fs.writeFileSync(alvo, `${titulo}\n`, { flag: 'wx' });
57
- } catch (e) {
58
- if (e.code !== 'EEXIST') throw e;
58
+ for (const sessao of sessoes) {
59
+ // Uma vez por sessao: o primeiro titulo fica, como o do Euriler.
60
+ try {
61
+ fs.writeFileSync(arquivoDoTitulo(sessao), `${titulo}\n`, { flag: 'wx' });
62
+ } catch (e) {
63
+ if (e.code !== 'EEXIST') throw e;
64
+ }
59
65
  }
60
66
  limparTitulosVelhos();
61
67
  }
@@ -72,12 +78,34 @@ function limparTitulosVelhos() {
72
78
 
73
79
  function mostrarTitulo(entrada) {
74
80
  const sessao = sessaoDoJson(entrada);
75
- if (!sessao) return '';
81
+ let titulo = '';
82
+ if (sessao) {
83
+ try {
84
+ titulo = fs.readFileSync(arquivoDoTitulo(sessao), 'utf8').split(/\r?\n/)[0] || '';
85
+ } catch {}
86
+ }
87
+ // Sem titulo gravado, o nome da sessao que o Grok (ou o /rename do Claude Code) da.
88
+ if (!titulo) {
89
+ try {
90
+ const nome = JSON.parse(entrada).session_name;
91
+ if (typeof nome === 'string') titulo = nome.replace(/[\u0000-\u001f\u007f]/g, '').trim();
92
+ } catch {}
93
+ }
94
+ return [...titulo].slice(0, 40).join('');
95
+ }
96
+
97
+ // O Grok segue a convencao do Claude Code, mas chama o contexto atual de `context_tokens`.
98
+ function jsonDoGrok(entrada) {
76
99
  try {
77
- const primeira = fs.readFileSync(arquivoDoTitulo(sessao), 'utf8').split(/\r?\n/)[0] || '';
78
- return [...primeira].slice(0, 40).join('');
100
+ const dados = JSON.parse(entrada);
101
+ const janela = dados.context_window;
102
+ if (janela && typeof janela.context_tokens === 'number' && !janela.current_usage) {
103
+ janela.current_usage = { input_tokens: janela.context_tokens, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 };
104
+ if (typeof janela.total_input_tokens !== 'number') janela.total_input_tokens = janela.context_tokens;
105
+ }
106
+ return JSON.stringify(dados);
79
107
  } catch {
80
- return '';
108
+ return entrada;
81
109
  }
82
110
  }
83
111
 
@@ -116,12 +144,12 @@ function repassarAoApp(entrada) {
116
144
  } catch {}
117
145
  }
118
146
 
119
- function desenhar(entrada) {
147
+ function desenhar(entrada, { layout = LAYOUT, env = process.env } = {}) {
120
148
  return new Promise(resolve => {
121
- if (!fs.existsSync(MOTOR)) return resolve();
149
+ if (!fs.existsSync(MOTOR) || !fs.existsSync(layout)) return resolve();
122
150
  let filho;
123
151
  try {
124
- filho = spawn(process.execPath, [MOTOR, '--config', LAYOUT], { stdio: ['pipe', 'inherit', 'ignore'], windowsHide: true });
152
+ filho = spawn(process.execPath, [MOTOR, '--config', layout], { stdio: ['pipe', 'inherit', 'ignore'], windowsHide: true, env });
125
153
  } catch {
126
154
  return resolve();
127
155
  }
@@ -138,6 +166,12 @@ async function principal() {
138
166
  if (modo === 'data') return process.stdout.write(dataCurta());
139
167
  const entrada = await lerEntrada();
140
168
  if (modo === 'topico') return process.stdout.write(mostrarTitulo(entrada));
169
+ if (modo === 'grok') {
170
+ // O Grok passa a largura da linha em COLUMNS; o ccstatusline usa para alinhar a direita.
171
+ const env = { ...process.env };
172
+ if (/^\d+$/.test(env.COLUMNS || '') && !env.CCSTATUSLINE_WIDTH) env.CCSTATUSLINE_WIDTH = env.COLUMNS;
173
+ return desenhar(jsonDoGrok(entrada), { layout: LAYOUT_GROK, env });
174
+ }
141
175
  repassarAoApp(entrada);
142
176
  await desenhar(entrada);
143
177
  }
@@ -146,4 +180,4 @@ if (require.main === module) {
146
180
  principal().catch(() => {}).finally(() => { process.exitCode = 0; });
147
181
  }
148
182
 
149
- module.exports = { dataCurta, sessaoDoJson, mostrarTitulo, gravarTitulo, scriptDoApp };
183
+ module.exports = { dataCurta, sessaoDoJson, mostrarTitulo, gravarTitulo, scriptDoApp, jsonDoGrok };
@@ -0,0 +1,98 @@
1
+ {
2
+ "version": 4,
3
+ "lines": [
4
+ [
5
+ {
6
+ "id": "l1-model",
7
+ "type": "model",
8
+ "color": "brightCyan",
9
+ "bold": true,
10
+ "rawValue": true
11
+ },
12
+ {
13
+ "id": "l1-effort",
14
+ "type": "thinking-effort",
15
+ "color": "magenta"
16
+ },
17
+ {
18
+ "id": "l1-skill",
19
+ "type": "skills",
20
+ "color": "brightBlack",
21
+ "metadata": {
22
+ "hide": "empty"
23
+ }
24
+ },
25
+ {
26
+ "id": "l1-topic",
27
+ "type": "custom-command",
28
+ "color": "yellow",
29
+ "commandPath": "{{BARRA}} topico",
30
+ "timeout": 1000
31
+ },
32
+ {
33
+ "id": "l1-flex",
34
+ "type": "flex-separator"
35
+ },
36
+ {
37
+ "id": "l1-clock-ico",
38
+ "type": "custom-text",
39
+ "customText": "⏱",
40
+ "color": "brightYellow",
41
+ "merge": "no-padding"
42
+ },
43
+ {
44
+ "id": "l1-clock",
45
+ "type": "session-clock",
46
+ "color": "brightYellow",
47
+ "rawValue": true
48
+ }
49
+ ],
50
+ [
51
+ {
52
+ "id": "l2-cwd",
53
+ "type": "current-working-dir",
54
+ "color": "blue",
55
+ "metadata": {
56
+ "abbreviateHome": "true"
57
+ }
58
+ },
59
+ {
60
+ "id": "l2-branch",
61
+ "type": "git-branch",
62
+ "color": "green",
63
+ "metadata": {
64
+ "hide": "no-git"
65
+ }
66
+ },
67
+ {
68
+ "id": "l2-changes",
69
+ "type": "git-changes",
70
+ "color": "yellow",
71
+ "metadata": {
72
+ "hide": "zero"
73
+ }
74
+ }
75
+ ],
76
+ [
77
+ {
78
+ "id": "l3-ctx",
79
+ "type": "context-bar",
80
+ "color": "brightMagenta"
81
+ },
82
+ {
83
+ "id": "l3-flex",
84
+ "type": "flex-separator"
85
+ },
86
+ {
87
+ "id": "l4-date",
88
+ "type": "custom-command",
89
+ "color": "brightBlack",
90
+ "commandPath": "{{BARRA}} data",
91
+ "timeout": 1000
92
+ }
93
+ ]
94
+ ],
95
+ "flexMode": "full",
96
+ "colorLevel": 3,
97
+ "defaultPadding": " "
98
+ }
@@ -1,7 +1,11 @@
1
1
  'use strict';
2
- // Barra de status do Claude Code do aluno (2.6.8): a mesma barra do Euriler (modelo, esforco,
3
- // titulo da conversa, pasta, branch, contexto, cache, uso de 5 h e da semana, data), em qualquer
4
- // terminal do Mac ou do Windows e dentro do app Auroq OS.
2
+ // Barra de status do aluno (2.6.8; Codex e Grok desde 2.6.10): a mesma barra do Euriler (modelo,
3
+ // esforco, titulo da conversa, pasta, branch, contexto, cache, uso de 5 h e da semana, data), em
4
+ // qualquer terminal do Mac ou do Windows e dentro do app Auroq OS.
5
+ // - Claude Code: `statusLine` no ~/.claude/settings.json roda barra.cjs (ccstatusline).
6
+ // - Grok: `[ui.status_line]` no ~/.grok/config.toml roda `barra.cjs grok` (so se o Grok existe).
7
+ // - Codex: nao aceita comando na barra; liga a barra nativa com os mesmos itens no
8
+ // `[tui] status_line` do ~/.codex/config.toml (o app Auroq OS espelha esse arquivo nas contas).
5
9
  //
6
10
  // O que faz, sempre no HOME do aluno (nunca na pasta do negocio):
7
11
  // - copia o ccstatusline travado (MIT, sem dependencias) para ~/.auroq/barra/ccstatusline;
@@ -19,6 +23,9 @@ const VERSAO_CCSTATUSLINE = '2.2.30';
19
23
  const AQUI = __dirname;
20
24
 
21
25
  const ehNossa = comando => /[\\/]\.auroq[\\/]barra[\\/]barra\.cjs/.test(comando);
26
+ // Mesmos itens da barra do Euriler no Codex (~/.codex/config.toml dele, 24/09/2026).
27
+ const CODEX_ITENS = ['model-with-reasoning', 'thread-title', 'current-dir', 'git-branch', 'branch-changes', 'context-used', 'five-hour-limit', 'weekly-limit'];
28
+ const CODEX_LINHA = `status_line = [${CODEX_ITENS.map(i => `"${i}"`).join(', ')}]`;
22
29
  const ehDoApp = comando => /agent-hooks[\\/]+claude-statusline/.test(comando);
23
30
  const sha = texto => crypto.createHash('sha256').update(texto).digest('hex');
24
31
 
@@ -67,39 +74,106 @@ function copiarMotor(origem, destino) {
67
74
  return true;
68
75
  }
69
76
 
70
- function instalarBarra({
71
- home = os.homedir(),
72
- claudeDir = process.env.CLAUDE_CONFIG_DIR || path.join(home, '.claude'),
73
- origem = null,
74
- } = {}) {
75
- if (fs.existsSync(path.join(home, '.auroq', 'sem-barra'))) return { estado: 'desligada' };
77
+ // ─── TOML sem biblioteca: so acha a secao pelo cabecalho e mexe nas linhas dela. O resto do
78
+ // arquivo (comentarios, ordem, outras secoes) volta byte a byte como estava.
79
+ const CABECALHO = /^\s*\[\s*([^\[\]]+?)\s*\]\s*(#.*)?$/;
80
+ const CABECALHO_QUALQUER = /^\s*\[/;
76
81
 
77
- const pasta = path.join(home, '.auroq', 'barra');
78
- const script = path.join(pasta, 'barra.cjs').split(path.sep).join('/');
79
- // Aspas servem ao cmd.exe e aos shells POSIX; caminho com aspas, $, crase ou quebra de linha fica de fora.
80
- if (/["\r\n`$%]/.test(script)) return { estado: 'caminho-nao-suportado', detalhe: script };
81
- const comando = `node "${script}"`;
82
+ function lerToml(arquivo) {
83
+ const texto = fs.existsSync(arquivo) ? fs.readFileSync(arquivo, 'utf8') : '';
84
+ const quebra = texto.includes('\r\n') ? '\r\n' : '\n';
85
+ return { texto, quebra, linhas: texto === '' ? [] : texto.split(/\r?\n/) };
86
+ }
82
87
 
83
- fs.mkdirSync(pasta, { recursive: true });
84
- const motorNovo = copiarMotor(origem || origemPadrao(), path.join(pasta, 'ccstatusline'));
85
- gravarAtomico(path.join(pasta, 'barra.cjs'), fs.readFileSync(path.join(AQUI, 'barra.cjs')));
88
+ function secao(linhas, nome) {
89
+ const inicio = linhas.findIndex(l => (l.match(CABECALHO) || [])[1] === nome);
90
+ if (inicio === -1) return null;
91
+ let fim = linhas.length;
92
+ for (let i = inicio + 1; i < linhas.length; i++) {
93
+ if (CABECALHO_QUALQUER.test(linhas[i])) { fim = i; break; }
94
+ }
95
+ return { inicio, fim };
96
+ }
86
97
 
87
- // Layout: o nosso, a menos que o aluno tenha mexido no que gravamos da ultima vez.
88
- const registroPath = path.join(pasta, 'instalacao.json');
89
- const registro = lerJson(registroPath) || {};
90
- const layoutPath = path.join(pasta, 'ccstatusline.json');
91
- const layout = fs.readFileSync(path.join(AQUI, 'ccstatusline.json'), 'utf8').split('{{BARRA}}').join(comando.replace(/"/g, '\\"'));
92
- const layoutAtual = fs.existsSync(layoutPath) ? fs.readFileSync(layoutPath, 'utf8') : null;
93
- const layoutDoAluno = layoutAtual !== null && registro.layoutSha && sha(layoutAtual) !== registro.layoutSha && sha(layoutAtual) !== sha(layout);
94
- if (!layoutDoAluno && layoutAtual !== layout) gravarAtomico(layoutPath, layout);
98
+ function chaveNaSecao(linhas, s, chave) {
99
+ const re = new RegExp(`^\\s*${chave}\\s*=`);
100
+ for (let i = s.inicio + 1; i < s.fim; i++) if (re.test(linhas[i])) return i;
101
+ return -1;
102
+ }
103
+
104
+ function gravarToml(arquivo, { linhas, quebra }) {
105
+ fs.mkdirSync(path.dirname(arquivo), { recursive: true });
106
+ let texto = linhas.join(quebra);
107
+ if (!texto.endsWith(quebra)) texto += quebra;
108
+ gravarAtomico(arquivo, texto);
109
+ }
95
110
 
111
+ function acrescentarSecao(toml, novas) {
112
+ const linhas = [...toml.linhas];
113
+ while (linhas.length && linhas[linhas.length - 1].trim() === '') linhas.pop();
114
+ if (linhas.length) linhas.push('');
115
+ return { ...toml, linhas: [...linhas, ...novas] };
116
+ }
117
+
118
+ // Codex: barra nativa com os mesmos itens. So ocupa `[tui] status_line` quando ninguem pos.
119
+ function ligarCodex({ codexDir, registro }) {
120
+ const arquivo = path.join(codexDir, 'config.toml');
121
+ const toml = lerToml(arquivo);
122
+ if (/^\s*tui\s*\.\s*status_line\s*=/m.test(toml.texto)) return 'barra-do-aluno';
123
+ const tui = secao(toml.linhas, 'tui');
124
+ if (tui) {
125
+ const i = chaveNaSecao(toml.linhas, tui, 'status_line');
126
+ if (i !== -1) return toml.linhas[i].replace(/\s+/g, '') === CODEX_LINHA.replace(/\s+/g, '') ? 'em-dia' : 'barra-do-aluno';
127
+ if (registro.codexLigadaEm) return 'tirada-pelo-aluno';
128
+ const novas = [CODEX_LINHA];
129
+ if (chaveNaSecao(toml.linhas, tui, 'status_line_use_colors') === -1) novas.push('status_line_use_colors = true');
130
+ const linhas = [...toml.linhas];
131
+ linhas.splice(tui.inicio + 1, 0, ...novas);
132
+ gravarToml(arquivo, { ...toml, linhas });
133
+ return 'ligada';
134
+ }
135
+ if (registro.codexLigadaEm) return 'tirada-pelo-aluno';
136
+ gravarToml(arquivo, acrescentarSecao(toml, ['[tui]', CODEX_LINHA, 'status_line_use_colors = true']));
137
+ return 'ligada';
138
+ }
139
+
140
+ // Grok: `[ui.status_line]` roda a mesma barra. So mexe se o Grok existe neste computador.
141
+ function ligarGrok({ grokDir, registro, script }) {
142
+ if (!fs.existsSync(grokDir)) return 'sem-grok';
143
+ if (script.includes("'")) return 'caminho-nao-suportado';
144
+ const arquivo = path.join(grokDir, 'config.toml');
145
+ const toml = lerToml(arquivo);
146
+ // String literal do TOML (aspas simples): as aspas duplas do caminho passam sem escape.
147
+ const desejadas = ['[ui.status_line]', 'type = "command"', `command = 'node "${script}" grok'`, 'refresh_interval = 10'];
148
+ const atual = secao(toml.linhas, 'ui.status_line');
149
+ if (atual) {
150
+ const corpo = toml.linhas.slice(atual.inicio, atual.fim);
151
+ if (!corpo.some(l => /^\s*command\s*=/.test(l) && ehNossa(l))) return 'barra-do-aluno';
152
+ const sobra = corpo.slice(1).filter(l => l.trim() !== '' && !/^\s*(type|command|refresh_interval)\s*=/.test(l));
153
+ const novas = [...desejadas, ...sobra];
154
+ const limpas = corpo.filter(l => l.trim() !== '');
155
+ if (JSON.stringify(limpas) === JSON.stringify(novas)) return 'em-dia';
156
+ const linhas = [...toml.linhas];
157
+ const vazias = corpo.length - limpas.length;
158
+ linhas.splice(atual.inicio, corpo.length, ...novas, ...Array(vazias).fill(''));
159
+ gravarToml(arquivo, { ...toml, linhas });
160
+ return 'atualizada';
161
+ }
162
+ const ui = secao(toml.linhas, 'ui');
163
+ if (ui && chaveNaSecao(toml.linhas, ui, 'status_line') !== -1) return 'barra-do-aluno';
164
+ if (/^\s*ui\s*\.\s*status_line\s*[.=]/m.test(toml.texto)) return 'barra-do-aluno';
165
+ if (registro.grokLigadaEm) return 'tirada-pelo-aluno';
166
+ gravarToml(arquivo, acrescentarSecao(toml, desejadas));
167
+ return 'ligada';
168
+ }
169
+
170
+ // Claude Code: so a chave `statusLine` do ~/.claude/settings.json.
171
+ function ligarClaude({ claudeDir, registro, comando }) {
96
172
  const settingsPath = path.join(claudeDir, 'settings.json');
97
173
  let settings = {};
98
174
  if (fs.existsSync(settingsPath)) {
99
175
  settings = lerJson(settingsPath);
100
- if (!settings || typeof settings !== 'object' || Array.isArray(settings)) {
101
- return { estado: 'settings-ilegivel', detalhe: settingsPath };
102
- }
176
+ if (!settings || typeof settings !== 'object' || Array.isArray(settings)) return 'settings-ilegivel';
103
177
  }
104
178
  const atual = settings.statusLine && typeof settings.statusLine.command === 'string' ? settings.statusLine.command : '';
105
179
  let estado;
@@ -114,14 +188,66 @@ function instalarBarra({
114
188
  gravarAtomico(settingsPath, `${JSON.stringify({ ...settings, statusLine: { ...(estado === 'em-dia' ? settings.statusLine : {}), ...desejada } }, null, 2)}\n`);
115
189
  if (estado === 'em-dia') estado = 'atualizada';
116
190
  }
191
+ return estado;
192
+ }
193
+
194
+ function seguro(fn) {
195
+ try {
196
+ return fn();
197
+ } catch (e) {
198
+ return `erro: ${String(e && e.message || e).split(/\r?\n/)[0].slice(0, 160)}`;
199
+ }
200
+ }
201
+
202
+ function instalarBarra({
203
+ home = os.homedir(),
204
+ claudeDir = process.env.CLAUDE_CONFIG_DIR || path.join(home, '.claude'),
205
+ codexDir = process.env.CODEX_HOME || path.join(home, '.codex'),
206
+ grokDir = process.env.GROK_HOME || path.join(home, '.grok'),
207
+ origem = null,
208
+ } = {}) {
209
+ if (fs.existsSync(path.join(home, '.auroq', 'sem-barra'))) return { estado: 'desligada' };
210
+
211
+ const pasta = path.join(home, '.auroq', 'barra');
212
+ const script = path.join(pasta, 'barra.cjs').split(path.sep).join('/');
213
+ // Aspas servem ao cmd.exe e aos shells POSIX; caminho com aspas, $, crase ou quebra de linha fica de fora.
214
+ if (/["\r\n`$%]/.test(script)) return { estado: 'caminho-nao-suportado', detalhe: script };
215
+ const comando = `node "${script}"`;
216
+
217
+ fs.mkdirSync(pasta, { recursive: true });
218
+ const motorNovo = copiarMotor(origem || origemPadrao(), path.join(pasta, 'ccstatusline'));
219
+ gravarAtomico(path.join(pasta, 'barra.cjs'), fs.readFileSync(path.join(AQUI, 'barra.cjs')));
220
+
221
+ // Layout: o nosso, a menos que o aluno tenha mexido no que gravamos da ultima vez.
222
+ const registroPath = path.join(pasta, 'instalacao.json');
223
+ const registro = lerJson(registroPath) || {};
224
+ const gravarLayout = (nome, shaAnterior) => {
225
+ const destino = path.join(pasta, nome);
226
+ const novo = fs.readFileSync(path.join(AQUI, nome), 'utf8').split('{{BARRA}}').join(comando.replace(/"/g, '\\"'));
227
+ const atual = fs.existsSync(destino) ? fs.readFileSync(destino, 'utf8') : null;
228
+ const doAluno = Boolean(atual !== null && shaAnterior && sha(atual) !== shaAnterior && sha(atual) !== sha(novo));
229
+ if (!doAluno && atual !== novo) gravarAtomico(destino, novo);
230
+ return { doAluno, sha: doAluno ? shaAnterior : sha(novo) };
231
+ };
232
+ const layoutClaude = gravarLayout('ccstatusline.json', registro.layoutSha);
233
+ const layoutGrok = gravarLayout('ccstatusline-grok.json', registro.layoutGrokSha);
234
+ const layoutDoAluno = layoutClaude.doAluno;
235
+
236
+ const estado = ligarClaude({ claudeDir, registro, comando });
117
237
 
238
+ const codex = seguro(() => ligarCodex({ codexDir, registro }));
239
+ const grok = seguro(() => ligarGrok({ grokDir, registro, script }));
240
+ const agora = new Date().toISOString();
118
241
  const novoRegistro = {
119
242
  versaoCcstatusline: VERSAO_CCSTATUSLINE,
120
- layoutSha: layoutDoAluno ? registro.layoutSha : sha(layout),
121
- ligadaEm: registro.ligadaEm || (estado === 'ligada' ? new Date().toISOString() : undefined),
243
+ layoutSha: layoutClaude.sha,
244
+ layoutGrokSha: layoutGrok.sha,
245
+ ligadaEm: registro.ligadaEm || (estado === 'ligada' ? agora : undefined),
246
+ codexLigadaEm: registro.codexLigadaEm || (codex === 'ligada' ? agora : undefined),
247
+ grokLigadaEm: registro.grokLigadaEm || (grok === 'ligada' ? agora : undefined),
122
248
  };
123
249
  gravarAtomico(registroPath, `${JSON.stringify(novoRegistro, null, 2)}\n`);
124
- return { estado, comando, motorNovo, layoutDoAluno: Boolean(layoutDoAluno) };
250
+ return { estado, comando, motorNovo, layoutDoAluno, codex, grok };
125
251
  }
126
252
 
127
- module.exports = { instalarBarra, ehNossa, ehDoApp, VERSAO_CCSTATUSLINE };
253
+ module.exports = { instalarBarra, ehNossa, ehDoApp, VERSAO_CCSTATUSLINE, CODEX_ITENS };