auroq-os 2.1.2 → 2.1.4

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.1.2
4
+ version: 2.1.3
5
5
  installedAt: '2026-03-16'
6
6
 
7
7
  ide:
@@ -63,6 +63,8 @@ commands:
63
63
  description: "Setup do nucleo (Bootstrap 1) — ambiente, MCPs, GitHub, Vercel, Supabase, Companion"
64
64
  - name: bootstrap-2
65
65
  description: "Conexoes extras (Bootstrap 2, opcional recomendado) — valida o cofre e conecta Cloudflare, Drive, Gmail, Calendar, Notion e Canva"
66
+ - name: conectar-1password
67
+ description: "Conectar o cofre 1Password ao Auroq — o expert so copia o token (Ctrl+C), o Ops roda o resto"
66
68
  - name: yolo
67
69
  description: "Trocar modo de permissao do Claude Code (auto/acceptEdits/default)"
68
70
  - name: install
@@ -199,6 +201,8 @@ A validacao e ONLINE e obrigatoria, sem fallback offline — e a mesma trava do
199
201
 
200
202
  **Passo 4 — Aplicar atualizacao (framework only)**
201
203
 
204
+ > **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.
205
+
202
206
  **ATUALIZA (L1/L2/L3 — framework):**
203
207
  - `.auroq-core/` — constitution, config, synapse engine, development docs, dna operacional
204
208
  - `.claude/commands/AuroqOS/` — agentes core (ops.md)
@@ -853,8 +857,9 @@ Conexoes extras — OPCIONAL, RECOMENDADO. Roda depois do bootstrap principal, q
853
857
  - `OP_SERVICE_ACCOUNT_TOKEN` esta definido, mostrando somente `configurado` ou `ausente` — NUNCA imprimir o valor
854
858
  - uma leitura de item de teste no vault funciona, quando existir
855
859
  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.
856
- 4. SE nao configurou ou a verificacao falhar, explicar as duas opcoes e pedir uma escolha explicita:
857
- - **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
+ 4. SE nao configurou ou a verificacao falhar, explicar as opcoes e pedir uma escolha explicita:
861
+ - **Conectar agora pelo Ops (RECOMENDADO se ele ja tem o token):** rodar `*conectar-1password` — o expert so copia o token e o Ops faz o resto. Ao terminar, repetir este pre-gate.
862
+ - **Voltar ao Step anterior da plataforma:** se ele ainda nao criou a conta/token do 1Password. Parar o Bootstrap 2 sem marcar conexoes como concluidas. Quando o aluno voltar, repetir este pre-gate.
858
863
  - **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.
859
864
  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.
860
865
 
@@ -987,6 +992,44 @@ O fluxo e sempre o mesmo:
987
992
  Se o expert quiser conectar outros servicos que aparecem nas Integrations do claude.ai no futuro, o fluxo e esse mesmo.
988
993
 
989
994
 
