auroq-os 2.6.9 → 2.6.10

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.
@@ -1,7 +1,7 @@
1
1
  project:
2
2
  name: "Auroq OS"
3
3
  type: greenfield
4
- version: 2.6.9
4
+ version: 2.6.10
5
5
  installedAt: '2026-03-16'
6
6
 
7
7
  ide:
@@ -1,9 +1,9 @@
1
1
  # Título da conversa na barra de status
2
2
 
3
- A barra de status do Claude Code (embaixo da caixa de mensagem) mostra, em amarelo, o título da conversa. Quem escreve esse título é você.
3
+ A barra de status (embaixo da caixa de mensagem) mostra, em amarelo, o título da conversa. No Claude Code e no Grok, quem escreve esse título é você.
4
4
 
5
5
  - Na primeira resposta de cada conversa, grave em silêncio, uma única vez, um título de 3 a 5 palavras em português sobre o tema central:
6
6
  `node ~/.auroq/barra/barra.cjs titulo "Título em português"`
7
7
  - Não avise o expert, não comente o comando e não troque o título por causa de tarefa lateral (o primeiro fica).
8
8
  - Se o comando falhar ou o arquivo não existir, ignore e siga a conversa normalmente.
9
- - Vale só no Claude Code. No Codex, no Grok e em outras IAs, não rode.
9
+ - Vale no Claude Code e no Grok. No Codex não rode: ele mostra o título da conversa sozinho.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## 2.6.10 — 2026-10-16
2
+
3
+ ### A barra de status também no Codex e no Grok
4
+
5
+ - Pedido do Euriler: a barra tem que aparecer também no terminal com Codex ou Grok, não só no Claude Code.
6
+ - **Grok** (aceita comando na barra): `instalar`/`manutencao`/`clone` acrescentam `[ui.status_line]` no `~/.grok/config.toml` rodando a mesma barra (`node ~/.auroq/barra/barra.cjs grok`, `refresh_interval = 10`). Só quando o Grok existe no computador (`~/.grok` ou `GROK_HOME`); o resto do arquivo volta byte a byte. Layout próprio de 3 linhas (`ccstatusline-grok.json`): modelo, esforço, título e tempo; pasta e branch; contexto e data. Sem "Claude: ok", cache e uso de 5 h/semana, que o Grok não manda. A barra traduz o contexto do Grok (`context_tokens`) para o formato que o ccstatusline lê e usa a largura que o Grok passa em `COLUMNS`.
7
+ - **Codex** (não aceita comando, só itens prontos): liga a barra nativa com os mesmos itens da do Euriler no `[tui] status_line` do `~/.codex/config.toml` (`CODEX_HOME` respeitado): modelo e raciocínio, título da conversa, pasta, branch, alterações, contexto, uso de 5 h e da semana, com cores. O app Auroq OS espelha esse arquivo nas contas do Codex dele.
8
+ - Mesmas regras da 2.6.8 nos dois: nunca troca barra que o aluno já tinha (seção, chave dentro de `[ui]`/`[tui]` ou chave pontilhada), não põe de volta se o aluno tirou, `~/.auroq/sem-barra` desliga tudo. Falha em um não afeta os outros.
9
+ - Título: a regra `titulo-da-barra.md` passa a valer no Grok também (ele lê `.claude/rules` do projeto); a barra grava pelo `GROK_SESSION_ID` e, sem título, mostra o nome da sessão que o Grok dá. Grok aberto de dentro do Claude Code (ou o contrário) herda os dois ids: o título vai para os dois. O Codex mostra o título dele sozinho.
10
+ - Provado com os programas de verdade num terminal: Codex 0.158 mostra a barra com esses itens; Grok 1.0.41 mostra as 3 linhas com o título que ele mesmo gravou (configuração do Grok do Euriler ligada só durante o teste e devolvida idêntica). Testes novos em `tests/barra.test.mjs` (21 no total) e prova do Grok no Windows em `barra-windows.yml`.
11
+ - Mantem `arcane-mcp@0.7.0`. Instaladores do repositorio preparados para 2.6.10.
12
+
1
13
  ## 2.6.9 — 2026-10-16
