auroq-os 2.1.1 → 2.1.3
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/.claude/commands/AuroqOS/agents/ops.md +38 -65
- package/README.md +1 -0
- package/bin/auroq-os.js +23 -13
- package/docs/stories/AUROQ-2.1.0-hybrid-runtime.md +1 -0
- package/lib/onepassword.js +268 -0
- package/package.json +2 -2
- package/scripts/sync-codex-skills.mjs +5 -1
- package/tests/installer-hybrid.test.mjs +6 -0
- package/tests/onepassword.test.mjs +54 -0
- package/tests/sync-codex-skills.test.mjs +21 -0
|
@@ -42,7 +42,7 @@ persona:
|
|
|
42
42
|
7. Atualizar o Pack Arcane — atualizar os squads de marketing (update-packarcane)
|
|
43
43
|
8. Checar saude — diagnostico completo do ambiente
|
|
44
44
|
9. Setup do nucleo — bootstrap do zero (ambiente, MCPs, GitHub, Vercel, Supabase, Companion)
|
|
45
|
-
10. Conexoes extras — bootstrap-2 (opcional, recomendado):
|
|
45
|
+
10. Conexoes extras — bootstrap-2 (opcional, recomendado): Cloudflare, Drive, Gmail, Calendar, Notion, Canva
|
|
46
46
|
|
|
47
47
|
O que precisa?
|
|
48
48
|
|
|
@@ -62,7 +62,7 @@ commands:
|
|
|
62
62
|
- name: bootstrap
|
|
63
63
|
description: "Setup do nucleo (Bootstrap 1) — ambiente, MCPs, GitHub, Vercel, Supabase, Companion"
|
|
64
64
|
- name: bootstrap-2
|
|
65
|
-
description: "Conexoes extras (Bootstrap 2, opcional recomendado) —
|
|
65
|
+
description: "Conexoes extras (Bootstrap 2, opcional recomendado) — valida o cofre e conecta Cloudflare, Drive, Gmail, Calendar, Notion e Canva"
|
|
66
66
|
- name: yolo
|
|
67
67
|
description: "Trocar modo de permissao do Claude Code (auto/acceptEdits/default)"
|
|
68
68
|
- name: install
|
|
@@ -199,6 +199,8 @@ A validacao e ONLINE e obrigatoria, sem fallback offline — e a mesma trava do
|
|
|
199
199
|
|
|
200
200
|
**Passo 4 — Aplicar atualizacao (framework only)**
|
|
201
201
|
|
|
202
|
+
> **FONTE DA LISTA (OBRIGATORIO):** a lista ATUALIZA abaixo e a da versao INSTALADA — ela pode nao conhecer arquivos que a versao nova introduziu. ANTES de aplicar, abra o ops.md NOVO em `/tmp/auroq-update/package/.claude/commands/AuroqOS/agents/ops.md`, secao `*update-auroq`, e siga a lista ATUALIZA DE LA (mantendo as protecoes de NAO ATUALIZA de la tambem). SE nao conseguir ler o arquivo novo, fallback deterministico: rodar `node /tmp/auroq-update/package/bin/auroq-os.js init` na raiz do projeto (sobrescreve framework, preserva dados do expert) — mas AVISE antes se o `.claude/CLAUDE.md` foi personalizado, pois o init sobrescreve ele.
|
|
203
|
+
|
|
202
204
|
**ATUALIZA (L1/L2/L3 — framework):**
|
|
203
205
|
- `.auroq-core/` — constitution, config, synapse engine, development docs, dna operacional
|
|
204
206
|
- `.claude/commands/AuroqOS/` — agentes core (ops.md)
|
|
@@ -801,7 +803,8 @@ REGRAS DO FECHO:
|
|
|
801
803
|
- NAO revelar o comando de ativacao do Companion aqui. A plataforma faz isso no momento certo (First Win).
|
|
802
804
|
- Dizer "num chat novo" — as configuracoes (MCPs, comandos, CLAUDE.md) so carregam na abertura de um chat novo.
|
|
803
805
|
- Direcionar SEMPRE de volta pra plataforma. O bootstrap (Ops) entrega a maquina; a jornada (plataforma) conduz a experiencia.
|
|
804
|
-
-
|
|
806
|
+
- O sistema de senhas e credenciais (1Password ou vault local) e tratado no Step proprio da plataforma, depois deste bootstrap e antes do `*bootstrap-2`.
|
|
807
|
+
- As conexoes extras (Cloudflare, Drive, Gmail, Calendar, Notion, Canva) NAO entram aqui — sao o `*bootstrap-2` (opcional, recomendado), que o expert roda quando precisar. A plataforma chama na hora certa.
|
|
805
808
|
|
|
806
809
|
---
|
|
807
810
|
|
|
@@ -843,69 +846,39 @@ Conexoes extras — OPCIONAL, RECOMENDADO. Roda depois do bootstrap principal, q
|
|
|
843
846
|
|
|
844
847
|
**Mesmas regras do bootstrap principal:** uma coisa por vez, linguagem de leigo (nunca assumir que ele sabe termo tecnico), Ops FAZ / expert aprova, detectar Mac vs Windows, e GATE DE VERIFICACAO — nunca dizer "conectado" sem rodar o teste real.
|
|
845
848
|
|
|
849
|
+
**PRE-GATE OBRIGATORIO — sistema de senhas e credenciais:** antes de listar ou conectar qualquer servico, verificar como as credenciais serao armazenadas. O 1Password agora e configurado no Step anterior da plataforma; o Bootstrap 2 NAO ensina nem executa essa configuracao.
|
|
850
|
+
|
|
851
|
+
1. Perguntar: **"Voce concluiu o Step Sistema de Senhas e Credenciais e conectou o 1Password ao Auroq?"**
|
|
852
|
+
2. SE responder sim, verificar sem expor segredos:
|
|
853
|
+
- `op --version` funciona
|
|
854
|
+
- o vault `Claude` aparece em `op vault list`
|
|
855
|
+
- `OP_SERVICE_ACCOUNT_TOKEN` esta definido, mostrando somente `configurado` ou `ausente` — NUNCA imprimir o valor
|
|
856
|
+
- uma leitura de item de teste no vault funciona, quando existir
|
|
857
|
+
3. SE a verificacao passar: definir o modo desta execucao como **`1password`**. Toda nova credencial sensivel deve ser salva no vault `Claude`; em `business/vault/`, guardar apenas metadados/status e referencias `op://...`, nunca o valor.
|
|
858
|
+
4. SE nao configurou ou a verificacao falhar, explicar as duas opcoes e pedir uma escolha explicita:
|
|
859
|
+
- **Voltar ao Step anterior e configurar o 1Password (RECOMENDADO):** parar o Bootstrap 2 sem marcar conexoes como concluidas. Quando o aluno voltar, repetir este pre-gate.
|
|
860
|
+
- **Seguir com vault local (MENOS SEGURO):** continuar usando `business/vault/`, que fica fora do git, deixando claro que as credenciais permanecem em texto local e devem ser migradas depois.
|
|
861
|
+
5. Registrar somente a escolha e o estado em `business/vault/credential-storage.md`: `mode: 1password` ou `mode: local`, data e verificacoes realizadas. NUNCA registrar token, senha ou chave nesse arquivo quando o modo for 1Password.
|
|
862
|
+
|
|
863
|
+
**POLITICA DURANTE TODO O BOOTSTRAP 2:**
|
|
864
|
+
- Modo `1password`: salvar segredos novos diretamente no vault `Claude`, com nome claro por servico. Nao pedir que o aluno cole segredo no chat e nao ecoar valores no output. Arquivos locais recebem apenas referencia `op://Claude/...` e status.
|
|
865
|
+
- Modo `local`: salvar em `business/vault/{servico}.md`, garantir que `business/vault/` esta no `.gitignore` e lembrar no relatorio final que esse modo e menos seguro.
|
|
866
|
+
- OAuth/MCP sem chave manual: registrar apenas status e identificador da conexao; nao inventar credencial.
|
|
867
|
+
- Se a escolha de armazenamento estiver indefinida, NAO iniciar nenhuma conexao que gere ou solicite segredo.
|
|
868
|
+
|
|
846
869
|
**Menu:**
|
|
847
|
-
1. **
|
|
848
|
-
2. **
|
|
849
|
-
3. **
|
|
850
|
-
4. **
|
|
851
|
-
5. **
|
|
852
|
-
6. **
|
|
853
|
-
7. **Canva** — designs
|
|
870
|
+
1. **Cloudflare** — dominio proprio
|
|
871
|
+
2. **Google Drive** — arquivos e backup
|
|
872
|
+
3. **Gmail** — ler e enviar emails
|
|
873
|
+
4. **Google Calendar** — eventos
|
|
874
|
+
5. **Notion** — paginas
|
|
875
|
+
6. **Canva** — designs
|
|
854
876
|
|
|
855
877
|
(n8n, Z-API e WhatsApp em escala NAO estao aqui — sao infra de operacao avancada, ficam na fase Turbinando da jornada.)
|
|
856
878
|
|
|
857
879
|
---
|
|
858
880
|
|
|
859
|
-
#### 1.
|
|
860
|
-
|
|
861
|
-
**O que e:** hoje tuas chaves ficam num cofre local (`business/vault/`, fora do git). Funciona pra comecar. O 1Password sobe isso pra um cofre CIFRADO — mesmo que vaze, a chave fica inutil sem a senha mestra.
|
|
862
|
-
|
|
863
|
-
**Quando vale:** quando o expert ja tem credencial sensivel em uso (Service Key do Supabase, token de pagamento, Meta).
|
|
864
|
-
|
|
865
|
-
**OPCIONAL — a escolha do expert:** ele pode **(a) configurar o 1Password** (a sequencia abaixo) ou **(b) continuar so no cofre local** (`business/vault/`, que ja funciona e protege). **O Ops PERGUNTA antes de comecar** — so segue se o expert topar. E e tudo-ou-nada: faz a sequencia completa ou fica no cofre local; meio-1Password nao serve.
|
|
866
|
-
|
|
867
|
-
**🔒 REGRA DE OURO — INEGOCIAVEL (se for configurar):**
|
|
868
|
-
- **NUNCA pedir, ver, ecoar ou colar um token/credencial no chat.** Token que aparece no chat = COMPROMETIDO (vai pro historico). Tem que ser revogado.
|
|
869
|
-
- O token vai **DIRETO no arquivo, pelo PROPRIO expert**. O Ops **prepara a linha**; o expert **cola o valor**. O Ops **nunca ve** o token.
|
|
870
|
-
- Na migracao das chaves, o Ops **NAO le os valores** do `business/vault/` pra recriar (isso jogaria a credencial no contexto). Quem move os valores e o expert.
|
|
871
|
-
|
|
872
|
-
---
|
|
873
|
-
|
|
874
|
-
**PROCESSO PADRAO (a sequencia ideal):**
|
|
875
|
-
|
|
876
|
-
1. **App desktop** — instalar o app do 1Password (1password.com/downloads — o programa, nao a extensao do navegador) e logar na conta.
|
|
877
|
-
2. **CLI `op`** — instalar (e o programa que usa a credencial no terminal): macOS `brew install 1password-cli` · Windows `winget install AgileBits.1Password.CLI` · verificar `op --version`.
|
|
878
|
-
3. **Conectar o `op` ao app** — no app: **Settings → Developer → "Integrate with 1Password CLI"** + ligar Touch ID (Mac) / Windows Hello (Win). Testar: `op vault list` (pede biometria) — listou = CLI conectado.
|
|
879
|
-
4. **Vault "Claude"** — garantir que existe o vault **Claude** (so o que o Auroq vai acessar). Criar no app se ainda nao tiver.
|
|
880
|
-
5. **Gerar o token (Service Account)** — my.1password.com → Developer → Service Accounts → Criar → dar acesso **Read + Write ao vault "Claude"** → gerar o token (`ops_...`, aparece UMA vez so, copiar na hora). **NAO precisa de plano Business.**
|
|
881
|
-
6. **Colar o token no shell SEM expor na conversa.** O Ops prepara a linha (`# export OP_SERVICE_ACCOUNT_TOKEN="COLE_AQUI"`) e o expert cola o valor de um jeito que NAO ecoa (rodar no terminal dele):
|
|
882
|
-
`read -s TOKEN && echo "export OP_SERVICE_ACCOUNT_TOKEN=\"$TOKEN\"" >> ~/.zshenv && unset TOKEN`
|
|
883
|
-
Recarregar: terminal novo ou `source ~/.zshenv`. **O Ops NUNCA ve o valor.**
|
|
884
|
-
7. **Pronto** — o Auroq usa o token (headless, so o vault Claude); o expert gerencia tudo pelo app. Testar: `op read "op://Claude/<item>/<campo>"` retorna o valor.
|
|
885
|
-
|
|
886
|
-
(Por que app + CLI **e** token: o app+CLI deixa voce gerenciar o cofre por biometria; o token Service Account e o que o agente usa pra operar sozinho, sem precisar de Touch ID a cada vez.)
|
|
887
|
-
|
|
888
|
-
---
|
|
889
|
-
|
|
890
|
-
**MIGRAR AS CHAVES (sem o Ops tocar nos valores):**
|
|
891
|
-
1. Criar o vault **"Claude"** no 1Password (so o que o Auroq pode acessar; o resto fica privado).
|
|
892
|
-
2. **O EXPERT** copia cada credencial do `business/vault/*.md` e cria o item no vault "Claude" (pelo app). O Ops diz QUAIS itens criar (ex: "Supabase — Service Key", "Vercel"), mas **NAO le nem digita os valores**.
|
|
893
|
-
3. Migrado e testado → **esvaziar** `business/vault/` (deixar so um `README.md` dizendo que as chaves vivem no 1Password).
|
|
894
|
-
|
|
895
|
-
**GATE DE VERIFICACAO (BLOCKING):**
|
|
896
|
-
1. `op --version` retorna versao (CLI instalado)
|
|
897
|
-
2. `op vault list` funciona (CLI conectado ao app)
|
|
898
|
-
3. `op read "op://Claude/<item>/<campo>"` retorna o valor (token no env funcionando)
|
|
899
|
-
4. `business/vault/` sem chave em texto plano (so o README)
|
|
900
|
-
→ SE qualquer um falhar: resolver antes de dizer "conectado".
|
|
901
|
-
|
|
902
|
-
**Nota honesta:** mesmo cifrado, na hora que o agente USA a chave ela passa pelo terminal. O 1Password resolve o ARMAZENAMENTO (nao ficar em texto plano no disco/nuvem) — que e o grosso do risco.
|
|
903
|
-
|
|
904
|
-
→ Check: "1Password conectado (A: shell / B: desktop), chaves migradas, vault local esvaziado" ou "Pulado (segue no cofre local)"
|
|
905
|
-
|
|
906
|
-
---
|
|
907
|
-
|
|
908
|
-
#### 2. Cloudflare — dominio proprio
|
|
881
|
+
#### 1. Cloudflare — dominio proprio
|
|
909
882
|
|
|
910
883
|
**O que e:** o Cloudflare gerencia teu dominio (o endereco do negocio, tipo seunegocio.com). Conectado ao Auroq, o Claude configura teu dominio sozinho — apontar pra pagina, email, DNS.
|
|
911
884
|
|
|
@@ -930,7 +903,7 @@ Conexoes extras — OPCIONAL, RECOMENDADO. Roda depois do bootstrap principal, q
|
|
|
930
903
|
|
|
931
904
|
---
|
|
932
905
|
|
|
933
|
-
####
|
|
906
|
+
#### 2. Google Drive — arquivos e backup
|
|
934
907
|
|
|
935
908
|
**IMPORTANTE:** O rclone config e INTERATIVO — ele faz perguntas no terminal que o Claude Code nao consegue responder. Por isso, o expert precisa rodar em um terminal separado.
|
|
936
909
|
|
|
@@ -970,7 +943,7 @@ Quando o expert avisar que terminou:
|
|
|
970
943
|
|
|
971
944
|
---
|
|
972
945
|
|
|
973
|
-
####
|
|
946
|
+
#### 3. Gmail
|
|
974
947
|
1. Registrar: `claude mcp add gmail -- npx gmail-mcp-server`
|
|
975
948
|
2. Informar: "Agora preciso que voce digite `/mcp` e aperte Enter aqui no chat. Isso vai puxar a conexao do Gmail e abrir o navegador pra voce autorizar."
|
|
976
949
|
3. Expert digita `/mcp` → browser abre pra autenticar com Google
|
|
@@ -981,7 +954,7 @@ Quando o expert avisar que terminou:
|
|
|
981
954
|
|
|
982
955
|
---
|
|
983
956
|
|
|
984
|
-
####
|
|
957
|
+
#### 4. Google Calendar
|
|
985
958
|
1. Instruir: "Abre claude.ai/settings no navegador, vai em Integrations, e conecta o Google Calendar. Quando terminar, volta aqui."
|
|
986
959
|
2. Quando expert voltar: "Digita `/mcp` e aperta Enter."
|
|
987
960
|
3. Expert digita `/mcp` → ferramentas do Calendar aparecem
|
|
@@ -990,7 +963,7 @@ Quando o expert avisar que terminou:
|
|
|
990
963
|
|
|
991
964
|
---
|
|
992
965
|
|
|
993
|
-
####
|
|
966
|
+
#### 5. Notion
|
|
994
967
|
1. Instruir: "Abre claude.ai/settings no navegador, vai em Integrations, e conecta o Notion. Quando terminar, volta aqui."
|
|
995
968
|
2. Quando expert voltar: "Digita `/mcp` e aperta Enter."
|
|
996
969
|
3. Expert digita `/mcp` → ferramentas do Notion aparecem
|
|
@@ -999,7 +972,7 @@ Quando o expert avisar que terminou:
|
|
|
999
972
|
|
|
1000
973
|
---
|
|
1001
974
|
|
|
1002
|
-
####
|
|
975
|
+
#### 6. Canva
|
|
1003
976
|
1. Instruir: "Abre claude.ai/settings no navegador, vai em Integrations, e conecta o Canva. Quando terminar, volta aqui."
|
|
1004
977
|
2. Quando expert voltar: "Digita `/mcp` e aperta Enter."
|
|
1005
978
|
3. Expert digita `/mcp` → ferramentas do Canva aparecem
|
package/README.md
CHANGED
|
@@ -47,6 +47,7 @@ $companion
|
|
|
47
47
|
| `auroq-os logout` | Encerra sessao local (remove `~/.arcane/credentials.json`) |
|
|
48
48
|
| `auroq-os whoami` | Mostra usuario autenticado e status de acesso |
|
|
49
49
|
| `auroq-os sync-codex` | Regenera e verifica as skills locais do Codex |
|
|
50
|
+
| `auroq-os conectar-1password` | Conecta o 1Password CLI — le o token de Service Account direto do Ctrl+C (nunca digitado nem exposto), instala o `op` se faltar, valida online e salva permanente (Mac/Windows) |
|
|
50
51
|
|
|
51
52
|
Dentro do projeto instalado, os mesmos checks ficam disponiveis como
|
|
52
53
|
`npm run auroq:sync:codex`, `npm run auroq:sync:codex:check` e
|
package/bin/auroq-os.js
CHANGED
|
@@ -392,19 +392,9 @@ async function init() {
|
|
|
392
392
|
}
|
|
393
393
|
success(`${dirsCopied} modulos instalados (Meta Squads + scripts)`);
|
|
394
394
|
|
|
395
|
-
//
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
execSync('node scripts/sync-codex-skills.mjs --clean', { cwd: targetDir, stdio: 'pipe' });
|
|
399
|
-
execSync('node scripts/sync-codex-skills.mjs --check', { cwd: targetDir, stdio: 'pipe' });
|
|
400
|
-
success('Codex CLI pronto — skills locais verificadas ($companion, $ops, etc.)');
|
|
401
|
-
} catch {
|
|
402
|
-
warn('Sync de skills do Codex falhou — rode "npx auroq-os sync-codex" para diagnosticar');
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// ─── Fase 5: Package.json + npm install
|
|
406
|
-
step('Configurando dependencias...');
|
|
407
|
-
|
|
395
|
+
// O package.json precisa existir ANTES do sync do Codex: o ownership das
|
|
396
|
+
// skills usa o name do package — gerar skills antes dele criava marcador
|
|
397
|
+
// com o nome da pasta e todo sync seguinte recusava ("foreign").
|
|
408
398
|
const pkgPath = path.join(targetDir, 'package.json');
|
|
409
399
|
if (!(await fs.pathExists(pkgPath))) {
|
|
410
400
|
await fs.writeJSON(pkgPath, {
|
|
@@ -418,6 +408,19 @@ async function init() {
|
|
|
418
408
|
}, { spaces: 2 });
|
|
419
409
|
}
|
|
420
410
|
|
|
411
|
+
// ─── Fase 4c: Adaptador Codex CLI (skills locais por projeto)
|
|
412
|
+
step('Configurando Codex CLI...');
|
|
413
|
+
try {
|
|
414
|
+
execSync('node scripts/sync-codex-skills.mjs --clean', { cwd: targetDir, stdio: 'pipe' });
|
|
415
|
+
execSync('node scripts/sync-codex-skills.mjs --check', { cwd: targetDir, stdio: 'pipe' });
|
|
416
|
+
success('Codex CLI pronto — skills locais verificadas ($companion, $ops, etc.)');
|
|
417
|
+
} catch {
|
|
418
|
+
warn('Sync de skills do Codex falhou — rode "npx auroq-os sync-codex" para diagnosticar');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ─── Fase 5: npm install
|
|
422
|
+
step('Configurando dependencias...');
|
|
423
|
+
|
|
421
424
|
// Comandos namespaced: adiciona a manutencao Auroq sem sobrescrever scripts do negocio.
|
|
422
425
|
const targetPackage = await fs.readJSON(pkgPath);
|
|
423
426
|
targetPackage.scripts = targetPackage.scripts || {};
|
|
@@ -563,6 +566,12 @@ if (command === 'init' || command === 'install') {
|
|
|
563
566
|
log(` 3. Digite: ${CYAN}*update${RESET}`);
|
|
564
567
|
log('');
|
|
565
568
|
log(`${DIM}O Ops atualiza o framework sem tocar nos seus dados.${RESET}`);
|
|
569
|
+
} else if (command === 'conectar-1password' || command === 'connect-1password') {
|
|
570
|
+
const onepassword = require('../lib/onepassword');
|
|
571
|
+
onepassword.connect().catch(e => {
|
|
572
|
+
error(`Falha ao conectar 1Password: ${e.message}`);
|
|
573
|
+
process.exit(1);
|
|
574
|
+
});
|
|
566
575
|
} else if (command === 'sync-codex') {
|
|
567
576
|
// Regenera as skills do Codex CLI a partir dos comandos atuais do projeto.
|
|
568
577
|
try {
|
|
@@ -583,6 +592,7 @@ if (command === 'init' || command === 'install') {
|
|
|
583
592
|
log(` ${CYAN}check-access${RESET} Validar acesso ativo (online; exit 0=ativo, 1=bloqueado)`);
|
|
584
593
|
log(` ${CYAN}update${RESET} Atualizar (via Ops dentro do Claude Code)`);
|
|
585
594
|
log(` ${CYAN}sync-codex${RESET} Gerar e verificar as skills locais do Codex CLI ($nome)`);
|
|
595
|
+
log(` ${CYAN}conectar-1password${RESET} Conectar o 1Password CLI (token lido do Ctrl+C, nunca digitado)`);
|
|
586
596
|
log('');
|
|
587
597
|
log(`${DIM}Uso: npx auroq-os init${RESET}`);
|
|
588
598
|
log('');
|
|
@@ -61,3 +61,4 @@ Entregar o Auroq OS pronto para o aluno operar no Claude Code ou no Codex CLI, c
|
|
|
61
61
|
- 2026-06-09 — Story criada e implementacao iniciada.
|
|
62
62
|
- 2026-06-09 — Implementacao concluida e movida para InReview.
|
|
63
63
|
- 2026-06-09 — Patch 2.1.1 removeu wrapper orfao do Squad Edicao e integrou o sync automatico do Pack Arcane.
|
|
64
|
+
- 2026-06-10 — Patch 2.1.2 separou o Step de credenciais do Bootstrap 2 e adicionou o gate 1Password/vault local no Ops.
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Auroq OS — Conexao 1Password CLI
|
|
5
|
+
*
|
|
6
|
+
* Fluxo seguro: o aluno copia o token de Service Account (ops_...) e roda
|
|
7
|
+
* `npx auroq-os conectar-1password`. O token e lido direto da area de
|
|
8
|
+
* transferencia, validado online e gravado de forma persistente — nunca e
|
|
9
|
+
* digitado, impresso ou passado por argumento de linha de comando.
|
|
10
|
+
*
|
|
11
|
+
* @module auroq-os/onepassword
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const os = require('os');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const fs = require('fs-extra');
|
|
17
|
+
const { execSync, execFileSync } = require('child_process');
|
|
18
|
+
|
|
19
|
+
const PURPLE = '\x1b[38;2;120;80;200m';
|
|
20
|
+
const GOLD = '\x1b[38;2;245;158;11m';
|
|
21
|
+
const CYAN = '\x1b[38;2;34;211;238m';
|
|
22
|
+
const GREEN = '\x1b[38;2;52;211;153m';
|
|
23
|
+
const DIM = '\x1b[2m';
|
|
24
|
+
const BOLD = '\x1b[1m';
|
|
25
|
+
const RESET = '\x1b[0m';
|
|
26
|
+
|
|
27
|
+
function log(msg) { console.log(` ${msg}`); }
|
|
28
|
+
function step(msg) { console.log(`\n ${CYAN}▸${RESET} ${msg}`); }
|
|
29
|
+
function ok(msg) { console.log(` ${GREEN}✓${RESET} ${msg}`); }
|
|
30
|
+
function warn(msg) { console.log(` ${GOLD}!${RESET} ${msg}`); }
|
|
31
|
+
function err(msg) { console.log(` ${PURPLE}✗${RESET} ${msg}`); }
|
|
32
|
+
|
|
33
|
+
const TOKEN_VAR = 'OP_SERVICE_ACCOUNT_TOKEN';
|
|
34
|
+
|
|
35
|
+
// ─── Helpers puros (testaveis) ───────────────────────────
|
|
36
|
+
|
|
37
|
+
function isValidToken(value) {
|
|
38
|
+
return typeof value === 'string' && /^ops_[A-Za-z0-9_=+/.-]{20,}$/.test(value);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Remove qualquer linha antiga do token e devolve o conteudo novo do arquivo
|
|
43
|
+
* de ambiente com a linha export atualizada no final.
|
|
44
|
+
*/
|
|
45
|
+
const ENV_COMMENT = '# 1Password CLI - Service Account (gravado pelo Auroq OS)';
|
|
46
|
+
|
|
47
|
+
const TOKEN_LINE_RE = /^\s*(export\s+)?OP_SERVICE_ACCOUNT_TOKEN=/;
|
|
48
|
+
|
|
49
|
+
function upsertEnvFileContent(existing, token) {
|
|
50
|
+
// Remove apenas linhas que DEFINEM a variavel (nao comentarios do usuario
|
|
51
|
+
// que so mencionam o nome) + o marcador de bloco do Auroq.
|
|
52
|
+
const kept = String(existing || '')
|
|
53
|
+
.split('\n')
|
|
54
|
+
.filter((line) => !TOKEN_LINE_RE.test(line) && line.trim() !== ENV_COMMENT);
|
|
55
|
+
while (kept.length > 0 && kept[kept.length - 1].trim() === '') kept.pop();
|
|
56
|
+
kept.push('');
|
|
57
|
+
kept.push(ENV_COMMENT);
|
|
58
|
+
kept.push(`export ${TOKEN_VAR}='${token}'`);
|
|
59
|
+
kept.push('');
|
|
60
|
+
return kept.join('\n');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ─── Deteccao e instalacao do op ─────────────────────────
|
|
64
|
+
|
|
65
|
+
function hasCommand(cmd) {
|
|
66
|
+
try {
|
|
67
|
+
const probe = process.platform === 'win32' ? `where ${cmd}` : `command -v ${cmd}`;
|
|
68
|
+
execSync(probe, { stdio: 'pipe' });
|
|
69
|
+
return true;
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function opVersion() {
|
|
76
|
+
try {
|
|
77
|
+
return execSync('op --version', { stdio: 'pipe', encoding: 'utf8' }).trim();
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function installOp() {
|
|
84
|
+
if (process.platform === 'darwin') {
|
|
85
|
+
if (!hasCommand('brew')) {
|
|
86
|
+
err('Homebrew nao encontrado. Instale em https://brew.sh e rode este comando de novo.');
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
log(`${DIM}Instalando via Homebrew (pode levar um minuto)...${RESET}`);
|
|
90
|
+
try {
|
|
91
|
+
execSync('brew install --cask 1password-cli', { stdio: 'pipe', maxBuffer: 64 * 1024 * 1024 });
|
|
92
|
+
return true;
|
|
93
|
+
} catch {
|
|
94
|
+
err('Instalacao via Homebrew falhou. Tente manualmente: brew install --cask 1password-cli');
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (process.platform === 'win32') {
|
|
100
|
+
if (!hasCommand('winget')) {
|
|
101
|
+
err('winget nao encontrado. Instale o 1Password CLI manualmente:');
|
|
102
|
+
err('https://developer.1password.com/docs/cli/get-started/');
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
log(`${DIM}Instalando via winget (pode levar um minuto)...${RESET}`);
|
|
106
|
+
try {
|
|
107
|
+
execSync('winget install --id AgileBits.1Password.CLI --accept-source-agreements --accept-package-agreements', { stdio: 'pipe', maxBuffer: 64 * 1024 * 1024 });
|
|
108
|
+
return true;
|
|
109
|
+
} catch {
|
|
110
|
+
err('Instalacao via winget falhou. Tente manualmente: winget install AgileBits.1Password.CLI');
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
err('Instalacao automatica disponivel apenas no macOS e Windows.');
|
|
116
|
+
err('Instale o 1Password CLI manualmente: https://developer.1password.com/docs/cli/get-started/');
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ─── Clipboard (sem dependencia externa) ─────────────────
|
|
121
|
+
|
|
122
|
+
function readClipboard() {
|
|
123
|
+
const opts = { stdio: 'pipe', encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 };
|
|
124
|
+
try {
|
|
125
|
+
if (process.platform === 'darwin') {
|
|
126
|
+
return execSync('pbpaste', opts);
|
|
127
|
+
}
|
|
128
|
+
if (process.platform === 'win32') {
|
|
129
|
+
return execSync('powershell -NoProfile -Command "Get-Clipboard -Raw"', opts);
|
|
130
|
+
}
|
|
131
|
+
// Linux: tenta xclip, depois xsel
|
|
132
|
+
if (hasCommand('xclip')) {
|
|
133
|
+
return execSync('xclip -selection clipboard -o', opts);
|
|
134
|
+
}
|
|
135
|
+
if (hasCommand('xsel')) {
|
|
136
|
+
return execSync('xsel --clipboard --output', opts);
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
} catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ─── Validacao online e persistencia ─────────────────────
|
|
145
|
+
|
|
146
|
+
function testToken(token) {
|
|
147
|
+
try {
|
|
148
|
+
const out = execFileSync('op', ['whoami'], {
|
|
149
|
+
stdio: 'pipe',
|
|
150
|
+
encoding: 'utf8',
|
|
151
|
+
env: { ...process.env, [TOKEN_VAR]: token },
|
|
152
|
+
});
|
|
153
|
+
return out.trim();
|
|
154
|
+
} catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function persistToken(token) {
|
|
160
|
+
if (process.platform === 'win32') {
|
|
161
|
+
// Variavel de ambiente do usuario — token passa por env var, nunca por argv
|
|
162
|
+
const psEnv = { stdio: 'pipe', env: { ...process.env, AUROQ_OP_TOKEN: token } };
|
|
163
|
+
try {
|
|
164
|
+
execSync(
|
|
165
|
+
`powershell -NoProfile -Command "[Environment]::SetEnvironmentVariable('${TOKEN_VAR}', $env:AUROQ_OP_TOKEN, 'User')"`,
|
|
166
|
+
psEnv
|
|
167
|
+
);
|
|
168
|
+
} catch {
|
|
169
|
+
// Maquinas corporativas com Constrained Language Mode bloqueiam chamadas
|
|
170
|
+
// .NET — fallback via registro (Set-ItemProperty e cmdlet, permitido)
|
|
171
|
+
execSync(
|
|
172
|
+
`powershell -NoProfile -Command "Set-ItemProperty -Path 'HKCU:\\Environment' -Name '${TOKEN_VAR}' -Value $env:AUROQ_OP_TOKEN"`,
|
|
173
|
+
psEnv
|
|
174
|
+
);
|
|
175
|
+
warn('Gravado via registro (modo restrito detectado). Se o terminal novo nao enxergar, reinicie o computador uma vez.');
|
|
176
|
+
}
|
|
177
|
+
return 'variavel de ambiente do usuario (Windows)';
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// macOS: sempre ~/.zshenv (zsh e o shell padrao do sistema).
|
|
181
|
+
// Linux: ~/.bashrc se o shell for bash, senao ~/.zshenv.
|
|
182
|
+
const shell = process.env.SHELL || '';
|
|
183
|
+
const envFile = (process.platform !== 'darwin' && shell.includes('bash'))
|
|
184
|
+
? path.join(os.homedir(), '.bashrc')
|
|
185
|
+
: path.join(os.homedir(), '.zshenv');
|
|
186
|
+
|
|
187
|
+
const existing = fs.existsSync(envFile) ? fs.readFileSync(envFile, 'utf8') : '';
|
|
188
|
+
fs.writeFileSync(envFile, upsertEnvFileContent(existing, token), { mode: 0o600 });
|
|
189
|
+
return envFile.replace(os.homedir(), '~');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ─── Comando principal ───────────────────────────────────
|
|
193
|
+
|
|
194
|
+
async function connect() {
|
|
195
|
+
console.log(`\n${PURPLE}${BOLD} Conectar 1Password — Auroq OS${RESET}`);
|
|
196
|
+
|
|
197
|
+
// 1. Garantir que o op existe
|
|
198
|
+
step('Verificando 1Password CLI...');
|
|
199
|
+
let version = opVersion();
|
|
200
|
+
if (!version) {
|
|
201
|
+
warn('1Password CLI (op) nao encontrado — instalando...');
|
|
202
|
+
if (!installOp()) process.exit(1);
|
|
203
|
+
version = opVersion();
|
|
204
|
+
if (!version) {
|
|
205
|
+
err('op instalado mas nao encontrado no PATH. Feche e abra o terminal, e rode de novo.');
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
ok(`1Password CLI v${version}`);
|
|
210
|
+
|
|
211
|
+
// 2. Ler o token da area de transferencia
|
|
212
|
+
step('Lendo token da area de transferencia...');
|
|
213
|
+
const raw = readClipboard();
|
|
214
|
+
if (raw === null) {
|
|
215
|
+
err('Nao consegui ler a area de transferencia neste sistema.');
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
const token = raw.replace(/\s/g, '');
|
|
219
|
+
|
|
220
|
+
if (!isValidToken(token)) {
|
|
221
|
+
err('A area de transferencia nao contem um token valido (deve comecar com ops_).');
|
|
222
|
+
log('');
|
|
223
|
+
log(`${BOLD}Como resolver:${RESET}`);
|
|
224
|
+
log(` 1. Abra o 1Password e copie o seu token de Service Account (${CYAN}Ctrl/Cmd + C${RESET})`);
|
|
225
|
+
log(` 2. ${BOLD}Sem copiar mais nada${RESET}, rode este comando de novo`);
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
ok('Token encontrado (formato valido)');
|
|
229
|
+
|
|
230
|
+
// 3. Validar online ANTES de gravar
|
|
231
|
+
step('Testando conexao com o 1Password...');
|
|
232
|
+
const whoami = testToken(token);
|
|
233
|
+
if (!whoami) {
|
|
234
|
+
err('O token foi lido mas a conexao falhou. Possiveis causas:');
|
|
235
|
+
log(' - Token revogado ou expirado — gere um novo no painel do 1Password');
|
|
236
|
+
log(' - Sem internet no momento — confira a conexao e rode de novo');
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
ok('Conexao validada');
|
|
240
|
+
console.log(whoami.split('\n').map((l) => ` ${DIM}${l}${RESET}`).join('\n'));
|
|
241
|
+
|
|
242
|
+
// 4. Gravar persistente
|
|
243
|
+
step('Salvando conexao...');
|
|
244
|
+
const where = persistToken(token);
|
|
245
|
+
ok(`Token salvo em ${where}`);
|
|
246
|
+
|
|
247
|
+
// 5. Resultado
|
|
248
|
+
const restartHint = process.platform === 'win32'
|
|
249
|
+
? `${DIM} Importante: feche TODAS as janelas do terminal (se usa VS Code ou${RESET}
|
|
250
|
+
${DIM} Windows Terminal, feche o programa inteiro) e abra de novo para${RESET}
|
|
251
|
+
${DIM} que eles enxerguem a conexao nova.${RESET}`
|
|
252
|
+
: `${DIM} Importante: feche e abra de novo o terminal (e o Claude/Codex) para${RESET}
|
|
253
|
+
${DIM} que eles enxerguem a conexao nova.${RESET}`;
|
|
254
|
+
|
|
255
|
+
console.log(`
|
|
256
|
+
${GREEN}${BOLD} ✅ 1Password conectado com sucesso.${RESET}
|
|
257
|
+
|
|
258
|
+
${DIM} A conexao e permanente — voce nao precisa repetir este processo.${RESET}
|
|
259
|
+
${restartHint}
|
|
260
|
+
`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
module.exports = {
|
|
264
|
+
connect,
|
|
265
|
+
// exportados para testes
|
|
266
|
+
isValidToken,
|
|
267
|
+
upsertEnvFileContent,
|
|
268
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auroq-os",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.3",
|
|
4
4
|
"description": "Auroq OS — Sistema Operacional de IA para Experts",
|
|
5
5
|
"bin": {
|
|
6
6
|
"auroq-os": "bin/auroq-os.js"
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"auroq:sync:codex:check": "npm run sync:codex:check",
|
|
19
19
|
"auroq:validate": "npm run validate:hybrid",
|
|
20
20
|
"validate:hybrid": "node scripts/validate-hybrid.mjs",
|
|
21
|
-
"lint": "node --check bin/auroq-os.js && node --check lib/auth.js && node --check lib/credentials.js && node --check lib/supabase.js && node --check scripts/sync-codex-skills.mjs && node --check scripts/validate-hybrid.mjs",
|
|
21
|
+
"lint": "node --check bin/auroq-os.js && node --check lib/auth.js && node --check lib/credentials.js && node --check lib/onepassword.js && node --check lib/supabase.js && node --check scripts/sync-codex-skills.mjs && node --check scripts/validate-hybrid.mjs",
|
|
22
22
|
"typecheck": "npm run validate:hybrid",
|
|
23
23
|
"test": "node --test tests/*.test.mjs",
|
|
24
24
|
"build": "npm pack --dry-run --json > /dev/null"
|
|
@@ -123,7 +123,11 @@ function readOwner(dir) {
|
|
|
123
123
|
if (content === legacyMarker) return { kind: 'legacy' };
|
|
124
124
|
try {
|
|
125
125
|
const data = JSON.parse(content);
|
|
126
|
-
|
|
126
|
+
if (data.projectId === projectId) return { kind: 'owned', data };
|
|
127
|
+
// Mesma pasta de projeto = mesmo dono, mesmo que o name do package tenha
|
|
128
|
+
// mudado depois (ex: marcador gerado antes do package.json existir).
|
|
129
|
+
if (data.sourceRoot && path.resolve(data.sourceRoot) === root) return { kind: 'owned', data };
|
|
130
|
+
return { kind: 'foreign', data };
|
|
127
131
|
} catch {
|
|
128
132
|
return { kind: 'foreign', data: { invalidMarker: true } };
|
|
129
133
|
}
|
|
@@ -31,6 +31,12 @@ test('installer entrega projeto hibrido funcional sem sobrescrever package scrip
|
|
|
31
31
|
assert.ok(fs.existsSync(path.join(target, '.agents', 'skills', 'ops', 'SKILL.md')));
|
|
32
32
|
assert.equal(fs.existsSync(path.join(fakeHome, '.agents', 'skills')), false);
|
|
33
33
|
|
|
34
|
+
const installedOps = fs.readFileSync(path.join(target, '.claude', 'commands', 'AuroqOS', 'agents', 'ops.md'), 'utf8');
|
|
35
|
+
assert.match(installedOps, /PRE-GATE OBRIGATORIO — sistema de senhas e credenciais/);
|
|
36
|
+
assert.match(installedOps, /Voltar ao Step anterior e configurar o 1Password/);
|
|
37
|
+
assert.match(installedOps, /Seguir com vault local \(MENOS SEGURO\)/);
|
|
38
|
+
assert.doesNotMatch(installedOps, /#### 1\. 1Password — cofre cifrado/);
|
|
39
|
+
|
|
34
40
|
const check = execFileSync(process.execPath, [path.join(target, 'scripts', 'sync-codex-skills.mjs'), '--check'], {
|
|
35
41
|
cwd: os.tmpdir(),
|
|
36
42
|
encoding: 'utf8',
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
const { isValidToken, upsertEnvFileContent } = require('../lib/onepassword.js');
|
|
7
|
+
|
|
8
|
+
test('isValidToken aceita token de service account real', () => {
|
|
9
|
+
assert.equal(isValidToken('ops_' + 'a'.repeat(800)), true);
|
|
10
|
+
assert.equal(isValidToken('ops_eyJzaWduSW5BZGRyZXNzIjoibXkuMXBhc3N3b3JkLmNvbSJ9'), true);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('isValidToken rejeita lixo do clipboard', () => {
|
|
14
|
+
assert.equal(isValidToken(''), false);
|
|
15
|
+
assert.equal(isValidToken('pbpaste > ~/.op-token'), false);
|
|
16
|
+
assert.equal(isValidToken('ops_'), false); // prefixo sem corpo
|
|
17
|
+
assert.equal(isValidToken('ops_curto'), false); // corpo curto demais
|
|
18
|
+
assert.equal(isValidToken('token ops_abc'), false); // nao comeca com ops_
|
|
19
|
+
assert.equal(isValidToken(null), false);
|
|
20
|
+
assert.equal(isValidToken(undefined), false);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('upsertEnvFileContent adiciona o export em arquivo vazio', () => {
|
|
24
|
+
const out = upsertEnvFileContent('', 'ops_' + 'x'.repeat(40));
|
|
25
|
+
assert.match(out, /export OP_SERVICE_ACCOUNT_TOKEN='ops_x+'/);
|
|
26
|
+
assert.match(out, /gravado pelo Auroq OS/);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('upsertEnvFileContent remove definicao antiga mas preserva comentarios do usuario', () => {
|
|
30
|
+
const existing = [
|
|
31
|
+
'export PATH="$HOME/bin:$PATH"',
|
|
32
|
+
'# comentario do usuario sobre OP_SERVICE_ACCOUNT_TOKEN — nao apagar',
|
|
33
|
+
"export OP_SERVICE_ACCOUNT_TOKEN='ops_velho1234567890123456789'",
|
|
34
|
+
" OP_SERVICE_ACCOUNT_TOKEN='ops_sem_export_indentado_123'",
|
|
35
|
+
'alias ll="ls -la"',
|
|
36
|
+
].join('\n');
|
|
37
|
+
const out = upsertEnvFileContent(existing, 'ops_' + 'n'.repeat(40));
|
|
38
|
+
assert.equal(out.includes('ops_velho'), false);
|
|
39
|
+
assert.equal(out.includes('ops_sem_export'), false);
|
|
40
|
+
// comentario do usuario que so MENCIONA a var fica intacto
|
|
41
|
+
assert.equal(out.includes('# comentario do usuario sobre OP_SERVICE_ACCOUNT_TOKEN'), true);
|
|
42
|
+
assert.equal(out.includes('export PATH="$HOME/bin:$PATH"'), true);
|
|
43
|
+
assert.equal(out.includes('alias ll="ls -la"'), true);
|
|
44
|
+
// exatamente UMA linha de export do token
|
|
45
|
+
const exports = out.split('\n').filter((l) => l.startsWith('export OP_SERVICE_ACCOUNT_TOKEN'));
|
|
46
|
+
assert.equal(exports.length, 1);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('upsertEnvFileContent e idempotente', () => {
|
|
50
|
+
const token = 'ops_' + 'i'.repeat(40);
|
|
51
|
+
const once = upsertEnvFileContent('export FOO=bar', token);
|
|
52
|
+
const twice = upsertEnvFileContent(once, token);
|
|
53
|
+
assert.equal(once, twice);
|
|
54
|
+
});
|
|
@@ -65,3 +65,24 @@ test('check detecta drift e sync bloqueia colisao externa', () => {
|
|
|
65
65
|
fs.rmSync(home, { recursive: true, force: true });
|
|
66
66
|
}
|
|
67
67
|
});
|
|
68
|
+
|
|
69
|
+
test('sync local continua dono apos rename do package (marcador antigo da mesma pasta)', () => {
|
|
70
|
+
const proj = fs.mkdtempSync(path.join(os.tmpdir(), 'auroq-os-rename-'));
|
|
71
|
+
try {
|
|
72
|
+
fs.mkdirSync(path.join(proj, 'scripts'), { recursive: true });
|
|
73
|
+
fs.mkdirSync(path.join(proj, '.claude', 'commands'), { recursive: true });
|
|
74
|
+
fs.copyFileSync(script, path.join(proj, 'scripts', 'sync-codex-skills.mjs'));
|
|
75
|
+
fs.writeFileSync(path.join(proj, '.claude', 'commands', 'test-agent.md'), '# test-agent\n\nAgente de teste.\n');
|
|
76
|
+
// Cenario do bug: primeiro sync SEM package.json (name = nome da pasta)...
|
|
77
|
+
const projScript = path.join(proj, 'scripts', 'sync-codex-skills.mjs');
|
|
78
|
+
execFileSync(process.execPath, [projScript, '--clean'], { cwd: proj, encoding: 'utf8' });
|
|
79
|
+
assert.ok(fs.existsSync(path.join(proj, '.agents', 'skills', 'test-agent', 'SKILL.md')));
|
|
80
|
+
// ...depois o init cria o package.json com outro name (meu-negocio).
|
|
81
|
+
fs.writeFileSync(path.join(proj, 'package.json'), JSON.stringify({ name: 'meu-negocio', version: '1.0.0' }));
|
|
82
|
+
// Antes do fix: "Recusando sobrescrever ...; ownership: foreign."
|
|
83
|
+
execFileSync(process.execPath, [projScript, '--clean'], { cwd: proj, encoding: 'utf8' });
|
|
84
|
+
execFileSync(process.execPath, [projScript, '--check'], { cwd: proj, encoding: 'utf8' });
|
|
85
|
+
} finally {
|
|
86
|
+
fs.rmSync(proj, { recursive: true, force: true });
|
|
87
|
+
}
|
|
88
|
+
});
|