auroq-os 2.6.8 → 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.8
4
+ version: 2.6.10
5
5
  installedAt: '2026-03-16'
6
6
 
7
7
  ide:
package/.claude/CLAUDE.md CHANGED
@@ -4,6 +4,6 @@ Sistema operacional de IA para o negócio. Você manda e julga; a IA executa.
4
4
 
5
5
  Ative um agente pelo comando (`/companion`, `$companion`, `@companion`). Todo conteúdo Arcane (Companion interno, Consultor, forjas, Pack) vem pelo MCP `arcane` — nunca procurar em `agents/`.
6
6
 
7
- Sua memória e seus documentos ficam na sua máquina e no seu GitHub privado (senhas e `business/vault/` ficam fora). O histórico das sessões com os squads só vai para a Arka se você autorizar na plataforma, e você pode retirar a autorização quando quiser. Além disso, a Arka registra qual conteúdo licenciado foi entregue, quando e pra qual conta — igual a uma nota de entrega.
7
+ Sua memória e seus documentos ficam na sua máquina e no seu GitHub privado (senhas e `business/vault/` ficam fora). O histórico das sessões com os squads está previsto nos Termos de Uso: vem ligado para quem aceita os termos na plataforma (quem já tinha dito "não" continua sem gravar) e você desliga quando quiser em Minha Conta, "Guardar minhas conversas". Se a pasta do seu negócio tem dados de clientes, desligue. Além disso, a Arka registra qual conteúdo licenciado foi entregue, quando e pra qual conta — igual a uma nota de entrega.
8
8
 
9
9
  Rules em `.claude/rules/`. Constitution em `.auroq-core/constitution.md`.
@@ -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,26 @@
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
+
13
+ ## 2.6.9 — 2026-10-16
14
+
15
+ ### Historico das sessoes: texto no modelo "atualizamos os termos"
16
+
17
+ - O historico passa para o modelo "atualizamos os termos": a gravacao das conversas das sessoes com os squads da Arcane esta prevista nos Termos de Uso e na Politica de Privacidade da plataforma. Ela vem ligada para quem aceita os termos no aviso da plataforma e o aluno desliga quando quiser em Minha Conta (arcane.arka.education/conta), em "Guardar minhas conversas", ou pedindo ao proprio agente para nao guardar. Religar e so pela plataforma.
18
+ - Quem ja tinha dito "nao" continua sem gravar: aceitar os termos nao desfaz essa recusa. Quem tinha dito "sim" continua com o "sim".
19
+ - Os textos do pacote acompanham o modelo novo: `casca/.claude/CLAUDE.md` (o que o aluno le dentro do projeto), a secao "Historico de uso (AHI)" do README e a linha de ajuda e a mensagem do comando `auroq-os historico pausar` (agora diz como desligar em Minha Conta ou pelo agente, sem precisar desligar o MCP). Saiu a frase "so grava quem marcou sim / caixa desmarcada = nao". Entrou o aviso: se a pasta do negocio tem dados de clientes, desligue.
20
+ - Alcance do texto novo de `casca/.claude/CLAUDE.md`: ele vale para instalacoes novas. A manutencao nunca sobrescreve o `.claude/CLAUDE.md` que o aluno ja tem, entao quem ja instalou continua com o paragrafo antigo no arquivo dele (esse comportamento nao mudou). O caminho dessas pessoas e o aviso de termos na plataforma e o que o Arcano explica a elas.
21
+ - Nenhuma mudanca no coletor, nos hooks nem nos dados ja guardados: so textos, versao e teste (`tests/vercelignore-ops.test.mjs` agora fixa o texto novo da casca).
22
+ - Mantem `arcane-mcp@0.7.0`. Instaladores do repositorio preparados para 2.6.9.
23
+
1
24
  ## 2.6.8 — 2026-10-16
2
25
 
3
26
  ### Barra de status do Claude Code igual à do Euriler
package/README.md CHANGED
@@ -132,12 +132,17 @@ proprio pacote.
132
132
  ## Historico de uso (AHI)
133
133
 
134
134
  `auroq-os historico status` mostra captura comprovada, fila pendente, ultimo ACK,
