auroq-os 2.8.9 → 2.8.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.8.9
4
+ version: 2.8.10
5
5
  installedAt: '2026-03-16'
6
6
 
7
7
  ide:
package/CHANGELOG.md CHANGED
@@ -1,5 +1,6 @@
1
1
  ## Unreleased
2
2
 
3
+ - 2.8.10 — Cofre no Mac, segunda rodada do teste real num usuário novo do macOS (07/11/2026). **Guardar senha no 1Password voltou a funcionar:** no 1Password CLI 2.41, o molde do item por pipe parou de funcionar (`item create -` recusa com "provide the item category" e `item edit` por pipe sai 0 sem mudar nada); `segredo gravar` e a cópia da chave no cofre pessoal passam a usar o molde num arquivo temporário só do usuário (`--template`, apagado em seguida), como o Windows já fazia. **Mac, "acessar dados de outros apps":** o `op` lê a pasta do 1Password e o Mac pergunta se o Auroq OS (ou o Terminal) pode acessar dados de outros apps; negada ou cancelada, a conta some e o aluno ouvia "ligue a integração" em laço. Agora, sem conta à vista no Mac, o comando zera só essa decisão do programa que o abriu (`tccutil reset SystemPolicyAppData <app>`) para a pergunta voltar e diz para clicar em Permitir. O atalho para Configurações > Desenvolvedor abre o 1Password atual, não o "1Password 7" antigo. **Infisical no Safari:** a página do Infisical não consegue entregar o login ao computador no Safari e mostra "Unable to reach CLI" com "Copy to clipboard"; sem terminal o programa do Infisical não aceitava colar. Agora, enquanto o login espera (Mac e Windows), o comando reconhece esse código na área de transferência (só esse formato), entrega ao programa pelo endereço local e limpa a área de transferência: o aluno só clica em "Copy to clipboard". Textos do Mac: "Desbloquear usando Touch ID" e, sem Touch ID, a janela pede só "Autorizar". Catálogo `auroq-core` 2.3.56 (peça do 1Password da skill do cofre).
3
4
  - 2.8.9 — 1Password no Mac sem Homebrew (teste real num usuário novo do macOS, 06/11/2026): o Mac de aluno não tem Homebrew, e o `conectar-1password` parava em `app-nao-instalou` e `sem-op`. Agora, sem Homebrew (ou se ele falhar), o comando baixa o app oficial do 1Password (`1Password-latest`, assinatura da AgileBits conferida com `codesign --deep` e Team ID 2BUA8C4S2C) e copia para a pasta Aplicativos, e instala o 1Password CLI 2.41.1 oficial (SHA-256 fixo, o mesmo do app Auroq OS, mais a assinatura) em `~/.auroq/op/bin`, sem senha de administrador. `segredo` acha o `op` nessa pasta mesmo num terminal que não a tem no PATH.
4
5
  - 1Password no Windows: o CLI do 1Password passa pelo PowerShell, como no teste real do Marcos; isso evita a falha de sessão que ocorria quando o Node o iniciava diretamente. `--estado` usa o mesmo caminho para detectar o CLI, e a lista de contas deixa de depender do shell em que o aluno abriu o comando. Os três ganchos do Claude Code usam o caminho do projeto mesmo depois de entrar numa subpasta. A skill do cofre orienta o diagnóstico quando a aprovação se repete. Catálogo `auroq-core` 2.3.55.
5
6
 
package/README.md CHANGED
@@ -290,13 +290,13 @@ a parte tecnica e com o Companion.
290
290
 
291
291
  ## Observabilidade e integridade do motor
292
292
 
293
- Apos publicacao, `npx -y auroq-os@2.8.9 doctor` mostra a fila local segura e os comandos pinados de
294
- manutencao/diagnostico. `npx -y auroq-os@2.8.9 manutencao` preserva o caminho existente
293
+ Apos publicacao, `npx -y auroq-os@2.8.10 doctor` mostra a fila local segura e os comandos pinados de
294
+ manutencao/diagnostico. `npx -y auroq-os@2.8.10 manutencao` preserva o caminho existente
295
295
  de consentimento legado, studentFiles, customizacoes e dados do negocio. Nao ha
296
296
  migracao forcada. O registro MCP aponta para `arcane-mcp@0.7.1` — o pin mora no campo
297
297
  `arcaneMcpPin` do package.json (fonte unica; o servidor le o mesmo campo no npm).
298
298
  `npm run release:verify` recusa publicar com um pin que o npm nao resolva.
