create-genia-mission-control 1.0.0
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/README.md +47 -0
- package/bin/index.js +16 -0
- package/lib/auth.js +75 -0
- package/lib/checks.js +50 -0
- package/lib/install-claude.js +23 -0
- package/lib/launch.js +41 -0
- package/lib/main.js +87 -0
- package/lib/segundo-cerebro.js +302 -0
- package/lib/setup.js +46 -0
- package/lib/ui.js +34 -0
- package/package.json +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# create-genia-mission-control
|
|
2
|
+
|
|
3
|
+
> Instale o GEN.IA Mission Control com um único comando.
|
|
4
|
+
|
|
5
|
+
## Uso
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx create-genia-mission-control
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Pré-requisito único:** Node.js 18+ — instale em [nodejs.org](https://nodejs.org)
|
|
12
|
+
|
|
13
|
+
## O que instala
|
|
14
|
+
|
|
15
|
+
O wizard configura automaticamente:
|
|
16
|
+
|
|
17
|
+
1. **Claude Code CLI** — se não estiver instalado
|
|
18
|
+
2. **Autenticação** — login via browser ou API key
|
|
19
|
+
3. **Segundo Cérebro** — seu repositório privado de contexto
|
|
20
|
+
4. **GEN.IA SQUAD OS** — 9 agentes de dev + 13 Xquads consultores
|
|
21
|
+
5. **Mission Control** — interface visual estilo Gather Town
|
|
22
|
+
|
|
23
|
+
## Após a instalação
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
cd seu-projeto/mission-control
|
|
27
|
+
node server.js
|
|
28
|
+
# Abre localhost:3001
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## O Segundo Cérebro
|
|
32
|
+
|
|
33
|
+
Repositório privado no seu GitHub que os agentes leem em toda sessão.
|
|
34
|
+
Criado automaticamente a partir do template:
|
|
35
|
+
[segundo-cerebro-template](https://github.com/elidyizzy/segundo-cerebro-template)
|
|
36
|
+
|
|
37
|
+
## Três caminhos para o Segundo Cérebro
|
|
38
|
+
|
|
39
|
+
| Situação | Caminho |
|
|
40
|
+
|----------|---------|
|
|
41
|
+
| Tem GitHub + repo existente | Clona direto |
|
|
42
|
+
| Tem GitHub + sem repo | Cria repo privado via API |
|
|
43
|
+
| Sem GitHub / não quer agora | Cria localmente (pode conectar depois) |
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
Criado por [Elidy Izidio](https://github.com/elidyizzy) — GEN.IA SQUAD
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Verificar Node.js >= 18
|
|
4
|
+
const [major] = process.versions.node.split('.').map(Number);
|
|
5
|
+
if (major < 18) {
|
|
6
|
+
console.error('\n❌ Node.js 18 ou superior é necessário.');
|
|
7
|
+
console.error(` Versão atual: ${process.version}`);
|
|
8
|
+
console.error(' Instale em: https://nodejs.org\n');
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Entry point
|
|
13
|
+
import('../lib/main.js').catch(err => {
|
|
14
|
+
console.error('\n❌ Erro fatal:', err.message);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
});
|
package/lib/auth.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
import { prompt } from 'enquirer';
|
|
3
|
+
import { ui } from './ui.js';
|
|
4
|
+
|
|
5
|
+
export async function authenticate() {
|
|
6
|
+
ui.nl();
|
|
7
|
+
console.log(ui.gold('Como você quer autenticar com o Claude?'));
|
|
8
|
+
ui.nl();
|
|
9
|
+
|
|
10
|
+
const { method } = await prompt({
|
|
11
|
+
type: 'select',
|
|
12
|
+
name: 'method',
|
|
13
|
+
message: 'Método de autenticação:',
|
|
14
|
+
choices: [
|
|
15
|
+
{ name: 'browser', message: '🌐 Login pelo browser (recomendado)' },
|
|
16
|
+
{ name: 'apikey', message: '🔑 Usar API key da Anthropic' },
|
|
17
|
+
],
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
if (method === 'browser') {
|
|
21
|
+
return await authBrowser();
|
|
22
|
+
} else {
|
|
23
|
+
return await authAPIKey();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function authBrowser() {
|
|
28
|
+
console.log(ui.info('Abrindo autenticação no browser...'));
|
|
29
|
+
console.log(ui.dim(' O browser vai abrir. Faça login e volte aqui.'));
|
|
30
|
+
ui.nl();
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
// claude login abre o browser automaticamente
|
|
34
|
+
// No Windows, tenta 'claude.cmd' se 'claude' falhar
|
|
35
|
+
for (const cmd of ['claude', 'claude.cmd']) {
|
|
36
|
+
try {
|
|
37
|
+
await execa(cmd, ['login'], { stdio: 'inherit' });
|
|
38
|
+
console.log(ui.ok('Autenticado com sucesso'));
|
|
39
|
+
return { method: 'browser' };
|
|
40
|
+
} catch (e) {
|
|
41
|
+
if (e.code !== 'ENOENT') throw e;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
throw new Error('Claude CLI não encontrado');
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.log(ui.warn('Não foi possível autenticar automaticamente'));
|
|
47
|
+
console.log(ui.dim(' Execute manualmente: claude login'));
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function authAPIKey() {
|
|
53
|
+
const { apiKey } = await prompt({
|
|
54
|
+
type: 'password',
|
|
55
|
+
name: 'apiKey',
|
|
56
|
+
message: 'Cole sua API key da Anthropic:',
|
|
57
|
+
validate: (v) => v.startsWith('sk-ant-') ? true : 'API key inválida (deve começar com sk-ant-)',
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Salvar na variável de ambiente para uso durante a instalação
|
|
61
|
+
process.env.ANTHROPIC_API_KEY = apiKey;
|
|
62
|
+
|
|
63
|
+
// Tentar configurar no claude CLI
|
|
64
|
+
for (const cmd of ['claude', 'claude.cmd']) {
|
|
65
|
+
try {
|
|
66
|
+
await execa(cmd, ['config', 'set', 'apiKey', apiKey], { stdio: 'pipe' });
|
|
67
|
+
break;
|
|
68
|
+
} catch {
|
|
69
|
+
// continua
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log(ui.ok('API key configurada'));
|
|
74
|
+
return { method: 'apikey', apiKey };
|
|
75
|
+
}
|
package/lib/checks.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
import { ui } from './ui.js';
|
|
3
|
+
|
|
4
|
+
export async function checkEnvironment() {
|
|
5
|
+
const results = {
|
|
6
|
+
node: false,
|
|
7
|
+
git: false,
|
|
8
|
+
claudeCLI: false,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// Node.js (já verificado no bin, mas confirma versão)
|
|
12
|
+
const [major] = process.versions.node.split('.').map(Number);
|
|
13
|
+
results.node = major >= 18;
|
|
14
|
+
|
|
15
|
+
// Git
|
|
16
|
+
try {
|
|
17
|
+
await execa('git', ['--version']);
|
|
18
|
+
results.git = true;
|
|
19
|
+
} catch {
|
|
20
|
+
results.git = false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Claude Code CLI — tenta 'claude' e 'claude.cmd' (Windows)
|
|
24
|
+
for (const cmd of ['claude', 'claude.cmd']) {
|
|
25
|
+
try {
|
|
26
|
+
await execa(cmd, ['--version']);
|
|
27
|
+
results.claudeCLI = true;
|
|
28
|
+
results.claudeCmd = cmd;
|
|
29
|
+
break;
|
|
30
|
+
} catch {
|
|
31
|
+
// continua
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return results;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function printCheckResults(results) {
|
|
39
|
+
console.log(results.node
|
|
40
|
+
? ui.ok(`Node.js ${process.version}`)
|
|
41
|
+
: ui.fail('Node.js < 18'));
|
|
42
|
+
|
|
43
|
+
console.log(results.git
|
|
44
|
+
? ui.ok('Git instalado')
|
|
45
|
+
: ui.fail('Git não encontrado — instale em https://git-scm.com'));
|
|
46
|
+
|
|
47
|
+
console.log(results.claudeCLI
|
|
48
|
+
? ui.ok('Claude Code CLI instalado')
|
|
49
|
+
: ui.warn('Claude Code CLI não encontrado — será instalado'));
|
|
50
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { ui } from './ui.js';
|
|
4
|
+
|
|
5
|
+
export async function installClaudeCLI() {
|
|
6
|
+
const spinner = ora({
|
|
7
|
+
text: 'Instalando Claude Code CLI...',
|
|
8
|
+
color: 'yellow',
|
|
9
|
+
}).start();
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
await execa('npm', ['install', '-g', '@anthropic-ai/claude-code'], {
|
|
13
|
+
stdio: 'pipe',
|
|
14
|
+
});
|
|
15
|
+
spinner.succeed(ui.ok('Claude Code CLI instalado'));
|
|
16
|
+
return true;
|
|
17
|
+
} catch (err) {
|
|
18
|
+
spinner.fail(ui.fail('Falha ao instalar Claude Code CLI'));
|
|
19
|
+
console.error(ui.dim(' Tente manualmente: npm i -g @anthropic-ai/claude-code'));
|
|
20
|
+
console.error(ui.dim(` Erro: ${err.message}`));
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
package/lib/launch.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import open from 'open';
|
|
4
|
+
import { ui } from './ui.js';
|
|
5
|
+
|
|
6
|
+
export async function launch(projectDir) {
|
|
7
|
+
const mcDir = path.join(projectDir, 'mission-control');
|
|
8
|
+
const serverPath = path.join(mcDir, 'server.js');
|
|
9
|
+
|
|
10
|
+
ui.nl();
|
|
11
|
+
ui.separator();
|
|
12
|
+
console.log(ui.gold(' 🚀 INICIANDO MISSION CONTROL'));
|
|
13
|
+
ui.separator();
|
|
14
|
+
ui.nl();
|
|
15
|
+
console.log(ui.ok('Projeto configurado em: ' + projectDir));
|
|
16
|
+
console.log(ui.ok('Servidor: localhost:3001'));
|
|
17
|
+
ui.nl();
|
|
18
|
+
console.log(ui.dim(' Para iniciar novamente no futuro:'));
|
|
19
|
+
console.log(ui.dim(` cd ${path.basename(projectDir)}/mission-control`));
|
|
20
|
+
console.log(ui.dim(' node server.js'));
|
|
21
|
+
ui.nl();
|
|
22
|
+
|
|
23
|
+
// Abrir browser após 2 segundos
|
|
24
|
+
setTimeout(async () => {
|
|
25
|
+
await open('http://localhost:3001');
|
|
26
|
+
}, 2000);
|
|
27
|
+
|
|
28
|
+
console.log(ui.gold('Iniciando servidor... (Ctrl+C para parar)\n'));
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
await execa('node', [serverPath], {
|
|
32
|
+
cwd: mcDir,
|
|
33
|
+
stdio: 'inherit',
|
|
34
|
+
});
|
|
35
|
+
} catch (err) {
|
|
36
|
+
// 130 = Ctrl+C (SIGINT) — saída esperada
|
|
37
|
+
if (err.exitCode !== 130) {
|
|
38
|
+
console.error(ui.fail('Servidor encerrou com erro: ' + err.message));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
package/lib/main.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { prompt } from 'enquirer';
|
|
3
|
+
import { ui } from './ui.js';
|
|
4
|
+
import { checkEnvironment, printCheckResults } from './checks.js';
|
|
5
|
+
import { installClaudeCLI } from './install-claude.js';
|
|
6
|
+
import { authenticate } from './auth.js';
|
|
7
|
+
import { setupSegundoCerebro } from './segundo-cerebro.js';
|
|
8
|
+
import { setupGenieOS } from './setup.js';
|
|
9
|
+
import { launch } from './launch.js';
|
|
10
|
+
|
|
11
|
+
async function main() {
|
|
12
|
+
// Banner
|
|
13
|
+
ui.banner();
|
|
14
|
+
console.log(ui.dim(' Instalando seu time de agentes IA em alguns passos.\n'));
|
|
15
|
+
|
|
16
|
+
// ── STEP 1: Verificar ambiente ──
|
|
17
|
+
console.log(ui.step('1/5', 'Verificando ambiente...'));
|
|
18
|
+
const env = await checkEnvironment();
|
|
19
|
+
printCheckResults(env);
|
|
20
|
+
|
|
21
|
+
if (!env.git) {
|
|
22
|
+
console.log(ui.fail('\nGit é necessário. Instale em: https://git-scm.com'));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ── STEP 2: Instalar Claude Code CLI ──
|
|
27
|
+
if (!env.claudeCLI) {
|
|
28
|
+
console.log(ui.step('2/5', 'Instalando Claude Code CLI...'));
|
|
29
|
+
const ok = await installClaudeCLI();
|
|
30
|
+
if (!ok) {
|
|
31
|
+
console.log(ui.warn('\nInstale manualmente e rode novamente:'));
|
|
32
|
+
console.log(ui.dim(' npm i -g @anthropic-ai/claude-code'));
|
|
33
|
+
console.log(ui.dim(' npx create-genia-mission-control'));
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
console.log(ui.step('2/5', 'Claude Code CLI ✅ (já instalado)'));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── STEP 3: Autenticação ──
|
|
41
|
+
console.log(ui.step('3/5', 'Autenticação com Claude'));
|
|
42
|
+
await authenticate();
|
|
43
|
+
|
|
44
|
+
// ── STEP 4a: Nome do projeto ──
|
|
45
|
+
ui.nl();
|
|
46
|
+
const { projectName } = await prompt({
|
|
47
|
+
type: 'input',
|
|
48
|
+
name: 'projectName',
|
|
49
|
+
message: 'Nome da pasta do projeto:',
|
|
50
|
+
initial: 'meu-projeto-genia',
|
|
51
|
+
validate: (v) => /^[a-z0-9\-_]+$/i.test(v) ? true : 'Use apenas letras, números e hífens',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const projectDir = path.join(process.cwd(), projectName);
|
|
55
|
+
|
|
56
|
+
// ── STEP 4b: Segundo Cérebro ──
|
|
57
|
+
console.log(ui.step('4/5', 'Segundo Cérebro — seu contexto persistente'));
|
|
58
|
+
const cerebroResult = await setupSegundoCerebro(projectDir);
|
|
59
|
+
|
|
60
|
+
// ── STEP 5: GEN.IA OS + Mission Control ──
|
|
61
|
+
console.log(ui.step('5/5', 'Instalando GEN.IA OS e Mission Control...'));
|
|
62
|
+
await setupGenieOS(projectDir, cerebroResult.businessDir);
|
|
63
|
+
|
|
64
|
+
// ── Resumo ──
|
|
65
|
+
ui.nl();
|
|
66
|
+
ui.separator();
|
|
67
|
+
console.log(ui.gold(' ✅ INSTALAÇÃO CONCLUÍDA'));
|
|
68
|
+
ui.separator();
|
|
69
|
+
ui.nl();
|
|
70
|
+
console.log(ui.ok('Projeto: ' + projectDir));
|
|
71
|
+
if (cerebroResult.repoUrl) {
|
|
72
|
+
console.log(ui.ok('Segundo Cérebro: ' + cerebroResult.repoUrl));
|
|
73
|
+
} else {
|
|
74
|
+
console.log(ui.ok('Segundo Cérebro: local (sem GitHub)'));
|
|
75
|
+
console.log(ui.dim(' Para conectar ao GitHub depois: veja .business/README.md'));
|
|
76
|
+
}
|
|
77
|
+
ui.nl();
|
|
78
|
+
|
|
79
|
+
// ── Launch ──
|
|
80
|
+
await launch(projectDir);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
main().catch(err => {
|
|
84
|
+
console.error(ui.fail('\nErro inesperado: ' + err.message));
|
|
85
|
+
if (process.env.DEBUG) console.error(err.stack);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
});
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { prompt } from 'enquirer';
|
|
2
|
+
import { execa } from 'execa';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import fetch from 'node-fetch';
|
|
6
|
+
import open from 'open';
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import { ui } from './ui.js';
|
|
9
|
+
|
|
10
|
+
const TEMPLATE_REPO = 'elidyizzy/segundo-cerebro-template';
|
|
11
|
+
|
|
12
|
+
export async function setupSegundoCerebro(projectDir) {
|
|
13
|
+
ui.nl();
|
|
14
|
+
console.log(ui.gold('Segundo Cérebro — Seu contexto persistente'));
|
|
15
|
+
console.log(ui.dim(' Os agentes lerão isso em toda sessão para te conhecer.'));
|
|
16
|
+
ui.nl();
|
|
17
|
+
|
|
18
|
+
const { useGitHub } = await prompt({
|
|
19
|
+
type: 'confirm',
|
|
20
|
+
name: 'useGitHub',
|
|
21
|
+
message: 'Quer conectar o Segundo Cérebro ao GitHub? (persiste entre máquinas)',
|
|
22
|
+
initial: true,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (!useGitHub) {
|
|
26
|
+
return await createLocalOnly(projectDir);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { hasRepo } = await prompt({
|
|
30
|
+
type: 'confirm',
|
|
31
|
+
name: 'hasRepo',
|
|
32
|
+
message: 'Você já tem um repositório "segundo-cerebro" no GitHub?',
|
|
33
|
+
initial: false,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (hasRepo) {
|
|
37
|
+
return await cloneExistingCerebro(projectDir);
|
|
38
|
+
} else {
|
|
39
|
+
return await createNewCerebro(projectDir);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ── Caminho local (sem GitHub) ──────────────────────────────────────────────
|
|
44
|
+
async function createLocalOnly(projectDir) {
|
|
45
|
+
ui.nl();
|
|
46
|
+
console.log(ui.info('Criando Segundo Cérebro localmente.'));
|
|
47
|
+
console.log(ui.dim(' Você pode conectar ao GitHub depois quando quiser.'));
|
|
48
|
+
ui.nl();
|
|
49
|
+
|
|
50
|
+
const answers = await prompt([
|
|
51
|
+
{
|
|
52
|
+
type: 'input',
|
|
53
|
+
name: 'name',
|
|
54
|
+
message: 'Seu nome:',
|
|
55
|
+
validate: (v) => v.length > 0 ? true : 'Obrigatório',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
type: 'input',
|
|
59
|
+
name: 'empresa',
|
|
60
|
+
message: 'Nome da sua empresa:',
|
|
61
|
+
validate: (v) => v.length > 0 ? true : 'Obrigatório',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
type: 'input',
|
|
65
|
+
name: 'cargo',
|
|
66
|
+
message: 'Seu cargo/role:',
|
|
67
|
+
initial: 'Fundador(a)',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
type: 'input',
|
|
71
|
+
name: 'descricao',
|
|
72
|
+
message: 'Em uma frase: o que você faz?',
|
|
73
|
+
validate: (v) => v.length > 0 ? true : 'Obrigatório',
|
|
74
|
+
},
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
const businessDir = path.join(projectDir, '.business');
|
|
78
|
+
const spinner = ora('Criando Segundo Cérebro local...').start();
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
await execa('git', ['clone', `https://github.com/${TEMPLATE_REPO}.git`, businessDir]);
|
|
82
|
+
await fs.remove(path.join(businessDir, '.git'));
|
|
83
|
+
await fillPlaceholders(businessDir, { ...answers, githubUser: '' });
|
|
84
|
+
spinner.succeed(ui.ok('Segundo Cérebro criado localmente'));
|
|
85
|
+
} catch {
|
|
86
|
+
spinner.warn(ui.warn('Criando estrutura mínima...'));
|
|
87
|
+
await createMinimalBusiness(businessDir, answers);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
ui.nl();
|
|
91
|
+
console.log(ui.dim(' 📁 Localização: ' + businessDir));
|
|
92
|
+
console.log(ui.dim(' ✏️ Edite OWNER.md e EMPRESA.md para personalizar.'));
|
|
93
|
+
console.log(ui.dim(' 🔗 Para conectar ao GitHub depois: git init && git remote add origin <url>'));
|
|
94
|
+
ui.nl();
|
|
95
|
+
|
|
96
|
+
return { businessDir, repoUrl: null, isLocal: true };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ── Clonar repo existente ───────────────────────────────────────────────────
|
|
100
|
+
async function cloneExistingCerebro(projectDir) {
|
|
101
|
+
const { repoUrl } = await prompt({
|
|
102
|
+
type: 'input',
|
|
103
|
+
name: 'repoUrl',
|
|
104
|
+
message: 'URL do repositório (ex: https://github.com/seunome/segundo-cerebro):',
|
|
105
|
+
validate: (v) => v.includes('github.com') ? true : 'URL inválida',
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const spinner = ora('Clonando Segundo Cérebro...').start();
|
|
109
|
+
const businessDir = path.join(projectDir, '.business');
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
await execa('git', ['clone', repoUrl, businessDir]);
|
|
113
|
+
spinner.succeed(ui.ok('Segundo Cérebro conectado'));
|
|
114
|
+
return { businessDir, repoUrl };
|
|
115
|
+
} catch (err) {
|
|
116
|
+
spinner.fail(ui.fail('Erro ao clonar repositório'));
|
|
117
|
+
console.error(ui.dim(' Certifique-se de ter acesso ao repositório'));
|
|
118
|
+
console.error(ui.dim(` Erro: ${err.message}`));
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── Criar novo repo via API do GitHub ──────────────────────────────────────
|
|
124
|
+
async function createNewCerebro(projectDir) {
|
|
125
|
+
console.log(ui.info('Vamos criar seu Segundo Cérebro no GitHub.'));
|
|
126
|
+
ui.nl();
|
|
127
|
+
|
|
128
|
+
const answers = await prompt([
|
|
129
|
+
{
|
|
130
|
+
type: 'input',
|
|
131
|
+
name: 'githubUser',
|
|
132
|
+
message: 'Seu usuário do GitHub:',
|
|
133
|
+
validate: (v) => v.length > 0 ? true : 'Obrigatório',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
type: 'input',
|
|
137
|
+
name: 'name',
|
|
138
|
+
message: 'Seu nome completo:',
|
|
139
|
+
validate: (v) => v.length > 0 ? true : 'Obrigatório',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
type: 'input',
|
|
143
|
+
name: 'empresa',
|
|
144
|
+
message: 'Nome da sua empresa:',
|
|
145
|
+
validate: (v) => v.length > 0 ? true : 'Obrigatório',
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
type: 'input',
|
|
149
|
+
name: 'cargo',
|
|
150
|
+
message: 'Seu cargo/role:',
|
|
151
|
+
initial: 'Fundador(a)',
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
type: 'input',
|
|
155
|
+
name: 'descricao',
|
|
156
|
+
message: 'Em uma frase: o que você faz?',
|
|
157
|
+
validate: (v) => v.length > 0 ? true : 'Obrigatório',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
type: 'password',
|
|
161
|
+
name: 'githubToken',
|
|
162
|
+
message: 'GitHub Personal Access Token (para criar o repo privado):',
|
|
163
|
+
hint: 'Gere em: github.com/settings/tokens — permissão: repo',
|
|
164
|
+
validate: (v) => v.startsWith('ghp_') || v.startsWith('github_pat_')
|
|
165
|
+
? true
|
|
166
|
+
: 'Token inválido (deve começar com ghp_ ou github_pat_)',
|
|
167
|
+
},
|
|
168
|
+
]);
|
|
169
|
+
|
|
170
|
+
const spinner = ora('Criando repositório privado no GitHub...').start();
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
// Criar repo a partir do template público
|
|
174
|
+
const createRes = await fetch(
|
|
175
|
+
`https://api.github.com/repos/${TEMPLATE_REPO}/generate`,
|
|
176
|
+
{
|
|
177
|
+
method: 'POST',
|
|
178
|
+
headers: {
|
|
179
|
+
'Authorization': `Bearer ${answers.githubToken}`,
|
|
180
|
+
'Accept': 'application/vnd.github+json',
|
|
181
|
+
'Content-Type': 'application/json',
|
|
182
|
+
},
|
|
183
|
+
body: JSON.stringify({
|
|
184
|
+
owner: answers.githubUser,
|
|
185
|
+
name: 'segundo-cerebro',
|
|
186
|
+
description: 'Meu contexto persistente para o GEN.IA Mission Control',
|
|
187
|
+
private: true,
|
|
188
|
+
include_all_branches: false,
|
|
189
|
+
}),
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
if (!createRes.ok) {
|
|
194
|
+
const err = await createRes.json();
|
|
195
|
+
throw new Error(err.message || 'Erro na API do GitHub');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const repo = await createRes.json();
|
|
199
|
+
spinner.succeed(ui.ok(`Repositório criado: ${repo.html_url}`));
|
|
200
|
+
|
|
201
|
+
// Clonar localmente com token na URL
|
|
202
|
+
const businessDir = path.join(projectDir, '.business');
|
|
203
|
+
const cloneSpinner = ora('Clonando para o projeto...').start();
|
|
204
|
+
const authUrl = repo.clone_url.replace('https://', `https://${answers.githubToken}@`);
|
|
205
|
+
await execa('git', ['clone', authUrl, businessDir]);
|
|
206
|
+
cloneSpinner.succeed(ui.ok('Segundo Cérebro clonado'));
|
|
207
|
+
|
|
208
|
+
// Substituir placeholders
|
|
209
|
+
const fillSpinner = ora('Preenchendo informações básicas...').start();
|
|
210
|
+
await fillPlaceholders(businessDir, answers);
|
|
211
|
+
fillSpinner.succeed(ui.ok('Informações básicas preenchidas'));
|
|
212
|
+
|
|
213
|
+
// Commit inicial
|
|
214
|
+
const commitSpinner = ora('Salvando no GitHub...').start();
|
|
215
|
+
await execa('git', ['add', '.'], { cwd: businessDir });
|
|
216
|
+
await execa('git', ['commit', '-m', 'feat: configuração inicial do segundo cérebro'], { cwd: businessDir });
|
|
217
|
+
await execa('git', ['push'], { cwd: businessDir });
|
|
218
|
+
commitSpinner.succeed(ui.ok('Segundo Cérebro salvo no GitHub'));
|
|
219
|
+
|
|
220
|
+
// Oferecer abrir no browser para completar
|
|
221
|
+
ui.nl();
|
|
222
|
+
console.log(ui.gold('📝 Complete seu Segundo Cérebro:'));
|
|
223
|
+
console.log(ui.dim(` ${repo.html_url}`));
|
|
224
|
+
console.log(ui.dim(' Preencha OWNER.md e EMPRESA.md com seus dados reais.'));
|
|
225
|
+
ui.nl();
|
|
226
|
+
|
|
227
|
+
const { openBrowser } = await prompt({
|
|
228
|
+
type: 'confirm',
|
|
229
|
+
name: 'openBrowser',
|
|
230
|
+
message: 'Abrir no browser agora para completar?',
|
|
231
|
+
initial: true,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
if (openBrowser) {
|
|
235
|
+
await open(repo.html_url);
|
|
236
|
+
await prompt({
|
|
237
|
+
type: 'input',
|
|
238
|
+
name: '_',
|
|
239
|
+
message: 'Pressione ENTER quando terminar de preencher...',
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const pullSpinner = ora('Sincronizando alterações...').start();
|
|
243
|
+
try {
|
|
244
|
+
await execa('git', ['pull'], { cwd: businessDir });
|
|
245
|
+
pullSpinner.succeed(ui.ok('Segundo Cérebro sincronizado'));
|
|
246
|
+
} catch {
|
|
247
|
+
pullSpinner.warn(ui.warn('Não foi possível sincronizar — continue assim mesmo'));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return { businessDir, repoUrl: repo.html_url };
|
|
252
|
+
|
|
253
|
+
} catch (err) {
|
|
254
|
+
spinner.fail(ui.fail('Erro ao criar repositório'));
|
|
255
|
+
console.error(ui.dim(` ${err.message}`));
|
|
256
|
+
|
|
257
|
+
// Fallback: criar .business/ local
|
|
258
|
+
console.log(ui.warn('Criando .business/ localmente (sem GitHub)...'));
|
|
259
|
+
const businessDir = path.join(projectDir, '.business');
|
|
260
|
+
await createMinimalBusiness(businessDir, answers);
|
|
261
|
+
return { businessDir, repoUrl: null };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
266
|
+
async function fillPlaceholders(dir, answers) {
|
|
267
|
+
const files = await fs.readdir(dir);
|
|
268
|
+
for (const file of files) {
|
|
269
|
+
if (!file.endsWith('.md')) continue;
|
|
270
|
+
const filePath = path.join(dir, file);
|
|
271
|
+
let content = await fs.readFile(filePath, 'utf8');
|
|
272
|
+
content = content
|
|
273
|
+
.replace(/\{\{SEU_NOME\}\}/g, answers.name)
|
|
274
|
+
.replace(/\{\{SUA_EMPRESA\}\}/g, answers.empresa)
|
|
275
|
+
.replace(/\{\{NOME_DA_EMPRESA\}\}/g, answers.empresa)
|
|
276
|
+
.replace(/\{\{SEU_CARGO\}\}/g, answers.cargo)
|
|
277
|
+
.replace(/\{\{DESCREVA_EM_2_FRASES_O_QUE_VOCE_FAZ\}\}/g, answers.descricao)
|
|
278
|
+
.replace(/\{\{CIDADE\}\}/g, 'Brasil');
|
|
279
|
+
await fs.writeFile(filePath, content);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function createMinimalBusiness(dir, answers) {
|
|
284
|
+
await fs.ensureDir(dir);
|
|
285
|
+
await fs.ensureDir(path.join(dir, 'clientes'));
|
|
286
|
+
await fs.writeFile(
|
|
287
|
+
path.join(dir, 'OWNER.md'),
|
|
288
|
+
`# ${answers.name}\n\n${answers.descricao}\n\n- **Empresa:** ${answers.empresa}\n- **Cargo:** ${answers.cargo}\n`
|
|
289
|
+
);
|
|
290
|
+
await fs.writeFile(
|
|
291
|
+
path.join(dir, 'EMPRESA.md'),
|
|
292
|
+
`# ${answers.empresa}\n\n> Preencha este arquivo com o contexto da sua empresa.\n`
|
|
293
|
+
);
|
|
294
|
+
await fs.writeFile(
|
|
295
|
+
path.join(dir, 'PRIORIDADES.md'),
|
|
296
|
+
`# Prioridades\n\n> Atualize semanalmente.\n\n## Esta semana\n- [ ] \n`
|
|
297
|
+
);
|
|
298
|
+
await fs.writeFile(
|
|
299
|
+
path.join(dir, 'ACORDOS.md'),
|
|
300
|
+
`# Acordos\n\n- Nunca assumir — perguntar antes\n- Sempre confirmar antes de deletar ou fazer push\n- Português brasileiro\n- Commits em português\n`
|
|
301
|
+
);
|
|
302
|
+
}
|
package/lib/setup.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import { ui } from './ui.js';
|
|
6
|
+
|
|
7
|
+
const GENIA_OS_REPO = 'https://github.com/elidyizzy/GENIA-SQUAD-OS.git';
|
|
8
|
+
|
|
9
|
+
export async function setupGenieOS(projectDir, businessDir) {
|
|
10
|
+
await fs.ensureDir(projectDir);
|
|
11
|
+
|
|
12
|
+
// Clonar GENIA-SQUAD-OS
|
|
13
|
+
const spinner = ora('Clonando GEN.IA SQUAD OS...').start();
|
|
14
|
+
try {
|
|
15
|
+
await execa('git', ['clone', GENIA_OS_REPO, '.'], { cwd: projectDir });
|
|
16
|
+
spinner.succeed(ui.ok('GEN.IA SQUAD OS clonado'));
|
|
17
|
+
} catch (err) {
|
|
18
|
+
spinner.fail(ui.fail('Erro ao clonar GENIA-SQUAD-OS'));
|
|
19
|
+
throw err;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Substituir .business/ do repo clonado pelo Segundo Cérebro do usuário
|
|
23
|
+
const defaultBusiness = path.join(projectDir, '.business');
|
|
24
|
+
if (businessDir !== defaultBusiness) {
|
|
25
|
+
const linkSpinner = ora('Conectando Segundo Cérebro...').start();
|
|
26
|
+
try {
|
|
27
|
+
await fs.remove(defaultBusiness);
|
|
28
|
+
await fs.copy(businessDir, defaultBusiness);
|
|
29
|
+
linkSpinner.succeed(ui.ok('Segundo Cérebro conectado ao projeto'));
|
|
30
|
+
} catch (err) {
|
|
31
|
+
linkSpinner.warn(ui.warn('Segundo Cérebro: mantendo cópia local'));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Instalar dependências do Mission Control (se existir)
|
|
36
|
+
const mcDir = path.join(projectDir, 'mission-control');
|
|
37
|
+
if (await fs.pathExists(mcDir)) {
|
|
38
|
+
const depsSpinner = ora('Instalando dependências do Mission Control...').start();
|
|
39
|
+
try {
|
|
40
|
+
await execa('npm', ['install'], { cwd: mcDir });
|
|
41
|
+
depsSpinner.succeed(ui.ok('Dependências instaladas'));
|
|
42
|
+
} catch {
|
|
43
|
+
depsSpinner.warn(ui.warn('Instale manualmente: cd mission-control && npm install'));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
package/lib/ui.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
export const ui = {
|
|
4
|
+
// Cores do sistema
|
|
5
|
+
gold: (t) => chalk.hex('#D4A843')(t),
|
|
6
|
+
green: (t) => chalk.hex('#3EE07A')(t),
|
|
7
|
+
dim: (t) => chalk.hex('#6A6460')(t),
|
|
8
|
+
red: (t) => chalk.red(t),
|
|
9
|
+
blue: (t) => chalk.hex('#5A9EF5')(t),
|
|
10
|
+
|
|
11
|
+
// Ícones
|
|
12
|
+
ok: (t) => `${chalk.hex('#3EE07A')('✅')} ${t}`,
|
|
13
|
+
fail: (t) => `${chalk.red('❌')} ${t}`,
|
|
14
|
+
warn: (t) => `${chalk.hex('#F0B824')('⚠️')} ${t}`,
|
|
15
|
+
info: (t) => `${chalk.hex('#5A9EF5')('ℹ')} ${t}`,
|
|
16
|
+
step: (n, t) => `\n${chalk.hex('#D4A843')(`[${n}]`)} ${chalk.white(t)}`,
|
|
17
|
+
|
|
18
|
+
// Banner
|
|
19
|
+
banner: () => {
|
|
20
|
+
console.log('\n' + chalk.hex('#D4A843')(`
|
|
21
|
+
╔══════════════════════════════════════════════╗
|
|
22
|
+
║ ║
|
|
23
|
+
║ GEN.IA MISSION CONTROL ║
|
|
24
|
+
║ Instalador v1.0 ║
|
|
25
|
+
║ ║
|
|
26
|
+
║ github.com/elidyizzy/GENIA-SQUAD-OS ║
|
|
27
|
+
║ ║
|
|
28
|
+
╚══════════════════════════════════════════════╝
|
|
29
|
+
`));
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
separator: () => console.log(chalk.hex('#6A6460')('─'.repeat(48))),
|
|
33
|
+
nl: () => console.log(),
|
|
34
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-genia-mission-control",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Instalador do GEN.IA Mission Control — um comando para ter seu time de agentes IA funcionando",
|
|
5
|
+
"bin": "./bin/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "node bin/index.js"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"chalk": "^5.3.0",
|
|
11
|
+
"enquirer": "^2.4.1",
|
|
12
|
+
"ora": "^8.0.1",
|
|
13
|
+
"execa": "^8.0.1",
|
|
14
|
+
"open": "^10.1.0",
|
|
15
|
+
"node-fetch": "^3.3.2",
|
|
16
|
+
"fs-extra": "^11.2.0"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18.0.0"
|
|
20
|
+
},
|
|
21
|
+
"keywords": ["claude", "ai", "genia", "mission-control", "claude-code"],
|
|
22
|
+
"author": "Elidy Izidio <elidy@geniasquad.com.br>",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"type": "module"
|
|
25
|
+
}
|