135
- exclusoes e problemas. So ha registro para quem marcou "sim" na pergunta unica da
136
- plataforma Arcane (caixa desmarcada = nao), e so quando a sessao usa o MCP Arcane
137
- com conta autenticada e licenca validada pelo servidor. Para nao guardar, basta
138
- deixar a caixa desmarcada; quem ja marcou pode tirar a autorizacao pelo proprio
139
- agente (ele registra o "nao") ou pelo e-mail euriler@arka.education. Nao e preciso
140
- desligar o MCP nem os hooks. Instalar ou apenas abrir o projeto nao prova captura.
135
+ exclusoes e problemas. O registro das sessoes com os squads da Arcane esta previsto
136
+ nos Termos de Uso e na Politica de Privacidade da plataforma: vem ligado para quem
137
+ aceita os termos (aviso na plataforma) e so acontece quando a sessao usa o MCP Arcane
138
+ com conta autenticada e licenca validada pelo servidor. Quem ja tinha dito "nao"
139
+ continua sem gravar: aceitar os termos nao desfaz essa recusa. Para desligar, entre
140
+ em Minha Conta (arcane.arka.education/conta) e desligue "Guardar minhas conversas",
141
+ ou peca ao proprio agente (ele registra o "nao"). Religar so pela plataforma. Se a
142
+ pasta do seu negocio tem dados de clientes, desligue. Para pedir a exclusao do que ja
143
+ foi guardado, use os comandos de exclusao abaixo ou o e-mail euriler@arka.education.
144
+ Nao e preciso desligar o MCP nem os hooks. Instalar ou apenas abrir o projeto nao
145
+ prova captura.
141
146
 
142
147
  O adaptador `ahi-hooks-v2` registra mensagens e entradas/saidas de ferramentas
143
148
  que os hooks visiveis do Claude Code/Codex disponibilizam. A cobertura e parcial:
@@ -188,7 +193,10 @@ as paginas do mesmo recorte e grava um novo arquivo privado em
188
193
  `~/.arcane/ahi-exports/`; uma falha nunca e apresentada como exportacao completa.
189
194
  Direitos de consulta/exclusao continuam disponiveis com autenticacao mesmo sem
190
195
  licenca de captura. Para apagar o historico da conta inteira, o escopo explicito
191
- exigido e `excluir --account --confirm-delete`.
196
+ exigido e `excluir --account --confirm-delete`. Apagar o historico da conta inteira
197
+ (`excluir --account`) tambem encerra a gravacao nessa conta (marca de exclusao
198
+ permanente); para voltar a gravar, escreva para euriler@arka.education. Excluir uma
199
+ sessao nao encerra a gravacao.
192
200
 
193
201
  A atualizacao do coletor exige a manutencao no projeto (peca ao Ops "atualiza o sistema")
194
202
  e reabrir o host. O aviso de nova versao ao iniciar o Claude Code nao instala essa correcao.
@@ -246,13 +254,13 @@ agents/ → Seu exercito (companion, workers, minds, squads)
246
254
 
247
255
  ## Observabilidade e integridade do motor
248
256
 
