nex-framework-cli 1.0.1
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/LICENSE +51 -0
- package/README.md +484 -0
- package/cli/nex-cli.js +321 -0
- package/package.json +56 -0
- package/registry/.meta/registry.yaml +72 -0
- package/registry/README.md +181 -0
- package/registry/bmad/README.md +433 -0
- package/registry/bmad/ada/manifest.yaml +205 -0
- package/registry/bmad/rex/manifest.yaml +242 -0
- package/registry/bmad/vex/README.md +500 -0
- package/registry/bmad/vex/manifest.yaml +242 -0
- package/registry/planning/anx/CHANGELOG.md +63 -0
- package/registry/planning/anx/README.md +224 -0
- package/registry/planning/anx/manifest.yaml +172 -0
- package/registry/planning/arx/manifest.yaml +98 -0
- package/registry/planning/pmx/manifest.yaml +96 -0
- package/src/services/nex-installer/agentLoader.js +108 -0
- package/src/services/nex-installer/agentValidator.js +93 -0
- package/src/services/nex-installer/dependencyResolver.js +59 -0
- package/src/services/nex-installer/installer.js +226 -0
- package/src/services/nex-installer/registry.js +75 -0
- package/src/services/nex-marketplace/NEXMarketplace.js +964 -0
|
@@ -0,0 +1,964 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NEX Marketplace - Complete Agent Marketplace System
|
|
3
|
+
* Integrates with Supabase, local registry, and NEX Store
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs-extra'
|
|
7
|
+
import path from 'path'
|
|
8
|
+
import yaml from 'yaml'
|
|
9
|
+
import chalk from 'chalk'
|
|
10
|
+
import ora from 'ora'
|
|
11
|
+
import semver from 'semver'
|
|
12
|
+
import { createClient } from '@supabase/supabase-js'
|
|
13
|
+
import { fileURLToPath } from 'url'
|
|
14
|
+
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
16
|
+
const __dirname = path.dirname(__filename)
|
|
17
|
+
|
|
18
|
+
export default class NEXMarketplace {
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this.projectRoot = options.projectRoot || process.cwd()
|
|
21
|
+
this.registryPath = options.registryPath || path.join(this.projectRoot, 'registry')
|
|
22
|
+
this.installPath = options.installPath || path.join(this.projectRoot, '.nex-core', 'agents')
|
|
23
|
+
|
|
24
|
+
// Supabase client
|
|
25
|
+
this.supabase = null
|
|
26
|
+
this.initializeSupabase()
|
|
27
|
+
|
|
28
|
+
// Config
|
|
29
|
+
this.config = null
|
|
30
|
+
this.loadConfig()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Initialize Supabase client
|
|
35
|
+
*/
|
|
36
|
+
initializeSupabase() {
|
|
37
|
+
const supabaseUrl = process.env.VITE_SUPABASE_URL
|
|
38
|
+
const supabaseKey = process.env.VITE_SUPABASE_ANON_KEY
|
|
39
|
+
|
|
40
|
+
if (supabaseUrl && supabaseKey) {
|
|
41
|
+
this.supabase = createClient(supabaseUrl, supabaseKey)
|
|
42
|
+
} else {
|
|
43
|
+
console.warn(chalk.yellow('⚠️ Supabase not configured. Marketplace will work in local-only mode.'))
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Load registry configuration
|
|
49
|
+
*/
|
|
50
|
+
async loadConfig() {
|
|
51
|
+
const configPath = path.join(this.registryPath, '.meta', 'registry.yaml')
|
|
52
|
+
|
|
53
|
+
if (await fs.pathExists(configPath)) {
|
|
54
|
+
const configFile = await fs.readFile(configPath, 'utf8')
|
|
55
|
+
this.config = yaml.parse(configFile)
|
|
56
|
+
} else {
|
|
57
|
+
this.config = this.getDefaultConfig()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return this.config
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Default configuration
|
|
65
|
+
*/
|
|
66
|
+
getDefaultConfig() {
|
|
67
|
+
return {
|
|
68
|
+
registry: {
|
|
69
|
+
name: 'NEX Expert Agent Marketplace',
|
|
70
|
+
version: '1.0.0',
|
|
71
|
+
type: 'hybrid'
|
|
72
|
+
},
|
|
73
|
+
defaults: {
|
|
74
|
+
install_location: '.nex-core/agents',
|
|
75
|
+
method: 'symlink',
|
|
76
|
+
backup_before_update: true
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
82
|
+
// SEARCH & DISCOVERY
|
|
83
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Search for agents
|
|
87
|
+
*/
|
|
88
|
+
async search(query, options = {}) {
|
|
89
|
+
const spinner = ora(`Searching for "${query}"...`).start()
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
let results = []
|
|
93
|
+
|
|
94
|
+
// Search in Supabase if available
|
|
95
|
+
if (this.supabase) {
|
|
96
|
+
results = await this.searchRemote(query, options)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Also search local registry
|
|
100
|
+
const localResults = await this.searchLocal(query, options)
|
|
101
|
+
|
|
102
|
+
// Merge results (deduplicate by agent_id)
|
|
103
|
+
const merged = this.mergeResults(results, localResults)
|
|
104
|
+
|
|
105
|
+
spinner.succeed(`Found ${merged.length} agents`)
|
|
106
|
+
|
|
107
|
+
// Display results
|
|
108
|
+
this.displaySearchResults(merged)
|
|
109
|
+
|
|
110
|
+
return merged
|
|
111
|
+
|
|
112
|
+
} catch (error) {
|
|
113
|
+
spinner.fail('Search failed')
|
|
114
|
+
console.error(chalk.red(`Error: ${error.message}`))
|
|
115
|
+
throw error
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Search in Supabase
|
|
121
|
+
*/
|
|
122
|
+
async searchRemote(query, options = {}) {
|
|
123
|
+
let dbQuery = this.supabase
|
|
124
|
+
.from('nex_marketplace_agents')
|
|
125
|
+
.select('*')
|
|
126
|
+
.eq('is_active', true)
|
|
127
|
+
|
|
128
|
+
// Text search in name, description, tags
|
|
129
|
+
if (query) {
|
|
130
|
+
dbQuery = dbQuery.or(`name.ilike.%${query}%,description.ilike.%${query}%,tags.cs.{${query}}`)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Filters
|
|
134
|
+
if (options.category) {
|
|
135
|
+
dbQuery = dbQuery.eq('category', options.category)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (options.official) {
|
|
139
|
+
dbQuery = dbQuery.eq('is_official', true)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Ordering
|
|
143
|
+
const orderBy = options.sort || 'total_installs'
|
|
144
|
+
dbQuery = dbQuery.order(orderBy, { ascending: false })
|
|
145
|
+
|
|
146
|
+
// Limit
|
|
147
|
+
const limit = options.limit || 50
|
|
148
|
+
dbQuery = dbQuery.limit(limit)
|
|
149
|
+
|
|
150
|
+
const { data, error } = await dbQuery
|
|
151
|
+
|
|
152
|
+
if (error) throw error
|
|
153
|
+
|
|
154
|
+
return data || []
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Search in local registry
|
|
159
|
+
*/
|
|
160
|
+
async searchLocal(query, options = {}) {
|
|
161
|
+
const results = []
|
|
162
|
+
|
|
163
|
+
const categories = ['planning', 'execution', 'community']
|
|
164
|
+
|
|
165
|
+
for (const category of categories) {
|
|
166
|
+
const categoryPath = path.join(this.registryPath, category)
|
|
167
|
+
|
|
168
|
+
if (!await fs.pathExists(categoryPath)) continue
|
|
169
|
+
|
|
170
|
+
const agents = await fs.readdir(categoryPath)
|
|
171
|
+
|
|
172
|
+
for (const agentId of agents) {
|
|
173
|
+
const manifestPath = path.join(categoryPath, agentId, 'manifest.yaml')
|
|
174
|
+
|
|
175
|
+
if (!await fs.pathExists(manifestPath)) continue
|
|
176
|
+
|
|
177
|
+
const manifestFile = await fs.readFile(manifestPath, 'utf8')
|
|
178
|
+
const manifest = yaml.parse(manifestFile)
|
|
179
|
+
|
|
180
|
+
// Filter by query
|
|
181
|
+
if (query) {
|
|
182
|
+
const searchable = [
|
|
183
|
+
manifest.name,
|
|
184
|
+
manifest.description,
|
|
185
|
+
manifest.tagline,
|
|
186
|
+
...(manifest.tags || [])
|
|
187
|
+
].join(' ').toLowerCase()
|
|
188
|
+
|
|
189
|
+
if (!searchable.includes(query.toLowerCase())) {
|
|
190
|
+
continue
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Filter by category
|
|
195
|
+
if (options.category && manifest.category !== options.category) {
|
|
196
|
+
continue
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
results.push(manifest)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return results
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Merge and deduplicate results
|
|
208
|
+
*/
|
|
209
|
+
mergeResults(remote, local) {
|
|
210
|
+
const map = new Map()
|
|
211
|
+
|
|
212
|
+
// Add remote results first (they have stats)
|
|
213
|
+
remote.forEach(agent => {
|
|
214
|
+
map.set(agent.agent_id, { ...agent, source: 'remote' })
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
// Add local results if not already present
|
|
218
|
+
local.forEach(agent => {
|
|
219
|
+
if (!map.has(agent.id)) {
|
|
220
|
+
map.set(agent.id, {
|
|
221
|
+
agent_id: agent.id,
|
|
222
|
+
...agent,
|
|
223
|
+
source: 'local'
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
return Array.from(map.values())
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Display search results
|
|
233
|
+
*/
|
|
234
|
+
displaySearchResults(results) {
|
|
235
|
+
if (results.length === 0) {
|
|
236
|
+
console.log(chalk.yellow('\n😕 No agents found matching your search.\n'))
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
console.log('\n')
|
|
241
|
+
|
|
242
|
+
results.forEach(agent => {
|
|
243
|
+
const icon = agent.icon || '🤖'
|
|
244
|
+
const name = agent.name
|
|
245
|
+
const version = agent.current_version || agent.version
|
|
246
|
+
const tagline = agent.tagline || agent.description?.substring(0, 60) + '...'
|
|
247
|
+
|
|
248
|
+
console.log(chalk.bold.cyan(`${icon} ${name} v${version}`))
|
|
249
|
+
console.log(chalk.gray(` ${tagline}`))
|
|
250
|
+
|
|
251
|
+
if (agent.total_installs || agent.stats?.installs) {
|
|
252
|
+
const installs = agent.total_installs || agent.stats?.installs || 0
|
|
253
|
+
const rating = agent.average_rating || agent.stats?.rating || 0
|
|
254
|
+
console.log(chalk.gray(` ${installs} installs • ⭐ ${rating}/5`))
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (agent.tags && agent.tags.length > 0) {
|
|
258
|
+
const tags = Array.isArray(agent.tags) ? agent.tags : Object.values(agent.tags)
|
|
259
|
+
console.log(chalk.gray(` Tags: ${tags.slice(0, 5).join(', ')}`))
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
console.log(chalk.dim(` ${chalk.cyan('nex agent install')} ${agent.agent_id || agent.id}`))
|
|
263
|
+
console.log()
|
|
264
|
+
})
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
268
|
+
// AGENT INFO
|
|
269
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Get detailed agent information
|
|
273
|
+
*/
|
|
274
|
+
async info(agentId) {
|
|
275
|
+
const spinner = ora(`Loading info for ${agentId}...`).start()
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
// Try to load from Supabase first
|
|
279
|
+
let agent = null
|
|
280
|
+
|
|
281
|
+
if (this.supabase) {
|
|
282
|
+
const { data } = await this.supabase
|
|
283
|
+
.from('nex_marketplace_agents')
|
|
284
|
+
.select('*')
|
|
285
|
+
.eq('agent_id', agentId)
|
|
286
|
+
.single()
|
|
287
|
+
|
|
288
|
+
agent = data
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Fallback to local
|
|
292
|
+
if (!agent) {
|
|
293
|
+
agent = await this.loadLocalManifest(agentId)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (!agent) {
|
|
297
|
+
spinner.fail(`Agent ${agentId} not found`)
|
|
298
|
+
return null
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
spinner.succeed(`Loaded info for ${agentId}`)
|
|
302
|
+
|
|
303
|
+
// Display detailed info
|
|
304
|
+
this.displayAgentInfo(agent)
|
|
305
|
+
|
|
306
|
+
return agent
|
|
307
|
+
|
|
308
|
+
} catch (error) {
|
|
309
|
+
spinner.fail('Failed to load agent info')
|
|
310
|
+
console.error(chalk.red(`Error: ${error.message}`))
|
|
311
|
+
throw error
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Load manifest from local registry
|
|
317
|
+
*/
|
|
318
|
+
async loadLocalManifest(agentId) {
|
|
319
|
+
const categories = ['planning', 'execution', 'community']
|
|
320
|
+
|
|
321
|
+
for (const category of categories) {
|
|
322
|
+
const manifestPath = path.join(this.registryPath, category, agentId, 'manifest.yaml')
|
|
323
|
+
|
|
324
|
+
if (await fs.pathExists(manifestPath)) {
|
|
325
|
+
const manifestFile = await fs.readFile(manifestPath, 'utf8')
|
|
326
|
+
const manifest = yaml.parse(manifestFile)
|
|
327
|
+
return { agent_id: agentId, ...manifest }
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return null
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Display detailed agent information
|
|
336
|
+
*/
|
|
337
|
+
displayAgentInfo(agent) {
|
|
338
|
+
const icon = agent.icon || '🤖'
|
|
339
|
+
const name = agent.name
|
|
340
|
+
const version = agent.current_version || agent.version
|
|
341
|
+
|
|
342
|
+
console.log('\n' + chalk.bold.cyan('═'.repeat(60)))
|
|
343
|
+
console.log(chalk.bold.cyan(`${icon} ${name} v${version}`))
|
|
344
|
+
console.log(chalk.bold.cyan('═'.repeat(60)))
|
|
345
|
+
|
|
346
|
+
if (agent.tagline) {
|
|
347
|
+
console.log(chalk.bold('\n💫 Tagline:'))
|
|
348
|
+
console.log(chalk.gray(` ${agent.tagline}`))
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
console.log(chalk.bold('\n📝 Description:'))
|
|
352
|
+
console.log(chalk.gray(` ${agent.description || agent.long_description || 'N/A'}`))
|
|
353
|
+
|
|
354
|
+
if (agent.author_name || agent.author?.name) {
|
|
355
|
+
console.log(chalk.bold('\n👤 Author:'))
|
|
356
|
+
const authorName = agent.author_name || agent.author.name
|
|
357
|
+
const authorEmail = agent.author_email || agent.author?.email
|
|
358
|
+
console.log(chalk.gray(` ${authorName}${authorEmail ? ` <${authorEmail}>` : ''}`))
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (agent.tags && agent.tags.length > 0) {
|
|
362
|
+
console.log(chalk.bold('\n🏷️ Tags:'))
|
|
363
|
+
const tags = Array.isArray(agent.tags) ? agent.tags : Object.values(agent.tags)
|
|
364
|
+
console.log(chalk.gray(` ${tags.join(', ')}`))
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (agent.capabilities) {
|
|
368
|
+
console.log(chalk.bold('\n⚡ Capabilities:'))
|
|
369
|
+
const capabilities = Array.isArray(agent.capabilities)
|
|
370
|
+
? agent.capabilities
|
|
371
|
+
: Object.values(agent.capabilities)
|
|
372
|
+
capabilities.slice(0, 10).forEach(cap => {
|
|
373
|
+
console.log(chalk.gray(` • ${cap}`))
|
|
374
|
+
})
|
|
375
|
+
if (capabilities.length > 10) {
|
|
376
|
+
console.log(chalk.gray(` ... and ${capabilities.length - 10} more`))
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (agent.total_installs !== undefined) {
|
|
381
|
+
console.log(chalk.bold('\n📊 Stats:'))
|
|
382
|
+
console.log(chalk.gray(` Installs: ${agent.total_installs}`))
|
|
383
|
+
console.log(chalk.gray(` Rating: ${'⭐'.repeat(Math.floor(agent.average_rating || 0))} (${agent.average_rating || 0}/5)`))
|
|
384
|
+
if (agent.total_stars) {
|
|
385
|
+
console.log(chalk.gray(` Stars: ${agent.total_stars}`))
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (agent.dependencies?.agents && agent.dependencies.agents.length > 0) {
|
|
390
|
+
console.log(chalk.bold('\n🔗 Dependencies:'))
|
|
391
|
+
agent.dependencies.agents.forEach(dep => {
|
|
392
|
+
console.log(chalk.gray(` • ${dep}`))
|
|
393
|
+
})
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
console.log(chalk.bold('\n🔧 Installation:'))
|
|
397
|
+
console.log(chalk.cyan(` nex agent install ${agent.agent_id || agent.id}`))
|
|
398
|
+
|
|
399
|
+
if (agent.repository_url || agent.links?.repository) {
|
|
400
|
+
console.log(chalk.bold('\n🌐 Links:'))
|
|
401
|
+
const repoUrl = agent.repository_url || agent.links?.repository
|
|
402
|
+
console.log(chalk.gray(` Repository: ${repoUrl}`))
|
|
403
|
+
|
|
404
|
+
if (agent.documentation_url || agent.links?.documentation) {
|
|
405
|
+
const docUrl = agent.documentation_url || agent.links?.documentation
|
|
406
|
+
console.log(chalk.gray(` Documentation: ${docUrl}`))
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
console.log('\n' + chalk.bold.cyan('═'.repeat(60)) + '\n')
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
414
|
+
// INSTALLATION
|
|
415
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Install an agent
|
|
419
|
+
*/
|
|
420
|
+
async install(agentId, options = {}) {
|
|
421
|
+
const spinner = ora(`Installing ${agentId}...`).start()
|
|
422
|
+
|
|
423
|
+
try {
|
|
424
|
+
// 1. Check if agent is registered in NEX Hub (if Supabase is configured)
|
|
425
|
+
if (this.supabase && !options.skipRegistrationCheck) {
|
|
426
|
+
spinner.text = 'Checking registration...'
|
|
427
|
+
|
|
428
|
+
const { data: { user } } = await this.supabase.auth.getUser()
|
|
429
|
+
|
|
430
|
+
if (user) {
|
|
431
|
+
// User is authenticated, check registration
|
|
432
|
+
const isRegistered = await this.checkAgentRegistration(agentId)
|
|
433
|
+
|
|
434
|
+
if (!isRegistered) {
|
|
435
|
+
spinner.fail('Agent not registered')
|
|
436
|
+
console.log(chalk.yellow(`\n⚠️ Agent "${agentId}" is not registered in your NEX Hub account.\n`))
|
|
437
|
+
console.log(chalk.cyan('💡 Register it first:'))
|
|
438
|
+
console.log(chalk.cyan(` nex agent register ${agentId}\n`))
|
|
439
|
+
console.log(chalk.gray(' Or browse available agents:'))
|
|
440
|
+
console.log(chalk.gray(` nex agent search ${agentId}\n`))
|
|
441
|
+
return false
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
spinner.text = `Installing ${agentId}...`
|
|
445
|
+
} else {
|
|
446
|
+
// User not authenticated, show warning but allow local install
|
|
447
|
+
spinner.warn('Not authenticated - installing locally only')
|
|
448
|
+
console.log(chalk.yellow('\n⚠️ You are not logged in to NEX Hub.'))
|
|
449
|
+
console.log(chalk.gray(' This agent will be installed locally only.\n'))
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// 2. Resolve version
|
|
454
|
+
const version = options.version || await this.getLatestVersion(agentId)
|
|
455
|
+
spinner.text = `Installing ${agentId}@${version}...`
|
|
456
|
+
|
|
457
|
+
// 3. Load agent manifest
|
|
458
|
+
const manifest = await this.loadLocalManifest(agentId)
|
|
459
|
+
|
|
460
|
+
if (!manifest) {
|
|
461
|
+
spinner.fail(`Agent ${agentId} not found in registry`)
|
|
462
|
+
return false
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// 4. Check dependencies
|
|
466
|
+
spinner.text = 'Checking dependencies...'
|
|
467
|
+
await this.checkDependencies(manifest)
|
|
468
|
+
|
|
469
|
+
// 5. Determine install location
|
|
470
|
+
const installPath = path.join(this.installPath, agentId)
|
|
471
|
+
|
|
472
|
+
// 5. Check if already installed
|
|
473
|
+
if (await fs.pathExists(installPath)) {
|
|
474
|
+
spinner.info(`Agent ${agentId} already installed`)
|
|
475
|
+
|
|
476
|
+
// Ask user if they want to update
|
|
477
|
+
return await this.update(agentId, options)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// 6. Install (symlink or copy)
|
|
481
|
+
const method = options.method || this.config.defaults.method || 'symlink'
|
|
482
|
+
const sourcePath = await this.getAgentSourcePath(agentId, version)
|
|
483
|
+
|
|
484
|
+
spinner.text = `Installing ${agentId} (${method})...`
|
|
485
|
+
|
|
486
|
+
if (method === 'symlink') {
|
|
487
|
+
await fs.ensureDir(path.dirname(installPath))
|
|
488
|
+
await fs.ensureSymlink(sourcePath, installPath)
|
|
489
|
+
} else {
|
|
490
|
+
await fs.copy(sourcePath, installPath)
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// 7. Track installation
|
|
494
|
+
await this.trackInstallation(agentId, version, method)
|
|
495
|
+
|
|
496
|
+
spinner.succeed(chalk.green(`✅ ${manifest.icon} ${manifest.name} v${version} installed!`))
|
|
497
|
+
|
|
498
|
+
// 8. Show quick start
|
|
499
|
+
console.log(chalk.cyan('\n📖 Quick Start:'))
|
|
500
|
+
console.log(chalk.gray(` @${agentId}`))
|
|
501
|
+
console.log(chalk.gray(` nex agent run ${agentId} <command>\n`))
|
|
502
|
+
|
|
503
|
+
return true
|
|
504
|
+
|
|
505
|
+
} catch (error) {
|
|
506
|
+
spinner.fail(chalk.red(`Failed to install ${agentId}`))
|
|
507
|
+
console.error(chalk.red(`Error: ${error.message}`))
|
|
508
|
+
throw error
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Get agent source path
|
|
514
|
+
*/
|
|
515
|
+
async getAgentSourcePath(agentId, version) {
|
|
516
|
+
const categories = ['planning', 'execution', 'community']
|
|
517
|
+
|
|
518
|
+
for (const category of categories) {
|
|
519
|
+
// Try versioned path first
|
|
520
|
+
let sourcePath = path.join(this.registryPath, category, agentId, 'versions', version)
|
|
521
|
+
|
|
522
|
+
if (await fs.pathExists(sourcePath)) {
|
|
523
|
+
return sourcePath
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Fallback to non-versioned
|
|
527
|
+
sourcePath = path.join(this.registryPath, category, agentId)
|
|
528
|
+
|
|
529
|
+
if (await fs.pathExists(sourcePath)) {
|
|
530
|
+
return sourcePath
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
throw new Error(`Agent source not found: ${agentId}@${version}`)
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Get latest version of agent
|
|
539
|
+
*/
|
|
540
|
+
async getLatestVersion(agentId) {
|
|
541
|
+
// Try Supabase first
|
|
542
|
+
if (this.supabase) {
|
|
543
|
+
const { data } = await this.supabase
|
|
544
|
+
.from('nex_marketplace_versions')
|
|
545
|
+
.select('version')
|
|
546
|
+
.eq('agent_id', agentId)
|
|
547
|
+
.eq('is_latest', true)
|
|
548
|
+
.single()
|
|
549
|
+
|
|
550
|
+
if (data) return data.version
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Fallback to manifest
|
|
554
|
+
const manifest = await this.loadLocalManifest(agentId)
|
|
555
|
+
return manifest?.version || manifest?.current_version || '1.0.0'
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Check agent dependencies
|
|
560
|
+
*/
|
|
561
|
+
async checkDependencies(manifest) {
|
|
562
|
+
if (!manifest.dependencies || !manifest.dependencies.agents) {
|
|
563
|
+
return true
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const missing = []
|
|
567
|
+
|
|
568
|
+
for (const depId of manifest.dependencies.agents) {
|
|
569
|
+
const depPath = path.join(this.installPath, depId)
|
|
570
|
+
|
|
571
|
+
if (!await fs.pathExists(depPath)) {
|
|
572
|
+
missing.push(depId)
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (missing.length > 0) {
|
|
577
|
+
console.log(chalk.yellow(`\n⚠️ Missing dependencies: ${missing.join(', ')}`))
|
|
578
|
+
console.log(chalk.gray('These agents will be installed automatically.\n'))
|
|
579
|
+
|
|
580
|
+
// Auto-install dependencies
|
|
581
|
+
for (const depId of missing) {
|
|
582
|
+
await this.install(depId)
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return true
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Track installation
|
|
591
|
+
*/
|
|
592
|
+
async trackInstallation(agentId, version, method) {
|
|
593
|
+
// Save to local registry
|
|
594
|
+
const installedFile = path.join(this.installPath, 'installed.json')
|
|
595
|
+
await fs.ensureDir(this.installPath)
|
|
596
|
+
|
|
597
|
+
let installed = {}
|
|
598
|
+
if (await fs.pathExists(installedFile)) {
|
|
599
|
+
installed = await fs.readJSON(installedFile)
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if (!installed.agents) {
|
|
603
|
+
installed.agents = []
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Remove old entry if exists
|
|
607
|
+
installed.agents = installed.agents.filter(a => a.id !== agentId)
|
|
608
|
+
|
|
609
|
+
// Add new entry
|
|
610
|
+
installed.agents.push({
|
|
611
|
+
id: agentId,
|
|
612
|
+
version: version,
|
|
613
|
+
method: method,
|
|
614
|
+
installed_at: new Date().toISOString(),
|
|
615
|
+
project_path: this.projectRoot
|
|
616
|
+
})
|
|
617
|
+
|
|
618
|
+
await fs.writeJSON(installedFile, installed, { spaces: 2 })
|
|
619
|
+
|
|
620
|
+
// Track in Supabase if available
|
|
621
|
+
if (this.supabase) {
|
|
622
|
+
await this.supabase
|
|
623
|
+
.from('nex_marketplace_installs')
|
|
624
|
+
.upsert({
|
|
625
|
+
agent_id: agentId,
|
|
626
|
+
version: version,
|
|
627
|
+
install_method: method,
|
|
628
|
+
project_path: this.projectRoot,
|
|
629
|
+
project_name: path.basename(this.projectRoot),
|
|
630
|
+
is_active: true
|
|
631
|
+
})
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
636
|
+
// LIST INSTALLED
|
|
637
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* List installed agents
|
|
641
|
+
*/
|
|
642
|
+
async list(options = {}) {
|
|
643
|
+
const installedFile = path.join(this.installPath, 'installed.json')
|
|
644
|
+
|
|
645
|
+
if (!await fs.pathExists(installedFile)) {
|
|
646
|
+
console.log(chalk.yellow('📭 No agents installed in this project.'))
|
|
647
|
+
console.log(chalk.gray('\nInstall agents with: nex agent install <agent-id>\n'))
|
|
648
|
+
return []
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const installed = await fs.readJSON(installedFile)
|
|
652
|
+
const agents = installed.agents || []
|
|
653
|
+
|
|
654
|
+
if (agents.length === 0) {
|
|
655
|
+
console.log(chalk.yellow('📭 No agents installed in this project.\n'))
|
|
656
|
+
return []
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
console.log(chalk.bold.cyan(`\n📦 Installed Agents (${agents.length})`))
|
|
660
|
+
console.log(chalk.gray(`Project: ${path.basename(this.projectRoot)}\n`))
|
|
661
|
+
|
|
662
|
+
for (const agent of agents) {
|
|
663
|
+
const manifest = await this.loadLocalManifest(agent.id)
|
|
664
|
+
|
|
665
|
+
if (manifest) {
|
|
666
|
+
const icon = manifest.icon || '🤖'
|
|
667
|
+
console.log(chalk.bold(`${icon} ${manifest.name || agent.id}`))
|
|
668
|
+
} else {
|
|
669
|
+
console.log(chalk.bold(`🤖 ${agent.id}`))
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
console.log(chalk.gray(` Version: ${agent.version}`))
|
|
673
|
+
console.log(chalk.gray(` Method: ${agent.method}`))
|
|
674
|
+
console.log(chalk.gray(` Installed: ${new Date(agent.installed_at).toLocaleDateString()}`))
|
|
675
|
+
console.log()
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
return agents
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Uninstall agent
|
|
683
|
+
*/
|
|
684
|
+
async uninstall(agentId) {
|
|
685
|
+
const spinner = ora(`Uninstalling ${agentId}...`).start()
|
|
686
|
+
|
|
687
|
+
try {
|
|
688
|
+
const installPath = path.join(this.installPath, agentId)
|
|
689
|
+
|
|
690
|
+
if (!await fs.pathExists(installPath)) {
|
|
691
|
+
spinner.fail(`Agent ${agentId} is not installed`)
|
|
692
|
+
return false
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// Remove files
|
|
696
|
+
await fs.remove(installPath)
|
|
697
|
+
|
|
698
|
+
// Update installed.json
|
|
699
|
+
const installedFile = path.join(this.installPath, 'installed.json')
|
|
700
|
+
const installed = await fs.readJSON(installedFile)
|
|
701
|
+
installed.agents = (installed.agents || []).filter(a => a.id !== agentId)
|
|
702
|
+
await fs.writeJSON(installedFile, installed, { spaces: 2 })
|
|
703
|
+
|
|
704
|
+
// Update Supabase
|
|
705
|
+
if (this.supabase) {
|
|
706
|
+
await this.supabase
|
|
707
|
+
.from('nex_marketplace_installs')
|
|
708
|
+
.update({ is_active: false, uninstalled_at: new Date().toISOString() })
|
|
709
|
+
.eq('agent_id', agentId)
|
|
710
|
+
.eq('project_path', this.projectRoot)
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
spinner.succeed(chalk.green(`✅ Agent ${agentId} uninstalled`))
|
|
714
|
+
return true
|
|
715
|
+
|
|
716
|
+
} catch (error) {
|
|
717
|
+
spinner.fail(`Failed to uninstall ${agentId}`)
|
|
718
|
+
console.error(chalk.red(`Error: ${error.message}`))
|
|
719
|
+
throw error
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* Update agent
|
|
725
|
+
*/
|
|
726
|
+
async update(agentId, options = {}) {
|
|
727
|
+
console.log(chalk.blue(`🔄 Updating ${agentId}...`))
|
|
728
|
+
|
|
729
|
+
// Uninstall old version
|
|
730
|
+
await this.uninstall(agentId)
|
|
731
|
+
|
|
732
|
+
// Install new version
|
|
733
|
+
return await this.install(agentId, options)
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
737
|
+
// AGENT REGISTRATION (HUB REGISTRY SYSTEM)
|
|
738
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Register an agent in the NEX Hub
|
|
742
|
+
*/
|
|
743
|
+
async register(agentId) {
|
|
744
|
+
const spinner = ora(`Registering ${agentId} in NEX Hub...`).start()
|
|
745
|
+
|
|
746
|
+
try {
|
|
747
|
+
// 1. Check if Supabase is configured
|
|
748
|
+
if (!this.supabase) {
|
|
749
|
+
spinner.fail('Supabase not configured')
|
|
750
|
+
console.log(chalk.yellow('\n⚠️ NEX Hub requires Supabase configuration.'))
|
|
751
|
+
console.log(chalk.cyan('💡 Set VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY environment variables.\n'))
|
|
752
|
+
return false
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// 2. Check if user is authenticated
|
|
756
|
+
const { data: { user }, error: authError } = await this.supabase.auth.getUser()
|
|
757
|
+
|
|
758
|
+
if (authError || !user) {
|
|
759
|
+
spinner.fail('Not authenticated')
|
|
760
|
+
console.log(chalk.yellow('\n⚠️ You need to be logged in to register agents.'))
|
|
761
|
+
console.log(chalk.cyan('💡 Run: nex auth login\n'))
|
|
762
|
+
return false
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// 3. Get agent info from database
|
|
766
|
+
const { data: agent, error: agentError } = await this.supabase
|
|
767
|
+
.from('nex_marketplace_agents')
|
|
768
|
+
.select('agent_id, name, agent_type, icon')
|
|
769
|
+
.eq('agent_id', agentId)
|
|
770
|
+
.single()
|
|
771
|
+
|
|
772
|
+
if (agentError || !agent) {
|
|
773
|
+
spinner.fail(`Agent ${agentId} not found`)
|
|
774
|
+
console.log(chalk.yellow(`\n⚠️ Agent "${agentId}" not found in NEX Hub.`))
|
|
775
|
+
console.log(chalk.cyan(`💡 Search for agents: nex agent search ${agentId}\n`))
|
|
776
|
+
return false
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// 4. Call Edge Function to register
|
|
780
|
+
const EDGE_FUNCTION_URL = process.env.VITE_SUPABASE_URL?.replace('.supabase.co', '.supabase.co/functions/v1')
|
|
781
|
+
const { data: session } = await this.supabase.auth.getSession()
|
|
782
|
+
|
|
783
|
+
const response = await fetch(`${EDGE_FUNCTION_URL}/nex-agent-registry/register`, {
|
|
784
|
+
method: 'POST',
|
|
785
|
+
headers: {
|
|
786
|
+
'Content-Type': 'application/json',
|
|
787
|
+
'Authorization': `Bearer ${session.session?.access_token}`,
|
|
788
|
+
'apikey': process.env.VITE_SUPABASE_ANON_KEY
|
|
789
|
+
},
|
|
790
|
+
body: JSON.stringify({ agent_id: agentId })
|
|
791
|
+
})
|
|
792
|
+
|
|
793
|
+
const result = await response.json()
|
|
794
|
+
|
|
795
|
+
if (!response.ok) {
|
|
796
|
+
spinner.fail('Registration failed')
|
|
797
|
+
|
|
798
|
+
if (result.details?.reason === 'limit_reached') {
|
|
799
|
+
console.log(chalk.red(`\n❌ ${result.error}\n`))
|
|
800
|
+
console.log(chalk.yellow(`📊 Your Plan: ${result.details.current_plan}`))
|
|
801
|
+
console.log(chalk.yellow(` Agent Type: ${result.details.agent_type}`))
|
|
802
|
+
console.log(chalk.yellow(` Current: ${result.details.current_count}/${result.details.max_allowed}\n`))
|
|
803
|
+
|
|
804
|
+
if (result.details.upgrade_required) {
|
|
805
|
+
console.log(chalk.cyan(`💡 Upgrade to ${result.details.upgrade_required} plan to register more agents.`))
|
|
806
|
+
console.log(chalk.cyan(` Visit: https://nexhub.dev/pricing\n`))
|
|
807
|
+
}
|
|
808
|
+
} else if (result.details?.reason === 'already_registered') {
|
|
809
|
+
spinner.info('Already registered')
|
|
810
|
+
console.log(chalk.blue(`\nℹ️ Agent "${agent.name}" is already registered.\n`))
|
|
811
|
+
return true
|
|
812
|
+
} else {
|
|
813
|
+
console.log(chalk.red(`\n❌ ${result.error}\n`))
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
return false
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// 5. Success!
|
|
820
|
+
spinner.succeed(chalk.green(`✅ Agent registered successfully!`))
|
|
821
|
+
console.log(chalk.cyan(`\n${agent.icon || '🤖'} ${agent.name} (${agentId})`))
|
|
822
|
+
console.log(chalk.gray(` Type: ${agent.agent_type}`))
|
|
823
|
+
console.log(chalk.gray(` Registration ID: ${result.registration?.registration_id}\n`))
|
|
824
|
+
console.log(chalk.green(`✨ You can now install this agent:`))
|
|
825
|
+
console.log(chalk.cyan(` nex agent install ${agentId}\n`))
|
|
826
|
+
|
|
827
|
+
return true
|
|
828
|
+
|
|
829
|
+
} catch (error) {
|
|
830
|
+
spinner.fail('Registration failed')
|
|
831
|
+
console.error(chalk.red(`\n❌ Error: ${error.message}\n`))
|
|
832
|
+
return false
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* List registered agents from NEX Hub
|
|
838
|
+
*/
|
|
839
|
+
async listRegisteredAgents() {
|
|
840
|
+
const spinner = ora('Loading your registered agents...').start()
|
|
841
|
+
|
|
842
|
+
try {
|
|
843
|
+
// 1. Check if Supabase is configured
|
|
844
|
+
if (!this.supabase) {
|
|
845
|
+
spinner.fail('Supabase not configured')
|
|
846
|
+
console.log(chalk.yellow('\n⚠️ NEX Hub requires Supabase configuration.\n'))
|
|
847
|
+
return []
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// 2. Check if user is authenticated
|
|
851
|
+
const { data: { user }, error: authError } = await this.supabase.auth.getUser()
|
|
852
|
+
|
|
853
|
+
if (authError || !user) {
|
|
854
|
+
spinner.fail('Not authenticated')
|
|
855
|
+
console.log(chalk.yellow('\n⚠️ You need to be logged in.\n'))
|
|
856
|
+
return []
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// 3. Call Edge Function to get registered agents
|
|
860
|
+
const EDGE_FUNCTION_URL = process.env.VITE_SUPABASE_URL?.replace('.supabase.co', '.supabase.co/functions/v1')
|
|
861
|
+
const { data: session } = await this.supabase.auth.getSession()
|
|
862
|
+
|
|
863
|
+
const response = await fetch(`${EDGE_FUNCTION_URL}/nex-agent-registry/my-agents`, {
|
|
864
|
+
method: 'GET',
|
|
865
|
+
headers: {
|
|
866
|
+
'Authorization': `Bearer ${session.session?.access_token}`,
|
|
867
|
+
'apikey': process.env.VITE_SUPABASE_ANON_KEY
|
|
868
|
+
}
|
|
869
|
+
})
|
|
870
|
+
|
|
871
|
+
const result = await response.json()
|
|
872
|
+
|
|
873
|
+
if (!response.ok) {
|
|
874
|
+
spinner.fail('Failed to load agents')
|
|
875
|
+
console.log(chalk.red(`\n❌ ${result.error}\n`))
|
|
876
|
+
return []
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
const agents = result.agents || []
|
|
880
|
+
|
|
881
|
+
spinner.succeed(`Found ${agents.length} registered agents`)
|
|
882
|
+
|
|
883
|
+
// 4. Display agents
|
|
884
|
+
if (agents.length === 0) {
|
|
885
|
+
console.log(chalk.yellow('\n😕 You have no agents registered yet.\n'))
|
|
886
|
+
console.log(chalk.cyan('💡 Register an agent:'))
|
|
887
|
+
console.log(chalk.cyan(' nex agent search <query>'))
|
|
888
|
+
console.log(chalk.cyan(' nex agent register <agent-id>\n'))
|
|
889
|
+
return []
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
console.log('\n')
|
|
893
|
+
console.log(chalk.bold.cyan('📦 YOUR REGISTERED AGENTS\n'))
|
|
894
|
+
|
|
895
|
+
agents.forEach(reg => {
|
|
896
|
+
const agent = reg.nex_marketplace_agents
|
|
897
|
+
const icon = agent?.icon || '🤖'
|
|
898
|
+
const name = agent?.name || reg.agent_id
|
|
899
|
+
const type = agent?.agent_type || 'unknown'
|
|
900
|
+
const registeredAt = new Date(reg.registered_at).toLocaleDateString()
|
|
901
|
+
|
|
902
|
+
console.log(chalk.bold(`${icon} ${name}`))
|
|
903
|
+
console.log(chalk.gray(` ID: ${reg.agent_id}`))
|
|
904
|
+
console.log(chalk.gray(` Type: ${type}`))
|
|
905
|
+
console.log(chalk.gray(` Registered: ${registeredAt}`))
|
|
906
|
+
console.log(chalk.dim(` ${chalk.cyan('nex agent install')} ${reg.agent_id}`))
|
|
907
|
+
console.log()
|
|
908
|
+
})
|
|
909
|
+
|
|
910
|
+
// 5. Show plan info
|
|
911
|
+
const { data: planData } = await this.supabase.rpc('get_user_plan', { p_user_id: user.id })
|
|
912
|
+
|
|
913
|
+
if (planData && planData.length > 0) {
|
|
914
|
+
const plan = planData[0]
|
|
915
|
+
const nexCount = agents.filter(a => a.nex_marketplace_agents?.agent_type === 'nex_free').length
|
|
916
|
+
const bmadCount = agents.filter(a => a.nex_marketplace_agents?.agent_type === 'bmad_free').length
|
|
917
|
+
const premiumCount = agents.filter(a => a.nex_marketplace_agents?.agent_type === 'premium').length
|
|
918
|
+
|
|
919
|
+
console.log(chalk.bold.cyan('📊 YOUR PLAN\n'))
|
|
920
|
+
console.log(chalk.bold(` ${plan.plan_name}`))
|
|
921
|
+
console.log(chalk.gray(` NEX Free: ${nexCount}/${plan.max_nex_free}`))
|
|
922
|
+
console.log(chalk.gray(` BMAD Free: ${bmadCount}/${plan.max_bmad_free}`))
|
|
923
|
+
console.log(chalk.gray(` Premium: ${premiumCount}/${plan.max_premium}`))
|
|
924
|
+
console.log()
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
return agents
|
|
928
|
+
|
|
929
|
+
} catch (error) {
|
|
930
|
+
spinner.fail('Failed to load agents')
|
|
931
|
+
console.error(chalk.red(`\n❌ Error: ${error.message}\n`))
|
|
932
|
+
return []
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* Check if agent is registered (internal method)
|
|
938
|
+
*/
|
|
939
|
+
async checkAgentRegistration(agentId) {
|
|
940
|
+
try {
|
|
941
|
+
if (!this.supabase) return false
|
|
942
|
+
|
|
943
|
+
const { data: { user } } = await this.supabase.auth.getUser()
|
|
944
|
+
if (!user) return false
|
|
945
|
+
|
|
946
|
+
const EDGE_FUNCTION_URL = process.env.VITE_SUPABASE_URL?.replace('.supabase.co', '.supabase.co/functions/v1')
|
|
947
|
+
const { data: session } = await this.supabase.auth.getSession()
|
|
948
|
+
|
|
949
|
+
const response = await fetch(`${EDGE_FUNCTION_URL}/nex-agent-registry/check/${agentId}`, {
|
|
950
|
+
method: 'GET',
|
|
951
|
+
headers: {
|
|
952
|
+
'Authorization': `Bearer ${session.session?.access_token}`,
|
|
953
|
+
'apikey': process.env.VITE_SUPABASE_ANON_KEY
|
|
954
|
+
}
|
|
955
|
+
})
|
|
956
|
+
|
|
957
|
+
const result = await response.json()
|
|
958
|
+
return result.is_registered || false
|
|
959
|
+
|
|
960
|
+
} catch (error) {
|
|
961
|
+
return false
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|