299
- Os instaladores do repositorio ficam preparados para 2.8.9; as copias publicas so recebem
299
+ Os instaladores do repositorio ficam preparados para 2.8.10; as copias publicas so recebem
300
300
  a versao apos os gates nativos e a verificacao do tarball publicado.
301
301
  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.
302
302
 
@@ -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.8.9 check-access
592
+ & (Get-AuroqNpx) -y auroq-os@2.8.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.8.9 instalar
680
+ & (Get-AuroqNpx) -y auroq-os@2.8.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.8.9 "$@"
222
+ npx -y auroq-os@2.8.10 "$@"
223
223
  else
224
- npx -y auroq-os@2.8.9 "$@" < /dev/tty
224
+ npx -y auroq-os@2.8.10 "$@" < /dev/tty
225
225
  fi
226
226
  }
227
227
 
package/lib/cofre.js CHANGED
@@ -521,7 +521,7 @@ function makeOpBackend(d) {
521
521
  return recomposicao;
522
522
  }
523
523
 
524
- async function call(tk, args, input = '') {
524
+ async function call(tk, args, input = '', extra = {}) {
525
525
  const o = mod(d, 'onepassword');
526
526
  const run = d.op || o.runOp;
527
527
  // o ambiente e montado a cada tentativa: o PATH recomposto vale so para quem copia process.env DEPOIS
@@ -532,7 +532,7 @@ function makeOpBackend(d) {
532
532
  for (const k of Object.keys(env)) {
533
533
  if (/^AUROQ_INFISICAL_/i.test(k)) delete env[k];
534
534
  }
535
- return run(args, { env, input, timeoutMs: OP_TIMEOUT_MS });
535
+ return run(args, { env, input, timeoutMs: OP_TIMEOUT_MS, ...extra });
536
536
  };
537
537
  let r = await tentar();
538
538
  if (r && r.missing && (ehWindows(d) || ehMac(d)) && (await recomporPathUmaVez())) r = await tentar();
@@ -541,16 +541,37 @@ function makeOpBackend(d) {
541
541
 
542
542
  const semChave = () => failure('cofre-nao-configurado', tipo);
543
543
 
544
+ /**
545
+ * Criar e editar item vao com o molde num arquivo temporario (`--template`), nunca por pipe: no 1Password CLI 2.41
546
+ * (teste real num Mac, 07/11/2026) o `item create -` por pipe recusa ("provide the item category with '--category'
547
+ * flag") e o `item edit` por pipe sai 0 SEM mudar nada. O arquivo (so deste usuario, 0600) e apagado em seguida,
548
+ * aconteca o que acontecer; o `op` roda com o stdin desligado (com `--template` ele nao aceita pipe nenhum).
549
+ */
550
+ async function runComMolde(args, body, secrets) {
551
+ const o = mod(d, 'onepassword');
552
+ let tmp = null;
553
+ try {
554
+ tmp = o.writeTemplateFile(body);
555
+ } catch (e) {
556
+ return { erro: failure('falha', tipo, `Nao consegui preparar o arquivo temporario do molde (${oneLine(e && e.message, 120)}).`) };
557
+ }
558
+ try {
559
+ return await run([...args, '--template', tmp.file], undefined, secrets, { noStdin: true });
560
+ } finally {
561
+ try { fs.rmSync(tmp.dir, { recursive: true, force: true }); } catch { /* fica na pasta temporaria do usuario */ }
562
+ }
563
+ }
564
+
544
565
  /** Gravar e apagar leem o item, mudam e gravam de volta: dois comandos juntos criariam item repetido ou perderiam um campo. Um por vez. */
545
566
  const comTrava = (fn) => withFileLock(path.join(dirOf(d), 'cofre-op.lock'), fn, {
546
567
  waitMs: d.lockWaitMs === undefined ? LOCK_WAIT_MS : d.lockWaitMs,
547
568
  onTimeout: () => fail('falha', 'Outro comando esta gravando no 1Password agora. Tente de novo em alguns instantes.'),
548
569
  });
549
570
 
550
- async function run(args, input, secrets) {
571
+ async function run(args, input, secrets, extra = {}) {
551
572
  let tk = await token();
552
573
  if (!tk) return { erro: semChave() };
553
- let r = await call(tk, args, input);
574
+ let r = await call(tk, args, input, extra);
554
575
  let c = r.ok ? null : classifyOp(d, r, secrets);
555
576
  // A variavel do programa pode estar velha: janela aberta com uma chave que depois foi revogada e trocada (a
556
577
  // nova esta gravada no computador). Chave RECUSADA vinda da variavel => tenta a gravada, se for outra. So esta
@@ -558,7 +579,7 @@ function makeOpBackend(d) {
558
579
  if (c && c.codigo === 'credencial-invalida' && tk === fromEnv()) {
559
580
  const gravada = await fromDisk();
560
581
  if (gravada && gravada !== tk) {
561
- const r2 = await call(gravada, args, input);
582
+ const r2 = await call(gravada, args, input, extra);
562
583
  tk = gravada;
563
584
  r = r2;
564
585
  c = r.ok ? null : classifyOp(d, r, secrets);
@@ -602,13 +623,13 @@ function makeOpBackend(d) {
602
623
  }
603
624
  let w;
604
625
  if (!item) {
605
- w = await run(['item', 'create', '-', '--vault', VAULT_NAME, '--tags', ITEM_TAG], JSON.stringify(itemTemplate(ref, valor)), secrets);
626
+ w = await runComMolde(['item', 'create', '--vault', VAULT_NAME, '--tags', ITEM_TAG], JSON.stringify(itemTemplate(ref, valor)), secrets);
606
627
  } else {
607
628
  // item que ja existe (pode ser do aluno, com varios campos): muda ou insere SO o campo pedido
608
629
  const campo = findField(item.fields, ref.campo);
609
630
  if (campo) campo.value = valor;
610
631
  else item.fields.push(ref.campo === CREDENTIAL ? { id: CREDENTIAL, type: 'CONCEALED', label: CREDENTIAL, value: valor } : { type: 'CONCEALED', label: ref.campo, value: valor });
611
- w = await run(['item', 'edit', ref.item, '--vault', VAULT_NAME], JSON.stringify(item), secrets);
632
+ w = await runComMolde(['item', 'edit', ref.item, '--vault', VAULT_NAME], JSON.stringify(item), secrets);
612
633
  }
613
634
  if (w.erro) return w.erro;
614
635
  return { ok: true, criado: !item };
@@ -31,7 +31,7 @@ const os = require('os');
31
31
  const path = require('path');
32
32
  const crypto = require('crypto');
33
33
  const fs = require('fs');
34
- const { spawn, spawnSync } = require('child_process');
34
+ const { spawn, spawnSync, execFileSync } = require('child_process');
35
35
 
36
36
  // Versao minima aceita: login por navegador SEM terminal + perfis (--profile/--save-as) so existem a partir daqui
37
37
  // (resultado-cli.md, secao 7: "Login por navegador seguro sem TTY: v0.43.139"). O projeto e criado pela API,
@@ -51,6 +51,7 @@ const LOGOUT_TIMEOUT_MS = 60000;
51
51
  const EXTRACT_TIMEOUT_MS = 300000;
52
52
  const INSTALL_TIMEOUT_MS = 15 * 60 * 1000;
53
53
  const DEFAULT_LOGIN_TIMEOUT_MS = 540000;
54
+ const CLIPBOARD_POLL_MS = 2000;
54
55
  const DOWNLOAD_TIMEOUT_MS = 15 * 60 * 1000;
55
56
  const DOWNLOAD_MAX_BYTES = 400 * 1024 * 1024;
56
57
  const OUTPUT_CAP = 16 * 1024 * 1024;
@@ -351,10 +352,74 @@ function makeDefaultCliDeps(overrides = {}) {
351
352
  tmpdir: os.tmpdir(),
352
353
  run: runProcess,
353
354
  download: (url) => defaultDownload(url, process.platform),
355
+ readClipboard: () => readClipboardText(process.platform),
356
+ clearClipboard: () => clearClipboardText(process.platform),
357
+ postLocal: postToLoopback,
358
+ sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
354
359
  ...overrides,
355
360
  };
356
361
  }
357
362
 
363
+ // ─── Reserva do login: o codigo copiado da tela "Unable to reach CLI" ─────────
364
+
365
+ /**
366
+ * Teste real num Mac (07/11/2026): o Safari nao deixa a pagina do Infisical (https) entregar o login ao CLI em
367
+ * http://127.0.0.1:<porta> e mostra "Unable to reach CLI" com o botao "Copy to clipboard". Sem terminal, o CLI nao
368
+ * aceita colar (so le o codigo quando tem TTY). Entao, enquanto o login espera, o Auroq olha a area de transferencia:
369
+ * quando o aluno clica "Copy to clipboard", o codigo (base64 de {JTWToken,email,privateKey}) vai do proprio Auroq para
370
+ * o CLI pelo mesmo endereco local que a pagina usaria, e a area de transferencia e limpa. Nunca mostra nem guarda o
371
+ * codigo; so aceita exatamente esse formato.
372
+ */
373
+ function readClipboardText(platform) {
374
+ try {
375
+ if (platform === 'darwin') return execFileSync('/usr/bin/pbpaste', [], { encoding: 'utf8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 1024 * 1024 });
376
+ if (platform === 'win32') return execFileSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', 'Get-Clipboard -Raw'], { encoding: 'utf8', timeout: 8000, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true, maxBuffer: 1024 * 1024 });
377
+ } catch { /* sem area de transferencia: segue esperando o navegador */ }
378
+ return '';
379
+ }
380
+
381
+ function clearClipboardText(platform) {
382
+ try {
383
+ if (platform === 'darwin') execFileSync('/usr/bin/pbcopy', [], { input: '', timeout: 5000, stdio: ['pipe', 'ignore', 'ignore'] });
384
+ else if (platform === 'win32') execFileSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', 'Set-Clipboard -Value $null'], { timeout: 8000, stdio: 'ignore', windowsHide: true });
385
+ } catch { /* melhor esforco */ }
386
+ }
387
+
388
+ /** POST JSON para http://127.0.0.1:<porta>/ (o servidor que o proprio CLI abriu para o login). Nunca lanca. */
389
+ function postToLoopback(port, body) {
390
+ return new Promise((resolve) => {
391
+ let req;
392
+ try {
393
+ req = require('http').request({ host: '127.0.0.1', port, path: '/', method: 'POST', timeout: 15000,
394
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } }, (res) => {
395
+ res.resume();
396
+ res.on('end', () => resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, status: res.statusCode }));
397
+ });
398
+ } catch { resolve({ ok: false }); return; }
399
+ req.on('timeout', () => req.destroy());
400
+ req.on('error', () => resolve({ ok: false }));
401
+ req.end(body);
402
+ });
403
+ }
404
+
405
+ /** O texto e o codigo da tela "Unable to reach CLI"? Devolve o JSON a entregar ao CLI, ou null. */
406
+ function parseCopiedLoginToken(text) {
407
+ const t = String(text || '').trim();
408
+ if (t.length < 40 || t.length > 20000 || !/^[A-Za-z0-9+/=]+$/.test(t)) return null;
409
+ let obj;
410
+ try { obj = JSON.parse(Buffer.from(t, 'base64').toString('utf8')); } catch { return null; }
411
+ if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return null;
412
+ if (typeof obj.JTWToken !== 'string' || !/^eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(obj.JTWToken)) return null;
413
+ if (typeof obj.email !== 'string' || !obj.email.includes('@')) return null;
414
+ return JSON.stringify({ JTWToken: obj.JTWToken, email: obj.email, privateKey: typeof obj.privateKey === 'string' ? obj.privateKey : '' });
415
+ }
416
+
417
+ function callbackPortOf(url) {
418
+ const m = /[?&]callback_port=(\d{2,5})(?:&|$)/.exec(String(url || ''));
419
+ const n = m ? Number(m[1]) : NaN;
420
+ return n > 0 && n < 65536 ? n : null;
421
+ }
422
+
358
423
  /** Completa o `d` recebido com os padroes reais (os testes passam so o que querem trocar). */
359
424
  function withDefaults(d) {
360
425
  const base = makeDefaultCliDeps();
@@ -784,14 +849,44 @@ async function browserLogin(dIn, cli, { domain, profile = PROFILE, timeoutMs = D
784
849
  found(extractLoginUrl(buffer));
785
850
  };
786
851
 
852
+ // Reserva (Mac e Windows): o aluno clica "Copy to clipboard" na tela "Unable to reach CLI" e o Auroq entrega.
853
+ let rodando = true;
854
+ let entregue = false;
855
+ const vigiarAreaDeTransferencia = async () => {
856
+ if (d.platform !== 'darwin' && d.platform !== 'win32') return;
857
+ if (typeof d.readClipboard !== 'function' || typeof d.postLocal !== 'function') return;
858
+ while (rodando && !entregue) {
859
+ await d.sleep(CLIPBOARD_POLL_MS);
860
+ if (!rodando) return;
861
+ const port = callbackPortOf(url);
862
+ if (!port) continue;
863
+ let copiado = '';
864
+ let corpo = null;
865
+ try { copiado = String(await d.readClipboard()); corpo = parseCopiedLoginToken(copiado); } catch { corpo = null; }
866
+ if (!corpo) continue;
867
+ const enviado = await d.postLocal(port, corpo);
868
+ if (!(enviado && enviado.ok)) continue; // o CLI nao aceitou: deixa a area de transferencia como estava
869
+ entregue = true;
870
+ // limpa so se ainda for o mesmo codigo (o aluno pode ter copiado outra coisa nesse meio tempo)
871
+ try {
872
+ if (typeof d.clearClipboard === 'function' && String(await d.readClipboard()).trim() === copiado.trim()) await d.clearClipboard();
873
+ } catch { /* melhor esforco */ }
874
+ }
875
+ };
876
+ const vigia = vigiarAreaDeTransferencia().catch(() => {});
877
+
787
878
  let r;
788
879
  try {
789
880
  r = await d.run(cli, ['login', '--domain', domain, '--save-as', profile, '--silent'], {
790
881
  env: cliEnv(d.env), timeoutMs, stdinIgnore: true, onStderr,
791
882
  });
792
883
  } catch {
884
+ rodando = false;
793
885
  return { ok: false, motivo: 'falhou', url, detalhe: 'nao consegui rodar o CLI' };
886
+ } finally {
887
+ rodando = false;
794
888
  }
889
+ void vigia; // para sozinho na proxima volta (nao segura o resultado do login)
795
890
  r = r || {};
796
891
  // `run` sem tempo real (ou saida que chegou toda no fim): olha o texto final
797
892
  if (!url) found(extractLoginUrl(`${buffer}\n${r.stderr || ''}\n`, { complete: true }));
@@ -863,6 +958,8 @@ module.exports = {
863
958
  runProcess,
864
959
  curlDownload,
865
960
  extractLoginUrl,
961
+ parseCopiedLoginToken,
962
+ callbackPortOf,
866
963
  releaseTarget,
867
964
  parseChecksums,
868
965
  scrub,
@@ -552,6 +552,8 @@ const SERVICE_ACCOUNT_NAME = 'Auroq';
552
552
  // `op service-account create` so existe desde a 2.26.0 e `op whoami` so diz QUEM esta falando (conta de
553
553
  // servico ou pessoa) desde a 2.30.0; o teste final precisa dessa resposta, entao o minimo e a 2.30.0.
554
554
  const MIN_OP_VERSION = '2.30.0';
555
+ const MAC_DADOS_DE_OUTROS_APPS = 'No Mac: se aparecer a pergunta se o Auroq OS (ou o Terminal) pode acessar dados de outros apps, clique em Permitir. Sem isso o 1Password fica invisivel para o Auroq.';
556
+ const MAC_APP_BUNDLE = 'com.1password.1password'; // o 1Password 8 (o "1Password 7" antigo e com.agilebits.onepassword7)
555
557
  const INTEGRATION_URL = 'onepassword://settings/developers'; // abre o app direto em Configuracoes > Desenvolvedor
556
558
  const BACKUP_TITLE = 'Auroq OS - token da conta de servico';
557
559
  const BACKUP_TAG = 'auroq-os';
@@ -746,7 +748,7 @@ function enableIntegrationSteps(platform) {
746
748
  ? 'Abra as Configuracoes: no menu de cima do Mac, 1Password > Configuracoes (ou Cmd + virgula). Se eu ja abri a tela de Desenvolvedor pra voce, pule pro passo 3.'
747
749
  : 'Abra as Configuracoes: clique no nome da sua conta (canto de cima, a esquerda) > Configuracoes. Se eu ja abri a tela de Desenvolvedor pra voce, pule pro passo 3.';
748
750
  const digital = platform === 'darwin'
749
- ? 'Para aprovar com a digital: Configuracoes > Seguranca > "Desbloquear com Touch ID". Sem isso o Mac pede a senha do seu usuario do Mac.'
751
+ ? 'Para aprovar com a digital (opcional): Configuracoes > Seguranca > "Desbloquear usando Touch ID" (Desbloquear com Touch ID). Sem Touch ID, a janela do 1Password pede so um clique em "Autorizar". Se o Mac perguntar se o Auroq OS (ou o Terminal) pode acessar dados de outros apps, clique em Permitir.'
750
752
  : 'Para aprovar com o Windows Hello: Configuracoes > Seguranca > "Desbloquear com Windows Hello". Sem o Windows Hello ligado a janela de aprovacao pode nem aparecer.';
751
753
  return [
752
754
  'Abra o app do 1Password, entre na sua conta e deixe o app aberto.',
@@ -758,15 +760,15 @@ function enableIntegrationSteps(platform) {
758
760
 
759
761
  /**
760
762
  * Receita curta para ligar a digital (Windows Hello / Touch ID) quando nao esta ligada. A documentacao da integracao
761
- * manda ligar o Windows Hello no Windows; no Mac o Touch ID e opcional (sem ele vale a senha do usuario do Mac). A
763
+ * manda ligar o Windows Hello no Windows; no Mac o Touch ID e opcional (sem ele a janela pede um clique em Autorizar). A
762
764
  * senha da conta 1Password NAO faz esse papel. O codigo nao liga nada disso por ele.
763
765
  */
764
766
  function biometricSteps(platform) {
765
767
  if (platform === 'darwin') {
766
768
  return [
767
769
  'No Mac, cadastre a digital: Ajustes do Sistema > Touch ID e Senha > Adicionar digital.',
768
- 'No 1Password: Configuracoes > Seguranca > marque "Desbloquear com Touch ID".',
769
- 'O Touch ID e opcional: se o seu Mac nao tem, o Mac pede a senha do seu usuario do Mac na janela de aprovacao.',
770
+ 'No 1Password: Configuracoes > Seguranca > marque "Desbloquear usando Touch ID" (Desbloquear com Touch ID).',
771
+ 'O Touch ID e opcional: sem ele, a janela de aprovacao do 1Password pede so um clique em "Autorizar" (ou a senha do seu usuario do Mac, se voce ligou "Desbloqueie o aplicativo com a senha do Mac").',
770
772
  ];
771
773
  }
772
774
  return [
@@ -854,11 +856,19 @@ function runOpInFreshShell(args) {
854
856
  function openUrl(url) {
855
857
  if (process.env.AUROQ_NAO_ABRIR_APP) return;
856
858
  try {
859
+ // Mac: o link onepassword:// pode abrir o "1Password 7" antigo, se ele ainda estiver instalado (teste real num Mac,
860
+ // 07/11/2026). Pede o app atual pelo identificador; se ele nao estiver la, abre do jeito de sempre.
857
861
  const [cmd, args] = process.platform === 'darwin'
858
- ? ['open', [url]]
862
+ ? ['open', ['-b', MAC_APP_BUNDLE, url]]
859
863
  : (process.platform === 'win32' ? ['cmd', ['/c', 'start', '', url]] : ['xdg-open', [url]]);
860
864
  const child = spawn(cmd, args, { stdio: 'ignore', detached: true, windowsHide: true, env: opEnv() });
861
865
  child.on('error', () => {});
866
+ if (process.platform === 'darwin') {
867
+ child.on('exit', (code) => {
868
+ if (code === 0) return;
869
+ try { const outro = spawn('open', [url], { stdio: 'ignore', detached: true, env: opEnv() }); outro.on('error', () => {}); outro.unref(); } catch { /* receita escrita vale */ }
870
+ });
871
+ }
862
872
  child.unref();
863
873
  } catch {
864
874
  /* sem problema: a receita escrita continua valendo */
@@ -1091,12 +1101,12 @@ const APP_TEMP_PREFIX = 'auroq-1p-app-';
1091
1101
  * `finally` nao roda se o processo e morto). So mexe em pasta com esse prefixo e mais velha que `maxAgeMs`. Nunca lanca.
1092
1102
  * Devolve quantas apagou.
1093
1103
  */
1094
- function sweepStaleAppDirs(root = os.tmpdir(), { maxAgeMs = 60 * 60 * 1000, now = Date.now() } = {}) {
1104
+ function sweepStaleAppDirs(root = os.tmpdir(), { maxAgeMs = 60 * 60 * 1000, now = Date.now(), prefix = APP_TEMP_PREFIX } = {}) {
1095
1105
  let apagadas = 0;
1096
1106
  let nomes = [];
1097
1107
  try { nomes = fs.readdirSync(root); } catch { return 0; }
1098
1108
  for (const nome of nomes) {
1099
- if (!nome.startsWith(APP_TEMP_PREFIX)) continue;
1109
+ if (!nome.startsWith(prefix)) continue;
1100
1110
  const full = path.join(root, nome);
1101
1111
  try {
1102
1112
  const st = fs.lstatSync(full);
@@ -1424,11 +1434,30 @@ function makeProgress(d) {
1424
1434
  };
1425
1435
  }
1426
1436
 
1437
+ /**
1438
+ * Mac: zera a decisao "acessar dados de outros apps" (TCC, servico SystemPolicyAppData) do programa que abriu este
1439
+ * comando (o app Auroq OS ou o Terminal), para a pergunta do Mac voltar. Teste real (07/11/2026): o `op` le a pasta do
1440
+ * 1Password, o Mac pergunta se o Auroq OS pode acessar dados de outros apps e, se a resposta foi "nao" (ou a pergunta
1441
+ * foi cancelada), fica "nao" para sempre e calado: o `op` so ve "nenhuma conta" e o aluno ouvia "ligue a integracao" em
1442
+ * laco. Zerar so faz o Mac perguntar de novo (nao libera nada sozinho). Nunca lanca; true se zerou.
1443
+ */
1444
+ function resetMacAppData(env = process.env, run = spawnSync) {
1445
+ const bundle = String(env.__CFBundleIdentifier || '');
1446
+ if (!/^[A-Za-z0-9][A-Za-z0-9.-]{2,200}$/.test(bundle)) return false;
1447
+ try {
1448
+ const r = run('/usr/bin/tccutil', ['reset', 'SystemPolicyAppData', bundle], { stdio: 'ignore', timeout: 15000 });
1449
+ return Boolean(r && !r.error && r.status === 0);
1450
+ } catch {
1451
+ return false;
1452
+ }
1453
+ }
1454
+
1427
1455
  function makeDefaultDeps() {
1428
1456
  return {
1429
1457
  platform: process.platform,
1430
1458
  openUrl,
1431
1459
  refreshPath,
1460
+ resetAppData: () => (process.platform === 'darwin' ? resetMacAppData() : false),
1432
1461
  appInstalled: () => appInstalled(),
1433
1462
  lock: makeLock(),
1434
1463
  state: { read: readState, write: writeState },
@@ -1544,12 +1573,9 @@ function saveBackup(d, account, token) {
1544
1573
  // conforme o plano (e pode vir traduzido), por isso nunca se usa o nome.
1545
1574
  const body = JSON.stringify(backupItemTemplate(token));
1546
1575
  const common = ['--tags', BACKUP_TAG, '--account', accountKey(account), '--format', 'json'];
1547
- if (d.platform !== 'win32') {
1548
- const r = d.op(['item', 'create', '-', ...common], { env: opEnv(), input: body });
1549
- return r.ok ? { ok: true } : { ok: false, detalhe: oneLine(r.stderr) };
1550
- }
1551
- // Windows: o `op item create -` por pipe falha no PowerShell 5.1 ("invalid JSON in piped input", teste real de 27/10),
1552
- // entao o molde vai num arquivo temporario (UTF-8 sem BOM) so deste usuario, que o `op` le com --template. O token
1576
+ // Todos os sistemas: o molde vai num arquivo temporario. Por pipe, o `op item create -` falha no PowerShell 5.1
1577
+ // ("invalid JSON in piped input", teste real de 27/10) e, no 1Password CLI 2.41, tambem no Mac ("provide the item
1578
+ // category with '--category' flag", teste real de 07/11/2026). O molde vai num arquivo temporario (UTF-8 sem BOM) so deste usuario, que o `op` le com --template. O token
1553
1579
  // continua fora de argumento e de log; o arquivo e apagado em seguida, aconteca o que acontecer. O `op` roda com o
1554
1580
  // stdin desligado (`noStdin`): com `--template` ele nao aceita nenhum pipe, nem vazio.
1555
1581
  let tmp = null;
@@ -1571,8 +1597,15 @@ function saveBackup(d, account, token) {
1571
1597
  * (%TEMP% do perfil, que so ele, o SYSTEM e os administradores leem; o modo 0600 vale no Mac e no Linux e e ignorado no
1572
1598
  * Windows). UTF-8 SEM BOM: o BOM faria o `op` recusar o JSON. Devolve { dir, file }; quem chama apaga a pasta.
1573
1599
  */
1600
+ const MOLDE_PREFIX = 'auroq-molde-';
1601
+
1602
+ /**
1603
+ * Molde do item (com o valor) num arquivo temporario so do usuario. Antes, apaga moldes de execucoes anteriores que
1604
+ * morreram no meio (Ctrl+C ou janela fechada nao deixam o `finally` rodar) e que tenham mais de 10 minutos.
1605
+ */
1574
1606
  function writeTemplateFile(content) {
1575
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'auroq-op-'));
1607
+ try { sweepStaleAppDirs(os.tmpdir(), { prefix: MOLDE_PREFIX, maxAgeMs: 10 * 60 * 1000 }); } catch { /* faxina e extra */ }
1608
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), MOLDE_PREFIX));
1576
1609
  const file = path.join(dir, 'molde.json');
1577
1610
  try {
1578
1611
  fs.writeFileSync(file, Buffer.from(content, 'utf8'), { flag: 'wx', mode: 0o600 });
@@ -1812,6 +1845,11 @@ async function runConnect(opts, d, prog) {
1812
1845
  prog.reset('app');
1813
1846
  return fim('aguardando', 'instalar-app', 'Nao achei o app do 1Password neste computador. Instale com: npx -y auroq-os@latest conectar-1password --instalar-app --agente (ou baixe em https://1password.com/downloads). Se ele ja esta instalado num lugar fora do comum, abra o app, entre na sua conta e ligue "Integrar com a CLI do 1Password" em Configuracoes > Desenvolvedor; depois rode de novo.');
1814
1847
  }
1848
+ if (!accounts.length && d.platform === 'darwin' && typeof d.resetAppData === 'function' && d.resetAppData()) {
1849
+ // a conta pode estar escondida pela protecao "dados de outros apps" do Mac: a pergunta volta e o aluno permite
1850
+ out.log(`${BOLD}${MAC_DADOS_DE_OUTROS_APPS}${RESET}`);
1851
+ accounts = listAccounts(d);
1852
+ }
1815
1853
  if (!accounts.length) {
1816
1854
  showIntegrationHelp(d, out);
1817
1855
  if (waitSeconds > 0) {
@@ -1826,7 +1864,7 @@ async function runConnect(opts, d, prog) {
1826
1864
  }
1827
1865
  if (!accounts.length) {
1828
1866
  prog.reset('app');
1829
- return fim('aguardando', 'ligar-integracao', 'Falta ligar "Integrate with 1Password CLI" nas configuracoes do app do 1Password (Configuracoes > Desenvolvedor). Depois e so rodar de novo.');
1867
+ return fim('aguardando', 'ligar-integracao', `Falta ligar "Integrate with 1Password CLI" nas configuracoes do app do 1Password (Configuracoes > Desenvolvedor). Depois e so rodar de novo.${d.platform === 'darwin' ? ` ${MAC_DADOS_DE_OUTROS_APPS}` : ''}`);
1830
1868
  }
1831
1869
  }
1832
1870
  prog.mark('conta', 'app');
@@ -2198,6 +2236,7 @@ module.exports = {
2198
2236
  refreshPath,
2199
2237
  installOpDirect,
2200
2238
  installMacAppDirect,
2239
+ resetMacAppData,
2201
2240
  auroqOpBin,
2202
2241
  OP_DIRETO,
2203
2242
  runOp,
@@ -2,7 +2,7 @@
2
2
  "schema": 1,
3
3
  "algorithm": "sha256",
4
4
  "scope": "packaged-runtime-and-cascas",
5
- "version": "2.8.9",
5
+ "version": "2.8.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": "7eeba357e5203bb8d0d9ade5d4a53ecfa99d7edd05212a5625b59d6425f641f4"
13
+ "sha256": "11f011d4d20d781521606c4d0f2625ebf6bee36861ca2056697c9d73cd30fd2f"
14
14
  },
15
15
  {
16
16
  "path": ".auroq-core/core/synapse/context/context-builder.js",
@@ -254,7 +254,7 @@
254
254
  },
255
255
  {
256
256
  "path": "lib/cofre.js",
257
- "sha256": "a7142a3a49e7cb71f4927d567fa834b838c19987f12bd1d7fe02e1cb744378c1"
257
+ "sha256": "c3648b80ad72df055726571aa107608489808287d557386625fd8157f9f6279f"
258
258
  },
259
259
  {
260
260
  "path": "lib/credentials.js",
@@ -270,7 +270,7 @@
270
270
  },
271
271
  {
272
272
  "path": "lib/infisical-cli.js",
273
- "sha256": "33c826f731a8f8f930f1797821d9faee7b6f2558c30da5c211319bf4f3e83982"
273
+ "sha256": "6ebd63594fedb7a3b0bee58b70a2ce1b070d91c9dc20fd6495abaab35c8b84af"
274
274
  },
275
275
  {
276
276
  "path": "lib/infisical.js",
@@ -282,7 +282,7 @@
282
282
  },
283
283
  {
284
284
  "path": "lib/onepassword.js",
285
- "sha256": "34ce88cb11b2fc9bd52905acc19903430df88db5c94b3ed9bc1000bfb1f4a666"
285
+ "sha256": "eb54a524ad488c79577be06ea7496e421660f9e4dbc8fce3b843124b893f0780"
286
286
  },
287
287
  {
288
288
  "path": "lib/supabase.js",
@@ -298,7 +298,7 @@
298
298
  },
299
299
  {
300
300
  "path": "package.json",
301
- "sha256": "0f06b1ac3cb3201dd3f21c3ea980b47ba637fd40a300534d07610de091f54e6b"
301
+ "sha256": "f78cae244b3efba3bd354a6efbd0805bb761f2d6acece93426dfd0b02bbc7b0f"
302
302
  },
303
303
  {
304
304
  "path": "scripts/build-pack.mjs",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auroq-os",
3
- "version": "2.8.9",
3
+ "version": "2.8.10",
4
4
  "arcaneMcpPin": "0.7.1",
5
5
  "description": "Auroq OS — Sistema Operacional de IA para o negócio do aluno da Arcane",
6
6
  "bin": {