auroq-os 2.8.8 → 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.
- package/.auroq-core/core-config.yaml +1 -1
- package/CHANGELOG.md +2 -0
- package/README.md +3 -3
- package/installer/install.ps1 +2 -2
- package/installer/install.sh +2 -2
- package/lib/cofre.js +35 -10
- package/lib/infisical-cli.js +98 -1
- package/lib/onepassword.js +232 -29
- package/lib/runtime-integrity.json +6 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
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).
|
|
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.
|
|
3
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.
|
|
4
6
|
|
|
5
7
|
- Conteúdo ao vivo (catálogo `auroq-core` 2.3.54, 04/11): a peça do 1Password da skill do cofre avisa o Companion de que, no Windows, o app pode estar instalado só como pacote MSIX (é o que o `winget` instala hoje) e não aparece nas pastas de sempre: antes de dizer "o app não está instalado" (resultado `instalar-app`, ou o aluno dizendo que não acha) ele confere a lista de pacotes do Windows, e, se o app estiver lá, vai direto para entrar na conta e ligar a chave. Também descreve o instalador de reserva com limite de 3 minutos; o mesmo aviso entra no pré-gate da skill `configurar-computador`. Precisa do auroq-os 2.8.6 no npm (a detecção e o limite vivem no comando).
|
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.
|
|
294
|
-
manutencao/diagnostico. `npx -y auroq-os@2.8.
|
|
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.
|
|
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
|
|
package/installer/install.ps1
CHANGED
|
@@ -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.
|
|
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.
|
|
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.'
|
package/installer/install.sh
CHANGED
|
@@ -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.
|
|
222
|
+
npx -y auroq-os@2.8.10 "$@"
|
|
223
223
|
else
|
|
224
|
-
npx -y auroq-os@2.8.
|
|
224
|
+
npx -y auroq-os@2.8.10 "$@" < /dev/tty
|
|
225
225
|
fi
|
|
226
226
|
}
|
|
227
227
|
|
package/lib/cofre.js
CHANGED
|
@@ -88,6 +88,10 @@ function ehWindows(d) {
|
|
|
88
88
|
return ((d && d.platform) || process.platform) === 'win32';
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
function ehMac(d) {
|
|
92
|
+
return ((d && d.platform) || process.platform) === 'darwin';
|
|
93
|
+
}
|
|
94
|
+
|
|
91
95
|
function nowOf(d) {
|
|
92
96
|
return d && typeof d.now === 'function' ? d.now() : Date.now();
|
|
93
97
|
}
|
|
@@ -501,14 +505,14 @@ function makeOpBackend(d) {
|
|
|
501
505
|
}
|
|
502
506
|
|
|
503
507
|
// Windows: o `op` instalado DEPOIS de o programa-pai abrir (winget so muda o PATH no registro) nao esta no PATH deste
|
|
504
|
-
// processo: o spawn da ENOENT. Recompoe o PATH UMA vez por backend (cada recomposicao abre o PowerShell) e tenta o
|
|
508
|
+
// processo: o spawn da ENOENT. Mac sem Homebrew: o `op` mora em ~/.auroq/op/bin, que um terminal comum nao tem no PATH. Recompoe o PATH UMA vez por backend (cada recomposicao abre o PowerShell) e tenta o
|
|
505
509
|
// comando de novo; chamadas em paralelo esperam a mesma recomposicao. Devolve false se nao ha como recompor.
|
|
506
510
|
let recomposicao = null;
|
|
507
511
|
function recomporPathUmaVez() {
|
|
508
512
|
if (!recomposicao) {
|
|
509
513
|
recomposicao = (async () => {
|
|
510
514
|
const o = mod(d, 'onepassword');
|
|
511
|
-
const recompor = typeof d.refreshPath === 'function' ? d.refreshPath : o.refreshWindowsPath;
|
|
515
|
+
const recompor = typeof d.refreshPath === 'function' ? d.refreshPath : (ehWindows(d) ? o.refreshWindowsPath : o.refreshPath);
|
|
512
516
|
if (typeof recompor !== 'function') return false;
|
|
513
517
|
try { await recompor(); } catch { /* melhor esforco: o comando decide se achou */ }
|
|
514
518
|
return true;
|
|
@@ -517,7 +521,7 @@ function makeOpBackend(d) {
|
|
|
517
521
|
return recomposicao;
|
|
518
522
|
}
|
|
519
523
|
|
|
520
|
-
async function call(tk, args, input = '') {
|
|
524
|
+
async function call(tk, args, input = '', extra = {}) {
|
|
521
525
|
const o = mod(d, 'onepassword');
|
|
522
526
|
const run = d.op || o.runOp;
|
|
523
527
|
// o ambiente e montado a cada tentativa: o PATH recomposto vale so para quem copia process.env DEPOIS
|
|
@@ -528,25 +532,46 @@ function makeOpBackend(d) {
|
|
|
528
532
|
for (const k of Object.keys(env)) {
|
|
529
533
|
if (/^AUROQ_INFISICAL_/i.test(k)) delete env[k];
|
|
530
534
|
}
|
|
531
|
-
return run(args, { env, input, timeoutMs: OP_TIMEOUT_MS });
|
|
535
|
+
return run(args, { env, input, timeoutMs: OP_TIMEOUT_MS, ...extra });
|
|
532
536
|
};
|
|
533
537
|
let r = await tentar();
|
|
534
|
-
if (r && r.missing && ehWindows(d) && (await recomporPathUmaVez())) r = await tentar();
|
|
538
|
+
if (r && r.missing && (ehWindows(d) || ehMac(d)) && (await recomporPathUmaVez())) r = await tentar();
|
|
535
539
|
return r;
|
|
536
540
|
}
|
|
537
541
|
|
|
538
542
|
const semChave = () => failure('cofre-nao-configurado', tipo);
|
|
539
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
|
+
|
|
540
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. */
|
|
541
566
|
const comTrava = (fn) => withFileLock(path.join(dirOf(d), 'cofre-op.lock'), fn, {
|
|
542
567
|
waitMs: d.lockWaitMs === undefined ? LOCK_WAIT_MS : d.lockWaitMs,
|
|
543
568
|
onTimeout: () => fail('falha', 'Outro comando esta gravando no 1Password agora. Tente de novo em alguns instantes.'),
|
|
544
569
|
});
|
|
545
570
|
|
|
546
|
-
async function run(args, input, secrets) {
|
|
571
|
+
async function run(args, input, secrets, extra = {}) {
|
|
547
572
|
let tk = await token();
|
|
548
573
|
if (!tk) return { erro: semChave() };
|
|
549
|
-
let r = await call(tk, args, input);
|
|
574
|
+
let r = await call(tk, args, input, extra);
|
|
550
575
|
let c = r.ok ? null : classifyOp(d, r, secrets);
|
|
551
576
|
// A variavel do programa pode estar velha: janela aberta com uma chave que depois foi revogada e trocada (a
|
|
552
577
|
// nova esta gravada no computador). Chave RECUSADA vinda da variavel => tenta a gravada, se for outra. So esta
|
|
@@ -554,7 +579,7 @@ function makeOpBackend(d) {
|
|
|
554
579
|
if (c && c.codigo === 'credencial-invalida' && tk === fromEnv()) {
|
|
555
580
|
const gravada = await fromDisk();
|
|
556
581
|
if (gravada && gravada !== tk) {
|
|
557
|
-
const r2 = await call(gravada, args, input);
|
|
582
|
+
const r2 = await call(gravada, args, input, extra);
|
|
558
583
|
tk = gravada;
|
|
559
584
|
r = r2;
|
|
560
585
|
c = r.ok ? null : classifyOp(d, r, secrets);
|
|
@@ -598,13 +623,13 @@ function makeOpBackend(d) {
|
|
|
598
623
|
}
|
|
599
624
|
let w;
|
|
600
625
|
if (!item) {
|
|
601
|
-
w = await
|
|
626
|
+
w = await runComMolde(['item', 'create', '--vault', VAULT_NAME, '--tags', ITEM_TAG], JSON.stringify(itemTemplate(ref, valor)), secrets);
|
|
602
627
|
} else {
|
|
603
628
|
// item que ja existe (pode ser do aluno, com varios campos): muda ou insere SO o campo pedido
|
|
604
629
|
const campo = findField(item.fields, ref.campo);
|
|
605
630
|
if (campo) campo.value = valor;
|
|
606
631
|
else item.fields.push(ref.campo === CREDENTIAL ? { id: CREDENTIAL, type: 'CONCEALED', label: CREDENTIAL, value: valor } : { type: 'CONCEALED', label: ref.campo, value: valor });
|
|
607
|
-
w = await
|
|
632
|
+
w = await runComMolde(['item', 'edit', ref.item, '--vault', VAULT_NAME], JSON.stringify(item), secrets);
|
|
608
633
|
}
|
|
609
634
|
if (w.erro) return w.erro;
|
|
610
635
|
return { ok: true, criado: !item };
|
package/lib/infisical-cli.js
CHANGED
|
@@ -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,
|
package/lib/onepassword.js
CHANGED
|
@@ -84,20 +84,102 @@ function opVersion() {
|
|
|
84
84
|
return r.ok ? r.stdout.trim() : null;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
// 1Password CLI oficial para Mac SEM Homebrew (teste real num Mac novo, 06/11/2026: aluno nao tem `brew`, e instalar o
|
|
88
|
+
// Homebrew pede a senha de admin e as ferramentas da Apple). Mesma versao, mesmo endereco e mesmo SHA-256 que o app
|
|
89
|
+
// Auroq OS usa (auroq-os-app, auroq/primeira-execucao/instalador-do-op.ts), na mesma pasta: ~/.auroq/op/bin.
|
|
90
|
+
const OP_DIRETO = {
|
|
91
|
+
versao: '2.41.1',
|
|
92
|
+
pacotes: {
|
|
93
|
+
arm64: '2289771b68b521ee3dc1d63a013a6a1c5691e0c53e4c4fe79af8042b6ad3c7f5',
|
|
94
|
+
x64: 'e77fc9a8ebb23c176fed7c56ea6e4a7184d2b89957e9a5d9a2feb724e7e0574b',
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
// Team ID da AgileBits (dona do 1Password) na assinatura da Apple: o `op` e o app so entram se vierem assinados por ela.
|
|
98
|
+
const AGILEBITS_TEAM_ID = '2BUA8C4S2C';
|
|
99
|
+
|
|
100
|
+
/** Pasta do `op` instalado sem Homebrew (a mesma do app Auroq OS, que ja a poe no PATH dos terminais dele). */
|
|
101
|
+
function auroqOpBin(home = os.homedir()) {
|
|
102
|
+
return path.join(home, '.auroq', 'op', 'bin');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function opDiretoUrl(arch) {
|
|
106
|
+
const pacote = arch === 'arm64' ? 'arm64' : 'amd64';
|
|
107
|
+
return `https://cache.agilebits.com/dist/1P/op2/pkg/v${OP_DIRETO.versao}/op_darwin_${pacote}_v${OP_DIRETO.versao}.zip`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Requisito de assinatura conferido pelo proprio macOS: certificado emitido pela Apple (Developer ID) para a equipe
|
|
111
|
+
// da AgileBits. So o texto "TeamIdentifier=" nao basta: uma assinatura propria poderia imitar o identificador.
|
|
112
|
+
const AGILEBITS_REQUISITO = `anchor apple generic and certificate leaf[subject.OU] = "${AGILEBITS_TEAM_ID}"`;
|
|
113
|
+
|
|
114
|
+
/** O programa tem assinatura valida, emitida pela Apple para a AgileBits (`codesign -R`)? Nunca lanca. */
|
|
115
|
+
function assinadoPelaAgilebits(alvo, run, { deep = false } = {}) {
|
|
116
|
+
const v = run('/usr/bin/codesign', ['--verify', '--strict', ...(deep ? ['--deep'] : []), `-R=${AGILEBITS_REQUISITO}`, alvo]);
|
|
117
|
+
if (!v || !v.ok) return false;
|
|
118
|
+
const info = run('/usr/bin/codesign', ['-dv', '--verbose=2', alvo]);
|
|
119
|
+
const texto = `${(info && info.stdout) || ''}\n${(info && info.stderr) || ''}`;
|
|
120
|
+
return new RegExp(`^TeamIdentifier=${AGILEBITS_TEAM_ID}$`, 'm').test(texto);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Mac sem Homebrew: baixa o `op` oficial (versao fixa), confere o SHA-256 e a assinatura da AgileBits ANTES de por no
|
|
125
|
+
* lugar e instala em ~/.auroq/op/bin, sem senha. Poe a pasta no PATH deste processo. Nunca lanca; devolve true/false.
|
|
126
|
+
* `deps` so existe para os testes.
|
|
127
|
+
*/
|
|
128
|
+
function installOpDirect(deps = {}) {
|
|
129
|
+
const d = {
|
|
130
|
+
arch: process.arch,
|
|
131
|
+
home: os.homedir(),
|
|
132
|
+
run: (cmd, args, o) => runProgram(cmd, args, o),
|
|
133
|
+
pacotes: OP_DIRETO.pacotes,
|
|
134
|
+
tmpdir: os.tmpdir(),
|
|
135
|
+
out: { log, err },
|
|
136
|
+
...deps,
|
|
137
|
+
};
|
|
138
|
+
const esperado = d.pacotes[d.arch === 'arm64' ? 'arm64' : 'x64'];
|
|
139
|
+
const destino = auroqOpBin(d.home);
|
|
140
|
+
let tmp = null;
|
|
141
|
+
try {
|
|
142
|
+
tmp = fs.mkdtempSync(path.join(d.tmpdir, 'auroq-op-'));
|
|
143
|
+
const zip = path.join(tmp, 'op.zip');
|
|
144
|
+
const aberto = path.join(tmp, 'aberto');
|
|
145
|
+
d.out.log(`${DIM}Baixando o 1Password CLI oficial ${OP_DIRETO.versao} (sem Homebrew)...${RESET}`);
|
|
146
|
+
const baixou = d.run('/usr/bin/curl', ['-fL', '--proto', '=https', '--proto-redir', '=https', '--silent', '--show-error', '--connect-timeout', '30', '--max-time', '900', '-o', zip, opDiretoUrl(d.arch)]);
|
|
147
|
+
if (!baixou.ok || !fs.existsSync(zip)) { d.out.err('Nao consegui baixar o 1Password CLI oficial (internet?).'); return false; }
|
|
148
|
+
const obtido = crypto.createHash('sha256').update(fs.readFileSync(zip)).digest('hex');
|
|
149
|
+
if (obtido !== esperado) { d.out.err('O arquivo baixado do 1Password CLI nao confere (SHA-256 diferente); nao instalei nada.'); return false; }
|
|
150
|
+
fs.mkdirSync(aberto, { recursive: true });
|
|
151
|
+
const abriu = d.run('/usr/bin/ditto', ['-x', '-k', zip, aberto]);
|
|
152
|
+
const novo = path.join(aberto, 'op');
|
|
153
|
+
if (!abriu.ok || !fs.existsSync(novo)) { d.out.err('Nao consegui abrir o pacote do 1Password CLI.'); return false; }
|
|
154
|
+
if (!assinadoPelaAgilebits(novo, d.run)) { d.out.err('O 1Password CLI baixado nao tem a assinatura da AgileBits; nao instalei.'); return false; }
|
|
155
|
+
fs.mkdirSync(destino, { recursive: true });
|
|
156
|
+
const final = path.join(destino, 'op');
|
|
157
|
+
const provisorio = path.join(destino, `.op.auroq-${process.pid}.tmp`);
|
|
158
|
+
fs.copyFileSync(novo, provisorio);
|
|
159
|
+
fs.chmodSync(provisorio, 0o755);
|
|
160
|
+
fs.renameSync(provisorio, final);
|
|
161
|
+
d.run('/usr/bin/xattr', ['-d', 'com.apple.quarantine', final]); // melhor esforco: curl nao marca quarentena
|
|
162
|
+
refreshPath({ platform: 'darwin', home: d.home });
|
|
163
|
+
return true;
|
|
164
|
+
} catch (e) {
|
|
165
|
+
d.out.err(`Nao consegui instalar o 1Password CLI em ${destino} (${oneLine(e && e.message, 120)}).`);
|
|
166
|
+
return false;
|
|
167
|
+
} finally {
|
|
168
|
+
if (tmp) { try { fs.rmSync(tmp, { recursive: true, force: true }); } catch { /* fica no temp do sistema */ } }
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
87
172
|
function installOp({ upgrade = false } = {}) {
|
|
88
173
|
if (process.platform === 'darwin') {
|
|
89
|
-
if (!hasCommand('brew'))
|
|
90
|
-
err('Homebrew nao encontrado. Instale em https://brew.sh e rode este comando de novo.');
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
174
|
+
if (!hasCommand('brew')) return installOpDirect();
|
|
93
175
|
log(`${DIM}${upgrade ? 'Atualizando' : 'Instalando'} via Homebrew (pode levar um minuto)...${RESET}`);
|
|
94
176
|
const brewCmd = upgrade ? 'brew upgrade --cask 1password-cli' : 'brew install --cask 1password-cli';
|
|
95
177
|
try {
|
|
96
178
|
execSync(brewCmd, { stdio: 'pipe', maxBuffer: 64 * 1024 * 1024, env: opEnv() });
|
|
97
179
|
return true;
|
|
98
180
|
} catch {
|
|
99
|
-
|
|
100
|
-
return
|
|
181
|
+
warn(`${upgrade ? 'Atualizacao' : 'Instalacao'} via Homebrew falhou; vou usar o pacote oficial do 1Password.`);
|
|
182
|
+
return installOpDirect();
|
|
101
183
|
}
|
|
102
184
|
}
|
|
103
185
|
|
|
@@ -392,6 +474,7 @@ async function connectManual() {
|
|
|
392
474
|
|
|
393
475
|
// 1. Garantir que o op existe
|
|
394
476
|
step('Verificando 1Password CLI...');
|
|
477
|
+
refreshPath();
|
|
395
478
|
let version = opVersion();
|
|
396
479
|
if (!version) {
|
|
397
480
|
warn('1Password CLI (op) nao encontrado — instalando...');
|
|
@@ -469,6 +552,8 @@ const SERVICE_ACCOUNT_NAME = 'Auroq';
|
|
|
469
552
|
// `op service-account create` so existe desde a 2.26.0 e `op whoami` so diz QUEM esta falando (conta de
|
|
470
553
|
// servico ou pessoa) desde a 2.30.0; o teste final precisa dessa resposta, entao o minimo e a 2.30.0.
|
|
471
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)
|
|
472
557
|
const INTEGRATION_URL = 'onepassword://settings/developers'; // abre o app direto em Configuracoes > Desenvolvedor
|
|
473
558
|
const BACKUP_TITLE = 'Auroq OS - token da conta de servico';
|
|
474
559
|
const BACKUP_TAG = 'auroq-os';
|
|
@@ -663,7 +748,7 @@ function enableIntegrationSteps(platform) {
|
|
|
663
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.'
|
|
664
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.';
|
|
665
750
|
const digital = platform === 'darwin'
|
|
666
|
-
? 'Para aprovar com a digital: Configuracoes > Seguranca > "Desbloquear com Touch ID
|
|
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.'
|
|
667
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.';
|
|
668
753
|
return [
|
|
669
754
|
'Abra o app do 1Password, entre na sua conta e deixe o app aberto.',
|
|
@@ -675,15 +760,15 @@ function enableIntegrationSteps(platform) {
|
|
|
675
760
|
|
|
676
761
|
/**
|
|
677
762
|
* Receita curta para ligar a digital (Windows Hello / Touch ID) quando nao esta ligada. A documentacao da integracao
|
|
678
|
-
* manda ligar o Windows Hello no Windows; no Mac o Touch ID e opcional (sem ele
|
|
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
|
|
679
764
|
* senha da conta 1Password NAO faz esse papel. O codigo nao liga nada disso por ele.
|
|
680
765
|
*/
|
|
681
766
|
function biometricSteps(platform) {
|
|
682
767
|
if (platform === 'darwin') {
|
|
683
768
|
return [
|
|
684
769
|
'No Mac, cadastre a digital: Ajustes do Sistema > Touch ID e Senha > Adicionar digital.',
|
|
685
|
-
'No 1Password: Configuracoes > Seguranca > marque "Desbloquear com Touch ID
|
|
686
|
-
'O Touch ID e opcional:
|
|
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").',
|
|
687
772
|
];
|
|
688
773
|
}
|
|
689
774
|
return [
|
|
@@ -771,11 +856,19 @@ function runOpInFreshShell(args) {
|
|
|
771
856
|
function openUrl(url) {
|
|
772
857
|
if (process.env.AUROQ_NAO_ABRIR_APP) return;
|
|
773
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.
|
|
774
861
|
const [cmd, args] = process.platform === 'darwin'
|
|
775
|
-
? ['open', [url]]
|
|
862
|
+
? ['open', ['-b', MAC_APP_BUNDLE, url]]
|
|
776
863
|
: (process.platform === 'win32' ? ['cmd', ['/c', 'start', '', url]] : ['xdg-open', [url]]);
|
|
777
864
|
const child = spawn(cmd, args, { stdio: 'ignore', detached: true, windowsHide: true, env: opEnv() });
|
|
778
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
|
+
}
|
|
779
872
|
child.unref();
|
|
780
873
|
} catch {
|
|
781
874
|
/* sem problema: a receita escrita continua valendo */
|
|
@@ -786,6 +879,17 @@ function openUrl(url) {
|
|
|
786
879
|
* Windows: o winget so muda o PATH no registro, nao no processo que ja esta rodando. Recompoe o
|
|
787
880
|
* PATH deste processo (maquina + usuario + pasta de links do winget) para achar o `op` recem-instalado.
|
|
788
881
|
*/
|
|
882
|
+
/**
|
|
883
|
+
* Recompõe o PATH deste processo para achar um `op` instalado depois que ele abriu. Mac/Linux: a pasta do `op`
|
|
884
|
+
* instalado sem Homebrew (~/.auroq/op/bin), se existir. Windows: o PATH do registro (o winget so muda o registro).
|
|
885
|
+
*/
|
|
886
|
+
function refreshPath({ platform = process.platform, home = os.homedir() } = {}) {
|
|
887
|
+
if (platform === 'win32') return refreshWindowsPath();
|
|
888
|
+
const bin = auroqOpBin(home);
|
|
889
|
+
const atual = String(process.env.PATH || '').split(':');
|
|
890
|
+
if (!atual.includes(bin) && fs.existsSync(path.join(bin, 'op'))) process.env.PATH = [bin, ...atual.filter(Boolean)].join(':');
|
|
891
|
+
}
|
|
892
|
+
|
|
789
893
|
function refreshWindowsPath() {
|
|
790
894
|
if (process.platform !== 'win32') return;
|
|
791
895
|
const parts = [];
|
|
@@ -997,12 +1101,12 @@ const APP_TEMP_PREFIX = 'auroq-1p-app-';
|
|
|
997
1101
|
* `finally` nao roda se o processo e morto). So mexe em pasta com esse prefixo e mais velha que `maxAgeMs`. Nunca lanca.
|
|
998
1102
|
* Devolve quantas apagou.
|
|
999
1103
|
*/
|
|
1000
|
-
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 } = {}) {
|
|
1001
1105
|
let apagadas = 0;
|
|
1002
1106
|
let nomes = [];
|
|
1003
1107
|
try { nomes = fs.readdirSync(root); } catch { return 0; }
|
|
1004
1108
|
for (const nome of nomes) {
|
|
1005
|
-
if (!nome.startsWith(
|
|
1109
|
+
if (!nome.startsWith(prefix)) continue;
|
|
1006
1110
|
const full = path.join(root, nome);
|
|
1007
1111
|
try {
|
|
1008
1112
|
const st = fs.lstatSync(full);
|
|
@@ -1020,23 +1124,82 @@ function makeAppDeps() {
|
|
|
1020
1124
|
lock: makeLock(),
|
|
1021
1125
|
hasCommand,
|
|
1022
1126
|
appInstalled: () => appInstalled(),
|
|
1023
|
-
refreshPath
|
|
1127
|
+
refreshPath,
|
|
1024
1128
|
run: runProgram,
|
|
1025
1129
|
runInstaller: runInstallerProgram,
|
|
1026
1130
|
onInterrupt: onInterruptCleanup,
|
|
1027
1131
|
sweepStale: () => sweepStaleAppDirs(),
|
|
1028
1132
|
download: downloadToFile,
|
|
1029
1133
|
checkSignature: checkInstallerSignature,
|
|
1134
|
+
macAppDirect: () => installMacAppDirect(),
|
|
1030
1135
|
mkdtemp: () => fs.mkdtempSync(path.join(os.tmpdir(), APP_TEMP_PREFIX)),
|
|
1031
1136
|
out: { log, step, ok, warn, err },
|
|
1032
1137
|
};
|
|
1033
1138
|
}
|
|
1034
1139
|
|
|
1140
|
+
// App do 1Password para Mac: o zip oficial do app inteiro (o mesmo arquivo que o Homebrew baixa, sem fixar versao).
|
|
1141
|
+
const MAC_APP_URL = (arch) => `https://downloads.1password.com/mac/1Password-latest-${arch === 'arm64' ? 'aarch64' : 'x86_64'}.zip`;
|
|
1142
|
+
|
|
1143
|
+
/**
|
|
1144
|
+
* Mac sem Homebrew: baixa o app oficial, confere a assinatura da AgileBits (Team ID e `codesign --deep`) e copia para
|
|
1145
|
+
* /Applications (o aluno administrador do Mac grava ali sem senha) ou, sem permissao, para ~/Applications.
|
|
1146
|
+
* Nunca lanca. { ok, motivo, destino }. `deps` so existe para os testes.
|
|
1147
|
+
*/
|
|
1148
|
+
async function installMacAppDirect(deps = {}) {
|
|
1149
|
+
const d = {
|
|
1150
|
+
arch: process.arch,
|
|
1151
|
+
home: os.homedir(),
|
|
1152
|
+
run: (cmd, args, o) => runProgram(cmd, args, o),
|
|
1153
|
+
download: downloadToFile,
|
|
1154
|
+
mkdtemp: () => fs.mkdtempSync(path.join(os.tmpdir(), APP_TEMP_PREFIX)),
|
|
1155
|
+
gravavel: (dir) => { try { fs.accessSync(dir, fs.constants.W_OK); return true; } catch { return false; } },
|
|
1156
|
+
aplicativos: '/Applications',
|
|
1157
|
+
out: { log },
|
|
1158
|
+
...deps,
|
|
1159
|
+
};
|
|
1160
|
+
let dir = null;
|
|
1161
|
+
let provisorio = null;
|
|
1162
|
+
try {
|
|
1163
|
+
dir = d.mkdtemp();
|
|
1164
|
+
const zip = path.join(dir, '1Password.zip');
|
|
1165
|
+
const aberto = path.join(dir, 'aberto');
|
|
1166
|
+
d.out.log(`${DIM}Baixando o app oficial do 1Password (uns 220 MB, pode levar alguns minutos)...${RESET}`);
|
|
1167
|
+
try {
|
|
1168
|
+
await d.download(MAC_APP_URL(d.arch), zip);
|
|
1169
|
+
} catch (e) {
|
|
1170
|
+
return { ok: false, motivo: `Nao consegui baixar o app oficial (${oneLine(e && e.message, 120)}).` };
|
|
1171
|
+
}
|
|
1172
|
+
fs.mkdirSync(aberto, { recursive: true });
|
|
1173
|
+
const abriu = d.run('/usr/bin/ditto', ['-x', '-k', zip, aberto]);
|
|
1174
|
+
const app = path.join(aberto, '1Password.app');
|
|
1175
|
+
if (!abriu.ok || !fs.existsSync(app)) return { ok: false, motivo: 'Nao consegui abrir o pacote do app.' };
|
|
1176
|
+
if (!assinadoPelaAgilebits(app, d.run, { deep: true })) return { ok: false, motivo: 'O app baixado nao tem a assinatura da AgileBits; nao instalei.' };
|
|
1177
|
+
const pasta = d.gravavel(d.aplicativos) ? d.aplicativos : path.join(d.home, 'Applications');
|
|
1178
|
+
fs.mkdirSync(pasta, { recursive: true });
|
|
1179
|
+
const destino = path.join(pasta, '1Password.app');
|
|
1180
|
+
// copia para um nome provisorio na mesma pasta, confere a COPIA e so entao poe o nome final: uma copia pela metade
|
|
1181
|
+
// nunca fica com cara de app instalado (o appInstalled olha so se a pasta existe)
|
|
1182
|
+
provisorio = path.join(pasta, `.1Password.app.auroq-${process.pid}.tmp`);
|
|
1183
|
+
const copiou = d.run('/usr/bin/ditto', [app, provisorio]);
|
|
1184
|
+
if (!copiou.ok) return { ok: false, motivo: `Nao consegui copiar o app para ${pasta}.` };
|
|
1185
|
+
if (!assinadoPelaAgilebits(provisorio, d.run, { deep: true })) return { ok: false, motivo: `A copia do app em ${pasta} nao passou na conferencia da assinatura; nao instalei.` };
|
|
1186
|
+
fs.renameSync(provisorio, destino);
|
|
1187
|
+
provisorio = null;
|
|
1188
|
+
return { ok: true, destino };
|
|
1189
|
+
} catch (e) {
|
|
1190
|
+
return { ok: false, motivo: `Nao consegui instalar o app (${oneLine(e && e.message, 120)}).` };
|
|
1191
|
+
} finally {
|
|
1192
|
+
if (provisorio) { try { fs.rmSync(provisorio, { recursive: true, force: true }); } catch { /* sobra so uma pasta oculta */ } }
|
|
1193
|
+
if (dir) { try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* fica no temp; a proxima execucao varre */ } }
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1035
1197
|
const APP_NEXT_STEPS = 'Agora abra o app do 1Password, entre na sua conta (ou crie uma: guarde a Secret Key e o Kit de Emergencia que ele oferece) e ligue "Integrar com a CLI do 1Password" em Configuracoes > Desenvolvedor. Depois rode o conectar-1password de novo.';
|
|
1036
1198
|
|
|
1037
1199
|
/**
|
|
1038
1200
|
* Instala o app do 1Password (`conectar-1password --instalar-app`). Windows: winget e, se ele falhar, o instalador
|
|
1039
|
-
* oficial, so rodado depois de conferir a assinatura (valida e do publicador Agilebits). Mac: Homebrew (cask)
|
|
1201
|
+
* oficial, so rodado depois de conferir a assinatura (valida e do publicador Agilebits). Mac: Homebrew (cask), se o aluno
|
|
1202
|
+
* tiver; senao (ou se ele falhar), o pacote oficial com a assinatura da AgileBits conferida (installMacAppDirect).
|
|
1040
1203
|
* O app e um programa novo no computador do aluno: quem chama pede o OK dele antes.
|
|
1041
1204
|
*/
|
|
1042
1205
|
async function installApp(deps = {}) {
|
|
@@ -1062,11 +1225,16 @@ async function installApp(deps = {}) {
|
|
|
1062
1225
|
};
|
|
1063
1226
|
|
|
1064
1227
|
if (d.platform === 'darwin') {
|
|
1065
|
-
if (
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1228
|
+
if (d.hasCommand('brew')) {
|
|
1229
|
+
out.log(`${DIM}Instalando via Homebrew (pode levar um minuto)...${RESET}`);
|
|
1230
|
+
const r = d.run('brew', ['install', '--cask', '1password']);
|
|
1231
|
+
if (r.ok || d.appInstalled()) return pronto('Homebrew');
|
|
1232
|
+
out.warn(`A instalacao via Homebrew falhou (${r.timedOut ? 'demorou demais' : `codigo de saida ${r.status}`}). Vou usar o pacote oficial do 1Password.`);
|
|
1233
|
+
}
|
|
1234
|
+
// Mac de aluno nao tem Homebrew (e instalar o Homebrew pede senha de admin): pacote oficial, assinatura conferida.
|
|
1235
|
+
const m = await d.macAppDirect();
|
|
1236
|
+
if (m.ok) return pronto('pacote oficial do 1Password');
|
|
1237
|
+
return falhou(m.motivo || 'Nao consegui instalar o app pelo pacote oficial.');
|
|
1070
1238
|
}
|
|
1071
1239
|
|
|
1072
1240
|
// Windows 1: winget (rapido, mas falhou no teste real com 0x80070002 mesmo depois de conferir o hash)
|
|
@@ -1266,11 +1434,30 @@ function makeProgress(d) {
|
|
|
1266
1434
|
};
|
|
1267
1435
|
}
|
|
1268
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
|
+
|
|
1269
1455
|
function makeDefaultDeps() {
|
|
1270
1456
|
return {
|
|
1271
1457
|
platform: process.platform,
|
|
1272
1458
|
openUrl,
|
|
1273
|
-
refreshPath
|
|
1459
|
+
refreshPath,
|
|
1460
|
+
resetAppData: () => (process.platform === 'darwin' ? resetMacAppData() : false),
|
|
1274
1461
|
appInstalled: () => appInstalled(),
|
|
1275
1462
|
lock: makeLock(),
|
|
1276
1463
|
state: { read: readState, write: writeState },
|
|
@@ -1386,12 +1573,9 @@ function saveBackup(d, account, token) {
|
|
|
1386
1573
|
// conforme o plano (e pode vir traduzido), por isso nunca se usa o nome.
|
|
1387
1574
|
const body = JSON.stringify(backupItemTemplate(token));
|
|
1388
1575
|
const common = ['--tags', BACKUP_TAG, '--account', accountKey(account), '--format', 'json'];
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
}
|
|
1393
|
-
// Windows: o `op item create -` por pipe falha no PowerShell 5.1 ("invalid JSON in piped input", teste real de 27/10),
|
|
1394
|
-
// 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
|
|
1395
1579
|
// continua fora de argumento e de log; o arquivo e apagado em seguida, aconteca o que acontecer. O `op` roda com o
|
|
1396
1580
|
// stdin desligado (`noStdin`): com `--template` ele nao aceita nenhum pipe, nem vazio.
|
|
1397
1581
|
let tmp = null;
|
|
@@ -1413,8 +1597,15 @@ function saveBackup(d, account, token) {
|
|
|
1413
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
|
|
1414
1598
|
* Windows). UTF-8 SEM BOM: o BOM faria o `op` recusar o JSON. Devolve { dir, file }; quem chama apaga a pasta.
|
|
1415
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
|
+
*/
|
|
1416
1606
|
function writeTemplateFile(content) {
|
|
1417
|
-
|
|
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));
|
|
1418
1609
|
const file = path.join(dir, 'molde.json');
|
|
1419
1610
|
try {
|
|
1420
1611
|
fs.writeFileSync(file, Buffer.from(content, 'utf8'), { flag: 'wx', mode: 0o600 });
|
|
@@ -1654,6 +1845,11 @@ async function runConnect(opts, d, prog) {
|
|
|
1654
1845
|
prog.reset('app');
|
|
1655
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.');
|
|
1656
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
|
+
}
|
|
1657
1853
|
if (!accounts.length) {
|
|
1658
1854
|
showIntegrationHelp(d, out);
|
|
1659
1855
|
if (waitSeconds > 0) {
|
|
@@ -1668,7 +1864,7 @@ async function runConnect(opts, d, prog) {
|
|
|
1668
1864
|
}
|
|
1669
1865
|
if (!accounts.length) {
|
|
1670
1866
|
prog.reset('app');
|
|
1671
|
-
return fim('aguardando', 'ligar-integracao',
|
|
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}` : ''}`);
|
|
1672
1868
|
}
|
|
1673
1869
|
}
|
|
1674
1870
|
prog.mark('conta', 'app');
|
|
@@ -1884,6 +2080,7 @@ async function finishWithToken(d, out, account, token, prog, { reaproveitada, ol
|
|
|
1884
2080
|
function describeState(d) {
|
|
1885
2081
|
const saved = d.state.read();
|
|
1886
2082
|
const etapas = saved ? { ...saved.etapas } : emptyEtapas();
|
|
2083
|
+
if (typeof d.refreshPath === 'function') d.refreshPath(); // Mac sem Homebrew: o op mora em ~/.auroq/op/bin
|
|
1887
2084
|
const version = d.opVersion();
|
|
1888
2085
|
const opOk = Boolean(version && isVersionAtLeast(version));
|
|
1889
2086
|
etapas.op = opOk ? 'feito' : 'pendente';
|
|
@@ -2036,6 +2233,12 @@ module.exports = {
|
|
|
2036
2233
|
auroqDir,
|
|
2037
2234
|
openUrl,
|
|
2038
2235
|
refreshWindowsPath,
|
|
2236
|
+
refreshPath,
|
|
2237
|
+
installOpDirect,
|
|
2238
|
+
installMacAppDirect,
|
|
2239
|
+
resetMacAppData,
|
|
2240
|
+
auroqOpBin,
|
|
2241
|
+
OP_DIRETO,
|
|
2039
2242
|
runOp,
|
|
2040
2243
|
TOKEN_VAR,
|
|
2041
2244
|
MIN_OP_VERSION,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schema": 1,
|
|
3
3
|
"algorithm": "sha256",
|
|
4
4
|
"scope": "packaged-runtime-and-cascas",
|
|
5
|
-
"version": "2.8.
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
301
|
+
"sha256": "f78cae244b3efba3bd354a6efbd0805bb761f2d6acece93426dfd0b02bbc7b0f"
|
|
302
302
|
},
|
|
303
303
|
{
|
|
304
304
|
"path": "scripts/build-pack.mjs",
|