2
14
 
3
15
  ### Historico das sessoes: texto no modelo "atualizamos os termos"
package/README.md CHANGED
@@ -254,13 +254,13 @@ agents/ → Seu exercito (companion, workers, minds, squads)
254
254
 
255
255
  ## Observabilidade e integridade do motor
256
256
 
257
- Apos publicacao, `npx -y auroq-os@2.6.9 doctor` mostra a fila local segura e os comandos pinados de
258
- manutencao/diagnostico. `npx -y auroq-os@2.6.9 manutencao` preserva o caminho existente
257
+ Apos publicacao, `npx -y auroq-os@2.6.10 doctor` mostra a fila local segura e os comandos pinados de
258
+ manutencao/diagnostico. `npx -y auroq-os@2.6.10 manutencao` preserva o caminho existente
259
259
  de consentimento legado, studentFiles, customizacoes e dados do negocio. Nao ha
260
260
  migracao forcada. O registro MCP aponta para `arcane-mcp@0.7.0` — o pin mora no campo
261
261
  `arcaneMcpPin` do package.json (fonte unica; o servidor le o mesmo campo no npm).
262
262
  `npm run release:verify` recusa publicar com um pin que o npm nao resolva.
263
- Os instaladores do repositorio ficam preparados para 2.6.9; as copias publicas so recebem
263
+ Os instaladores do repositorio ficam preparados para 2.6.10; as copias publicas so recebem
264
264
  a versao apos os gates nativos e a verificacao do tarball publicado.
265
265
  O pacote inclui `lib/runtime-integrity.json`: inventario SHA-256 gerado depois da montagem da casca, verificavel com `npm run integrity:verify` dentro do pacote extraido. Ele comprova consistencia do conteudo; nao e uma assinatura de autoria.
266
266
 
package/bin/auroq-os.js CHANGED
@@ -1549,14 +1549,14 @@ function opcoesDeLoginDeTeste() {
1549
1549
  }
1550
1550
 
1551
1551
  // ─── INSTALACAO ──────────────────────────────────────────
1552
- // Barra de status do Claude Code (2.6.8): a mesma do Euriler, no HOME do aluno. Nunca derruba a
1552
+ // 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
1553
  // instalacao: sem a barra o sistema funciona igual, entao qualquer falha vira aviso.