995
+ ### *conectar-1password
996
+
997
+ Conecta o cofre 1Password ao Auroq. O expert NAO abre terminal e NAO digita comando — ele so copia o token e o Ops roda tudo. Aciona por linguagem natural ("conecta meu 1Password", "configura o cofre") ou pelo comando.
998
+
999
+ **REGRA DE OURO — o token NUNCA passa pelo chat:**
1000
+ - NUNCA pedir pro expert colar o token na conversa. Se ele colar por conta propria, avisar que aquele token foi exposto e deve ser revogado e gerado de novo no painel do 1Password.
1001
+ - NUNCA imprimir, ecoar ou ler o valor do token em nenhum comando (`echo`, `cat`, `pbpaste` solto, etc.). O comando `conectar-1password` do CLI ja le a area de transferencia internamente sem expor nada.
1002
+
1003
+ **Fluxo:**
1004
+
1005
+ 1. **Checar se ja esta conectado** (sem expor segredos):
1006
+ ```bash
1007
+ op whoami
1008
+ ```
1009
+ - SE funciona → ja conectado. Informar e encerrar (rodar de novo so se o expert quiser trocar o token).
1010
+ - SE `op` nem existe ou falha → seguir.
1011
+
1012
+ 2. **Confirmar que o expert tem o token em maos.** Perguntar: "Voce ja gerou o seu token de Service Account no 1Password (aquele codigo grandao que comeca com `ops_`)?"
1013
+ - SE nao → orientar a voltar no Step Sistema de Senhas e Credenciais da plataforma e gerar. Parar aqui ate ele ter o token.
1014
+ - ATENCAO: o 1Password mostra o token UMA vez, na criacao. Se o expert fechou a tela sem salvar, precisa gerar um novo.
1015
+
1016
+ 3. **Pedir a UNICA acao dele:** "Copia o token agora (Cmd+C no Mac / Ctrl+C no Windows) e me avisa quando copiar. NAO cola ele aqui no chat — so copia."
1017
+
1018
+ 4. **Quando ele confirmar, rodar:**
1019
+ ```bash
1020
+ npx auroq-os conectar-1password
1021
+ ```
1022
+ O comando le o token direto da area de transferencia, instala o 1Password CLI se faltar, valida a conexao online e salva permanente. O output NUNCA contem o token — e seguro.
1023
+
1024
+ 5. **Interpretar o resultado pro expert:**
1025
+ - `✅ 1Password conectado com sucesso` → confirmar com gate real: rodar `op vault list` e mostrar que o cofre apareceu. Dai em diante toda credencial nova vai pro vault via `op`, nunca em texto plano.
1026
+ - `nao contem um token valido` → ele copiou outra coisa por cima (o clipboard guarda so a ULTIMA copia). Pedir pra copiar o token de novo e repetir o passo 4. Sem stress, acontece.
1027
+ - `token foi lido mas a conexao falhou` → token revogado/expirado ou sem internet. Orientar a gerar token novo no painel se a internet estiver ok.
1028
+
1029
+ 6. **Pos-conexao por sistema:**
1030
+ - **Mac:** os proximos comandos ja enxergam a conexao nesta mesma sessao. Nada a fazer.
1031
+ - **Windows:** a sessao atual NAO enxerga a conexao nova. Orientar: fechar TODAS as janelas do terminal (ou o VS Code inteiro), abrir de novo e voltar pro Claude. Verificar com `op whoami` quando ele voltar.
1032
+
990
1033
  ### *pr
991
1034
  1. Verificar branch atual
992
1035
  2. `git log` — resumir commits desde main
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
- // ─── Fase 4c: Adaptador Codex CLI (skills locais por projeto)
396
- step('Configurando Codex CLI...');
397
- try {
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('');
@@ -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.2",
3
+ "version": "2.1.4",
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
- return data.projectId === projectId ? { kind: 'owned', data } : { kind: 'foreign', data };
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
  }
@@ -33,8 +33,10 @@ test('installer entrega projeto hibrido funcional sem sobrescrever package scrip
33
33
 
34
34
  const installedOps = fs.readFileSync(path.join(target, '.claude', 'commands', 'AuroqOS', 'agents', 'ops.md'), 'utf8');
35
35
  assert.match(installedOps, /PRE-GATE OBRIGATORIO — sistema de senhas e credenciais/);
36
- assert.match(installedOps, /Voltar ao Step anterior e configurar o 1Password/);
36
+ assert.match(installedOps, /Conectar agora pelo Ops \(RECOMENDADO se ele ja tem o token\)/);
37
+ assert.match(installedOps, /Voltar ao Step anterior da plataforma/);
37
38
  assert.match(installedOps, /Seguir com vault local \(MENOS SEGURO\)/);
39
+ assert.match(installedOps, /\*conectar-1password/);
38
40
  assert.doesNotMatch(installedOps, /#### 1\. 1Password — cofre cifrado/);
39
41
 
40
42
  const check = execFileSync(process.execPath, [path.join(target, 'scripts', 'sync-codex-skills.mjs'), '--check'], {
@@ -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
+ });