249
- Apos publicacao, `npx -y auroq-os@2.6.8 doctor` mostra a fila local segura e os comandos pinados de
250
- manutencao/diagnostico. `npx -y auroq-os@2.6.8 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
251
259
  de consentimento legado, studentFiles, customizacoes e dados do negocio. Nao ha
252
260
  migracao forcada. O registro MCP aponta para `arcane-mcp@0.7.0` — o pin mora no campo
253
261
  `arcaneMcpPin` do package.json (fonte unica; o servidor le o mesmo campo no npm).
254
262
  `npm run release:verify` recusa publicar com um pin que o npm nao resolva.
255
- Os instaladores do repositorio ficam preparados para 2.6.8; as copias publicas so recebem
263
+ Os instaladores do repositorio ficam preparados para 2.6.10; as copias publicas so recebem
256
264
  a versao apos os gates nativos e a verificacao do tarball publicado.
257
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.
258
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.`);
@@ -2481,7 +2488,7 @@ if (['init', 'install', 'update'].includes(command)) {
2481
2488
  log(` ${CYAN}doctor${RESET} Diagnostico: arquivos, Node, MCP arcane, login e servidor`);
2482
2489
  log(` ${CYAN}register-mcp${RESET} Reescrever o registro do conteudo ao vivo (MCP arcane) no projeto`);
2483
2490
  log(` ${CYAN}sync-codex${RESET} Gerar e verificar as skills locais do Codex CLI ($nome)`);
2484
- log(` ${CYAN}historico${RESET} Estado da captura do trabalho (so com o "sim" dado na plataforma)`);
2491
+ log(` ${CYAN}historico${RESET} Estado da captura do trabalho (prevista nos Termos de Uso; desliga em Minha Conta)`);
2485
2492
  log(` ${CYAN}conectar-1password${RESET} Conectar o 1Password CLI (token lido do Ctrl+C, nunca digitado)`);
2486
2493
  log('');
2487
2494
  log(`${DIM}Primeira instalacao: https://arcane.arka.education/auroq${RESET}`);
@@ -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.8 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.8 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.8 "$@"
222
+ npx -y auroq-os@2.6.10 "$@"
223
223
  else
224
- npx -y auroq-os@2.6.8 "$@" < /dev/tty
224
+ npx -y auroq-os@2.6.10 "$@" < /dev/tty
225
225
  fi
226
226
  }
227
227
 
@@ -5,7 +5,7 @@ const require = createRequire(import.meta.url);
5
5
  const runtime = require('../../.claude/hooks/ahi/runtime.cjs');
6
6
  export function run(argv = [], cwd = process.cwd()) {
7
7
  const host = argv.includes('--codex') ? 'codex' : 'claude-code';
8
- if (['pausar', 'pause'].includes(argv[0])) return { ok: false, code: 'NO_PAUSE', error: 'Historico acompanha o uso do MCP. Desabilitar o MCP encerra novas capturas; direitos de acesso e exclusao continuam disponiveis.' };
8
+ if (['pausar', 'pause'].includes(argv[0])) return { ok: false, code: 'NO_PAUSE', error: 'Nao ha pausa por aqui. Para nao guardar suas conversas, desligue "Guardar minhas conversas" em Minha Conta (arcane.arka.education/conta) ou peca ao agente para registrar "nao". Nao e preciso desligar o MCP nem os hooks; direitos de acesso e exclusao continuam disponiveis.' };
9
9
  if (['sessoes', 'sessions', 'timeline', 'busca', 'search', 'exportar', 'export', 'excluir', 'delete'].includes(argv[0])) return runRemote(argv, cwd);
10
10
  if (argv[0] && !['status', '--codex'].includes(argv[0])) return { ok: false, code: 'UNKNOWN_COMMAND', error: 'Use auroq-os historico status [--codex].' };
11
11
  try { return runtime.status(cwd, host); } catch { return { ok: false, capture: 'error', reason: 'DIAGNOSTIC_UNAVAILABLE' }; }
@@ -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.8",
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": "1a2d275b50ead64cb01a6befd9b070a545ec957e4738caff8121df7284eb4a4a"
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": "dd043094b3b7999bd96e6de9d62f176530b88b287861c865b498be90469290a5"
213
+ "sha256": "65da747dd205bdd0d5aef71167a174936d516f201b63276fd27caa6af55f3628"
214
214
  },
215
215
  {
216
216
  "path": "lib/ahi/client.mjs",
@@ -218,7 +218,7 @@
218
218
  },
219
219
  {
220
220
  "path": "lib/ahi/historico.mjs",
221
- "sha256": "cb85e53dae59e793bdb83f59c3f8d14342912953b43dd9408d757a7b7182733a"
221
+ "sha256": "909c0d0b5f6c19a75d1c278b639e107d8f29efb015aac91464b3168d23388556"
222
222
  },
223
223
  {
224
224
  "path": "lib/ahi/install.cjs",
@@ -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": "c1425f8e2aad837ac27f75503c376c19fbb0787d7dcec19bbc1bc5456dcc5a34"
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.8",
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": {