nex-app 0.1.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 +74 -0
- package/cli/create.js +116 -0
- package/cli/fallback.js +96 -0
- package/generator/agent-installer.js +33 -0
- package/generator/cursor-integration.js +202 -0
- package/generator/index.js +141 -0
- package/generator/package-manager.js +40 -0
- package/generator/template-engine.js +18 -0
- package/lib/marketplace-client.js +75 -0
- package/package.json +55 -0
- package/server/index.js +53 -0
- package/server/routes.js +89 -0
- package/templates/api-only/README.md.tpl +30 -0
- package/templates/api-only/package.json.tpl +20 -0
- package/templates/api-only/src/index.js.tpl +9 -0
- package/templates/blank/.nex-core/config.yaml.tpl +14 -0
- package/templates/blank/README.md.tpl +48 -0
- package/templates/blank/package.json.tpl +18 -0
- package/templates/blank/src/index.js.tpl +9 -0
- package/templates/full-stack/README.md.tpl +33 -0
- package/templates/full-stack/package.json.tpl +20 -0
- package/templates/full-stack/src/client/index.html.tpl +12 -0
- package/templates/full-stack/src/server/index.js.tpl +9 -0
- package/ui/assets/app.js +255 -0
- package/ui/assets/styles.css +416 -0
- package/ui/index.html +113 -0
- package/utils/browser.js +40 -0
- package/utils/environment.js +42 -0
- package/utils/port-finder.js +37 -0
- package/utils/validation.js +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# nex-app
|
|
2
|
+
|
|
3
|
+
Crie projetos NEX com interface interativa (CLI + Web UI).
|
|
4
|
+
|
|
5
|
+
## 🚀 Uso
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Uso básico (detecção automática)
|
|
9
|
+
npx nex-app
|
|
10
|
+
|
|
11
|
+
# Forçar Web UI
|
|
12
|
+
npx nex-app --web
|
|
13
|
+
|
|
14
|
+
# Forçar CLI
|
|
15
|
+
npx nex-app --no-browser
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## ✨ Características
|
|
19
|
+
|
|
20
|
+
- **Modo Híbrido**: CLI rápido ou Web UI visual
|
|
21
|
+
- **Detecção Automática**: Escolhe o melhor modo automaticamente
|
|
22
|
+
- **Templates**: Blank, Full Stack, API Only
|
|
23
|
+
- **Integração Cursor**: Configuração automática
|
|
24
|
+
- **Marketplace**: Seleção de agentes durante criação
|
|
25
|
+
|
|
26
|
+
## 📋 Requisitos
|
|
27
|
+
|
|
28
|
+
- Node.js >= 18.0.0
|
|
29
|
+
- npm, pnpm ou yarn
|
|
30
|
+
|
|
31
|
+
## 🏗️ Arquitetura
|
|
32
|
+
|
|
33
|
+
Este pacote implementa a **Opção 3 (Híbrida)**:
|
|
34
|
+
- CLI como padrão (rápido, confiável)
|
|
35
|
+
- Web installer como opção (`--web`)
|
|
36
|
+
- Detecção automática de ambiente
|
|
37
|
+
|
|
38
|
+
## 📖 Documentação
|
|
39
|
+
|
|
40
|
+
- [Arquitetura Completa](../docs/ARQUITETURA_NPX_WEB_INSTALLER_NEX.md)
|
|
41
|
+
- [Implementação Detalhada](../docs/IMPLEMENTACAO_HIBRIDA_NPX.md)
|
|
42
|
+
- [Brainstorm](../docs/BRAINSTORM_NPX_WEB_INSTALLER.md)
|
|
43
|
+
|
|
44
|
+
## 🔧 Desenvolvimento
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Instalar dependências
|
|
48
|
+
npm install
|
|
49
|
+
|
|
50
|
+
# Testar localmente
|
|
51
|
+
node cli/create.js
|
|
52
|
+
|
|
53
|
+
# Testar modo web
|
|
54
|
+
node cli/create.js --web
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## 📝 Status
|
|
58
|
+
|
|
59
|
+
🟡 **Em Desenvolvimento** - MVP funcional
|
|
60
|
+
|
|
61
|
+
- [x] Estrutura base
|
|
62
|
+
- [x] CLI entrypoint
|
|
63
|
+
- [x] Servidor web
|
|
64
|
+
- [x] UI básica
|
|
65
|
+
- [x] Generator
|
|
66
|
+
- [x] Templates básicos
|
|
67
|
+
- [ ] Integração completa com Marketplace
|
|
68
|
+
- [ ] Integração completa com Cursor
|
|
69
|
+
- [ ] Testes
|
|
70
|
+
- [ ] Publicação no npm
|
|
71
|
+
|
|
72
|
+
## 📄 Licença
|
|
73
|
+
|
|
74
|
+
PROPRIETARY - INOSX
|
package/cli/create.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* NEX App Creator - Entrypoint principal
|
|
5
|
+
* Suporta modo CLI e Web UI (híbrido)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { startServer } from '../server/index.js'
|
|
9
|
+
import { openBrowser } from '../utils/browser.js'
|
|
10
|
+
import { detectEnvironment } from '../utils/environment.js'
|
|
11
|
+
import { runCLIFallback } from './fallback.js'
|
|
12
|
+
import chalk from 'chalk'
|
|
13
|
+
|
|
14
|
+
async function main() {
|
|
15
|
+
// Verificar flags especiais
|
|
16
|
+
const args = process.argv.slice(2)
|
|
17
|
+
|
|
18
|
+
// --version ou -v
|
|
19
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
20
|
+
const pkg = await import('../package.json', { assert: { type: 'json' } })
|
|
21
|
+
console.log(pkg.default.version)
|
|
22
|
+
process.exit(0)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// --help ou -h
|
|
26
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
27
|
+
console.log(chalk.blue('🚀 nex-app'))
|
|
28
|
+
console.log(chalk.gray('Create NEX projects with interactive installer\n'))
|
|
29
|
+
console.log('Usage:')
|
|
30
|
+
console.log(' npx nex-app # Auto-detect mode')
|
|
31
|
+
console.log(' npx nex-app --web # Force Web UI')
|
|
32
|
+
console.log(' npx nex-app --no-browser # Force CLI')
|
|
33
|
+
console.log(' npx nex-app --version # Show version')
|
|
34
|
+
process.exit(0)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Validações
|
|
38
|
+
const nodeVersion = process.version
|
|
39
|
+
if (nodeVersion < 'v18.0.0') {
|
|
40
|
+
console.error(chalk.red('❌ Node.js >= 18.0.0 required'))
|
|
41
|
+
console.error(chalk.gray(` Current: ${nodeVersion}`))
|
|
42
|
+
process.exit(1)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Detectar modo de operação
|
|
46
|
+
const useWeb = args.includes('--web')
|
|
47
|
+
const forceCLI = args.includes('--no-browser')
|
|
48
|
+
|
|
49
|
+
const env = detectEnvironment()
|
|
50
|
+
|
|
51
|
+
// Decisão: Web UI ou CLI?
|
|
52
|
+
let shouldUseWeb = false
|
|
53
|
+
|
|
54
|
+
if (forceCLI) {
|
|
55
|
+
shouldUseWeb = false
|
|
56
|
+
console.log(chalk.gray('ℹ️ Modo CLI forçado\n'))
|
|
57
|
+
} else if (useWeb) {
|
|
58
|
+
shouldUseWeb = true
|
|
59
|
+
console.log(chalk.blue('🌐 Modo Web UI forçado\n'))
|
|
60
|
+
} else {
|
|
61
|
+
// Detecção automática: usar web se ambiente suporta
|
|
62
|
+
shouldUseWeb = !env.isHeadless && !env.isCI && env.hasDisplay
|
|
63
|
+
if (shouldUseWeb) {
|
|
64
|
+
console.log(chalk.blue('🌐 Ambiente detectado: usando Web UI\n'))
|
|
65
|
+
} else {
|
|
66
|
+
console.log(chalk.gray('ℹ️ Ambiente headless/CI detectado: usando modo CLI\n'))
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Capturar diretório de trabalho inicial (onde o usuário executou o comando)
|
|
71
|
+
const initialCwd = process.cwd()
|
|
72
|
+
|
|
73
|
+
if (shouldUseWeb) {
|
|
74
|
+
// Modo Web UI
|
|
75
|
+
try {
|
|
76
|
+
const { port, server } = await startServer(initialCwd)
|
|
77
|
+
const url = `http://localhost:${port}`
|
|
78
|
+
|
|
79
|
+
await openBrowser(url)
|
|
80
|
+
|
|
81
|
+
console.log(chalk.green(`✅ Installer aberto em ${chalk.cyan(url)}`))
|
|
82
|
+
console.log(chalk.gray(` Diretório de trabalho: ${chalk.cyan(initialCwd)}`))
|
|
83
|
+
console.log(chalk.gray(' Pressione Ctrl+C para cancelar\n'))
|
|
84
|
+
|
|
85
|
+
// Aguardar finalização
|
|
86
|
+
process.on('SIGINT', () => {
|
|
87
|
+
console.log(chalk.yellow('\n\n⏹️ Cancelando...'))
|
|
88
|
+
server.close()
|
|
89
|
+
process.exit(0)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
// Timeout de segurança (10 minutos)
|
|
93
|
+
setTimeout(() => {
|
|
94
|
+
console.log(chalk.yellow('\n⏱️ Timeout: fechando servidor...'))
|
|
95
|
+
server.close()
|
|
96
|
+
process.exit(0)
|
|
97
|
+
}, 10 * 60 * 1000)
|
|
98
|
+
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error(chalk.red(`\n❌ Erro ao iniciar servidor web: ${error.message}`))
|
|
101
|
+
console.log(chalk.yellow('\n🔄 Fallback para modo CLI...\n'))
|
|
102
|
+
await runCLIFallback(initialCwd)
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
// Modo CLI Fallback
|
|
106
|
+
await runCLIFallback(initialCwd)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
main().catch((error) => {
|
|
111
|
+
console.error(chalk.red(`\n❌ Erro fatal: ${error.message}`))
|
|
112
|
+
if (error.stack) {
|
|
113
|
+
console.error(chalk.gray(error.stack))
|
|
114
|
+
}
|
|
115
|
+
process.exit(1)
|
|
116
|
+
})
|
package/cli/fallback.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Fallback - Modo interativo via terminal
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import inquirer from 'inquirer'
|
|
6
|
+
import chalk from 'chalk'
|
|
7
|
+
import path from 'path'
|
|
8
|
+
import fs from 'fs-extra'
|
|
9
|
+
import { generateProject } from '../generator/index.js'
|
|
10
|
+
import { searchAgents } from '../lib/marketplace-client.js'
|
|
11
|
+
|
|
12
|
+
export async function runCLIFallback(initialCwd = process.cwd()) {
|
|
13
|
+
console.log(chalk.blue('🚀 Create NEX App - Modo CLI\n'))
|
|
14
|
+
console.log(chalk.gray(`Diretório de trabalho: ${chalk.cyan(initialCwd)}\n`))
|
|
15
|
+
|
|
16
|
+
// Buscar agentes disponíveis
|
|
17
|
+
const availableAgents = await searchAgents({})
|
|
18
|
+
|
|
19
|
+
// Detectar nome do projeto (da pasta ou package.json)
|
|
20
|
+
let currentProjectName = path.basename(initialCwd)
|
|
21
|
+
try {
|
|
22
|
+
const packageJsonPath = path.join(initialCwd, 'package.json')
|
|
23
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
24
|
+
const packageJson = await fs.readJSON(packageJsonPath)
|
|
25
|
+
if (packageJson.name) {
|
|
26
|
+
currentProjectName = packageJson.name
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
} catch (error) {
|
|
30
|
+
// Ignore
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const answers = await inquirer.prompt([
|
|
34
|
+
{
|
|
35
|
+
type: 'list',
|
|
36
|
+
name: 'template',
|
|
37
|
+
message: 'Template:',
|
|
38
|
+
choices: [
|
|
39
|
+
{ name: 'Blank - Projeto vazio', value: 'blank' },
|
|
40
|
+
{ name: 'Full Stack - Frontend + Backend', value: 'full-stack' },
|
|
41
|
+
{ name: 'API Only - Apenas backend', value: 'api-only' }
|
|
42
|
+
],
|
|
43
|
+
default: 'blank'
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
type: 'checkbox',
|
|
47
|
+
name: 'agents',
|
|
48
|
+
message: 'Agentes iniciais (opcional):',
|
|
49
|
+
choices: availableAgents.map(agent => ({
|
|
50
|
+
name: `${agent.icon} ${agent.name} - ${agent.tagline}`,
|
|
51
|
+
value: agent.agent_id
|
|
52
|
+
})),
|
|
53
|
+
default: []
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
type: 'confirm',
|
|
57
|
+
name: 'cursorIntegration',
|
|
58
|
+
message: 'Configurar integração com Cursor IDE?',
|
|
59
|
+
default: true
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
type: 'list',
|
|
63
|
+
name: 'packageManager',
|
|
64
|
+
message: 'Package Manager:',
|
|
65
|
+
choices: ['npm', 'pnpm', 'yarn'],
|
|
66
|
+
default: 'npm'
|
|
67
|
+
}
|
|
68
|
+
])
|
|
69
|
+
|
|
70
|
+
console.log(chalk.blue('\n📦 Gerando projeto...\n'))
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const projectPath = await generateProject({
|
|
74
|
+
template: answers.template,
|
|
75
|
+
agents: answers.agents || [],
|
|
76
|
+
packageManager: answers.packageManager,
|
|
77
|
+
cursorIntegration: answers.cursorIntegration,
|
|
78
|
+
options: {},
|
|
79
|
+
targetDir: initialCwd
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
console.log(chalk.green(`\n✅ NEX inicializado em: ${chalk.cyan(projectPath)}\n`))
|
|
83
|
+
console.log(chalk.gray('Próximos passos:'))
|
|
84
|
+
console.log(chalk.cyan(` ${answers.packageManager} install`))
|
|
85
|
+
console.log(chalk.cyan(` ${answers.packageManager} start\n`))
|
|
86
|
+
console.log(chalk.gray('Para instalar agentes:'))
|
|
87
|
+
console.log(chalk.cyan(` nex agent list --all`))
|
|
88
|
+
console.log(chalk.cyan(` nex agent install <agent-id>\n`))
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error(chalk.red(`\n❌ Erro: ${error.message}\n`))
|
|
91
|
+
if (error.stack) {
|
|
92
|
+
console.error(chalk.gray(error.stack))
|
|
93
|
+
}
|
|
94
|
+
process.exit(1)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instalador de agentes
|
|
3
|
+
* TODO: Integrar com nex-installer service real
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs-extra'
|
|
7
|
+
import path from 'path'
|
|
8
|
+
|
|
9
|
+
export async function installAgents(projectPath, agentIds) {
|
|
10
|
+
// Por enquanto, apenas criar estrutura
|
|
11
|
+
// TODO: Integrar com AgentInstaller real do NEX
|
|
12
|
+
|
|
13
|
+
const agentsDir = path.join(projectPath, '.nex-core', 'agents')
|
|
14
|
+
await fs.ensureDir(agentsDir)
|
|
15
|
+
|
|
16
|
+
// Criar arquivo de manifest dos agentes instalados
|
|
17
|
+
const manifest = {
|
|
18
|
+
agents: agentIds.map(id => ({
|
|
19
|
+
agent_id: id,
|
|
20
|
+
installed_at: new Date().toISOString()
|
|
21
|
+
}))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
await fs.writeJSON(
|
|
25
|
+
path.join(agentsDir, 'manifest.json'),
|
|
26
|
+
manifest,
|
|
27
|
+
{ spaces: 2 }
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
// TODO: Instalar agentes reais do registry
|
|
31
|
+
console.log(`📦 Agentes selecionados: ${agentIds.join(', ')}`)
|
|
32
|
+
console.log(' (Instalação real será implementada na próxima fase)')
|
|
33
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integração com Cursor IDE
|
|
3
|
+
* Segue o padrão BMAD: .cursor/rules/nex/{category}/agents/{agentId}.mdc
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs-extra'
|
|
7
|
+
import path from 'path'
|
|
8
|
+
import { fileURLToPath } from 'url'
|
|
9
|
+
import yaml from 'yaml'
|
|
10
|
+
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
12
|
+
const __dirname = path.dirname(__filename)
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Carregar configuração global do NEX CLI
|
|
16
|
+
*/
|
|
17
|
+
async function getGlobalConfig() {
|
|
18
|
+
const os = await import('os')
|
|
19
|
+
const configPath = process.platform === 'win32'
|
|
20
|
+
? path.join(os.default.homedir(), 'AppData', 'Roaming', 'nex', 'config.json')
|
|
21
|
+
: path.join(os.default.homedir(), '.nex', 'config.json')
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
if (await fs.pathExists(configPath)) {
|
|
25
|
+
return await fs.readJSON(configPath)
|
|
26
|
+
}
|
|
27
|
+
} catch (error) {
|
|
28
|
+
// Ignore
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
user: {
|
|
33
|
+
name: 'Developer',
|
|
34
|
+
conversationLanguage: 'pt-br',
|
|
35
|
+
documentLanguage: 'pt-br'
|
|
36
|
+
},
|
|
37
|
+
cursor: {
|
|
38
|
+
enabled: true
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function integrateCursor(projectPath, agents) {
|
|
44
|
+
if (!agents || agents.length === 0) {
|
|
45
|
+
// Ainda assim criar estrutura básica
|
|
46
|
+
await createBasicCursorStructure(projectPath)
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const globalConfig = await getGlobalConfig()
|
|
51
|
+
const userConfig = globalConfig.user || {}
|
|
52
|
+
|
|
53
|
+
// 1. Criar nex/core/config.yaml (similar ao BMAD bmad/core/config.yaml)
|
|
54
|
+
const nexCoreDir = path.join(projectPath, 'nex', 'core')
|
|
55
|
+
await fs.ensureDir(nexCoreDir)
|
|
56
|
+
|
|
57
|
+
const configYaml = {
|
|
58
|
+
user_name: userConfig.name || 'Developer',
|
|
59
|
+
communication_language: userConfig.conversationLanguage || 'pt-br',
|
|
60
|
+
document_output_language: userConfig.documentLanguage || 'pt-br',
|
|
61
|
+
output_folder: '{project-root}/docs'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const configPath = path.join(nexCoreDir, 'config.yaml')
|
|
65
|
+
if (!await fs.pathExists(configPath)) {
|
|
66
|
+
const configYamlContent = yaml.stringify(configYaml)
|
|
67
|
+
await fs.writeFile(configPath, configYamlContent, 'utf8')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 2. Criar .cursor/rules/nex/ (seguindo padrão BMAD .cursor/rules/bmad/)
|
|
71
|
+
const cursorRulesDir = path.join(projectPath, '.cursor', 'rules', 'nex')
|
|
72
|
+
await fs.ensureDir(cursorRulesDir)
|
|
73
|
+
|
|
74
|
+
// 3. Criar index.mdc (similar ao BMAD index.mdc)
|
|
75
|
+
const indexContent = `---
|
|
76
|
+
description: NEX Framework - Master Index
|
|
77
|
+
globs:
|
|
78
|
+
alwaysApply: true
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
# NEX Framework - Cursor Rules Index
|
|
82
|
+
|
|
83
|
+
This is the master index for all NEX agents installed in your project.
|
|
84
|
+
|
|
85
|
+
## Installation Complete!
|
|
86
|
+
|
|
87
|
+
NEX rules have been installed to: \`.cursor/rules/nex/\`
|
|
88
|
+
|
|
89
|
+
**Note:** NEX does not modify your \`.cursorrules\` file. You manage that separately.
|
|
90
|
+
|
|
91
|
+
## How to Use
|
|
92
|
+
|
|
93
|
+
- Reference specific agents: @nex/{category}/agents/{agent-name}
|
|
94
|
+
- Reference this index: @nex/index
|
|
95
|
+
|
|
96
|
+
## Installed Agents
|
|
97
|
+
|
|
98
|
+
${agents.length > 0
|
|
99
|
+
? agents.map(agentId => `- @nex/${agentId} - ${agentId}`).join('\n')
|
|
100
|
+
: 'No agents installed yet. Use: nex agent install <agent-id>'
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
## Quick Reference
|
|
104
|
+
|
|
105
|
+
- All NEX rules are Manual type - reference them explicitly when needed
|
|
106
|
+
- Agents provide persona-based assistance with specific expertise
|
|
107
|
+
- Each agent includes an activation block for proper initialization
|
|
108
|
+
|
|
109
|
+
## Configuration
|
|
110
|
+
|
|
111
|
+
NEX rules are configured as Manual rules (alwaysApply: false) to give you control
|
|
112
|
+
over when they're included in your context. Reference them explicitly when you need
|
|
113
|
+
specific agent expertise.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
**Project:** ${path.basename(projectPath)}
|
|
118
|
+
**Created:** ${new Date().toISOString()}
|
|
119
|
+
`
|
|
120
|
+
|
|
121
|
+
await fs.writeFile(
|
|
122
|
+
path.join(cursorRulesDir, 'index.mdc'),
|
|
123
|
+
indexContent,
|
|
124
|
+
'utf8'
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
console.log('🔗 Integração com Cursor configurada (padrão BMAD)')
|
|
128
|
+
console.log(` Config: nex/core/config.yaml`)
|
|
129
|
+
console.log(` Rules: .cursor/rules/nex/index.mdc`)
|
|
130
|
+
console.log(` Agentes selecionados: ${agents.length}`)
|
|
131
|
+
console.log(' (Para instalar agentes reais, execute: nex agent install <agent-id>)')
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Criar estrutura básica do Cursor mesmo sem agentes
|
|
136
|
+
*/
|
|
137
|
+
async function createBasicCursorStructure(projectPath) {
|
|
138
|
+
const globalConfig = await getGlobalConfig()
|
|
139
|
+
const userConfig = globalConfig.user || {}
|
|
140
|
+
|
|
141
|
+
// Criar nex/core/config.yaml
|
|
142
|
+
const nexCoreDir = path.join(projectPath, 'nex', 'core')
|
|
143
|
+
await fs.ensureDir(nexCoreDir)
|
|
144
|
+
|
|
145
|
+
const configYaml = {
|
|
146
|
+
user_name: userConfig.name || 'Developer',
|
|
147
|
+
communication_language: userConfig.conversationLanguage || 'pt-br',
|
|
148
|
+
document_output_language: userConfig.documentLanguage || 'pt-br',
|
|
149
|
+
output_folder: '{project-root}/docs'
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const configPath = path.join(nexCoreDir, 'config.yaml')
|
|
153
|
+
const configYamlContent = yaml.stringify(configYaml)
|
|
154
|
+
await fs.writeFile(configPath, configYamlContent, 'utf8')
|
|
155
|
+
|
|
156
|
+
// Criar .cursor/rules/nex/index.mdc básico
|
|
157
|
+
const cursorRulesDir = path.join(projectPath, '.cursor', 'rules', 'nex')
|
|
158
|
+
await fs.ensureDir(cursorRulesDir)
|
|
159
|
+
|
|
160
|
+
const indexContent = `---
|
|
161
|
+
description: NEX Framework - Master Index
|
|
162
|
+
globs:
|
|
163
|
+
alwaysApply: true
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
# NEX Framework - Cursor Rules Index
|
|
167
|
+
|
|
168
|
+
This is the master index for all NEX agents installed in your project.
|
|
169
|
+
|
|
170
|
+
## Installation Complete!
|
|
171
|
+
|
|
172
|
+
NEX rules have been installed to: \`.cursor/rules/nex/\`
|
|
173
|
+
|
|
174
|
+
**Note:** NEX does not modify your \`.cursorrules\` file. You manage that separately.
|
|
175
|
+
|
|
176
|
+
## How to Use
|
|
177
|
+
|
|
178
|
+
- Reference specific agents: @nex/{category}/agents/{agent-name}
|
|
179
|
+
- Reference this index: @nex/index
|
|
180
|
+
|
|
181
|
+
## Installed Agents
|
|
182
|
+
|
|
183
|
+
No agents installed yet. Use: nex agent install <agent-id>
|
|
184
|
+
|
|
185
|
+
## Quick Reference
|
|
186
|
+
|
|
187
|
+
- All NEX rules are Manual type - reference them explicitly when needed
|
|
188
|
+
- Agents provide persona-based assistance with specific expertise
|
|
189
|
+
- Each agent includes an activation block for proper initialization
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
**Project:** ${path.basename(projectPath)}
|
|
194
|
+
**Created:** ${new Date().toISOString()}
|
|
195
|
+
`
|
|
196
|
+
|
|
197
|
+
await fs.writeFile(
|
|
198
|
+
path.join(cursorRulesDir, 'index.mdc'),
|
|
199
|
+
indexContent,
|
|
200
|
+
'utf8'
|
|
201
|
+
)
|
|
202
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generator de projetos NEX
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from 'fs-extra'
|
|
6
|
+
import path from 'path'
|
|
7
|
+
import { fileURLToPath } from 'url'
|
|
8
|
+
import { renderTemplate } from './template-engine.js'
|
|
9
|
+
import { installAgents } from './agent-installer.js'
|
|
10
|
+
import { integrateCursor } from './cursor-integration.js'
|
|
11
|
+
import { installDependencies } from './package-manager.js'
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
14
|
+
const __dirname = path.dirname(__filename)
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Obter todos os arquivos de um template recursivamente
|
|
18
|
+
*/
|
|
19
|
+
async function getTemplateFiles(templatePath) {
|
|
20
|
+
const files = []
|
|
21
|
+
|
|
22
|
+
async function walkDir(dir) {
|
|
23
|
+
const entries = await fs.readdir(dir, { withFileTypes: true })
|
|
24
|
+
|
|
25
|
+
for (const entry of entries) {
|
|
26
|
+
const fullPath = path.join(dir, entry.name)
|
|
27
|
+
|
|
28
|
+
if (entry.isDirectory()) {
|
|
29
|
+
await walkDir(fullPath)
|
|
30
|
+
} else {
|
|
31
|
+
files.push(fullPath)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
await walkDir(templatePath)
|
|
37
|
+
return files
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function generateProject(config) {
|
|
41
|
+
const {
|
|
42
|
+
template,
|
|
43
|
+
agents = [],
|
|
44
|
+
packageManager = 'npm',
|
|
45
|
+
cursorIntegration = false,
|
|
46
|
+
options = {},
|
|
47
|
+
targetDir
|
|
48
|
+
} = config
|
|
49
|
+
|
|
50
|
+
// 1. Carregar template
|
|
51
|
+
const templatePath = path.join(__dirname, '../templates', template)
|
|
52
|
+
if (!await fs.pathExists(templatePath)) {
|
|
53
|
+
throw new Error(`Template "${template}" not found`)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 2. Usar diretório atual (padrão BMAD/NEX - não cria nova pasta)
|
|
57
|
+
const projectPath = targetDir
|
|
58
|
+
|
|
59
|
+
// Detectar nome do projeto (da pasta ou package.json existente)
|
|
60
|
+
let detectedProjectName = path.basename(projectPath)
|
|
61
|
+
try {
|
|
62
|
+
const packageJsonPath = path.join(projectPath, 'package.json')
|
|
63
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
64
|
+
const packageJson = await fs.readJSON(packageJsonPath)
|
|
65
|
+
if (packageJson.name) {
|
|
66
|
+
detectedProjectName = packageJson.name
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
} catch (error) {
|
|
70
|
+
// Ignore
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Verificar se diretório está vazio ou pode ser inicializado
|
|
74
|
+
const files = await fs.readdir(projectPath).catch(() => [])
|
|
75
|
+
const importantFiles = files.filter(f =>
|
|
76
|
+
!f.startsWith('.') &&
|
|
77
|
+
f !== 'node_modules' &&
|
|
78
|
+
f !== 'package-lock.json' &&
|
|
79
|
+
f !== 'pnpm-lock.yaml' &&
|
|
80
|
+
f !== 'yarn.lock'
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
if (importantFiles.length > 0) {
|
|
84
|
+
// Diretório não está vazio, mas podemos inicializar NEX nele (como nex init)
|
|
85
|
+
console.log(`⚠️ Diretório não está vazio. Inicializando NEX no diretório atual...`)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 3. Processar arquivos do template
|
|
89
|
+
const templateFiles = await getTemplateFiles(templatePath)
|
|
90
|
+
|
|
91
|
+
for (const file of templateFiles) {
|
|
92
|
+
const content = await fs.readFile(file, 'utf-8')
|
|
93
|
+
const relativePath = path.relative(templatePath, file)
|
|
94
|
+
const targetPath = path.join(projectPath, relativePath.replace('.tpl', ''))
|
|
95
|
+
|
|
96
|
+
// Não sobrescrever arquivos existentes (exceto se for .nex-core, .cursor ou nex/)
|
|
97
|
+
const isNexFile = relativePath.includes('.nex-core') ||
|
|
98
|
+
relativePath.includes('.cursor') ||
|
|
99
|
+
relativePath.includes('nex/')
|
|
100
|
+
if (!isNexFile && await fs.pathExists(targetPath)) {
|
|
101
|
+
console.log(`⏭️ Pulando ${relativePath} (já existe)`)
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const rendered = renderTemplate(content, {
|
|
106
|
+
projectName: detectedProjectName,
|
|
107
|
+
packageManager,
|
|
108
|
+
...options
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
await fs.ensureDir(path.dirname(targetPath))
|
|
112
|
+
await fs.writeFile(targetPath, rendered)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 4. Criar estrutura .nex-core (sempre, padrão NEX)
|
|
116
|
+
const nexCoreDir = path.join(projectPath, '.nex-core')
|
|
117
|
+
await fs.ensureDir(path.join(nexCoreDir, 'agents'))
|
|
118
|
+
await fs.ensureDir(path.join(nexCoreDir, 'data'))
|
|
119
|
+
await fs.ensureDir(path.join(nexCoreDir, 'documents'))
|
|
120
|
+
|
|
121
|
+
// Criar installed.json vazio (registry de agentes)
|
|
122
|
+
const installedJsonPath = path.join(nexCoreDir, 'agents', 'installed.json')
|
|
123
|
+
if (!await fs.pathExists(installedJsonPath)) {
|
|
124
|
+
await fs.writeJSON(installedJsonPath, {}, { spaces: 2 })
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 5. Instalar agentes (se selecionados)
|
|
128
|
+
if (agents.length > 0) {
|
|
129
|
+
await installAgents(projectPath, agents)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 6. Integrar com Cursor (se habilitado)
|
|
133
|
+
if (cursorIntegration) {
|
|
134
|
+
await integrateCursor(projectPath, agents)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 7. Instalar dependências
|
|
138
|
+
await installDependencies(projectPath, packageManager)
|
|
139
|
+
|
|
140
|
+
return projectPath
|
|
141
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instalador de dependências (npm/pnpm/yarn)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { exec } from 'child_process'
|
|
6
|
+
import { promisify } from 'util'
|
|
7
|
+
|
|
8
|
+
const execAsync = promisify(exec)
|
|
9
|
+
|
|
10
|
+
export async function installDependencies(projectPath, packageManager) {
|
|
11
|
+
const commands = {
|
|
12
|
+
npm: 'npm install',
|
|
13
|
+
pnpm: 'pnpm install',
|
|
14
|
+
yarn: 'yarn install'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const command = commands[packageManager]
|
|
18
|
+
if (!command) {
|
|
19
|
+
throw new Error(`Package manager "${packageManager}" não suportado`)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
console.log(`📦 Instalando dependências com ${packageManager}...`)
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
26
|
+
cwd: projectPath,
|
|
27
|
+
stdio: 'inherit'
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
if (stderr && !stderr.includes('npm warn')) {
|
|
31
|
+
console.warn(stderr)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log('✅ Dependências instaladas')
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.warn(`⚠️ Erro ao instalar dependências: ${error.message}`)
|
|
37
|
+
console.warn(' Você pode instalar manualmente depois com:')
|
|
38
|
+
console.warn(` cd ${projectPath} && ${command}`)
|
|
39
|
+
}
|
|
40
|
+
}
|