nex-framework-cli 1.0.16 → 1.0.18
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/cli/nex-cli.js +10 -3
- package/package.json +3 -1
- package/scripts/README.md +196 -0
- package/scripts/postinstall.js +155 -0
- package/src/services/nex-marketplace/NEXMarketplace.js +253 -7
package/cli/nex-cli.js
CHANGED
|
@@ -28,7 +28,7 @@ program.configureHelp({
|
|
|
28
28
|
program
|
|
29
29
|
.name('nex')
|
|
30
30
|
.description('NEX Framework - Framework completo de agentes AI')
|
|
31
|
-
|
|
31
|
+
.version('1.0.18', '-v, --version', 'Mostra a versão')
|
|
32
32
|
.addHelpText('before', chalk.bold.cyan(`
|
|
33
33
|
╔════════════════════════════════════════════════════════════════╗
|
|
34
34
|
║ NEX Framework - CLI v1.0.9 ║
|
|
@@ -62,6 +62,7 @@ Exemplos:
|
|
|
62
62
|
`))
|
|
63
63
|
.action(async (options) => {
|
|
64
64
|
console.log(chalk.blue('🚀 Inicializando projeto NEX...'))
|
|
65
|
+
console.log()
|
|
65
66
|
|
|
66
67
|
const answers = await inquirer.prompt([
|
|
67
68
|
{
|
|
@@ -84,10 +85,11 @@ Exemplos:
|
|
|
84
85
|
const projectName = options.name || answers.projectName
|
|
85
86
|
const template = options.template || answers.template
|
|
86
87
|
|
|
87
|
-
// Criar estrutura do projeto
|
|
88
|
+
// Criar estrutura do projeto (sem config, usa global)
|
|
88
89
|
await createProjectStructure(projectName, template)
|
|
89
90
|
|
|
90
|
-
console.log(chalk.green(
|
|
91
|
+
console.log(chalk.green(`\n✅ Projeto ${projectName} criado com sucesso!`))
|
|
92
|
+
console.log()
|
|
91
93
|
})
|
|
92
94
|
|
|
93
95
|
// Comando: plan
|
|
@@ -391,6 +393,11 @@ Projeto criado com NEX Framework.
|
|
|
391
393
|
npm install
|
|
392
394
|
npm start
|
|
393
395
|
\`\`\`
|
|
396
|
+
|
|
397
|
+
## NEX Configuration
|
|
398
|
+
|
|
399
|
+
As configurações do NEX (idioma, integração com Cursor) são gerenciadas globalmente.
|
|
400
|
+
Para ver ou alterar: \`nex config\`
|
|
394
401
|
`
|
|
395
402
|
await fs.writeFile(path.join(projectDir, 'README.md'), readme)
|
|
396
403
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nex-framework-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.18",
|
|
4
4
|
"description": "NEX CLI - Command-line interface for NEX Framework and Agent Marketplace",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "cli/nex-cli.js",
|
|
@@ -11,12 +11,14 @@
|
|
|
11
11
|
"cli/",
|
|
12
12
|
"src/services/nex-marketplace/",
|
|
13
13
|
"src/services/nex-installer/",
|
|
14
|
+
"scripts/postinstall.js",
|
|
14
15
|
"registry/",
|
|
15
16
|
"README.md",
|
|
16
17
|
"LICENSE"
|
|
17
18
|
],
|
|
18
19
|
"scripts": {
|
|
19
20
|
"prepublishOnly": "npm run test",
|
|
21
|
+
"postinstall": "node scripts/postinstall.js",
|
|
20
22
|
"test": "node cli/nex-cli.js --version",
|
|
21
23
|
"test:api": "node scripts/test-marketplace-api.js",
|
|
22
24
|
"publish:cli": "node scripts/publish-cli.js",
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# 📦 Scripts do NEX Framework
|
|
2
|
+
|
|
3
|
+
Scripts utilitários para gerenciar o projeto NEX.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🗄️ Scripts de Banco de Dados
|
|
8
|
+
|
|
9
|
+
### `check-database.js`
|
|
10
|
+
|
|
11
|
+
**Descrição:** Verifica o estado atual do banco de dados Supabase
|
|
12
|
+
|
|
13
|
+
**Uso:**
|
|
14
|
+
```bash
|
|
15
|
+
npm run db:check
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**O que faz:**
|
|
19
|
+
- ✅ Conecta ao Supabase
|
|
20
|
+
- ✅ Verifica se as 7 tabelas do NEX Marketplace existem
|
|
21
|
+
- ✅ Mostra status de cada tabela (✅ OK, ❌ Faltando, ⚠️ Erro)
|
|
22
|
+
- ✅ Conta quantos agentes estão cadastrados
|
|
23
|
+
- ✅ Lista os agentes existentes
|
|
24
|
+
|
|
25
|
+
**Pré-requisitos:**
|
|
26
|
+
```powershell
|
|
27
|
+
$env:NEXT_PUBLIC_SUPABASE_URL="https://seu-projeto.supabase.co"
|
|
28
|
+
$env:NEXT_PUBLIC_SUPABASE_ANON_KEY="sua-anon-key"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Exemplo de saída:**
|
|
32
|
+
```
|
|
33
|
+
🔗 Connecting to Supabase...
|
|
34
|
+
📍 URL: https://auqfubbpxjzuzzqfazdp.supabase.co
|
|
35
|
+
|
|
36
|
+
🔍 Checking for NEX Marketplace tables...
|
|
37
|
+
|
|
38
|
+
📊 Database Status:
|
|
39
|
+
|
|
40
|
+
✅ nex_marketplace_agents OK
|
|
41
|
+
✅ nex_marketplace_versions OK
|
|
42
|
+
✅ nex_marketplace_installs OK
|
|
43
|
+
✅ nex_marketplace_reviews OK
|
|
44
|
+
✅ nex_marketplace_bundles OK
|
|
45
|
+
✅ nex_marketplace_analytics OK
|
|
46
|
+
✅ nex_marketplace_stars OK
|
|
47
|
+
|
|
48
|
+
🎉 All tables exist! Database is ready.
|
|
49
|
+
|
|
50
|
+
📊 Checking data...
|
|
51
|
+
✅ Found 3 agents:
|
|
52
|
+
- anx: ANX - Analysis Expert (v1.0.0)
|
|
53
|
+
- pmx: PMX - Product Management Expert (v1.0.0)
|
|
54
|
+
- arx: ARX - Architecture Expert (v1.0.0)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
### `run-migration.js`
|
|
60
|
+
|
|
61
|
+
**Descrição:** Helper para executar migrations do Supabase
|
|
62
|
+
|
|
63
|
+
**Uso:**
|
|
64
|
+
```bash
|
|
65
|
+
npm run db:migrate
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**O que faz:**
|
|
69
|
+
- ✅ Lê o arquivo `supabase/migrations/002_nex_agent_marketplace.sql`
|
|
70
|
+
- ✅ Tenta executar via API do Supabase
|
|
71
|
+
- ✅ Se falhar, fornece instruções para execução manual
|
|
72
|
+
|
|
73
|
+
**Pré-requisitos:**
|
|
74
|
+
```powershell
|
|
75
|
+
$env:NEXT_PUBLIC_SUPABASE_URL="https://seu-projeto.supabase.co"
|
|
76
|
+
$env:SUPABASE_SERVICE_ROLE_KEY="sua-service-role-key"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Nota:** Na maioria dos casos, é melhor executar a migration **manualmente** via SQL Editor do Supabase. Este script é um helper que orienta o processo.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## ⚙️ Script de Setup do Supabase
|
|
84
|
+
|
|
85
|
+
### `supabase-setup.ps1`
|
|
86
|
+
|
|
87
|
+
**Descrição:** Script PowerShell para configurar o Supabase localmente (Docker)
|
|
88
|
+
|
|
89
|
+
**Uso:**
|
|
90
|
+
```powershell
|
|
91
|
+
.\scripts\supabase-setup.ps1
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**O que faz:**
|
|
95
|
+
- ✅ Verifica se Docker está instalado
|
|
96
|
+
- ✅ Inicializa projeto Supabase local
|
|
97
|
+
- ✅ Inicia containers Docker
|
|
98
|
+
- ✅ Aplica migrations
|
|
99
|
+
- ✅ Configura ambiente local
|
|
100
|
+
|
|
101
|
+
**Quando usar:** Para desenvolvimento local com Supabase rodando em Docker.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 🔧 Variáveis de Ambiente
|
|
106
|
+
|
|
107
|
+
Todos os scripts de banco de dados precisam de:
|
|
108
|
+
|
|
109
|
+
### Para Verificação (read-only):
|
|
110
|
+
```powershell
|
|
111
|
+
$env:NEXT_PUBLIC_SUPABASE_URL="https://seu-projeto.supabase.co"
|
|
112
|
+
$env:NEXT_PUBLIC_SUPABASE_ANON_KEY="sua-anon-key"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Para Migrations (write):
|
|
116
|
+
```powershell
|
|
117
|
+
$env:NEXT_PUBLIC_SUPABASE_URL="https://seu-projeto.supabase.co"
|
|
118
|
+
$env:SUPABASE_SERVICE_ROLE_KEY="sua-service-role-key"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Onde encontrar:**
|
|
122
|
+
👉 https://app.supabase.com/project/SEU_PROJECT_ID/settings/api
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 📝 Comandos NPM
|
|
127
|
+
|
|
128
|
+
Adicionados em `package.json`:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"scripts": {
|
|
133
|
+
"db:check": "node scripts/check-database.js",
|
|
134
|
+
"db:migrate": "node scripts/run-migration.js"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 🚀 Fluxo Recomendado
|
|
142
|
+
|
|
143
|
+
### Primeira vez (setup inicial):
|
|
144
|
+
|
|
145
|
+
1. **Configurar variáveis:**
|
|
146
|
+
```powershell
|
|
147
|
+
$env:NEXT_PUBLIC_SUPABASE_URL="https://auqfubbpxjzuzzqfazdp.supabase.co"
|
|
148
|
+
$env:NEXT_PUBLIC_SUPABASE_ANON_KEY="sua-key"
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
2. **Verificar estado:**
|
|
152
|
+
```bash
|
|
153
|
+
npm run db:check
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
3. **Executar migration (manualmente):**
|
|
157
|
+
- Abrir: https://app.supabase.com/project/auqfubbpxjzuzzqfazdp/sql
|
|
158
|
+
- Copiar: `supabase/migrations/002_nex_agent_marketplace.sql`
|
|
159
|
+
- Colar e executar (Ctrl+Enter)
|
|
160
|
+
|
|
161
|
+
4. **Verificar novamente:**
|
|
162
|
+
```bash
|
|
163
|
+
npm run db:check
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
5. **Popular com dados de teste:**
|
|
167
|
+
- Ver: `EXECUTAR_SUPABASE.md` (seção "Popular com dados")
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## 📚 Documentação Relacionada
|
|
172
|
+
|
|
173
|
+
- **`EXECUTAR_SUPABASE.md`** - Guia completo de setup do banco
|
|
174
|
+
- **`RODAR_MIGRATION_SUPABASE.md`** - Guia rápido de migrations
|
|
175
|
+
- **`SUPABASE_DATABASE_SETUP_SUMMARY.md`** - Resumo executivo
|
|
176
|
+
- **`SUPABASE_SETUP_INSTRUCTIONS.md`** - Instruções gerais do Supabase
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## ⚠️ Troubleshooting
|
|
181
|
+
|
|
182
|
+
### Erro: "Missing environment variables"
|
|
183
|
+
**Solução:** Configure as variáveis de ambiente (ver acima)
|
|
184
|
+
|
|
185
|
+
### Erro: "404 Not Found" na API
|
|
186
|
+
**Solução:** As tabelas não existem. Execute a migration primeiro.
|
|
187
|
+
|
|
188
|
+
### Erro: "Unauthorized"
|
|
189
|
+
**Solução:** Use a `SUPABASE_SERVICE_ROLE_KEY` (não a anon key)
|
|
190
|
+
|
|
191
|
+
### Erro: "function does not exist"
|
|
192
|
+
**Solução:** A função RPC não está disponível. Use execução manual via SQL Editor.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
**Precisa de ajuda?** Veja os guias completos nos arquivos `.md` na raiz do projeto! 🚀
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* NEX CLI Post-Install Script
|
|
4
|
+
* Runs after npm install to configure the CLI
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import inquirer from 'inquirer'
|
|
8
|
+
import fs from 'fs-extra'
|
|
9
|
+
import path from 'path'
|
|
10
|
+
import os from 'os'
|
|
11
|
+
import chalk from 'chalk'
|
|
12
|
+
import { fileURLToPath } from 'url'
|
|
13
|
+
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
15
|
+
const __dirname = path.dirname(__filename)
|
|
16
|
+
|
|
17
|
+
// Get global config path
|
|
18
|
+
function getGlobalConfigPath() {
|
|
19
|
+
const homeDir = os.homedir()
|
|
20
|
+
|
|
21
|
+
if (process.platform === 'win32') {
|
|
22
|
+
// Windows: %APPDATA%\nex\config.json
|
|
23
|
+
return path.join(process.env.APPDATA || path.join(homeDir, 'AppData', 'Roaming'), 'nex', 'config.json')
|
|
24
|
+
} else {
|
|
25
|
+
// Unix-like: ~/.nex/config.json
|
|
26
|
+
return path.join(homeDir, '.nex', 'config.json')
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function runSetup() {
|
|
31
|
+
// Check if config already exists
|
|
32
|
+
const configPath = getGlobalConfigPath()
|
|
33
|
+
const configDir = path.dirname(configPath)
|
|
34
|
+
|
|
35
|
+
if (await fs.pathExists(configPath)) {
|
|
36
|
+
try {
|
|
37
|
+
const existingConfig = await fs.readJSON(configPath)
|
|
38
|
+
// If config exists and is valid, skip setup
|
|
39
|
+
if (existingConfig.user && existingConfig.user.name) {
|
|
40
|
+
console.log(chalk.green('\n✅ NEX CLI já está configurado!'))
|
|
41
|
+
console.log(chalk.gray(` Config: ${configPath}`))
|
|
42
|
+
console.log(chalk.gray(` Para reconfigurar, delete o arquivo ou execute: nex config reset\n`))
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
// Config file exists but is invalid, continue with setup
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(chalk.blue('\n╔══════════════════════════════════════════════════════════╗'))
|
|
51
|
+
console.log(chalk.blue('║ 🚀 Configuração do NEX CLI ║'))
|
|
52
|
+
console.log(chalk.blue('╚══════════════════════════════════════════════════════════╝\n'))
|
|
53
|
+
|
|
54
|
+
const answers = await inquirer.prompt([
|
|
55
|
+
{
|
|
56
|
+
type: 'input',
|
|
57
|
+
name: 'userName',
|
|
58
|
+
message: 'Seu nome:',
|
|
59
|
+
default: process.env.USER || process.env.USERNAME || 'Developer',
|
|
60
|
+
validate: (input) => {
|
|
61
|
+
if (input.trim().length === 0) {
|
|
62
|
+
return 'Por favor, informe seu nome'
|
|
63
|
+
}
|
|
64
|
+
return true
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
type: 'list',
|
|
69
|
+
name: 'conversationLanguage',
|
|
70
|
+
message: 'Idioma de conversação:',
|
|
71
|
+
choices: [
|
|
72
|
+
{ name: 'Português (PT-BR)', value: 'pt-BR' },
|
|
73
|
+
{ name: 'English (EN-US)', value: 'en-US' },
|
|
74
|
+
{ name: 'Español (ES)', value: 'es' },
|
|
75
|
+
{ name: 'Français (FR)', value: 'fr' }
|
|
76
|
+
],
|
|
77
|
+
default: 'pt-BR'
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
type: 'list',
|
|
81
|
+
name: 'documentLanguage',
|
|
82
|
+
message: 'Idioma para criação de documentos:',
|
|
83
|
+
choices: [
|
|
84
|
+
{ name: 'Português (PT-BR)', value: 'pt-BR' },
|
|
85
|
+
{ name: 'English (EN-US)', value: 'en-US' },
|
|
86
|
+
{ name: 'Español (ES)', value: 'es' },
|
|
87
|
+
{ name: 'Français (FR)', value: 'fr' }
|
|
88
|
+
],
|
|
89
|
+
default: 'pt-BR'
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
type: 'confirm',
|
|
93
|
+
name: 'cursorIntegration',
|
|
94
|
+
message: 'Deseja habilitar integração com Cursor IDE?',
|
|
95
|
+
default: true
|
|
96
|
+
}
|
|
97
|
+
])
|
|
98
|
+
|
|
99
|
+
const config = {
|
|
100
|
+
user: {
|
|
101
|
+
name: answers.userName.trim(),
|
|
102
|
+
conversationLanguage: answers.conversationLanguage,
|
|
103
|
+
documentLanguage: answers.documentLanguage
|
|
104
|
+
},
|
|
105
|
+
cursor: {
|
|
106
|
+
enabled: answers.cursorIntegration
|
|
107
|
+
},
|
|
108
|
+
installedAt: new Date().toISOString(),
|
|
109
|
+
version: '1.0.18'
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Save config
|
|
113
|
+
await fs.ensureDir(configDir)
|
|
114
|
+
await fs.writeJSON(configPath, config, { spaces: 2 })
|
|
115
|
+
|
|
116
|
+
console.log(chalk.green('\n✅ NEX CLI configurado com sucesso!'))
|
|
117
|
+
console.log(chalk.gray(` Config salvo em: ${configPath}`))
|
|
118
|
+
console.log(chalk.gray(` Nome: ${config.user.name}`))
|
|
119
|
+
console.log(chalk.gray(` Idioma de conversação: ${config.user.conversationLanguage}`))
|
|
120
|
+
console.log(chalk.gray(` Idioma de documentos: ${config.user.documentLanguage}`))
|
|
121
|
+
console.log(chalk.gray(` Integração Cursor: ${config.cursor.enabled ? 'Habilitada' : 'Desabilitada'}`))
|
|
122
|
+
console.log(chalk.cyan('\n💡 Dica: Para reconfigurar, execute: nex config reset\n'))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Only run if not in CI/CD or if explicitly requested
|
|
126
|
+
if (process.env.CI !== 'true' && process.env.NEX_SKIP_SETUP !== 'true') {
|
|
127
|
+
runSetup().catch((error) => {
|
|
128
|
+
console.error(chalk.red('\n❌ Erro ao configurar NEX CLI:'))
|
|
129
|
+
console.error(chalk.red(error.message))
|
|
130
|
+
console.log(chalk.yellow('\n💡 Você pode configurar manualmente depois executando: nex config reset\n'))
|
|
131
|
+
process.exit(0) // Don't fail the install
|
|
132
|
+
})
|
|
133
|
+
} else {
|
|
134
|
+
// In CI/CD, create default config silently
|
|
135
|
+
const configPath = getGlobalConfigPath()
|
|
136
|
+
const configDir = path.dirname(configPath)
|
|
137
|
+
const defaultConfig = {
|
|
138
|
+
user: {
|
|
139
|
+
name: 'Developer',
|
|
140
|
+
conversationLanguage: 'pt-BR',
|
|
141
|
+
documentLanguage: 'pt-BR'
|
|
142
|
+
},
|
|
143
|
+
cursor: {
|
|
144
|
+
enabled: false
|
|
145
|
+
},
|
|
146
|
+
installedAt: new Date().toISOString(),
|
|
147
|
+
version: '1.0.18'
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
fs.ensureDir(configDir)
|
|
151
|
+
.then(() => fs.writeJSON(configPath, defaultConfig, { spaces: 2 }))
|
|
152
|
+
.catch(() => {
|
|
153
|
+
// Silently fail in CI
|
|
154
|
+
})
|
|
155
|
+
}
|
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
import fs from 'fs-extra'
|
|
7
7
|
import { existsSync } from 'fs'
|
|
8
8
|
import path from 'path'
|
|
9
|
+
import os from 'os'
|
|
9
10
|
import yaml from 'yaml'
|
|
10
11
|
import chalk from 'chalk'
|
|
11
12
|
import ora from 'ora'
|
|
12
13
|
import semver from 'semver'
|
|
14
|
+
import inquirer from 'inquirer'
|
|
13
15
|
import { createClient } from '@supabase/supabase-js'
|
|
14
16
|
import { fileURLToPath } from 'url'
|
|
15
17
|
|
|
@@ -26,8 +28,18 @@ export default class NEXMarketplace {
|
|
|
26
28
|
} else {
|
|
27
29
|
// Tentar encontrar o registry dentro do pacote npm instalado
|
|
28
30
|
try {
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
+
// Obter o caminho do arquivo atual
|
|
32
|
+
const currentFileUrl = new URL(import.meta.url)
|
|
33
|
+
// No Windows, file:/// pode ter 3 barras, precisamos normalizar
|
|
34
|
+
let currentFilePath = currentFileUrl.pathname
|
|
35
|
+
// Remover barra inicial no Windows (file:///C:/...)
|
|
36
|
+
if (process.platform === 'win32' && currentFilePath.startsWith('/')) {
|
|
37
|
+
currentFilePath = currentFilePath.substring(1)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Resolver caminho relativo: de src/services/nex-marketplace/NEXMarketplace.js para raiz do pacote
|
|
41
|
+
const currentDir = path.dirname(currentFilePath)
|
|
42
|
+
const packageRoot = path.resolve(currentDir, '..', '..', '..')
|
|
31
43
|
const npmRegistryPath = path.join(packageRoot, 'registry')
|
|
32
44
|
|
|
33
45
|
// Verificar se existe (quando instalado via npm)
|
|
@@ -37,7 +49,7 @@ export default class NEXMarketplace {
|
|
|
37
49
|
// Fallback para registry no projeto
|
|
38
50
|
this.registryPath = path.join(this.projectRoot, 'registry')
|
|
39
51
|
}
|
|
40
|
-
} catch {
|
|
52
|
+
} catch (error) {
|
|
41
53
|
// Fallback para registry no projeto
|
|
42
54
|
this.registryPath = path.join(this.projectRoot, 'registry')
|
|
43
55
|
}
|
|
@@ -47,11 +59,16 @@ export default class NEXMarketplace {
|
|
|
47
59
|
|
|
48
60
|
// Supabase client
|
|
49
61
|
this.supabase = null
|
|
62
|
+
this.apiUrl = null
|
|
63
|
+
this.anonKey = null
|
|
50
64
|
this.initializeSupabase()
|
|
51
65
|
|
|
52
66
|
// Config
|
|
53
67
|
this.config = null
|
|
68
|
+
this.globalConfig = null
|
|
69
|
+
this.projectConfig = null
|
|
54
70
|
this.loadConfig()
|
|
71
|
+
this.loadGlobalConfig()
|
|
55
72
|
}
|
|
56
73
|
|
|
57
74
|
/**
|
|
@@ -118,6 +135,103 @@ export default class NEXMarketplace {
|
|
|
118
135
|
return this.config
|
|
119
136
|
}
|
|
120
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Get global config path (user's home directory)
|
|
140
|
+
*/
|
|
141
|
+
getGlobalConfigPath() {
|
|
142
|
+
const homeDir = os.homedir()
|
|
143
|
+
|
|
144
|
+
if (process.platform === 'win32') {
|
|
145
|
+
// Windows: %APPDATA%\nex\config.json
|
|
146
|
+
return path.join(process.env.APPDATA || path.join(homeDir, 'AppData', 'Roaming'), 'nex', 'config.json')
|
|
147
|
+
} else {
|
|
148
|
+
// Unix-like: ~/.nex/config.json
|
|
149
|
+
return path.join(homeDir, '.nex', 'config.json')
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Load global configuration from user's home directory
|
|
155
|
+
*/
|
|
156
|
+
async loadGlobalConfig() {
|
|
157
|
+
try {
|
|
158
|
+
const configPath = this.getGlobalConfigPath()
|
|
159
|
+
|
|
160
|
+
if (await fs.pathExists(configPath)) {
|
|
161
|
+
this.globalConfig = await fs.readJSON(configPath)
|
|
162
|
+
return this.globalConfig
|
|
163
|
+
}
|
|
164
|
+
} catch (error) {
|
|
165
|
+
// Config file doesn't exist or is invalid
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
this.globalConfig = null
|
|
169
|
+
return null
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get global configuration with defaults
|
|
174
|
+
*/
|
|
175
|
+
getGlobalConfig() {
|
|
176
|
+
if (!this.globalConfig) {
|
|
177
|
+
return {
|
|
178
|
+
user: {
|
|
179
|
+
name: process.env.USER || process.env.USERNAME || 'Developer',
|
|
180
|
+
conversationLanguage: 'pt-BR',
|
|
181
|
+
documentLanguage: 'pt-BR'
|
|
182
|
+
},
|
|
183
|
+
cursor: {
|
|
184
|
+
enabled: false
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return this.globalConfig
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Load project configuration (.nex-core/config.json)
|
|
193
|
+
* @deprecated Use getGlobalConfig() instead - project config is now optional
|
|
194
|
+
*/
|
|
195
|
+
async loadProjectConfig() {
|
|
196
|
+
const configPath = path.join(this.projectRoot, '.nex-core', 'config.json')
|
|
197
|
+
|
|
198
|
+
if (await fs.pathExists(configPath)) {
|
|
199
|
+
try {
|
|
200
|
+
this.projectConfig = await fs.readJSON(configPath)
|
|
201
|
+
} catch (error) {
|
|
202
|
+
this.projectConfig = null
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
this.projectConfig = null
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return this.projectConfig
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Get project configuration with defaults
|
|
213
|
+
* @deprecated Use getGlobalConfig() instead
|
|
214
|
+
*/
|
|
215
|
+
getProjectConfig() {
|
|
216
|
+
// Prefer global config, fallback to project config
|
|
217
|
+
const global = this.getGlobalConfig()
|
|
218
|
+
if (this.projectConfig) {
|
|
219
|
+
return {
|
|
220
|
+
...global,
|
|
221
|
+
...this.projectConfig,
|
|
222
|
+
user: {
|
|
223
|
+
...global.user,
|
|
224
|
+
...(this.projectConfig.user || {})
|
|
225
|
+
},
|
|
226
|
+
cursor: {
|
|
227
|
+
...global.cursor,
|
|
228
|
+
...(this.projectConfig.cursor || {})
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return global
|
|
233
|
+
}
|
|
234
|
+
|
|
121
235
|
/**
|
|
122
236
|
* Default configuration
|
|
123
237
|
*/
|
|
@@ -834,12 +948,32 @@ export default class NEXMarketplace {
|
|
|
834
948
|
// 7. Track installation
|
|
835
949
|
await this.trackInstallation(agentId, version, method)
|
|
836
950
|
|
|
951
|
+
// 8. Integrate with Cursor if BMAD agent
|
|
952
|
+
if (manifest.bmad?.cursor_integration === true) {
|
|
953
|
+
// Use global config (set during CLI installation)
|
|
954
|
+
const globalConfig = this.getGlobalConfig()
|
|
955
|
+
const shouldIntegrate = globalConfig.cursor?.enabled === true
|
|
956
|
+
|
|
957
|
+
if (shouldIntegrate) {
|
|
958
|
+
await this.integrateWithCursor(agentId, manifest, sourcePath)
|
|
959
|
+
} else {
|
|
960
|
+
console.log(chalk.gray(`\n💡 Integração com Cursor desabilitada. Para habilitar, execute: nex config reset\n`))
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
837
964
|
spinner.succeed(chalk.green(`✅ ${manifest.icon} ${manifest.name} v${version} installed!`))
|
|
838
965
|
|
|
839
|
-
//
|
|
966
|
+
// 9. Show quick start
|
|
840
967
|
console.log(chalk.cyan('\n📖 Quick Start:'))
|
|
841
|
-
|
|
842
|
-
|
|
968
|
+
if (manifest.bmad?.cursor_integration === true) {
|
|
969
|
+
const trigger = manifest.bmad?.activation_trigger || `@${agentId}`
|
|
970
|
+
console.log(chalk.gray(` ${trigger} <command>`))
|
|
971
|
+
console.log(chalk.gray(` (No Cursor, use: nex agent run ${agentId} <command>)`))
|
|
972
|
+
} else {
|
|
973
|
+
console.log(chalk.gray(` @${agentId}`))
|
|
974
|
+
console.log(chalk.gray(` nex agent run ${agentId} <command>`))
|
|
975
|
+
}
|
|
976
|
+
console.log()
|
|
843
977
|
|
|
844
978
|
return true
|
|
845
979
|
|
|
@@ -856,9 +990,13 @@ export default class NEXMarketplace {
|
|
|
856
990
|
async getAgentSourcePath(agentId, version) {
|
|
857
991
|
const categories = ['planning', 'execution', 'community', 'bmad']
|
|
858
992
|
|
|
993
|
+
// Debug: log registry path being searched
|
|
994
|
+
const searchedPaths = []
|
|
995
|
+
|
|
859
996
|
for (const category of categories) {
|
|
860
997
|
// Try versioned path first
|
|
861
998
|
let sourcePath = path.join(this.registryPath, category, agentId, 'versions', version)
|
|
999
|
+
searchedPaths.push(sourcePath)
|
|
862
1000
|
|
|
863
1001
|
if (await fs.pathExists(sourcePath)) {
|
|
864
1002
|
return sourcePath
|
|
@@ -866,13 +1004,121 @@ export default class NEXMarketplace {
|
|
|
866
1004
|
|
|
867
1005
|
// Fallback to non-versioned
|
|
868
1006
|
sourcePath = path.join(this.registryPath, category, agentId)
|
|
1007
|
+
searchedPaths.push(sourcePath)
|
|
869
1008
|
|
|
870
1009
|
if (await fs.pathExists(sourcePath)) {
|
|
871
1010
|
return sourcePath
|
|
872
1011
|
}
|
|
873
1012
|
}
|
|
874
1013
|
|
|
875
|
-
|
|
1014
|
+
// Better error message with debugging info
|
|
1015
|
+
const errorMsg = `Agent source not found: ${agentId}@${version}\n` +
|
|
1016
|
+
`Registry path: ${this.registryPath}\n` +
|
|
1017
|
+
`Searched paths:\n${searchedPaths.map(p => ` - ${p}`).join('\n')}\n` +
|
|
1018
|
+
`\n💡 Tip: Make sure the agent exists in the registry. Try: nex agent search ${agentId}`
|
|
1019
|
+
|
|
1020
|
+
throw new Error(errorMsg)
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* Integrate BMAD agent with Cursor
|
|
1025
|
+
*/
|
|
1026
|
+
async integrateWithCursor(agentId, manifest, sourcePath) {
|
|
1027
|
+
try {
|
|
1028
|
+
const rulesFile = manifest.bmad?.rules_file
|
|
1029
|
+
if (!rulesFile) {
|
|
1030
|
+
return // No rules file specified
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// Path to .cursorrules file in agent directory
|
|
1034
|
+
let agentRulesPath = path.join(sourcePath, rulesFile)
|
|
1035
|
+
|
|
1036
|
+
// Check if rules file exists
|
|
1037
|
+
if (!await fs.pathExists(agentRulesPath)) {
|
|
1038
|
+
// Try alternative locations
|
|
1039
|
+
const alternativePaths = [
|
|
1040
|
+
path.join(sourcePath, '..', rulesFile), // Parent directory
|
|
1041
|
+
path.join(this.registryPath, manifest.category || 'bmad', agentId, rulesFile), // Registry root
|
|
1042
|
+
path.join(this.registryPath, 'bmad', agentId, rulesFile) // BMAD category
|
|
1043
|
+
]
|
|
1044
|
+
|
|
1045
|
+
let foundPath = null
|
|
1046
|
+
for (const altPath of alternativePaths) {
|
|
1047
|
+
if (await fs.pathExists(altPath)) {
|
|
1048
|
+
foundPath = altPath
|
|
1049
|
+
break
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
if (!foundPath) {
|
|
1054
|
+
// Create a basic .cursorrules file from manifest
|
|
1055
|
+
const trigger = manifest.bmad?.activation_trigger || `@${agentId}`
|
|
1056
|
+
const basicRules = `# ${manifest.name}
|
|
1057
|
+
# ${manifest.tagline || manifest.description?.substring(0, 100)}
|
|
1058
|
+
|
|
1059
|
+
# Activation: ${trigger}
|
|
1060
|
+
|
|
1061
|
+
# ${manifest.name} - ${manifest.description || 'BMAD Expert Agent'}
|
|
1062
|
+
|
|
1063
|
+
# Usage in Cursor:
|
|
1064
|
+
# ${trigger} <command>
|
|
1065
|
+
|
|
1066
|
+
# Example:
|
|
1067
|
+
# ${trigger} analyze-page https://example.com
|
|
1068
|
+
`
|
|
1069
|
+
|
|
1070
|
+
// Write basic rules file to agent directory
|
|
1071
|
+
await fs.writeFile(agentRulesPath, basicRules, 'utf8')
|
|
1072
|
+
console.log(chalk.cyan(`\n📝 Created basic Cursor rules file: ${rulesFile}`))
|
|
1073
|
+
} else {
|
|
1074
|
+
agentRulesPath = foundPath
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// Create .cursorrules directory in project (for storing individual agent rules)
|
|
1079
|
+
const cursorRulesDir = path.join(this.projectRoot, '.cursorrules')
|
|
1080
|
+
await fs.ensureDir(cursorRulesDir)
|
|
1081
|
+
|
|
1082
|
+
// Copy rules file to .cursorrules directory
|
|
1083
|
+
const targetRulesPath = path.join(cursorRulesDir, rulesFile)
|
|
1084
|
+
await fs.copy(agentRulesPath, targetRulesPath)
|
|
1085
|
+
|
|
1086
|
+
// Update main .cursorrules file (in project root)
|
|
1087
|
+
const mainCursorRulesPath = path.join(this.projectRoot, '.cursorrules')
|
|
1088
|
+
const importLine = `import .cursorrules/${rulesFile}`
|
|
1089
|
+
const commentLine = `# ${manifest.name} - ${manifest.tagline || manifest.description?.substring(0, 50)}`
|
|
1090
|
+
const trigger = manifest.bmad?.activation_trigger || `@${agentId}`
|
|
1091
|
+
const agentComment = `# Use: ${trigger} <command>`
|
|
1092
|
+
|
|
1093
|
+
let cursorRulesContent = ''
|
|
1094
|
+
if (await fs.pathExists(mainCursorRulesPath)) {
|
|
1095
|
+
cursorRulesContent = await fs.readFile(mainCursorRulesPath, 'utf8')
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// Check if already imported
|
|
1099
|
+
if (cursorRulesContent.includes(importLine) || cursorRulesContent.includes(rulesFile)) {
|
|
1100
|
+
// Already integrated, skip
|
|
1101
|
+
return
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// Add import to .cursorrules
|
|
1105
|
+
const newContent = cursorRulesContent +
|
|
1106
|
+
(cursorRulesContent ? '\n\n' : '') +
|
|
1107
|
+
commentLine + '\n' +
|
|
1108
|
+
agentComment + '\n' +
|
|
1109
|
+
importLine + '\n'
|
|
1110
|
+
|
|
1111
|
+
await fs.writeFile(mainCursorRulesPath, newContent, 'utf8')
|
|
1112
|
+
|
|
1113
|
+
console.log(chalk.green(`\n✅ Cursor integration complete!`))
|
|
1114
|
+
console.log(chalk.gray(` Added ${rulesFile} to .cursorrules`))
|
|
1115
|
+
const trigger = manifest.bmad?.activation_trigger || `@${agentId}`
|
|
1116
|
+
console.log(chalk.gray(` Use: ${trigger} <command> in Cursor\n`))
|
|
1117
|
+
|
|
1118
|
+
} catch (error) {
|
|
1119
|
+
console.log(chalk.yellow(`\n⚠️ Cursor integration failed: ${error.message}`))
|
|
1120
|
+
console.log(chalk.gray(` Agent installed but Cursor integration skipped.\n`))
|
|
1121
|
+
}
|
|
876
1122
|
}
|
|
877
1123
|
|
|
878
1124
|
/**
|