1554
1554
  function ligarBarraDeStatus() {
1555
- step('Ligando a barra de status do Claude Code...');
1555
+ step('Ligando a barra de status (Claude Code, Codex e Grok)...');
1556
1556
  try {
1557
1557
  const r = require('../lib/barra').instalarBarra();
1558
1558
  const textos = {
1559
- 'ligada': 'Barra de status ligada (vale em todo terminal do Claude Code)',
1559
+ 'ligada': 'Barra de status ligada no Claude Code (vale em todo terminal)',
1560
1560
  'atualizada': 'Barra de status em dia',
1561
1561
  'em-dia': 'Barra de status em dia',
1562
1562
  'barra-do-aluno': 'Voce ja tem uma barra de status propria no Claude Code; ficou como estava',
@@ -1565,6 +1565,13 @@ function ligarBarraDeStatus() {
1565
1565
  };
1566
1566
  if (textos[r.estado]) success(textos[r.estado]);
1567
1567
  else warn(`Barra de status nao ligada (${r.estado}${r.detalhe ? `: ${r.detalhe}` : ''})`);
1568
+ // 2.6.10: a mesma barra no Grok (se instalado) e a barra nativa do Codex com os mesmos itens.
1569
+ 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' };
1570
+ for (const [nome, estado] of [['Codex', r.codex], ['Grok', r.grok]]) {
1571
+ if (!estado || estado === 'sem-grok') continue;
1572
+ if (outras[estado]) success(`Barra de status no ${nome}: ${outras[estado]}`);
1573
+ else warn(`Barra de status no ${nome} nao ligada (${estado})`);
1574
+ }
1568
1575
  return r;
1569
1576
  } catch (e) {
1570
1577
  warn(`Barra de status nao ligada (${String(e && e.message || e).split(/\r?\n/)[0].slice(0, 200)}). O resto funciona normal.`);
@@ -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.6.10 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.6.10 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.6.10 "$@"
223
223
  else
224
- npx -y auroq-os@2.6.9 "$@" < /dev/tty
224
+ npx -y auroq-os@2.6.10 "$@" < /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 };
@@ -2,7 +2,7 @@
2
2
  "schema": 1,
3
3
  "algorithm": "sha256",
4
4
  "scope": "packaged-runtime-and-cascas",
5
- "version": "2.6.9",
5
+ "version": "2.6.10",
6
6
  "files": [
7
7
  {
8
8
  "path": ".auroq-core/constitution.md",
@@ -10,7 +10,7 @@
10
10
  },
11
11
  {
12
12
  "path": ".auroq-core/core-config.yaml",
13
- "sha256": "619e43f8d9e495c48269650e1e96b52f70d455d277fd3251d2c5f17a81ef7a69"
13
+ "sha256": "3a052bb9ae63bb567962ae6b06d3d65368174c1aa305d5c110c2473ac7db610f"
14
14
  },
15
15
  {
16
16
  "path": ".auroq-core/core/synapse/context/context-builder.js",
@@ -210,7 +210,7 @@
210
210
  },
211
211
  {
212
212
  "path": "bin/auroq-os.js",
213
- "sha256": "ed26b60a9309f55f5175cc8469c71e28f3bd58bcb60da05fae8dccbe92ac5892"
213
+ "sha256": "65da747dd205bdd0d5aef71167a174936d516f201b63276fd27caa6af55f3628"
214
214
  },
215
215
  {
216
216
  "path": "lib/ahi/client.mjs",
@@ -230,7 +230,11 @@
230
230
  },
231
231
  {
232
232
  "path": "lib/barra/barra.cjs",
233
- "sha256": "30fa51fd3561b8da7e17dd9b35fd55739d0a33d358dbbd99583f6b0eec1b576e"
233
+ "sha256": "fcffa577918fba0c7dff103c6b55f424a071a84a74d5117f486ddd70e984113e"
234
+ },
235
+ {
236
+ "path": "lib/barra/ccstatusline-grok.json",
237
+ "sha256": "1a1449a7b06d2ec7e61c6d6403937b6bc29c487418dec07fa3669991ee236d76"
234
238
  },
235
239
  {
236
240
  "path": "lib/barra/ccstatusline.json",
@@ -238,7 +242,7 @@
238
242
  },
239
243
  {
240
244
  "path": "lib/barra/index.js",
241
- "sha256": "a72edfd62af60bfb5b314300a618cbb3fc8e7e30b9092ecb83ff366a776fee90"
245
+ "sha256": "d5219d2a9f28f44633af56ec63e79a3f5f1ce2b4440f2daa8226c9f2868b5382"
242
246
  },
243
247
  {
244
248
  "path": "lib/codex-windows.cjs",
@@ -274,7 +278,7 @@
274
278
  },
275
279
  {
276
280
  "path": "package.json",
277
- "sha256": "ffc469c06ce3d8b5ac8c1353e3cbf412169dd420b8de3341ee4e648effc96768"
281
+ "sha256": "b953fc664535e2336ccc89db3a07d391ac3a31c2ea8452996b9a83adac4612a4"
278
282
  },
279
283
  {
280
284
  "path": "scripts/build-pack.mjs",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auroq-os",
3
- "version": "2.6.9",
3
+ "version": "2.6.10",
4
4
  "arcaneMcpPin": "0.7.0",
5
5
  "description": "Auroq OS — Sistema Operacional de IA para Experts",
6
6
  "bin": {